Skip to content

Commit 2cd02d6

Browse files
committed
added getblockbyhash and getblockbyheight RPC api calls
1 parent b83e2dc commit 2cd02d6

File tree

5 files changed

+413
-19
lines changed

5 files changed

+413
-19
lines changed

stackslib/src/chainstate/nakamoto/staging_blocks.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,17 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> {
282282
Ok(res)
283283
}
284284

285+
/// Get the rowid of a staging Nakamoto block by block_hash
286+
pub fn get_nakamoto_block_by_hash_rowid(
287+
&self,
288+
block_hash: &StacksBlockId,
289+
) -> Result<Option<i64>, ChainstateError> {
290+
let sql = "SELECT rowid FROM nakamoto_staging_blocks WHERE block_hash = ?1";
291+
let args = params![block_hash];
292+
let res: Option<i64> = query_row(self, sql, args)?;
293+
Ok(res)
294+
}
295+
285296
/// Get the tenure and parent block ID of a staging block.
286297
/// Used for downloads
287298
pub fn get_tenure_and_parent_block_id(
@@ -302,6 +313,44 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> {
302313
.optional()?)
303314
}
304315

316+
/// Get the block ID of a staging block from block hash.
317+
/// Used for downloads
318+
pub fn get_block_id_by_block_hash(
319+
&self,
320+
block_hash: &BlockHeaderHash,
321+
) -> Result<Option<StacksBlockId>, ChainstateError> {
322+
let sql = "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE block_hash = ?1";
323+
let args = params![block_hash];
324+
325+
let mut stmt = self.deref().prepare(sql)?;
326+
Ok(stmt
327+
.query_row(args, |row| {
328+
let block_id: StacksBlockId = row.get(0)?;
329+
330+
Ok(block_id)
331+
})
332+
.optional()?)
333+
}
334+
335+
/// Get the block ID of a staging block from block height.
336+
/// Used for downloads
337+
pub fn get_block_id_by_block_height(
338+
&self,
339+
block_height: u64,
340+
) -> Result<Option<StacksBlockId>, ChainstateError> {
341+
let sql = "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE height = ?1";
342+
let args = params![block_height];
343+
344+
let mut stmt = self.deref().prepare(sql)?;
345+
Ok(stmt
346+
.query_row(args, |row| {
347+
let block_id: StacksBlockId = row.get(0)?;
348+
349+
Ok(block_id)
350+
})
351+
.optional()?)
352+
}
353+
305354
/// Get a Nakamoto block by index block hash, as well as its size.
306355
/// Verifies its integrity.
307356
/// Returns Ok(Some(block, size)) if the block was present

stackslib/src/net/api/getblock_v3.rs

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,6 @@ pub struct RPCNakamotoBlockRequestHandler {
4646
pub block_id: Option<StacksBlockId>,
4747
}
4848

49-
impl RPCNakamotoBlockRequestHandler {
50-
pub fn new() -> Self {
51-
Self { block_id: None }
52-
}
53-
}
54-
5549
pub struct NakamotoBlockStream {
5650
/// index block hash of the block to download
5751
pub index_block_hash: StacksBlockId,
@@ -69,6 +63,27 @@ pub struct NakamotoBlockStream {
6963
pub rowid: i64,
7064
}
7165

66+
impl RPCNakamotoBlockRequestHandler {
67+
pub fn new() -> Self {
68+
Self { block_id: None }
69+
}
70+
71+
pub fn get_stream_by_node(
72+
block_id: &StacksBlockId,
73+
node: &mut StacksNodeState,
74+
) -> Result<NakamotoBlockStream, ChainError> {
75+
node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| {
76+
let Some((tenure_id, parent_block_id)) = chainstate
77+
.nakamoto_blocks_db()
78+
.get_tenure_and_parent_block_id(&block_id)?
79+
else {
80+
return Err(ChainError::NoSuchBlockError);
81+
};
82+
NakamotoBlockStream::new(chainstate, block_id.clone(), tenure_id, parent_block_id)
83+
})
84+
}
85+
}
86+
7287
impl NakamotoBlockStream {
7388
pub fn new(
7489
chainstate: &StacksChainState,
@@ -179,19 +194,8 @@ impl RPCRequestHandler for RPCNakamotoBlockRequestHandler {
179194
.take()
180195
.ok_or(NetError::SendError("Missing `block_id`".into()))?;
181196

182-
let stream_res =
183-
node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| {
184-
let Some((tenure_id, parent_block_id)) = chainstate
185-
.nakamoto_blocks_db()
186-
.get_tenure_and_parent_block_id(&block_id)?
187-
else {
188-
return Err(ChainError::NoSuchBlockError);
189-
};
190-
NakamotoBlockStream::new(chainstate, block_id.clone(), tenure_id, parent_block_id)
191-
});
192-
193197
// start loading up the block
194-
let stream = match stream_res {
198+
let stream = match Self::get_stream_by_node(&block_id, node) {
195199
Ok(stream) => stream,
196200
Err(ChainError::NoSuchBlockError) => {
197201
return StacksHttpResponse::new_error(
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2+
// Copyright (C) 2020-2024 Stacks Open Internet Foundation
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
use std::io::{Read, Seek, SeekFrom, Write};
18+
use std::{fs, io};
19+
20+
use regex::{Captures, Regex};
21+
use rusqlite::Connection;
22+
use serde::de::Error as de_Error;
23+
use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN};
24+
use stacks_common::types::chainstate::{BlockHeaderHash, ConsensusHash, StacksBlockId};
25+
use stacks_common::types::net::PeerHost;
26+
use {serde, serde_json};
27+
28+
use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState, NakamotoStagingBlocksConn};
29+
use crate::chainstate::stacks::db::StacksChainState;
30+
use crate::chainstate::stacks::Error as ChainError;
31+
use crate::net::api::getblock_v3::{NakamotoBlockStream, RPCNakamotoBlockRequestHandler};
32+
use crate::net::http::{
33+
parse_bytes, Error, HttpBadRequest, HttpChunkGenerator, HttpContentType, HttpNotFound,
34+
HttpRequest, HttpRequestContents, HttpRequestPreamble, HttpResponse, HttpResponseContents,
35+
HttpResponsePayload, HttpResponsePreamble, HttpServerError, HttpVersion,
36+
};
37+
use crate::net::httpcore::{
38+
HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp, StacksHttpRequest,
39+
StacksHttpResponse,
40+
};
41+
use crate::net::{Error as NetError, StacksNodeState, TipRequest, MAX_HEADERS};
42+
use crate::util_lib::db::{DBConn, Error as DBError};
43+
44+
#[derive(Clone)]
45+
pub struct RPCNakamotoBlockByHashRequestHandler {
46+
pub block_hash: Option<BlockHeaderHash>,
47+
}
48+
49+
impl RPCNakamotoBlockByHashRequestHandler {
50+
pub fn new() -> Self {
51+
Self { block_hash: None }
52+
}
53+
}
54+
55+
/// Decode the HTTP request
56+
impl HttpRequest for RPCNakamotoBlockByHashRequestHandler {
57+
fn verb(&self) -> &'static str {
58+
"GET"
59+
}
60+
61+
fn path_regex(&self) -> Regex {
62+
Regex::new(r#"^/v3/blockbyhash/(?P<block_hash>[0-9a-f]{64})$"#).unwrap()
63+
}
64+
65+
fn metrics_identifier(&self) -> &str {
66+
"/v3/blockbyhash/:block_hash"
67+
}
68+
69+
/// Try to decode this request.
70+
/// There's nothing to load here, so just make sure the request is well-formed.
71+
fn try_parse_request(
72+
&mut self,
73+
preamble: &HttpRequestPreamble,
74+
captures: &Captures,
75+
query: Option<&str>,
76+
_body: &[u8],
77+
) -> Result<HttpRequestContents, Error> {
78+
if preamble.get_content_length() != 0 {
79+
return Err(Error::DecodeError(
80+
"Invalid Http request: expected 0-length body".to_string(),
81+
));
82+
}
83+
84+
let block_hash_str = captures
85+
.name("block_hash")
86+
.ok_or_else(|| {
87+
Error::DecodeError("Failed to match path to block hash group".to_string())
88+
})?
89+
.as_str();
90+
91+
let block_hash = BlockHeaderHash::from_hex(block_hash_str).map_err(|_| {
92+
Error::DecodeError("Invalid path: unparseable consensus hash".to_string())
93+
})?;
94+
self.block_hash = Some(block_hash);
95+
96+
Ok(HttpRequestContents::new().query_string(query))
97+
}
98+
}
99+
100+
impl RPCRequestHandler for RPCNakamotoBlockByHashRequestHandler {
101+
/// Reset internal state
102+
fn restart(&mut self) {
103+
self.block_hash = None;
104+
}
105+
106+
/// Make the response
107+
fn try_handle_request(
108+
&mut self,
109+
preamble: HttpRequestPreamble,
110+
_contents: HttpRequestContents,
111+
node: &mut StacksNodeState,
112+
) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
113+
let block_hash = self
114+
.block_hash
115+
.take()
116+
.ok_or(NetError::SendError("Missing `block_hash`".into()))?;
117+
118+
let index_block_hash_res =
119+
node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| {
120+
chainstate
121+
.nakamoto_blocks_db()
122+
.get_block_id_by_block_hash(&block_hash)
123+
});
124+
125+
let block_id = match index_block_hash_res {
126+
Ok(index_block_hash_opt) => match index_block_hash_opt {
127+
Some(index_block_hash) => index_block_hash,
128+
None => {
129+
// block hash not found
130+
let msg = format!("No such block {:?}\n", &block_hash);
131+
warn!("{}", &msg);
132+
return StacksHttpResponse::new_error(&preamble, &HttpNotFound::new(msg))
133+
.try_into_contents()
134+
.map_err(NetError::from);
135+
}
136+
},
137+
Err(e) => {
138+
// error querying the db
139+
let msg = format!("Failed to load block {}: {:?}\n", &block_hash, &e);
140+
warn!("{}", &msg);
141+
return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg))
142+
.try_into_contents()
143+
.map_err(NetError::from);
144+
}
145+
};
146+
147+
// start loading up the block
148+
let stream = match RPCNakamotoBlockRequestHandler::get_stream_by_node(&block_id, node) {
149+
Ok(stream) => stream,
150+
Err(ChainError::NoSuchBlockError) => {
151+
return StacksHttpResponse::new_error(
152+
&preamble,
153+
&HttpNotFound::new(format!("No such block {:?}\n", &block_hash)),
154+
)
155+
.try_into_contents()
156+
.map_err(NetError::from)
157+
}
158+
Err(e) => {
159+
// nope -- error trying to check
160+
let msg = format!("Failed to load block {}: {:?}\n", &block_hash, &e);
161+
warn!("{}", &msg);
162+
return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg))
163+
.try_into_contents()
164+
.map_err(NetError::from);
165+
}
166+
};
167+
168+
let resp_preamble = HttpResponsePreamble::from_http_request_preamble(
169+
&preamble,
170+
200,
171+
"OK",
172+
None,
173+
HttpContentType::Bytes,
174+
);
175+
176+
Ok((
177+
resp_preamble,
178+
HttpResponseContents::from_stream(Box::new(stream)),
179+
))
180+
}
181+
}
182+
183+
/// Decode the HTTP response
184+
impl HttpResponse for RPCNakamotoBlockByHashRequestHandler {
185+
/// Decode this response from a byte stream. This is called by the client to decode this
186+
/// message
187+
fn try_parse_response(
188+
&self,
189+
preamble: &HttpResponsePreamble,
190+
body: &[u8],
191+
) -> Result<HttpResponsePayload, Error> {
192+
let bytes = parse_bytes(preamble, body, MAX_MESSAGE_LEN.into())?;
193+
Ok(HttpResponsePayload::Bytes(bytes))
194+
}
195+
}
196+
197+
impl StacksHttpRequest {
198+
pub fn new_get_nakamoto_block_by_hash(
199+
host: PeerHost,
200+
block_hash: BlockHeaderHash,
201+
) -> StacksHttpRequest {
202+
StacksHttpRequest::new_for_peer(
203+
host,
204+
"GET".into(),
205+
format!("/v3/blockbyhash/{}", &block_hash),
206+
HttpRequestContents::new(),
207+
)
208+
.expect("FATAL: failed to construct request from infallible data")
209+
}
210+
}

0 commit comments

Comments
 (0)