From dfee8f4d987e1e9667eb776ad64eaef7e52a9526 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Sat, 9 Nov 2024 06:40:53 +0100 Subject: [PATCH 01/33] added getblockbyhash and getblockbyheight RPC api calls --- .../src/chainstate/nakamoto/staging_blocks.rs | 49 ++++ stackslib/src/net/api/getblock_v3.rs | 40 ++-- stackslib/src/net/api/getblockbyhash.rs | 210 ++++++++++++++++++ stackslib/src/net/api/getblockbyheight.rs | 210 ++++++++++++++++++ stackslib/src/net/api/mod.rs | 4 + stackslib/src/net/api/tests/getblockbyhash.rs | 123 ++++++++++ .../src/net/api/tests/getblockbyheight.rs | 121 ++++++++++ stackslib/src/net/api/tests/mod.rs | 13 ++ 8 files changed, 752 insertions(+), 18 deletions(-) create mode 100644 stackslib/src/net/api/getblockbyhash.rs create mode 100644 stackslib/src/net/api/getblockbyheight.rs create mode 100644 stackslib/src/net/api/tests/getblockbyhash.rs create mode 100644 stackslib/src/net/api/tests/getblockbyheight.rs diff --git a/stackslib/src/chainstate/nakamoto/staging_blocks.rs b/stackslib/src/chainstate/nakamoto/staging_blocks.rs index 382c708850..972e83633a 100644 --- a/stackslib/src/chainstate/nakamoto/staging_blocks.rs +++ b/stackslib/src/chainstate/nakamoto/staging_blocks.rs @@ -282,6 +282,17 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { Ok(res) } + /// Get the rowid of a staging Nakamoto block by block_hash + pub fn get_nakamoto_block_by_hash_rowid( + &self, + block_hash: &StacksBlockId, + ) -> Result, ChainstateError> { + let sql = "SELECT rowid FROM nakamoto_staging_blocks WHERE block_hash = ?1"; + let args = params![block_hash]; + let res: Option = query_row(self, sql, args)?; + Ok(res) + } + /// Get the tenure and parent block ID of a staging block. /// Used for downloads pub fn get_tenure_and_parent_block_id( @@ -302,6 +313,44 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { .optional()?) } + /// Get the block ID of a staging block from block hash. + /// Used for downloads + pub fn get_block_id_by_block_hash( + &self, + block_hash: &BlockHeaderHash, + ) -> Result, ChainstateError> { + let sql = "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE block_hash = ?1"; + let args = params![block_hash]; + + let mut stmt = self.deref().prepare(sql)?; + Ok(stmt + .query_row(args, |row| { + let block_id: StacksBlockId = row.get(0)?; + + Ok(block_id) + }) + .optional()?) + } + + /// Get the block ID of a staging block from block height. + /// Used for downloads + pub fn get_block_id_by_block_height( + &self, + block_height: u64, + ) -> Result, ChainstateError> { + let sql = "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE height = ?1"; + let args = params![block_height]; + + let mut stmt = self.deref().prepare(sql)?; + Ok(stmt + .query_row(args, |row| { + let block_id: StacksBlockId = row.get(0)?; + + Ok(block_id) + }) + .optional()?) + } + /// Get a Nakamoto block by index block hash, as well as its size. /// Verifies its integrity. /// Returns Ok(Some(block, size)) if the block was present diff --git a/stackslib/src/net/api/getblock_v3.rs b/stackslib/src/net/api/getblock_v3.rs index 56e8063dda..058e39dd1a 100644 --- a/stackslib/src/net/api/getblock_v3.rs +++ b/stackslib/src/net/api/getblock_v3.rs @@ -46,12 +46,6 @@ pub struct RPCNakamotoBlockRequestHandler { pub block_id: Option, } -impl RPCNakamotoBlockRequestHandler { - pub fn new() -> Self { - Self { block_id: None } - } -} - pub struct NakamotoBlockStream { /// index block hash of the block to download pub index_block_hash: StacksBlockId, @@ -69,6 +63,27 @@ pub struct NakamotoBlockStream { pub rowid: i64, } +impl RPCNakamotoBlockRequestHandler { + pub fn new() -> Self { + Self { block_id: None } + } + + pub fn get_stream_by_node( + block_id: &StacksBlockId, + node: &mut StacksNodeState, + ) -> Result { + node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { + let Some((tenure_id, parent_block_id)) = chainstate + .nakamoto_blocks_db() + .get_tenure_and_parent_block_id(&block_id)? + else { + return Err(ChainError::NoSuchBlockError); + }; + NakamotoBlockStream::new(chainstate, block_id.clone(), tenure_id, parent_block_id) + }) + } +} + impl NakamotoBlockStream { pub fn new( chainstate: &StacksChainState, @@ -179,19 +194,8 @@ impl RPCRequestHandler for RPCNakamotoBlockRequestHandler { .take() .ok_or(NetError::SendError("Missing `block_id`".into()))?; - let stream_res = - node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { - let Some((tenure_id, parent_block_id)) = chainstate - .nakamoto_blocks_db() - .get_tenure_and_parent_block_id(&block_id)? - else { - return Err(ChainError::NoSuchBlockError); - }; - NakamotoBlockStream::new(chainstate, block_id.clone(), tenure_id, parent_block_id) - }); - // start loading up the block - let stream = match stream_res { + let stream = match Self::get_stream_by_node(&block_id, node) { Ok(stream) => stream, Err(ChainError::NoSuchBlockError) => { return StacksHttpResponse::new_error( diff --git a/stackslib/src/net/api/getblockbyhash.rs b/stackslib/src/net/api/getblockbyhash.rs new file mode 100644 index 0000000000..e27e34ab42 --- /dev/null +++ b/stackslib/src/net/api/getblockbyhash.rs @@ -0,0 +1,210 @@ +// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation +// Copyright (C) 2020-2024 Stacks Open Internet Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::io::{Read, Seek, SeekFrom, Write}; +use std::{fs, io}; + +use regex::{Captures, Regex}; +use rusqlite::Connection; +use serde::de::Error as de_Error; +use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; +use stacks_common::types::chainstate::{BlockHeaderHash, ConsensusHash, StacksBlockId}; +use stacks_common::types::net::PeerHost; +use {serde, serde_json}; + +use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState, NakamotoStagingBlocksConn}; +use crate::chainstate::stacks::db::StacksChainState; +use crate::chainstate::stacks::Error as ChainError; +use crate::net::api::getblock_v3::{NakamotoBlockStream, RPCNakamotoBlockRequestHandler}; +use crate::net::http::{ + parse_bytes, Error, HttpBadRequest, HttpChunkGenerator, HttpContentType, HttpNotFound, + HttpRequest, HttpRequestContents, HttpRequestPreamble, HttpResponse, HttpResponseContents, + HttpResponsePayload, HttpResponsePreamble, HttpServerError, HttpVersion, +}; +use crate::net::httpcore::{ + HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp, StacksHttpRequest, + StacksHttpResponse, +}; +use crate::net::{Error as NetError, StacksNodeState, TipRequest, MAX_HEADERS}; +use crate::util_lib::db::{DBConn, Error as DBError}; + +#[derive(Clone)] +pub struct RPCNakamotoBlockByHashRequestHandler { + pub block_hash: Option, +} + +impl RPCNakamotoBlockByHashRequestHandler { + pub fn new() -> Self { + Self { block_hash: None } + } +} + +/// Decode the HTTP request +impl HttpRequest for RPCNakamotoBlockByHashRequestHandler { + fn verb(&self) -> &'static str { + "GET" + } + + fn path_regex(&self) -> Regex { + Regex::new(r#"^/v3/blockbyhash/(?P[0-9a-f]{64})$"#).unwrap() + } + + fn metrics_identifier(&self) -> &str { + "/v3/blockbyhash/:block_hash" + } + + /// Try to decode this request. + /// There's nothing to load here, so just make sure the request is well-formed. + fn try_parse_request( + &mut self, + preamble: &HttpRequestPreamble, + captures: &Captures, + query: Option<&str>, + _body: &[u8], + ) -> Result { + if preamble.get_content_length() != 0 { + return Err(Error::DecodeError( + "Invalid Http request: expected 0-length body".to_string(), + )); + } + + let block_hash_str = captures + .name("block_hash") + .ok_or_else(|| { + Error::DecodeError("Failed to match path to block hash group".to_string()) + })? + .as_str(); + + let block_hash = BlockHeaderHash::from_hex(block_hash_str).map_err(|_| { + Error::DecodeError("Invalid path: unparseable consensus hash".to_string()) + })?; + self.block_hash = Some(block_hash); + + Ok(HttpRequestContents::new().query_string(query)) + } +} + +impl RPCRequestHandler for RPCNakamotoBlockByHashRequestHandler { + /// Reset internal state + fn restart(&mut self) { + self.block_hash = None; + } + + /// Make the response + fn try_handle_request( + &mut self, + preamble: HttpRequestPreamble, + _contents: HttpRequestContents, + node: &mut StacksNodeState, + ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> { + let block_hash = self + .block_hash + .take() + .ok_or(NetError::SendError("Missing `block_hash`".into()))?; + + let index_block_hash_res = + node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { + chainstate + .nakamoto_blocks_db() + .get_block_id_by_block_hash(&block_hash) + }); + + let block_id = match index_block_hash_res { + Ok(index_block_hash_opt) => match index_block_hash_opt { + Some(index_block_hash) => index_block_hash, + None => { + // block hash not found + let msg = format!("No such block {:?}\n", &block_hash); + warn!("{}", &msg); + return StacksHttpResponse::new_error(&preamble, &HttpNotFound::new(msg)) + .try_into_contents() + .map_err(NetError::from); + } + }, + Err(e) => { + // error querying the db + let msg = format!("Failed to load block {}: {:?}\n", &block_hash, &e); + warn!("{}", &msg); + return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg)) + .try_into_contents() + .map_err(NetError::from); + } + }; + + // start loading up the block + let stream = match RPCNakamotoBlockRequestHandler::get_stream_by_node(&block_id, node) { + Ok(stream) => stream, + Err(ChainError::NoSuchBlockError) => { + return StacksHttpResponse::new_error( + &preamble, + &HttpNotFound::new(format!("No such block {:?}\n", &block_hash)), + ) + .try_into_contents() + .map_err(NetError::from) + } + Err(e) => { + // nope -- error trying to check + let msg = format!("Failed to load block {}: {:?}\n", &block_hash, &e); + warn!("{}", &msg); + return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg)) + .try_into_contents() + .map_err(NetError::from); + } + }; + + let resp_preamble = HttpResponsePreamble::from_http_request_preamble( + &preamble, + 200, + "OK", + None, + HttpContentType::Bytes, + ); + + Ok(( + resp_preamble, + HttpResponseContents::from_stream(Box::new(stream)), + )) + } +} + +/// Decode the HTTP response +impl HttpResponse for RPCNakamotoBlockByHashRequestHandler { + /// Decode this response from a byte stream. This is called by the client to decode this + /// message + fn try_parse_response( + &self, + preamble: &HttpResponsePreamble, + body: &[u8], + ) -> Result { + let bytes = parse_bytes(preamble, body, MAX_MESSAGE_LEN.into())?; + Ok(HttpResponsePayload::Bytes(bytes)) + } +} + +impl StacksHttpRequest { + pub fn new_get_nakamoto_block_by_hash( + host: PeerHost, + block_hash: BlockHeaderHash, + ) -> StacksHttpRequest { + StacksHttpRequest::new_for_peer( + host, + "GET".into(), + format!("/v3/blockbyhash/{}", &block_hash), + HttpRequestContents::new(), + ) + .expect("FATAL: failed to construct request from infallible data") + } +} diff --git a/stackslib/src/net/api/getblockbyheight.rs b/stackslib/src/net/api/getblockbyheight.rs new file mode 100644 index 0000000000..2a2781175b --- /dev/null +++ b/stackslib/src/net/api/getblockbyheight.rs @@ -0,0 +1,210 @@ +// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation +// Copyright (C) 2020-2024 Stacks Open Internet Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::io::{Read, Seek, SeekFrom, Write}; +use std::{fs, io}; + +use regex::{Captures, Regex}; +use rusqlite::Connection; +use serde::de::Error as de_Error; +use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; +use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId}; +use stacks_common::types::net::PeerHost; +use {serde, serde_json}; + +use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState, NakamotoStagingBlocksConn}; +use crate::chainstate::stacks::db::StacksChainState; +use crate::chainstate::stacks::Error as ChainError; +use crate::net::api::getblock_v3::{NakamotoBlockStream, RPCNakamotoBlockRequestHandler}; +use crate::net::http::{ + parse_bytes, Error, HttpBadRequest, HttpChunkGenerator, HttpContentType, HttpNotFound, + HttpRequest, HttpRequestContents, HttpRequestPreamble, HttpResponse, HttpResponseContents, + HttpResponsePayload, HttpResponsePreamble, HttpServerError, HttpVersion, +}; +use crate::net::httpcore::{ + HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp, StacksHttpRequest, + StacksHttpResponse, +}; +use crate::net::{Error as NetError, StacksNodeState, TipRequest, MAX_HEADERS}; +use crate::util_lib::db::{DBConn, Error as DBError}; + +#[derive(Clone)] +pub struct RPCNakamotoBlockByHeightRequestHandler { + pub block_height: Option, +} + +impl RPCNakamotoBlockByHeightRequestHandler { + pub fn new() -> Self { + Self { block_height: None } + } +} + +/// Decode the HTTP request +impl HttpRequest for RPCNakamotoBlockByHeightRequestHandler { + fn verb(&self) -> &'static str { + "GET" + } + + fn path_regex(&self) -> Regex { + Regex::new(r#"^/v3/blockbyheight/(?P[0-9]{1,20})$"#).unwrap() + } + + fn metrics_identifier(&self) -> &str { + "/v3/blockbyheight/:block_height" + } + + /// Try to decode this request. + /// There's nothing to load here, so just make sure the request is well-formed. + fn try_parse_request( + &mut self, + preamble: &HttpRequestPreamble, + captures: &Captures, + query: Option<&str>, + _body: &[u8], + ) -> Result { + if preamble.get_content_length() != 0 { + return Err(Error::DecodeError( + "Invalid Http request: expected 0-length body".to_string(), + )); + } + + let block_height_str = captures + .name("block_height") + .ok_or_else(|| { + Error::DecodeError("Failed to match path to block height group".to_string()) + })? + .as_str(); + + let block_height = block_height_str.parse::().map_err(|_| { + Error::DecodeError("Invalid path: unparseable block height".to_string()) + })?; + self.block_height = Some(block_height); + + Ok(HttpRequestContents::new().query_string(query)) + } +} + +impl RPCRequestHandler for RPCNakamotoBlockByHeightRequestHandler { + /// Reset internal state + fn restart(&mut self) { + self.block_height = None; + } + + /// Make the response + fn try_handle_request( + &mut self, + preamble: HttpRequestPreamble, + _contents: HttpRequestContents, + node: &mut StacksNodeState, + ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> { + let block_height = self + .block_height + .take() + .ok_or(NetError::SendError("Missing `block_height`".into()))?; + + let index_block_hash_res = + node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { + chainstate + .nakamoto_blocks_db() + .get_block_id_by_block_height(block_height) + }); + + let block_id = match index_block_hash_res { + Ok(index_block_hash_opt) => match index_block_hash_opt { + Some(index_block_hash) => index_block_hash, + None => { + // block hash not found + let msg = format!("No such block #{:?}\n", block_height); + warn!("{}", &msg); + return StacksHttpResponse::new_error(&preamble, &HttpNotFound::new(msg)) + .try_into_contents() + .map_err(NetError::from); + } + }, + Err(e) => { + // error querying the db + let msg = format!("Failed to load block #{}: {:?}\n", block_height, &e); + warn!("{}", &msg); + return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg)) + .try_into_contents() + .map_err(NetError::from); + } + }; + + // start loading up the block + let stream = match RPCNakamotoBlockRequestHandler::get_stream_by_node(&block_id, node) { + Ok(stream) => stream, + Err(ChainError::NoSuchBlockError) => { + return StacksHttpResponse::new_error( + &preamble, + &HttpNotFound::new(format!("No such block #{:?}\n", &block_height)), + ) + .try_into_contents() + .map_err(NetError::from) + } + Err(e) => { + // nope -- error trying to check + let msg = format!("Failed to load block #{}: {:?}\n", block_height, &e); + warn!("{}", &msg); + return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg)) + .try_into_contents() + .map_err(NetError::from); + } + }; + + let resp_preamble = HttpResponsePreamble::from_http_request_preamble( + &preamble, + 200, + "OK", + None, + HttpContentType::Bytes, + ); + + Ok(( + resp_preamble, + HttpResponseContents::from_stream(Box::new(stream)), + )) + } +} + +/// Decode the HTTP response +impl HttpResponse for RPCNakamotoBlockByHeightRequestHandler { + /// Decode this response from a byte stream. This is called by the client to decode this + /// message + fn try_parse_response( + &self, + preamble: &HttpResponsePreamble, + body: &[u8], + ) -> Result { + let bytes = parse_bytes(preamble, body, MAX_MESSAGE_LEN.into())?; + Ok(HttpResponsePayload::Bytes(bytes)) + } +} + +impl StacksHttpRequest { + pub fn new_get_nakamoto_block_by_height( + host: PeerHost, + block_height: u64, + ) -> StacksHttpRequest { + StacksHttpRequest::new_for_peer( + host, + "GET".into(), + format!("/v3/blockbyheight/{}", block_height), + HttpRequestContents::new(), + ) + .expect("FATAL: failed to construct request from infallible data") + } +} diff --git a/stackslib/src/net/api/mod.rs b/stackslib/src/net/api/mod.rs index 72aa417204..673bbf260a 100644 --- a/stackslib/src/net/api/mod.rs +++ b/stackslib/src/net/api/mod.rs @@ -42,6 +42,8 @@ pub mod getattachment; pub mod getattachmentsinv; pub mod getblock; pub mod getblock_v3; +pub mod getblockbyhash; +pub mod getblockbyheight; pub mod getconstantval; pub mod getcontractabi; pub mod getcontractsrc; @@ -92,6 +94,8 @@ impl StacksHttp { self.register_rpc_endpoint(getattachmentsinv::RPCGetAttachmentsInvRequestHandler::new()); self.register_rpc_endpoint(getblock::RPCBlocksRequestHandler::new()); self.register_rpc_endpoint(getblock_v3::RPCNakamotoBlockRequestHandler::new()); + self.register_rpc_endpoint(getblockbyhash::RPCNakamotoBlockByHashRequestHandler::new()); + self.register_rpc_endpoint(getblockbyheight::RPCNakamotoBlockByHeightRequestHandler::new()); self.register_rpc_endpoint(getconstantval::RPCGetConstantValRequestHandler::new()); self.register_rpc_endpoint(getcontractabi::RPCGetContractAbiRequestHandler::new()); self.register_rpc_endpoint(getcontractsrc::RPCGetContractSrcRequestHandler::new()); diff --git a/stackslib/src/net/api/tests/getblockbyhash.rs b/stackslib/src/net/api/tests/getblockbyhash.rs new file mode 100644 index 0000000000..443587d83b --- /dev/null +++ b/stackslib/src/net/api/tests/getblockbyhash.rs @@ -0,0 +1,123 @@ +// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation +// Copyright (C) 2020-2024 Stacks Open Internet Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions}; +use clarity::vm::{ClarityName, ContractName}; +use stacks_common::codec::StacksMessageCodec; +use stacks_common::types::chainstate::{ + BlockHeaderHash, ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, +}; +use stacks_common::types::net::PeerHost; +use stacks_common::types::Address; + +use super::TestRPC; +use crate::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandle}; +use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState}; +use crate::chainstate::stacks::db::blocks::test::*; +use crate::chainstate::stacks::db::StacksChainState; +use crate::chainstate::stacks::{ + Error as chainstate_error, StacksBlock, StacksBlockHeader, StacksMicroblock, +}; +use crate::net::api::getblock_v3::NakamotoBlockStream; +use crate::net::api::*; +use crate::net::connection::ConnectionOptions; +use crate::net::http::HttpChunkGenerator; +use crate::net::httpcore::{ + HttpPreambleExtensions, HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp, + StacksHttpRequest, +}; +use crate::net::test::TestEventObserver; +use crate::net::tests::inv::nakamoto::make_nakamoto_peer_from_invs; +use crate::net::{ProtocolFamily, TipRequest}; +use crate::util_lib::db::DBConn; + +#[test] +fn test_try_parse_request() { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); + let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default()); + + let request = + StacksHttpRequest::new_get_nakamoto_block_by_hash(addr.into(), BlockHeaderHash([0x11; 32])); + let bytes = request.try_serialize().unwrap(); + + debug!("Request:\n{}\n", std::str::from_utf8(&bytes).unwrap()); + + let (parsed_preamble, offset) = http.read_preamble(&bytes).unwrap(); + let mut handler = getblockbyhash::RPCNakamotoBlockByHashRequestHandler::new(); + let mut parsed_request = http + .handle_try_parse_request( + &mut handler, + &parsed_preamble.expect_request(), + &bytes[offset..], + ) + .unwrap(); + + // parsed request consumes headers that would not be in a constructed reqeuest + parsed_request.clear_headers(); + let (preamble, contents) = parsed_request.destruct(); + + // consumed path args + assert_eq!(handler.block_hash, Some(BlockHeaderHash([0x11; 32]))); + + assert_eq!(&preamble, request.preamble()); + + handler.restart(); + assert!(handler.block_hash.is_none()); +} + +#[test] +fn test_try_make_response() { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); + + let test_observer = TestEventObserver::new(); + let rpc_test = TestRPC::setup_nakamoto(function_name!(), &test_observer); + + let nakamoto_chain_tip_hash = rpc_test.tip_hash.clone(); + let consensus_hash = rpc_test.consensus_hash.clone(); + + let mut requests = vec![]; + + // query existing block + let request = + StacksHttpRequest::new_get_nakamoto_block_by_hash(addr.into(), nakamoto_chain_tip_hash.clone()); + requests.push(request); + + // query non-existant block + let request = + StacksHttpRequest::new_get_nakamoto_block_by_hash(addr.into(), BlockHeaderHash([0x11; 32])); + requests.push(request); + + let mut responses = rpc_test.run(requests); + + // got the block + let response = responses.remove(0); + let resp = response.decode_nakamoto_block().unwrap(); + + assert_eq!( + resp.header.block_hash(), + nakamoto_chain_tip_hash + ); + + assert_eq!(resp.header.consensus_hash, consensus_hash); + + // no block + let response = responses.remove(0); + let (preamble, body) = response.destruct(); + + assert_eq!(preamble.status_code, 404); +} diff --git a/stackslib/src/net/api/tests/getblockbyheight.rs b/stackslib/src/net/api/tests/getblockbyheight.rs new file mode 100644 index 0000000000..4c1896ea97 --- /dev/null +++ b/stackslib/src/net/api/tests/getblockbyheight.rs @@ -0,0 +1,121 @@ +// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation +// Copyright (C) 2020-2024 Stacks Open Internet Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions}; +use clarity::vm::{ClarityName, ContractName}; +use stacks_common::codec::StacksMessageCodec; +use stacks_common::types::chainstate::{ + BlockHeaderHash, ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, +}; +use stacks_common::types::net::PeerHost; +use stacks_common::types::Address; + +use super::TestRPC; +use crate::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandle}; +use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState}; +use crate::chainstate::stacks::db::blocks::test::*; +use crate::chainstate::stacks::db::StacksChainState; +use crate::chainstate::stacks::{ + Error as chainstate_error, StacksBlock, StacksBlockHeader, StacksMicroblock, +}; +use crate::net::api::getblock_v3::NakamotoBlockStream; +use crate::net::api::*; +use crate::net::connection::ConnectionOptions; +use crate::net::http::HttpChunkGenerator; +use crate::net::httpcore::{ + HttpPreambleExtensions, HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp, + StacksHttpRequest, +}; +use crate::net::test::TestEventObserver; +use crate::net::tests::inv::nakamoto::make_nakamoto_peer_from_invs; +use crate::net::{ProtocolFamily, TipRequest}; +use crate::util_lib::db::DBConn; + +#[test] +fn test_try_parse_request() { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); + let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default()); + + let request = + StacksHttpRequest::new_get_nakamoto_block_by_height(addr.into(), 0x7ffffffffffffffe); + let bytes = request.try_serialize().unwrap(); + + debug!("Request:\n{}\n", std::str::from_utf8(&bytes).unwrap()); + + let (parsed_preamble, offset) = http.read_preamble(&bytes).unwrap(); + let mut handler = getblockbyheight::RPCNakamotoBlockByHeightRequestHandler::new(); + let mut parsed_request = http + .handle_try_parse_request( + &mut handler, + &parsed_preamble.expect_request(), + &bytes[offset..], + ) + .unwrap(); + + // parsed request consumes headers that would not be in a constructed reqeuest + parsed_request.clear_headers(); + let (preamble, contents) = parsed_request.destruct(); + + // consumed path args + assert_eq!(handler.block_height, Some(0x7ffffffffffffffe)); + + assert_eq!(&preamble, request.preamble()); + + handler.restart(); + assert!(handler.block_height.is_none()); +} + +#[test] +fn test_try_make_response() { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); + + let test_observer = TestEventObserver::new(); + let rpc_test = TestRPC::setup_nakamoto(function_name!(), &test_observer); + + let nakamoto_chain_tip_height = rpc_test.tip_height.clone(); + let consensus_hash = rpc_test.consensus_hash.clone(); + + let mut requests = vec![]; + + // query existing block + let request = StacksHttpRequest::new_get_nakamoto_block_by_height( + addr.into(), + nakamoto_chain_tip_height, + ); + requests.push(request); + + // query non-existant block + let request = + StacksHttpRequest::new_get_nakamoto_block_by_height(addr.into(), 0x7ffffffffffffffe); + requests.push(request); + + let mut responses = rpc_test.run(requests); + + // got the block + let response = responses.remove(0); + let resp = response.decode_nakamoto_block().unwrap(); + + assert_eq!(resp.header.consensus_hash, consensus_hash); + + // no block + let response = responses.remove(0); + let (preamble, body) = response.destruct(); + + assert_eq!(preamble.status_code, 404); +} + diff --git a/stackslib/src/net/api/tests/mod.rs b/stackslib/src/net/api/tests/mod.rs index d19854bf02..0f24bf4da2 100644 --- a/stackslib/src/net/api/tests/mod.rs +++ b/stackslib/src/net/api/tests/mod.rs @@ -60,6 +60,8 @@ mod getattachment; mod getattachmentsinv; mod getblock; mod getblock_v3; +mod getblockbyhash; +mod getblockbyheight; mod getconstantval; mod getcontractabi; mod getcontractsrc; @@ -199,6 +201,10 @@ pub struct TestRPC<'a> { pub convo_2: ConversationHttp, /// hash of the chain tip pub canonical_tip: StacksBlockId, + /// block header hash of the chain tip + pub tip_hash: BlockHeaderHash, + /// block height of the chain tip + pub tip_height: u64, /// consensus hash of the chain tip pub consensus_hash: ConsensusHash, /// hash of last microblock @@ -515,6 +521,7 @@ impl<'a> TestRPC<'a> { let microblock_txids = microblock.txs.iter().map(|tx| tx.txid()).collect(); let canonical_tip = StacksBlockHeader::make_index_block_hash(&consensus_hash, &stacks_block.block_hash()); + let tip_hash = stacks_block.block_hash(); if process_microblock { // store microblock stream @@ -812,6 +819,8 @@ impl<'a> TestRPC<'a> { 32, ); + let tip_height : u64 = 1; + TestRPC { privk1, privk2, @@ -822,6 +831,8 @@ impl<'a> TestRPC<'a> { convo_1, convo_2, canonical_tip, + tip_hash, + tip_height, consensus_hash, microblock_tip_hash: microblock.block_hash(), mempool_txids, @@ -909,6 +920,8 @@ impl<'a> TestRPC<'a> { convo_2, canonical_tip: nakamoto_tip.index_block_hash(), consensus_hash: nakamoto_tip.consensus_hash.clone(), + tip_hash: nakamoto_tip.anchored_header.block_hash(), + tip_height: nakamoto_tip.stacks_block_height, microblock_tip_hash: BlockHeaderHash([0x00; 32]), mempool_txids: vec![], microblock_txids: vec![], From a98fdb6a1f36c9c10cd63b2005b92f646ba05955 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Mon, 11 Nov 2024 11:21:07 +0100 Subject: [PATCH 02/33] removed useless get_nakamoto_block_by_hash_rowid and added notes about height test value --- stackslib/src/chainstate/nakamoto/staging_blocks.rs | 11 ----------- stackslib/src/net/api/tests/getblockbyheight.rs | 3 ++- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/staging_blocks.rs b/stackslib/src/chainstate/nakamoto/staging_blocks.rs index 972e83633a..ee295607a9 100644 --- a/stackslib/src/chainstate/nakamoto/staging_blocks.rs +++ b/stackslib/src/chainstate/nakamoto/staging_blocks.rs @@ -282,17 +282,6 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { Ok(res) } - /// Get the rowid of a staging Nakamoto block by block_hash - pub fn get_nakamoto_block_by_hash_rowid( - &self, - block_hash: &StacksBlockId, - ) -> Result, ChainstateError> { - let sql = "SELECT rowid FROM nakamoto_staging_blocks WHERE block_hash = ?1"; - let args = params![block_hash]; - let res: Option = query_row(self, sql, args)?; - Ok(res) - } - /// Get the tenure and parent block ID of a staging block. /// Used for downloads pub fn get_tenure_and_parent_block_id( diff --git a/stackslib/src/net/api/tests/getblockbyheight.rs b/stackslib/src/net/api/tests/getblockbyheight.rs index 4c1896ea97..b9234ced12 100644 --- a/stackslib/src/net/api/tests/getblockbyheight.rs +++ b/stackslib/src/net/api/tests/getblockbyheight.rs @@ -51,6 +51,7 @@ fn test_try_parse_request() { let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default()); + // NOTE: sqlite stores the height as a signed 64bit integer, so we are using the biggest positive value - 1 let request = StacksHttpRequest::new_get_nakamoto_block_by_height(addr.into(), 0x7ffffffffffffffe); let bytes = request.try_serialize().unwrap(); @@ -99,7 +100,7 @@ fn test_try_make_response() { ); requests.push(request); - // query non-existant block + // query non-existant block (with biggest positive i64 value - 1) let request = StacksHttpRequest::new_get_nakamoto_block_by_height(addr.into(), 0x7ffffffffffffffe); requests.push(request); From c7c899e5f442e8a6e3b8f1dea1acf29a07b39936 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Mon, 11 Nov 2024 11:34:12 +0100 Subject: [PATCH 03/33] updated docs for /v3/blockbyhash and /v3/blockbyheight --- docs/rpc-endpoints.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/rpc-endpoints.md b/docs/rpc-endpoints.md index 9f0e09fd20..28cfeda54d 100644 --- a/docs/rpc-endpoints.md +++ b/docs/rpc-endpoints.md @@ -503,6 +503,20 @@ data. This will return 404 if the block does not exist. +### GET /v3/blockbyhash/[Block Hash] + +Fetch a Nakamoto block given its block hash. This returns the raw block +data. + +This will return 404 if the block does not exist. + +### GET /v3/blockbyheight/[Block Height] + +Fetch a Nakamoto block given its block height. This returns the raw block +data. + +This will return 404 if the block does not exist. + ### GET /v3/tenures/[Block ID] Fetch a Nakamoto block and all of its ancestors in the same tenure, given its From 24a9c6180f12165092d99ffa2335f6ead3aea912 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Mon, 11 Nov 2024 11:37:20 +0100 Subject: [PATCH 04/33] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e66d126390..a61c025f7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE - Add index to `metadata_table` in Clarity DB on `blockhash` - Add `block_commit_delay_ms` to the config file to control the time to wait after seeing a new burn block, before submitting a block commit, to allow time for the first Nakamoto block of the new tenure to be mined, allowing this miner to avoid the need to RBF the block commit. - Add `tenure_cost_limit_per_block_percentage` to the miner config file to control the percentage remaining tenure cost limit to consume per nakamoto block. +- Add `/v3/blockbyhash` and `/v3/blockbyheight` rpc endpoints ## [3.0.0.0.1] From 94b85c372fadc24d5faec5be241ef9675f8c357b Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Mon, 11 Nov 2024 13:09:40 +0100 Subject: [PATCH 05/33] added integration tests for blockbyhash and blockbyheight --- .../src/tests/nakamoto_integrations.rs | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 81ab236066..1a62d2dc85 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -9303,6 +9303,249 @@ fn v3_signer_api_endpoint() { run_loop_thread.join().unwrap(); } +/// Test `/v3/blockbyhash` API endpoint +/// +/// This endpoint returns the block blob given a height +#[test] +#[ignore] +fn v3_blockbyhash_api_endpoint() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let (mut conf, _miner_account) = naka_neon_integration_conf(None); + let password = "12345".to_string(); + conf.connection_options.auth_token = Some(password.clone()); + conf.miner.wait_on_interim_blocks = Duration::from_secs(1); + let stacker_sk = setup_stacker(&mut conf); + let signer_sk = Secp256k1PrivateKey::new(); + let signer_addr = tests::to_addr(&signer_sk); + let sender_sk = Secp256k1PrivateKey::new(); + // setup sender + recipient for some test stx transfers + // these are necessary for the interim blocks to get mined at all + let sender_addr = tests::to_addr(&sender_sk); + let send_amt = 100; + let send_fee = 180; + conf.add_initial_balance( + PrincipalData::from(sender_addr).to_string(), + send_amt + send_fee, + ); + conf.add_initial_balance(PrincipalData::from(signer_addr).to_string(), 100000); + + // only subscribe to the block proposal events + test_observer::spawn(); + test_observer::register(&mut conf, &[EventKeyType::BlockProposal]); + + let mut btcd_controller = BitcoinCoreController::new(conf.clone()); + btcd_controller + .start_bitcoind() + .expect("Failed starting bitcoind"); + let mut btc_regtest_controller = BitcoinRegtestController::new(conf.clone(), None); + btc_regtest_controller.bootstrap_chain(201); + + let mut run_loop = boot_nakamoto::BootRunLoop::new(conf.clone()).unwrap(); + let run_loop_stopper = run_loop.get_termination_switch(); + let Counters { + blocks_processed, + naka_submitted_commits: commits_submitted, + naka_proposed_blocks: proposals_submitted, + .. + } = run_loop.counters(); + + let coord_channel = run_loop.coordinator_channels(); + + let run_loop_thread = thread::spawn(move || run_loop.start(None, 0)); + let mut signers = TestSigners::new(vec![signer_sk]); + wait_for_runloop(&blocks_processed); + boot_to_epoch_3( + &conf, + &blocks_processed, + &[stacker_sk], + &[signer_sk], + &mut Some(&mut signers), + &mut btc_regtest_controller, + ); + + info!("------------------------- Reached Epoch 3.0 -------------------------"); + + blind_signer(&conf, &signers, proposals_submitted); + + wait_for_first_naka_block_commit(60, &commits_submitted); + + // Mine 1 nakamoto tenure + next_block_and_mine_commit( + &mut btc_regtest_controller, + 60, + &coord_channel, + &commits_submitted, + ) + .unwrap(); + + let burnchain = conf.get_burnchain(); + let sortdb = burnchain.open_sortition_db(true).unwrap(); + let (chainstate, _) = StacksChainState::open( + conf.is_mainnet(), + conf.burnchain.chain_id, + &conf.get_chainstate_path_str(), + None, + ) + .unwrap(); + + info!("------------------------- Setup finished, run test -------------------------"); + + let http_origin = format!("http://{}", &conf.node.rpc_bind); + + let get_v3_block_by_hash = |block_hash: &str| { + let url = &format!( + "{http_origin}/v3/blockbyhash/{block_hash}" + ); + info!("Send request: GET {url}"); + reqwest::blocking::get(url) + .unwrap_or_else(|e| panic!("GET request failed: {e}")) + }; + + let tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb) + .unwrap() + .unwrap(); + + let block_hash = tip.anchored_header.as_stacks_nakamoto().unwrap().block_hash().to_string(); + let block_data = get_v3_block_by_hash(&block_hash); + assert!(block_data.status().is_success()); + assert!(block_data.bytes().unwrap().len() > 0); + + info!("------------------------- Test finished, clean up -------------------------"); + + coord_channel + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + run_loop_stopper.store(false, Ordering::SeqCst); + + run_loop_thread.join().unwrap(); +} + + +/// Test `/v3/blockbyheight` API endpoint +/// +/// This endpoint returns the block blob given a height +#[test] +#[ignore] +fn v3_blockbyheight_api_endpoint() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let (mut conf, _miner_account) = naka_neon_integration_conf(None); + let password = "12345".to_string(); + conf.connection_options.auth_token = Some(password.clone()); + conf.miner.wait_on_interim_blocks = Duration::from_secs(1); + let stacker_sk = setup_stacker(&mut conf); + let signer_sk = Secp256k1PrivateKey::new(); + let signer_addr = tests::to_addr(&signer_sk); + let sender_sk = Secp256k1PrivateKey::new(); + // setup sender + recipient for some test stx transfers + // these are necessary for the interim blocks to get mined at all + let sender_addr = tests::to_addr(&sender_sk); + let send_amt = 100; + let send_fee = 180; + conf.add_initial_balance( + PrincipalData::from(sender_addr).to_string(), + send_amt + send_fee, + ); + conf.add_initial_balance(PrincipalData::from(signer_addr).to_string(), 100000); + + // only subscribe to the block proposal events + test_observer::spawn(); + test_observer::register(&mut conf, &[EventKeyType::BlockProposal]); + + let mut btcd_controller = BitcoinCoreController::new(conf.clone()); + btcd_controller + .start_bitcoind() + .expect("Failed starting bitcoind"); + let mut btc_regtest_controller = BitcoinRegtestController::new(conf.clone(), None); + btc_regtest_controller.bootstrap_chain(201); + + let mut run_loop = boot_nakamoto::BootRunLoop::new(conf.clone()).unwrap(); + let run_loop_stopper = run_loop.get_termination_switch(); + let Counters { + blocks_processed, + naka_submitted_commits: commits_submitted, + naka_proposed_blocks: proposals_submitted, + .. + } = run_loop.counters(); + + let coord_channel = run_loop.coordinator_channels(); + + let run_loop_thread = thread::spawn(move || run_loop.start(None, 0)); + let mut signers = TestSigners::new(vec![signer_sk]); + wait_for_runloop(&blocks_processed); + boot_to_epoch_3( + &conf, + &blocks_processed, + &[stacker_sk], + &[signer_sk], + &mut Some(&mut signers), + &mut btc_regtest_controller, + ); + + info!("------------------------- Reached Epoch 3.0 -------------------------"); + + blind_signer(&conf, &signers, proposals_submitted); + + wait_for_first_naka_block_commit(60, &commits_submitted); + + // Mine 1 nakamoto tenure + next_block_and_mine_commit( + &mut btc_regtest_controller, + 60, + &coord_channel, + &commits_submitted, + ) + .unwrap(); + + let burnchain = conf.get_burnchain(); + let sortdb = burnchain.open_sortition_db(true).unwrap(); + let (chainstate, _) = StacksChainState::open( + conf.is_mainnet(), + conf.burnchain.chain_id, + &conf.get_chainstate_path_str(), + None, + ) + .unwrap(); + + info!("------------------------- Setup finished, run test -------------------------"); + + let http_origin = format!("http://{}", &conf.node.rpc_bind); + + let get_v3_block_by_height = |height: u64| { + let url = &format!( + "{http_origin}/v3/blockbyheight/{height}" + ); + info!("Send request: GET {url}"); + reqwest::blocking::get(url) + .unwrap_or_else(|e| panic!("GET request failed: {e}")) + }; + + let tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb) + .unwrap() + .unwrap(); + + let block_height = tip.stacks_block_height; + let block_data = get_v3_block_by_height(block_height); + assert!(block_data.status().is_success()); + assert!(block_data.bytes().unwrap().len() > 0); + + info!("------------------------- Test finished, clean up -------------------------"); + + coord_channel + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + run_loop_stopper.store(false, Ordering::SeqCst); + + run_loop_thread.join().unwrap(); +} + #[test] #[ignore] /// This test spins up a nakamoto-neon node. From 4a6276f3a3b927e8305a5e4d97e03c83daf8d530 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Mon, 11 Nov 2024 13:12:04 +0100 Subject: [PATCH 06/33] added tests::nakamoto_integrations::v3_blockbyhash_api_endpoint and tests::nakamoto_integrations::v3_blockbyheight_api_endpoint to github workflows --- .github/workflows/bitcoin-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/bitcoin-tests.yml b/.github/workflows/bitcoin-tests.yml index 12094e88ee..5b10594c54 100644 --- a/.github/workflows/bitcoin-tests.yml +++ b/.github/workflows/bitcoin-tests.yml @@ -141,6 +141,8 @@ jobs: - tests::nakamoto_integrations::v3_signer_api_endpoint - tests::nakamoto_integrations::signer_chainstate - tests::nakamoto_integrations::clarity_cost_spend_down + - tests::nakamoto_integrations::v3_blockbyhash_api_endpoint + - tests::nakamoto_integrations::v3_blockbyheight_api_endpoint # TODO: enable these once v1 signer is supported by a new nakamoto epoch # - tests::signer::v1::dkg # - tests::signer::v1::sign_request_rejected From 8d462020685ee5d9375f8ef105adb5f7189b4485 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Mon, 11 Nov 2024 13:28:39 +0100 Subject: [PATCH 07/33] reformatted nakamoto_integrations.rs --- .../src/tests/nakamoto_integrations.rs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 1a62d2dc85..22b37bef51 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -9396,19 +9396,21 @@ fn v3_blockbyhash_api_endpoint() { let http_origin = format!("http://{}", &conf.node.rpc_bind); let get_v3_block_by_hash = |block_hash: &str| { - let url = &format!( - "{http_origin}/v3/blockbyhash/{block_hash}" - ); + let url = &format!("{http_origin}/v3/blockbyhash/{block_hash}"); info!("Send request: GET {url}"); - reqwest::blocking::get(url) - .unwrap_or_else(|e| panic!("GET request failed: {e}")) + reqwest::blocking::get(url).unwrap_or_else(|e| panic!("GET request failed: {e}")) }; let tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb) .unwrap() .unwrap(); - - let block_hash = tip.anchored_header.as_stacks_nakamoto().unwrap().block_hash().to_string(); + + let block_hash = tip + .anchored_header + .as_stacks_nakamoto() + .unwrap() + .block_hash() + .to_string(); let block_data = get_v3_block_by_hash(&block_hash); assert!(block_data.status().is_success()); assert!(block_data.bytes().unwrap().len() > 0); @@ -9424,7 +9426,6 @@ fn v3_blockbyhash_api_endpoint() { run_loop_thread.join().unwrap(); } - /// Test `/v3/blockbyheight` API endpoint /// /// This endpoint returns the block blob given a height @@ -9518,18 +9519,15 @@ fn v3_blockbyheight_api_endpoint() { let http_origin = format!("http://{}", &conf.node.rpc_bind); let get_v3_block_by_height = |height: u64| { - let url = &format!( - "{http_origin}/v3/blockbyheight/{height}" - ); + let url = &format!("{http_origin}/v3/blockbyheight/{height}"); info!("Send request: GET {url}"); - reqwest::blocking::get(url) - .unwrap_or_else(|e| panic!("GET request failed: {e}")) + reqwest::blocking::get(url).unwrap_or_else(|e| panic!("GET request failed: {e}")) }; let tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb) .unwrap() .unwrap(); - + let block_height = tip.stacks_block_height; let block_data = get_v3_block_by_height(block_height); assert!(block_data.status().is_success()); From cb97c2d013fc0df824c4dd52167f88d8538ac8b9 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Mon, 11 Nov 2024 13:33:37 +0100 Subject: [PATCH 08/33] reformat unit tests --- stackslib/src/net/api/tests/getblockbyhash.rs | 11 +++++------ stackslib/src/net/api/tests/getblockbyheight.rs | 7 ++----- stackslib/src/net/api/tests/mod.rs | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/stackslib/src/net/api/tests/getblockbyhash.rs b/stackslib/src/net/api/tests/getblockbyhash.rs index 443587d83b..3abc1c8ea6 100644 --- a/stackslib/src/net/api/tests/getblockbyhash.rs +++ b/stackslib/src/net/api/tests/getblockbyhash.rs @@ -93,8 +93,10 @@ fn test_try_make_response() { let mut requests = vec![]; // query existing block - let request = - StacksHttpRequest::new_get_nakamoto_block_by_hash(addr.into(), nakamoto_chain_tip_hash.clone()); + let request = StacksHttpRequest::new_get_nakamoto_block_by_hash( + addr.into(), + nakamoto_chain_tip_hash.clone(), + ); requests.push(request); // query non-existant block @@ -108,10 +110,7 @@ fn test_try_make_response() { let response = responses.remove(0); let resp = response.decode_nakamoto_block().unwrap(); - assert_eq!( - resp.header.block_hash(), - nakamoto_chain_tip_hash - ); + assert_eq!(resp.header.block_hash(), nakamoto_chain_tip_hash); assert_eq!(resp.header.consensus_hash, consensus_hash); diff --git a/stackslib/src/net/api/tests/getblockbyheight.rs b/stackslib/src/net/api/tests/getblockbyheight.rs index b9234ced12..2e562e1623 100644 --- a/stackslib/src/net/api/tests/getblockbyheight.rs +++ b/stackslib/src/net/api/tests/getblockbyheight.rs @@ -94,10 +94,8 @@ fn test_try_make_response() { let mut requests = vec![]; // query existing block - let request = StacksHttpRequest::new_get_nakamoto_block_by_height( - addr.into(), - nakamoto_chain_tip_height, - ); + let request = + StacksHttpRequest::new_get_nakamoto_block_by_height(addr.into(), nakamoto_chain_tip_height); requests.push(request); // query non-existant block (with biggest positive i64 value - 1) @@ -119,4 +117,3 @@ fn test_try_make_response() { assert_eq!(preamble.status_code, 404); } - diff --git a/stackslib/src/net/api/tests/mod.rs b/stackslib/src/net/api/tests/mod.rs index 0f24bf4da2..58cb827f17 100644 --- a/stackslib/src/net/api/tests/mod.rs +++ b/stackslib/src/net/api/tests/mod.rs @@ -819,7 +819,7 @@ impl<'a> TestRPC<'a> { 32, ); - let tip_height : u64 = 1; + let tip_height: u64 = 1; TestRPC { privk1, From d46bfcd005a79ab6c59c35c61c27d2f3ac42792f Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Tue, 12 Nov 2024 16:51:53 +0100 Subject: [PATCH 09/33] fixed blockbyheight to use the marf --- stackslib/src/net/api/getblockbyheight.rs | 20 +++-- .../src/net/api/tests/getblockbyheight.rs | 74 ++++++++++++++++--- 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/stackslib/src/net/api/getblockbyheight.rs b/stackslib/src/net/api/getblockbyheight.rs index 2a2781175b..675211f999 100644 --- a/stackslib/src/net/api/getblockbyheight.rs +++ b/stackslib/src/net/api/getblockbyheight.rs @@ -25,7 +25,9 @@ use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId}; use stacks_common::types::net::PeerHost; use {serde, serde_json}; -use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState, NakamotoStagingBlocksConn}; +use crate::chainstate::nakamoto::{ + NakamotoBlock, NakamotoChainState, NakamotoStagingBlocksConn, StacksDBIndexed, +}; use crate::chainstate::stacks::db::StacksChainState; use crate::chainstate::stacks::Error as ChainError; use crate::net::api::getblock_v3::{NakamotoBlockStream, RPCNakamotoBlockRequestHandler}; @@ -107,7 +109,7 @@ impl RPCRequestHandler for RPCNakamotoBlockByHeightRequestHandler { fn try_handle_request( &mut self, preamble: HttpRequestPreamble, - _contents: HttpRequestContents, + contents: HttpRequestContents, node: &mut StacksNodeState, ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> { let block_height = self @@ -115,11 +117,18 @@ impl RPCRequestHandler for RPCNakamotoBlockByHeightRequestHandler { .take() .ok_or(NetError::SendError("Missing `block_height`".into()))?; + let tip = match node.load_stacks_chain_tip(&preamble, &contents) { + Ok(tip) => tip, + Err(error_resp) => { + return error_resp.try_into_contents().map_err(NetError::from); + } + }; + let index_block_hash_res = node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { chainstate - .nakamoto_blocks_db() - .get_block_id_by_block_height(block_height) + .index_conn() + .get_ancestor_block_hash(block_height, &tip) }); let block_id = match index_block_hash_res { @@ -198,12 +207,13 @@ impl StacksHttpRequest { pub fn new_get_nakamoto_block_by_height( host: PeerHost, block_height: u64, + tip: TipRequest, ) -> StacksHttpRequest { StacksHttpRequest::new_for_peer( host, "GET".into(), format!("/v3/blockbyheight/{}", block_height), - HttpRequestContents::new(), + HttpRequestContents::new().for_tip(tip), ) .expect("FATAL: failed to construct request from infallible data") } diff --git a/stackslib/src/net/api/tests/getblockbyheight.rs b/stackslib/src/net/api/tests/getblockbyheight.rs index 2e562e1623..7f65289426 100644 --- a/stackslib/src/net/api/tests/getblockbyheight.rs +++ b/stackslib/src/net/api/tests/getblockbyheight.rs @@ -51,9 +51,12 @@ fn test_try_parse_request() { let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default()); - // NOTE: sqlite stores the height as a signed 64bit integer, so we are using the biggest positive value - 1 - let request = - StacksHttpRequest::new_get_nakamoto_block_by_height(addr.into(), 0x7ffffffffffffffe); + // NOTE: MARF enforces the height to be a u32 value + let request = StacksHttpRequest::new_get_nakamoto_block_by_height( + addr.into(), + 0xfffffffe, + TipRequest::UseLatestAnchoredTip, + ); let bytes = request.try_serialize().unwrap(); debug!("Request:\n{}\n", std::str::from_utf8(&bytes).unwrap()); @@ -73,7 +76,7 @@ fn test_try_parse_request() { let (preamble, contents) = parsed_request.destruct(); // consumed path args - assert_eq!(handler.block_height, Some(0x7ffffffffffffffe)); + assert_eq!(handler.block_height, Some(0xfffffffe)); assert_eq!(&preamble, request.preamble()); @@ -93,14 +96,47 @@ fn test_try_make_response() { let mut requests = vec![]; - // query existing block - let request = - StacksHttpRequest::new_get_nakamoto_block_by_height(addr.into(), nakamoto_chain_tip_height); + // query existing block (empty tip) + let request = StacksHttpRequest::new_get_nakamoto_block_by_height( + addr.into(), + nakamoto_chain_tip_height, + TipRequest::UseLatestAnchoredTip, + ); requests.push(request); - // query non-existant block (with biggest positive i64 value - 1) - let request = - StacksHttpRequest::new_get_nakamoto_block_by_height(addr.into(), 0x7ffffffffffffffe); + // query non-existant block (with biggest positive u32 value - 1 as MARF enforces it) + let request = StacksHttpRequest::new_get_nakamoto_block_by_height( + addr.into(), + 0xfffffffe, + TipRequest::UseLatestAnchoredTip, + ); + requests.push(request); + + // query existing block using the canonical_tip + let request = StacksHttpRequest::new_get_nakamoto_block_by_height( + addr.into(), + nakamoto_chain_tip_height, + TipRequest::SpecificTip(rpc_test.canonical_tip), + ); + requests.push(request); + + // query existing block using the unconfirmed tip + let request = StacksHttpRequest::new_get_nakamoto_block_by_height( + addr.into(), + nakamoto_chain_tip_height, + TipRequest::UseLatestUnconfirmedTip, + ); + requests.push(request); + + // dummy hack for generating an invalid tip + let mut dummy_tip = rpc_test.canonical_tip.clone(); + dummy_tip.0[0] = dummy_tip.0[0].wrapping_add(1); + + let request = StacksHttpRequest::new_get_nakamoto_block_by_height( + addr.into(), + nakamoto_chain_tip_height, + TipRequest::SpecificTip(dummy_tip), + ); requests.push(request); let mut responses = rpc_test.run(requests); @@ -116,4 +152,22 @@ fn test_try_make_response() { let (preamble, body) = response.destruct(); assert_eq!(preamble.status_code, 404); + + // got the block from the tip + let response = responses.remove(0); + let resp = response.decode_nakamoto_block().unwrap(); + + assert_eq!(resp.header.consensus_hash, consensus_hash); + + // got the block from the tip (unconfirmed) + let response = responses.remove(0); + let resp = response.decode_nakamoto_block().unwrap(); + + assert_eq!(resp.header.consensus_hash, consensus_hash); + + // no block for dummy tip + let response = responses.remove(0); + let (preamble, body) = response.destruct(); + + assert_eq!(preamble.status_code, 404); } From 6cc44c6e1749b31e789886e91fb4ab0ea5d1f8f9 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Tue, 12 Nov 2024 18:35:57 +0100 Subject: [PATCH 10/33] removed get_block_id_by_block_height over staging db --- .../src/chainstate/nakamoto/staging_blocks.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/staging_blocks.rs b/stackslib/src/chainstate/nakamoto/staging_blocks.rs index ee295607a9..335b304a45 100644 --- a/stackslib/src/chainstate/nakamoto/staging_blocks.rs +++ b/stackslib/src/chainstate/nakamoto/staging_blocks.rs @@ -321,25 +321,6 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { .optional()?) } - /// Get the block ID of a staging block from block height. - /// Used for downloads - pub fn get_block_id_by_block_height( - &self, - block_height: u64, - ) -> Result, ChainstateError> { - let sql = "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE height = ?1"; - let args = params![block_height]; - - let mut stmt = self.deref().prepare(sql)?; - Ok(stmt - .query_row(args, |row| { - let block_id: StacksBlockId = row.get(0)?; - - Ok(block_id) - }) - .optional()?) - } - /// Get a Nakamoto block by index block hash, as well as its size. /// Verifies its integrity. /// Returns Ok(Some(block, size)) if the block was present From ce533e07465bd36d0785dfce6935a0a46aa00874 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Tue, 12 Nov 2024 18:40:49 +0100 Subject: [PATCH 11/33] more restful path for blockbyheight --- stackslib/src/net/api/getblockbyheight.rs | 6 +++--- testnet/stacks-node/src/tests/nakamoto_integrations.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/stackslib/src/net/api/getblockbyheight.rs b/stackslib/src/net/api/getblockbyheight.rs index 675211f999..33105b3a16 100644 --- a/stackslib/src/net/api/getblockbyheight.rs +++ b/stackslib/src/net/api/getblockbyheight.rs @@ -61,11 +61,11 @@ impl HttpRequest for RPCNakamotoBlockByHeightRequestHandler { } fn path_regex(&self) -> Regex { - Regex::new(r#"^/v3/blockbyheight/(?P[0-9]{1,20})$"#).unwrap() + Regex::new(r#"^/v3/blocks/height/(?P[0-9]{1,20})$"#).unwrap() } fn metrics_identifier(&self) -> &str { - "/v3/blockbyheight/:block_height" + "/v3/blocks/height/:block_height" } /// Try to decode this request. @@ -212,7 +212,7 @@ impl StacksHttpRequest { StacksHttpRequest::new_for_peer( host, "GET".into(), - format!("/v3/blockbyheight/{}", block_height), + format!("/v3/blocks/height/{}", block_height), HttpRequestContents::new().for_tip(tip), ) .expect("FATAL: failed to construct request from infallible data") diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 22b37bef51..6edd2cd5f9 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -9426,7 +9426,7 @@ fn v3_blockbyhash_api_endpoint() { run_loop_thread.join().unwrap(); } -/// Test `/v3/blockbyheight` API endpoint +/// Test `/v3/blocks/height` API endpoint /// /// This endpoint returns the block blob given a height #[test] @@ -9519,7 +9519,7 @@ fn v3_blockbyheight_api_endpoint() { let http_origin = format!("http://{}", &conf.node.rpc_bind); let get_v3_block_by_height = |height: u64| { - let url = &format!("{http_origin}/v3/blockbyheight/{height}"); + let url = &format!("{http_origin}/v3/blocks/height/{height}"); info!("Send request: GET {url}"); reqwest::blocking::get(url).unwrap_or_else(|e| panic!("GET request failed: {e}")) }; From 1e628f7b1d4c31451e96b953eceb02ab94e0ef57 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Wed, 13 Nov 2024 12:00:39 +0100 Subject: [PATCH 12/33] removed getblockbyhash --- CHANGELOG.md | 1 + docs/rpc-endpoints.md | 12 +- .../src/chainstate/nakamoto/staging_blocks.rs | 19 --- stackslib/src/net/api/mod.rs | 2 - .../src/tests/nakamoto_integrations.rs | 123 ------------------ 5 files changed, 5 insertions(+), 152 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a61c025f7a..0298a24802 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE - Add `block_commit_delay_ms` to the config file to control the time to wait after seeing a new burn block, before submitting a block commit, to allow time for the first Nakamoto block of the new tenure to be mined, allowing this miner to avoid the need to RBF the block commit. - Add `tenure_cost_limit_per_block_percentage` to the miner config file to control the percentage remaining tenure cost limit to consume per nakamoto block. - Add `/v3/blockbyhash` and `/v3/blockbyheight` rpc endpoints +- Add `/v3/blocks/height/:block_height` rpc endpoint ## [3.0.0.0.1] diff --git a/docs/rpc-endpoints.md b/docs/rpc-endpoints.md index 28cfeda54d..08aca31f15 100644 --- a/docs/rpc-endpoints.md +++ b/docs/rpc-endpoints.md @@ -503,20 +503,16 @@ data. This will return 404 if the block does not exist. -### GET /v3/blockbyhash/[Block Hash] - -Fetch a Nakamoto block given its block hash. This returns the raw block -data. - -This will return 404 if the block does not exist. - -### GET /v3/blockbyheight/[Block Height] +### GET /v3/blocks/height/[Block Height] Fetch a Nakamoto block given its block height. This returns the raw block data. This will return 404 if the block does not exist. +This endpoint also accepts a querystring parameter `?tip=` which when supplied will return the +block relative to the specified tip. + ### GET /v3/tenures/[Block ID] Fetch a Nakamoto block and all of its ancestors in the same tenure, given its diff --git a/stackslib/src/chainstate/nakamoto/staging_blocks.rs b/stackslib/src/chainstate/nakamoto/staging_blocks.rs index 335b304a45..382c708850 100644 --- a/stackslib/src/chainstate/nakamoto/staging_blocks.rs +++ b/stackslib/src/chainstate/nakamoto/staging_blocks.rs @@ -302,25 +302,6 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { .optional()?) } - /// Get the block ID of a staging block from block hash. - /// Used for downloads - pub fn get_block_id_by_block_hash( - &self, - block_hash: &BlockHeaderHash, - ) -> Result, ChainstateError> { - let sql = "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE block_hash = ?1"; - let args = params![block_hash]; - - let mut stmt = self.deref().prepare(sql)?; - Ok(stmt - .query_row(args, |row| { - let block_id: StacksBlockId = row.get(0)?; - - Ok(block_id) - }) - .optional()?) - } - /// Get a Nakamoto block by index block hash, as well as its size. /// Verifies its integrity. /// Returns Ok(Some(block, size)) if the block was present diff --git a/stackslib/src/net/api/mod.rs b/stackslib/src/net/api/mod.rs index 673bbf260a..8fc8ee33ba 100644 --- a/stackslib/src/net/api/mod.rs +++ b/stackslib/src/net/api/mod.rs @@ -42,7 +42,6 @@ pub mod getattachment; pub mod getattachmentsinv; pub mod getblock; pub mod getblock_v3; -pub mod getblockbyhash; pub mod getblockbyheight; pub mod getconstantval; pub mod getcontractabi; @@ -94,7 +93,6 @@ impl StacksHttp { self.register_rpc_endpoint(getattachmentsinv::RPCGetAttachmentsInvRequestHandler::new()); self.register_rpc_endpoint(getblock::RPCBlocksRequestHandler::new()); self.register_rpc_endpoint(getblock_v3::RPCNakamotoBlockRequestHandler::new()); - self.register_rpc_endpoint(getblockbyhash::RPCNakamotoBlockByHashRequestHandler::new()); self.register_rpc_endpoint(getblockbyheight::RPCNakamotoBlockByHeightRequestHandler::new()); self.register_rpc_endpoint(getconstantval::RPCGetConstantValRequestHandler::new()); self.register_rpc_endpoint(getcontractabi::RPCGetContractAbiRequestHandler::new()); diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 6edd2cd5f9..49f627f1bd 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -9303,129 +9303,6 @@ fn v3_signer_api_endpoint() { run_loop_thread.join().unwrap(); } -/// Test `/v3/blockbyhash` API endpoint -/// -/// This endpoint returns the block blob given a height -#[test] -#[ignore] -fn v3_blockbyhash_api_endpoint() { - if env::var("BITCOIND_TEST") != Ok("1".into()) { - return; - } - - let (mut conf, _miner_account) = naka_neon_integration_conf(None); - let password = "12345".to_string(); - conf.connection_options.auth_token = Some(password.clone()); - conf.miner.wait_on_interim_blocks = Duration::from_secs(1); - let stacker_sk = setup_stacker(&mut conf); - let signer_sk = Secp256k1PrivateKey::new(); - let signer_addr = tests::to_addr(&signer_sk); - let sender_sk = Secp256k1PrivateKey::new(); - // setup sender + recipient for some test stx transfers - // these are necessary for the interim blocks to get mined at all - let sender_addr = tests::to_addr(&sender_sk); - let send_amt = 100; - let send_fee = 180; - conf.add_initial_balance( - PrincipalData::from(sender_addr).to_string(), - send_amt + send_fee, - ); - conf.add_initial_balance(PrincipalData::from(signer_addr).to_string(), 100000); - - // only subscribe to the block proposal events - test_observer::spawn(); - test_observer::register(&mut conf, &[EventKeyType::BlockProposal]); - - let mut btcd_controller = BitcoinCoreController::new(conf.clone()); - btcd_controller - .start_bitcoind() - .expect("Failed starting bitcoind"); - let mut btc_regtest_controller = BitcoinRegtestController::new(conf.clone(), None); - btc_regtest_controller.bootstrap_chain(201); - - let mut run_loop = boot_nakamoto::BootRunLoop::new(conf.clone()).unwrap(); - let run_loop_stopper = run_loop.get_termination_switch(); - let Counters { - blocks_processed, - naka_submitted_commits: commits_submitted, - naka_proposed_blocks: proposals_submitted, - .. - } = run_loop.counters(); - - let coord_channel = run_loop.coordinator_channels(); - - let run_loop_thread = thread::spawn(move || run_loop.start(None, 0)); - let mut signers = TestSigners::new(vec![signer_sk]); - wait_for_runloop(&blocks_processed); - boot_to_epoch_3( - &conf, - &blocks_processed, - &[stacker_sk], - &[signer_sk], - &mut Some(&mut signers), - &mut btc_regtest_controller, - ); - - info!("------------------------- Reached Epoch 3.0 -------------------------"); - - blind_signer(&conf, &signers, proposals_submitted); - - wait_for_first_naka_block_commit(60, &commits_submitted); - - // Mine 1 nakamoto tenure - next_block_and_mine_commit( - &mut btc_regtest_controller, - 60, - &coord_channel, - &commits_submitted, - ) - .unwrap(); - - let burnchain = conf.get_burnchain(); - let sortdb = burnchain.open_sortition_db(true).unwrap(); - let (chainstate, _) = StacksChainState::open( - conf.is_mainnet(), - conf.burnchain.chain_id, - &conf.get_chainstate_path_str(), - None, - ) - .unwrap(); - - info!("------------------------- Setup finished, run test -------------------------"); - - let http_origin = format!("http://{}", &conf.node.rpc_bind); - - let get_v3_block_by_hash = |block_hash: &str| { - let url = &format!("{http_origin}/v3/blockbyhash/{block_hash}"); - info!("Send request: GET {url}"); - reqwest::blocking::get(url).unwrap_or_else(|e| panic!("GET request failed: {e}")) - }; - - let tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb) - .unwrap() - .unwrap(); - - let block_hash = tip - .anchored_header - .as_stacks_nakamoto() - .unwrap() - .block_hash() - .to_string(); - let block_data = get_v3_block_by_hash(&block_hash); - assert!(block_data.status().is_success()); - assert!(block_data.bytes().unwrap().len() > 0); - - info!("------------------------- Test finished, clean up -------------------------"); - - coord_channel - .lock() - .expect("Mutex poisoned") - .stop_chains_coordinator(); - run_loop_stopper.store(false, Ordering::SeqCst); - - run_loop_thread.join().unwrap(); -} - /// Test `/v3/blocks/height` API endpoint /// /// This endpoint returns the block blob given a height From 4bea12a341314cd297f034c7ef546e1b0e8e5fff Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Wed, 13 Nov 2024 12:01:23 +0100 Subject: [PATCH 13/33] removed files for getblockbyhash --- stackslib/src/net/api/getblockbyhash.rs | 210 ------------------ stackslib/src/net/api/tests/getblockbyhash.rs | 122 ---------- 2 files changed, 332 deletions(-) delete mode 100644 stackslib/src/net/api/getblockbyhash.rs delete mode 100644 stackslib/src/net/api/tests/getblockbyhash.rs diff --git a/stackslib/src/net/api/getblockbyhash.rs b/stackslib/src/net/api/getblockbyhash.rs deleted file mode 100644 index e27e34ab42..0000000000 --- a/stackslib/src/net/api/getblockbyhash.rs +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation -// Copyright (C) 2020-2024 Stacks Open Internet Foundation -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::io::{Read, Seek, SeekFrom, Write}; -use std::{fs, io}; - -use regex::{Captures, Regex}; -use rusqlite::Connection; -use serde::de::Error as de_Error; -use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; -use stacks_common::types::chainstate::{BlockHeaderHash, ConsensusHash, StacksBlockId}; -use stacks_common::types::net::PeerHost; -use {serde, serde_json}; - -use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState, NakamotoStagingBlocksConn}; -use crate::chainstate::stacks::db::StacksChainState; -use crate::chainstate::stacks::Error as ChainError; -use crate::net::api::getblock_v3::{NakamotoBlockStream, RPCNakamotoBlockRequestHandler}; -use crate::net::http::{ - parse_bytes, Error, HttpBadRequest, HttpChunkGenerator, HttpContentType, HttpNotFound, - HttpRequest, HttpRequestContents, HttpRequestPreamble, HttpResponse, HttpResponseContents, - HttpResponsePayload, HttpResponsePreamble, HttpServerError, HttpVersion, -}; -use crate::net::httpcore::{ - HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp, StacksHttpRequest, - StacksHttpResponse, -}; -use crate::net::{Error as NetError, StacksNodeState, TipRequest, MAX_HEADERS}; -use crate::util_lib::db::{DBConn, Error as DBError}; - -#[derive(Clone)] -pub struct RPCNakamotoBlockByHashRequestHandler { - pub block_hash: Option, -} - -impl RPCNakamotoBlockByHashRequestHandler { - pub fn new() -> Self { - Self { block_hash: None } - } -} - -/// Decode the HTTP request -impl HttpRequest for RPCNakamotoBlockByHashRequestHandler { - fn verb(&self) -> &'static str { - "GET" - } - - fn path_regex(&self) -> Regex { - Regex::new(r#"^/v3/blockbyhash/(?P[0-9a-f]{64})$"#).unwrap() - } - - fn metrics_identifier(&self) -> &str { - "/v3/blockbyhash/:block_hash" - } - - /// Try to decode this request. - /// There's nothing to load here, so just make sure the request is well-formed. - fn try_parse_request( - &mut self, - preamble: &HttpRequestPreamble, - captures: &Captures, - query: Option<&str>, - _body: &[u8], - ) -> Result { - if preamble.get_content_length() != 0 { - return Err(Error::DecodeError( - "Invalid Http request: expected 0-length body".to_string(), - )); - } - - let block_hash_str = captures - .name("block_hash") - .ok_or_else(|| { - Error::DecodeError("Failed to match path to block hash group".to_string()) - })? - .as_str(); - - let block_hash = BlockHeaderHash::from_hex(block_hash_str).map_err(|_| { - Error::DecodeError("Invalid path: unparseable consensus hash".to_string()) - })?; - self.block_hash = Some(block_hash); - - Ok(HttpRequestContents::new().query_string(query)) - } -} - -impl RPCRequestHandler for RPCNakamotoBlockByHashRequestHandler { - /// Reset internal state - fn restart(&mut self) { - self.block_hash = None; - } - - /// Make the response - fn try_handle_request( - &mut self, - preamble: HttpRequestPreamble, - _contents: HttpRequestContents, - node: &mut StacksNodeState, - ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> { - let block_hash = self - .block_hash - .take() - .ok_or(NetError::SendError("Missing `block_hash`".into()))?; - - let index_block_hash_res = - node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { - chainstate - .nakamoto_blocks_db() - .get_block_id_by_block_hash(&block_hash) - }); - - let block_id = match index_block_hash_res { - Ok(index_block_hash_opt) => match index_block_hash_opt { - Some(index_block_hash) => index_block_hash, - None => { - // block hash not found - let msg = format!("No such block {:?}\n", &block_hash); - warn!("{}", &msg); - return StacksHttpResponse::new_error(&preamble, &HttpNotFound::new(msg)) - .try_into_contents() - .map_err(NetError::from); - } - }, - Err(e) => { - // error querying the db - let msg = format!("Failed to load block {}: {:?}\n", &block_hash, &e); - warn!("{}", &msg); - return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg)) - .try_into_contents() - .map_err(NetError::from); - } - }; - - // start loading up the block - let stream = match RPCNakamotoBlockRequestHandler::get_stream_by_node(&block_id, node) { - Ok(stream) => stream, - Err(ChainError::NoSuchBlockError) => { - return StacksHttpResponse::new_error( - &preamble, - &HttpNotFound::new(format!("No such block {:?}\n", &block_hash)), - ) - .try_into_contents() - .map_err(NetError::from) - } - Err(e) => { - // nope -- error trying to check - let msg = format!("Failed to load block {}: {:?}\n", &block_hash, &e); - warn!("{}", &msg); - return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg)) - .try_into_contents() - .map_err(NetError::from); - } - }; - - let resp_preamble = HttpResponsePreamble::from_http_request_preamble( - &preamble, - 200, - "OK", - None, - HttpContentType::Bytes, - ); - - Ok(( - resp_preamble, - HttpResponseContents::from_stream(Box::new(stream)), - )) - } -} - -/// Decode the HTTP response -impl HttpResponse for RPCNakamotoBlockByHashRequestHandler { - /// Decode this response from a byte stream. This is called by the client to decode this - /// message - fn try_parse_response( - &self, - preamble: &HttpResponsePreamble, - body: &[u8], - ) -> Result { - let bytes = parse_bytes(preamble, body, MAX_MESSAGE_LEN.into())?; - Ok(HttpResponsePayload::Bytes(bytes)) - } -} - -impl StacksHttpRequest { - pub fn new_get_nakamoto_block_by_hash( - host: PeerHost, - block_hash: BlockHeaderHash, - ) -> StacksHttpRequest { - StacksHttpRequest::new_for_peer( - host, - "GET".into(), - format!("/v3/blockbyhash/{}", &block_hash), - HttpRequestContents::new(), - ) - .expect("FATAL: failed to construct request from infallible data") - } -} diff --git a/stackslib/src/net/api/tests/getblockbyhash.rs b/stackslib/src/net/api/tests/getblockbyhash.rs deleted file mode 100644 index 3abc1c8ea6..0000000000 --- a/stackslib/src/net/api/tests/getblockbyhash.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation -// Copyright (C) 2020-2024 Stacks Open Internet Foundation -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - -use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions}; -use clarity::vm::{ClarityName, ContractName}; -use stacks_common::codec::StacksMessageCodec; -use stacks_common::types::chainstate::{ - BlockHeaderHash, ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, -}; -use stacks_common::types::net::PeerHost; -use stacks_common::types::Address; - -use super::TestRPC; -use crate::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandle}; -use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState}; -use crate::chainstate::stacks::db::blocks::test::*; -use crate::chainstate::stacks::db::StacksChainState; -use crate::chainstate::stacks::{ - Error as chainstate_error, StacksBlock, StacksBlockHeader, StacksMicroblock, -}; -use crate::net::api::getblock_v3::NakamotoBlockStream; -use crate::net::api::*; -use crate::net::connection::ConnectionOptions; -use crate::net::http::HttpChunkGenerator; -use crate::net::httpcore::{ - HttpPreambleExtensions, HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp, - StacksHttpRequest, -}; -use crate::net::test::TestEventObserver; -use crate::net::tests::inv::nakamoto::make_nakamoto_peer_from_invs; -use crate::net::{ProtocolFamily, TipRequest}; -use crate::util_lib::db::DBConn; - -#[test] -fn test_try_parse_request() { - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); - let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default()); - - let request = - StacksHttpRequest::new_get_nakamoto_block_by_hash(addr.into(), BlockHeaderHash([0x11; 32])); - let bytes = request.try_serialize().unwrap(); - - debug!("Request:\n{}\n", std::str::from_utf8(&bytes).unwrap()); - - let (parsed_preamble, offset) = http.read_preamble(&bytes).unwrap(); - let mut handler = getblockbyhash::RPCNakamotoBlockByHashRequestHandler::new(); - let mut parsed_request = http - .handle_try_parse_request( - &mut handler, - &parsed_preamble.expect_request(), - &bytes[offset..], - ) - .unwrap(); - - // parsed request consumes headers that would not be in a constructed reqeuest - parsed_request.clear_headers(); - let (preamble, contents) = parsed_request.destruct(); - - // consumed path args - assert_eq!(handler.block_hash, Some(BlockHeaderHash([0x11; 32]))); - - assert_eq!(&preamble, request.preamble()); - - handler.restart(); - assert!(handler.block_hash.is_none()); -} - -#[test] -fn test_try_make_response() { - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); - - let test_observer = TestEventObserver::new(); - let rpc_test = TestRPC::setup_nakamoto(function_name!(), &test_observer); - - let nakamoto_chain_tip_hash = rpc_test.tip_hash.clone(); - let consensus_hash = rpc_test.consensus_hash.clone(); - - let mut requests = vec![]; - - // query existing block - let request = StacksHttpRequest::new_get_nakamoto_block_by_hash( - addr.into(), - nakamoto_chain_tip_hash.clone(), - ); - requests.push(request); - - // query non-existant block - let request = - StacksHttpRequest::new_get_nakamoto_block_by_hash(addr.into(), BlockHeaderHash([0x11; 32])); - requests.push(request); - - let mut responses = rpc_test.run(requests); - - // got the block - let response = responses.remove(0); - let resp = response.decode_nakamoto_block().unwrap(); - - assert_eq!(resp.header.block_hash(), nakamoto_chain_tip_hash); - - assert_eq!(resp.header.consensus_hash, consensus_hash); - - // no block - let response = responses.remove(0); - let (preamble, body) = response.destruct(); - - assert_eq!(preamble.status_code, 404); -} From 6acfddd16add6ae4ffc1c2710fa12de1f7ccd354 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Wed, 13 Nov 2024 12:07:52 +0100 Subject: [PATCH 14/33] reverted getblock_v3 patch --- stackslib/src/net/api/getblock_v3.rs | 40 ++++++++++------------- stackslib/src/net/api/getblockbyheight.rs | 13 +++++++- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/stackslib/src/net/api/getblock_v3.rs b/stackslib/src/net/api/getblock_v3.rs index 058e39dd1a..56e8063dda 100644 --- a/stackslib/src/net/api/getblock_v3.rs +++ b/stackslib/src/net/api/getblock_v3.rs @@ -46,6 +46,12 @@ pub struct RPCNakamotoBlockRequestHandler { pub block_id: Option, } +impl RPCNakamotoBlockRequestHandler { + pub fn new() -> Self { + Self { block_id: None } + } +} + pub struct NakamotoBlockStream { /// index block hash of the block to download pub index_block_hash: StacksBlockId, @@ -63,27 +69,6 @@ pub struct NakamotoBlockStream { pub rowid: i64, } -impl RPCNakamotoBlockRequestHandler { - pub fn new() -> Self { - Self { block_id: None } - } - - pub fn get_stream_by_node( - block_id: &StacksBlockId, - node: &mut StacksNodeState, - ) -> Result { - node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { - let Some((tenure_id, parent_block_id)) = chainstate - .nakamoto_blocks_db() - .get_tenure_and_parent_block_id(&block_id)? - else { - return Err(ChainError::NoSuchBlockError); - }; - NakamotoBlockStream::new(chainstate, block_id.clone(), tenure_id, parent_block_id) - }) - } -} - impl NakamotoBlockStream { pub fn new( chainstate: &StacksChainState, @@ -194,8 +179,19 @@ impl RPCRequestHandler for RPCNakamotoBlockRequestHandler { .take() .ok_or(NetError::SendError("Missing `block_id`".into()))?; + let stream_res = + node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { + let Some((tenure_id, parent_block_id)) = chainstate + .nakamoto_blocks_db() + .get_tenure_and_parent_block_id(&block_id)? + else { + return Err(ChainError::NoSuchBlockError); + }; + NakamotoBlockStream::new(chainstate, block_id.clone(), tenure_id, parent_block_id) + }); + // start loading up the block - let stream = match Self::get_stream_by_node(&block_id, node) { + let stream = match stream_res { Ok(stream) => stream, Err(ChainError::NoSuchBlockError) => { return StacksHttpResponse::new_error( diff --git a/stackslib/src/net/api/getblockbyheight.rs b/stackslib/src/net/api/getblockbyheight.rs index 33105b3a16..65c343f209 100644 --- a/stackslib/src/net/api/getblockbyheight.rs +++ b/stackslib/src/net/api/getblockbyheight.rs @@ -153,8 +153,19 @@ impl RPCRequestHandler for RPCNakamotoBlockByHeightRequestHandler { } }; + let stream_res = + node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { + let Some((tenure_id, parent_block_id)) = chainstate + .nakamoto_blocks_db() + .get_tenure_and_parent_block_id(&block_id)? + else { + return Err(ChainError::NoSuchBlockError); + }; + NakamotoBlockStream::new(chainstate, block_id.clone(), tenure_id, parent_block_id) + }); + // start loading up the block - let stream = match RPCNakamotoBlockRequestHandler::get_stream_by_node(&block_id, node) { + let stream = match stream_res { Ok(stream) => stream, Err(ChainError::NoSuchBlockError) => { return StacksHttpResponse::new_error( From f81749aa99337892be8590df85de05bc054f8eaa Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Wed, 13 Nov 2024 12:09:09 +0100 Subject: [PATCH 15/33] removed test for old getblockbyhash --- stackslib/src/net/api/tests/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/stackslib/src/net/api/tests/mod.rs b/stackslib/src/net/api/tests/mod.rs index 58cb827f17..40d329686d 100644 --- a/stackslib/src/net/api/tests/mod.rs +++ b/stackslib/src/net/api/tests/mod.rs @@ -60,7 +60,6 @@ mod getattachment; mod getattachmentsinv; mod getblock; mod getblock_v3; -mod getblockbyhash; mod getblockbyheight; mod getconstantval; mod getcontractabi; From 67dc987dab5e24286f9481defddaeabfc118ebdd Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Thu, 14 Nov 2024 08:09:45 +0200 Subject: [PATCH 16/33] improved docs as suggested by jcnelson --- docs/rpc-endpoints.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/rpc-endpoints.md b/docs/rpc-endpoints.md index 08aca31f15..94a5479613 100644 --- a/docs/rpc-endpoints.md +++ b/docs/rpc-endpoints.md @@ -510,8 +510,9 @@ data. This will return 404 if the block does not exist. -This endpoint also accepts a querystring parameter `?tip=` which when supplied will return the -block relative to the specified tip. +This endpoint also accepts a querystring parameter `?tip=` which when supplied +will return the block relative to the specified tip allowing the querying of +sibling blocks (same height, different tip) too. ### GET /v3/tenures/[Block ID] From d778b2fe94874a50e9745325dea774fcca477d90 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Thu, 14 Nov 2024 10:31:52 +0200 Subject: [PATCH 17/33] improved integration test for get_v3_block_by_height --- testnet/stacks-node/src/tests/nakamoto_integrations.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 49f627f1bd..5d712ad550 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -9407,8 +9407,16 @@ fn v3_blockbyheight_api_endpoint() { let block_height = tip.stacks_block_height; let block_data = get_v3_block_by_height(block_height); + assert!(block_data.status().is_success()); - assert!(block_data.bytes().unwrap().len() > 0); + let block_bytes_vec = block_data.bytes().unwrap().to_vec(); + assert!(block_bytes_vec.len() > 0); + + // does the block id of the returned blob matches ? + let block_id = NakamotoBlockHeader::consensus_deserialize(&mut block_bytes_vec.as_slice()) + .unwrap() + .block_id(); + assert_eq!(block_id, tip.index_block_hash()); info!("------------------------- Test finished, clean up -------------------------"); From cc064a52cc25c3035aefd574e353a9c2616c5e3f Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Thu, 14 Nov 2024 17:01:22 +0200 Subject: [PATCH 18/33] removed useless .clone() --- stackslib/src/net/api/getblockbyheight.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackslib/src/net/api/getblockbyheight.rs b/stackslib/src/net/api/getblockbyheight.rs index 65c343f209..9a17589c5d 100644 --- a/stackslib/src/net/api/getblockbyheight.rs +++ b/stackslib/src/net/api/getblockbyheight.rs @@ -161,7 +161,7 @@ impl RPCRequestHandler for RPCNakamotoBlockByHeightRequestHandler { else { return Err(ChainError::NoSuchBlockError); }; - NakamotoBlockStream::new(chainstate, block_id.clone(), tenure_id, parent_block_id) + NakamotoBlockStream::new(chainstate, block_id, tenure_id, parent_block_id) }); // start loading up the block From 5cc1a1c206a278402f2a6cb8921a71d298f282b0 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Fri, 15 Nov 2024 09:14:16 +0200 Subject: [PATCH 19/33] fixed typos in tests comments --- stackslib/src/net/api/tests/getblockbyheight.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stackslib/src/net/api/tests/getblockbyheight.rs b/stackslib/src/net/api/tests/getblockbyheight.rs index 7f65289426..2dbccb1514 100644 --- a/stackslib/src/net/api/tests/getblockbyheight.rs +++ b/stackslib/src/net/api/tests/getblockbyheight.rs @@ -71,7 +71,7 @@ fn test_try_parse_request() { ) .unwrap(); - // parsed request consumes headers that would not be in a constructed reqeuest + // parsed request consumes headers that would not be in a constructed request parsed_request.clear_headers(); let (preamble, contents) = parsed_request.destruct(); @@ -104,7 +104,7 @@ fn test_try_make_response() { ); requests.push(request); - // query non-existant block (with biggest positive u32 value - 1 as MARF enforces it) + // query non-existent block (with biggest positive u32 value - 1 as MARF enforces it) let request = StacksHttpRequest::new_get_nakamoto_block_by_height( addr.into(), 0xfffffffe, From c0f53ffc0f72f0cf1137c31be2581837222b1f15 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Fri, 15 Nov 2024 09:27:07 +0200 Subject: [PATCH 20/33] added /v3/blocks/height/{block_height} in openapi definitions --- docs/rpc/openapi.yaml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/rpc/openapi.yaml b/docs/rpc/openapi.yaml index c4dd06721c..db36da8bac 100644 --- a/docs/rpc/openapi.yaml +++ b/docs/rpc/openapi.yaml @@ -627,6 +627,40 @@ paths: content: application/text-plain: {} + /v3/blocks/height/{block_height}: + get: + summary: Fetch a Nakamoto block by its height and optional tip + tags: + - Blocks + operationId: get_block_v3_by_height + description: + Fetch a Nakamoto block by its height and optional tip. + parameters: + - name: block_height + in: path + description: The block's height + required: true + schema: + type: integer + - name: tip + in: query + schema: + type: string + description: The Stacks chain tip to query from. If tip == latest or empty, the query will be run + from the latest known tip. + responses: + "200": + description: The raw SIP-003-encoded block will be returned. + content: + application/octet-stream: + schema: + type: string + format: binary + "404": + description: The block could not be found + content: + application/text-plain: {} + /v3/tenures/info: get: summary: Fetch metadata about the ongoing Nakamoto tenure From 2e6d279cfdbb452c08354e1b1d7276741cf0b083 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Tue, 19 Nov 2024 13:32:11 +0100 Subject: [PATCH 21/33] added block_id test over canonical_tip in test_try_make_response --- stackslib/src/net/api/tests/getblockbyheight.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stackslib/src/net/api/tests/getblockbyheight.rs b/stackslib/src/net/api/tests/getblockbyheight.rs index 2dbccb1514..f030197b46 100644 --- a/stackslib/src/net/api/tests/getblockbyheight.rs +++ b/stackslib/src/net/api/tests/getblockbyheight.rs @@ -92,6 +92,7 @@ fn test_try_make_response() { let rpc_test = TestRPC::setup_nakamoto(function_name!(), &test_observer); let nakamoto_chain_tip_height = rpc_test.tip_height.clone(); + let canonical_tip = rpc_test.canonical_tip.clone(); let consensus_hash = rpc_test.consensus_hash.clone(); let mut requests = vec![]; @@ -146,6 +147,7 @@ fn test_try_make_response() { let resp = response.decode_nakamoto_block().unwrap(); assert_eq!(resp.header.consensus_hash, consensus_hash); + assert_eq!(resp.header.block_id(), canonical_tip); // no block let response = responses.remove(0); @@ -158,12 +160,14 @@ fn test_try_make_response() { let resp = response.decode_nakamoto_block().unwrap(); assert_eq!(resp.header.consensus_hash, consensus_hash); + assert_eq!(resp.header.block_id(), canonical_tip); // got the block from the tip (unconfirmed) let response = responses.remove(0); let resp = response.decode_nakamoto_block().unwrap(); assert_eq!(resp.header.consensus_hash, consensus_hash); + assert_eq!(resp.header.block_id(), canonical_tip); // no block for dummy tip let response = responses.remove(0); From b83e2dcdd70de7cf5c820636ba1318cf104ff6da Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Tue, 19 Nov 2024 21:58:08 +0100 Subject: [PATCH 22/33] merged with develop --- .github/workflows/bitcoin-tests.yml | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/bitcoin-tests.yml b/.github/workflows/bitcoin-tests.yml index 5b10594c54..9f586c18ff 100644 --- a/.github/workflows/bitcoin-tests.yml +++ b/.github/workflows/bitcoin-tests.yml @@ -143,6 +143,7 @@ jobs: - tests::nakamoto_integrations::clarity_cost_spend_down - tests::nakamoto_integrations::v3_blockbyhash_api_endpoint - tests::nakamoto_integrations::v3_blockbyheight_api_endpoint + - tests::nakamoto_integrations::clarity_cost_spend_down # TODO: enable these once v1 signer is supported by a new nakamoto epoch # - tests::signer::v1::dkg # - tests::signer::v1::sign_request_rejected diff --git a/CHANGELOG.md b/CHANGELOG.md index 0298a24802..2da6463b30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE - Add `tenure_cost_limit_per_block_percentage` to the miner config file to control the percentage remaining tenure cost limit to consume per nakamoto block. - Add `/v3/blockbyhash` and `/v3/blockbyheight` rpc endpoints - Add `/v3/blocks/height/:block_height` rpc endpoint +- Add `tenure_cost_limit_per_block_percentage` to the miner config file to control the percentage remaining tenure cost limit to consume per nakamoto block. ## [3.0.0.0.1] From 2cd02d61f273fd5d18e221fe3b6f13a9f1c909df Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Sat, 9 Nov 2024 06:40:53 +0100 Subject: [PATCH 23/33] added getblockbyhash and getblockbyheight RPC api calls --- .../src/chainstate/nakamoto/staging_blocks.rs | 49 ++++ stackslib/src/net/api/getblock_v3.rs | 40 ++-- stackslib/src/net/api/getblockbyhash.rs | 210 ++++++++++++++++++ stackslib/src/net/api/tests/getblockbyhash.rs | 123 ++++++++++ stackslib/src/net/api/tests/mod.rs | 10 +- 5 files changed, 413 insertions(+), 19 deletions(-) create mode 100644 stackslib/src/net/api/getblockbyhash.rs create mode 100644 stackslib/src/net/api/tests/getblockbyhash.rs diff --git a/stackslib/src/chainstate/nakamoto/staging_blocks.rs b/stackslib/src/chainstate/nakamoto/staging_blocks.rs index 382c708850..972e83633a 100644 --- a/stackslib/src/chainstate/nakamoto/staging_blocks.rs +++ b/stackslib/src/chainstate/nakamoto/staging_blocks.rs @@ -282,6 +282,17 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { Ok(res) } + /// Get the rowid of a staging Nakamoto block by block_hash + pub fn get_nakamoto_block_by_hash_rowid( + &self, + block_hash: &StacksBlockId, + ) -> Result, ChainstateError> { + let sql = "SELECT rowid FROM nakamoto_staging_blocks WHERE block_hash = ?1"; + let args = params![block_hash]; + let res: Option = query_row(self, sql, args)?; + Ok(res) + } + /// Get the tenure and parent block ID of a staging block. /// Used for downloads pub fn get_tenure_and_parent_block_id( @@ -302,6 +313,44 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { .optional()?) } + /// Get the block ID of a staging block from block hash. + /// Used for downloads + pub fn get_block_id_by_block_hash( + &self, + block_hash: &BlockHeaderHash, + ) -> Result, ChainstateError> { + let sql = "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE block_hash = ?1"; + let args = params![block_hash]; + + let mut stmt = self.deref().prepare(sql)?; + Ok(stmt + .query_row(args, |row| { + let block_id: StacksBlockId = row.get(0)?; + + Ok(block_id) + }) + .optional()?) + } + + /// Get the block ID of a staging block from block height. + /// Used for downloads + pub fn get_block_id_by_block_height( + &self, + block_height: u64, + ) -> Result, ChainstateError> { + let sql = "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE height = ?1"; + let args = params![block_height]; + + let mut stmt = self.deref().prepare(sql)?; + Ok(stmt + .query_row(args, |row| { + let block_id: StacksBlockId = row.get(0)?; + + Ok(block_id) + }) + .optional()?) + } + /// Get a Nakamoto block by index block hash, as well as its size. /// Verifies its integrity. /// Returns Ok(Some(block, size)) if the block was present diff --git a/stackslib/src/net/api/getblock_v3.rs b/stackslib/src/net/api/getblock_v3.rs index 56e8063dda..058e39dd1a 100644 --- a/stackslib/src/net/api/getblock_v3.rs +++ b/stackslib/src/net/api/getblock_v3.rs @@ -46,12 +46,6 @@ pub struct RPCNakamotoBlockRequestHandler { pub block_id: Option, } -impl RPCNakamotoBlockRequestHandler { - pub fn new() -> Self { - Self { block_id: None } - } -} - pub struct NakamotoBlockStream { /// index block hash of the block to download pub index_block_hash: StacksBlockId, @@ -69,6 +63,27 @@ pub struct NakamotoBlockStream { pub rowid: i64, } +impl RPCNakamotoBlockRequestHandler { + pub fn new() -> Self { + Self { block_id: None } + } + + pub fn get_stream_by_node( + block_id: &StacksBlockId, + node: &mut StacksNodeState, + ) -> Result { + node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { + let Some((tenure_id, parent_block_id)) = chainstate + .nakamoto_blocks_db() + .get_tenure_and_parent_block_id(&block_id)? + else { + return Err(ChainError::NoSuchBlockError); + }; + NakamotoBlockStream::new(chainstate, block_id.clone(), tenure_id, parent_block_id) + }) + } +} + impl NakamotoBlockStream { pub fn new( chainstate: &StacksChainState, @@ -179,19 +194,8 @@ impl RPCRequestHandler for RPCNakamotoBlockRequestHandler { .take() .ok_or(NetError::SendError("Missing `block_id`".into()))?; - let stream_res = - node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { - let Some((tenure_id, parent_block_id)) = chainstate - .nakamoto_blocks_db() - .get_tenure_and_parent_block_id(&block_id)? - else { - return Err(ChainError::NoSuchBlockError); - }; - NakamotoBlockStream::new(chainstate, block_id.clone(), tenure_id, parent_block_id) - }); - // start loading up the block - let stream = match stream_res { + let stream = match Self::get_stream_by_node(&block_id, node) { Ok(stream) => stream, Err(ChainError::NoSuchBlockError) => { return StacksHttpResponse::new_error( diff --git a/stackslib/src/net/api/getblockbyhash.rs b/stackslib/src/net/api/getblockbyhash.rs new file mode 100644 index 0000000000..e27e34ab42 --- /dev/null +++ b/stackslib/src/net/api/getblockbyhash.rs @@ -0,0 +1,210 @@ +// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation +// Copyright (C) 2020-2024 Stacks Open Internet Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::io::{Read, Seek, SeekFrom, Write}; +use std::{fs, io}; + +use regex::{Captures, Regex}; +use rusqlite::Connection; +use serde::de::Error as de_Error; +use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; +use stacks_common::types::chainstate::{BlockHeaderHash, ConsensusHash, StacksBlockId}; +use stacks_common::types::net::PeerHost; +use {serde, serde_json}; + +use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState, NakamotoStagingBlocksConn}; +use crate::chainstate::stacks::db::StacksChainState; +use crate::chainstate::stacks::Error as ChainError; +use crate::net::api::getblock_v3::{NakamotoBlockStream, RPCNakamotoBlockRequestHandler}; +use crate::net::http::{ + parse_bytes, Error, HttpBadRequest, HttpChunkGenerator, HttpContentType, HttpNotFound, + HttpRequest, HttpRequestContents, HttpRequestPreamble, HttpResponse, HttpResponseContents, + HttpResponsePayload, HttpResponsePreamble, HttpServerError, HttpVersion, +}; +use crate::net::httpcore::{ + HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp, StacksHttpRequest, + StacksHttpResponse, +}; +use crate::net::{Error as NetError, StacksNodeState, TipRequest, MAX_HEADERS}; +use crate::util_lib::db::{DBConn, Error as DBError}; + +#[derive(Clone)] +pub struct RPCNakamotoBlockByHashRequestHandler { + pub block_hash: Option, +} + +impl RPCNakamotoBlockByHashRequestHandler { + pub fn new() -> Self { + Self { block_hash: None } + } +} + +/// Decode the HTTP request +impl HttpRequest for RPCNakamotoBlockByHashRequestHandler { + fn verb(&self) -> &'static str { + "GET" + } + + fn path_regex(&self) -> Regex { + Regex::new(r#"^/v3/blockbyhash/(?P[0-9a-f]{64})$"#).unwrap() + } + + fn metrics_identifier(&self) -> &str { + "/v3/blockbyhash/:block_hash" + } + + /// Try to decode this request. + /// There's nothing to load here, so just make sure the request is well-formed. + fn try_parse_request( + &mut self, + preamble: &HttpRequestPreamble, + captures: &Captures, + query: Option<&str>, + _body: &[u8], + ) -> Result { + if preamble.get_content_length() != 0 { + return Err(Error::DecodeError( + "Invalid Http request: expected 0-length body".to_string(), + )); + } + + let block_hash_str = captures + .name("block_hash") + .ok_or_else(|| { + Error::DecodeError("Failed to match path to block hash group".to_string()) + })? + .as_str(); + + let block_hash = BlockHeaderHash::from_hex(block_hash_str).map_err(|_| { + Error::DecodeError("Invalid path: unparseable consensus hash".to_string()) + })?; + self.block_hash = Some(block_hash); + + Ok(HttpRequestContents::new().query_string(query)) + } +} + +impl RPCRequestHandler for RPCNakamotoBlockByHashRequestHandler { + /// Reset internal state + fn restart(&mut self) { + self.block_hash = None; + } + + /// Make the response + fn try_handle_request( + &mut self, + preamble: HttpRequestPreamble, + _contents: HttpRequestContents, + node: &mut StacksNodeState, + ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> { + let block_hash = self + .block_hash + .take() + .ok_or(NetError::SendError("Missing `block_hash`".into()))?; + + let index_block_hash_res = + node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { + chainstate + .nakamoto_blocks_db() + .get_block_id_by_block_hash(&block_hash) + }); + + let block_id = match index_block_hash_res { + Ok(index_block_hash_opt) => match index_block_hash_opt { + Some(index_block_hash) => index_block_hash, + None => { + // block hash not found + let msg = format!("No such block {:?}\n", &block_hash); + warn!("{}", &msg); + return StacksHttpResponse::new_error(&preamble, &HttpNotFound::new(msg)) + .try_into_contents() + .map_err(NetError::from); + } + }, + Err(e) => { + // error querying the db + let msg = format!("Failed to load block {}: {:?}\n", &block_hash, &e); + warn!("{}", &msg); + return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg)) + .try_into_contents() + .map_err(NetError::from); + } + }; + + // start loading up the block + let stream = match RPCNakamotoBlockRequestHandler::get_stream_by_node(&block_id, node) { + Ok(stream) => stream, + Err(ChainError::NoSuchBlockError) => { + return StacksHttpResponse::new_error( + &preamble, + &HttpNotFound::new(format!("No such block {:?}\n", &block_hash)), + ) + .try_into_contents() + .map_err(NetError::from) + } + Err(e) => { + // nope -- error trying to check + let msg = format!("Failed to load block {}: {:?}\n", &block_hash, &e); + warn!("{}", &msg); + return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg)) + .try_into_contents() + .map_err(NetError::from); + } + }; + + let resp_preamble = HttpResponsePreamble::from_http_request_preamble( + &preamble, + 200, + "OK", + None, + HttpContentType::Bytes, + ); + + Ok(( + resp_preamble, + HttpResponseContents::from_stream(Box::new(stream)), + )) + } +} + +/// Decode the HTTP response +impl HttpResponse for RPCNakamotoBlockByHashRequestHandler { + /// Decode this response from a byte stream. This is called by the client to decode this + /// message + fn try_parse_response( + &self, + preamble: &HttpResponsePreamble, + body: &[u8], + ) -> Result { + let bytes = parse_bytes(preamble, body, MAX_MESSAGE_LEN.into())?; + Ok(HttpResponsePayload::Bytes(bytes)) + } +} + +impl StacksHttpRequest { + pub fn new_get_nakamoto_block_by_hash( + host: PeerHost, + block_hash: BlockHeaderHash, + ) -> StacksHttpRequest { + StacksHttpRequest::new_for_peer( + host, + "GET".into(), + format!("/v3/blockbyhash/{}", &block_hash), + HttpRequestContents::new(), + ) + .expect("FATAL: failed to construct request from infallible data") + } +} diff --git a/stackslib/src/net/api/tests/getblockbyhash.rs b/stackslib/src/net/api/tests/getblockbyhash.rs new file mode 100644 index 0000000000..443587d83b --- /dev/null +++ b/stackslib/src/net/api/tests/getblockbyhash.rs @@ -0,0 +1,123 @@ +// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation +// Copyright (C) 2020-2024 Stacks Open Internet Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions}; +use clarity::vm::{ClarityName, ContractName}; +use stacks_common::codec::StacksMessageCodec; +use stacks_common::types::chainstate::{ + BlockHeaderHash, ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, +}; +use stacks_common::types::net::PeerHost; +use stacks_common::types::Address; + +use super::TestRPC; +use crate::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandle}; +use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState}; +use crate::chainstate::stacks::db::blocks::test::*; +use crate::chainstate::stacks::db::StacksChainState; +use crate::chainstate::stacks::{ + Error as chainstate_error, StacksBlock, StacksBlockHeader, StacksMicroblock, +}; +use crate::net::api::getblock_v3::NakamotoBlockStream; +use crate::net::api::*; +use crate::net::connection::ConnectionOptions; +use crate::net::http::HttpChunkGenerator; +use crate::net::httpcore::{ + HttpPreambleExtensions, HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp, + StacksHttpRequest, +}; +use crate::net::test::TestEventObserver; +use crate::net::tests::inv::nakamoto::make_nakamoto_peer_from_invs; +use crate::net::{ProtocolFamily, TipRequest}; +use crate::util_lib::db::DBConn; + +#[test] +fn test_try_parse_request() { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); + let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default()); + + let request = + StacksHttpRequest::new_get_nakamoto_block_by_hash(addr.into(), BlockHeaderHash([0x11; 32])); + let bytes = request.try_serialize().unwrap(); + + debug!("Request:\n{}\n", std::str::from_utf8(&bytes).unwrap()); + + let (parsed_preamble, offset) = http.read_preamble(&bytes).unwrap(); + let mut handler = getblockbyhash::RPCNakamotoBlockByHashRequestHandler::new(); + let mut parsed_request = http + .handle_try_parse_request( + &mut handler, + &parsed_preamble.expect_request(), + &bytes[offset..], + ) + .unwrap(); + + // parsed request consumes headers that would not be in a constructed reqeuest + parsed_request.clear_headers(); + let (preamble, contents) = parsed_request.destruct(); + + // consumed path args + assert_eq!(handler.block_hash, Some(BlockHeaderHash([0x11; 32]))); + + assert_eq!(&preamble, request.preamble()); + + handler.restart(); + assert!(handler.block_hash.is_none()); +} + +#[test] +fn test_try_make_response() { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); + + let test_observer = TestEventObserver::new(); + let rpc_test = TestRPC::setup_nakamoto(function_name!(), &test_observer); + + let nakamoto_chain_tip_hash = rpc_test.tip_hash.clone(); + let consensus_hash = rpc_test.consensus_hash.clone(); + + let mut requests = vec![]; + + // query existing block + let request = + StacksHttpRequest::new_get_nakamoto_block_by_hash(addr.into(), nakamoto_chain_tip_hash.clone()); + requests.push(request); + + // query non-existant block + let request = + StacksHttpRequest::new_get_nakamoto_block_by_hash(addr.into(), BlockHeaderHash([0x11; 32])); + requests.push(request); + + let mut responses = rpc_test.run(requests); + + // got the block + let response = responses.remove(0); + let resp = response.decode_nakamoto_block().unwrap(); + + assert_eq!( + resp.header.block_hash(), + nakamoto_chain_tip_hash + ); + + assert_eq!(resp.header.consensus_hash, consensus_hash); + + // no block + let response = responses.remove(0); + let (preamble, body) = response.destruct(); + + assert_eq!(preamble.status_code, 404); +} diff --git a/stackslib/src/net/api/tests/mod.rs b/stackslib/src/net/api/tests/mod.rs index 40d329686d..f3e41d44bf 100644 --- a/stackslib/src/net/api/tests/mod.rs +++ b/stackslib/src/net/api/tests/mod.rs @@ -61,6 +61,8 @@ mod getattachmentsinv; mod getblock; mod getblock_v3; mod getblockbyheight; +mod getblockbyhash; +mod getblockbyheight; mod getconstantval; mod getcontractabi; mod getcontractsrc; @@ -204,6 +206,10 @@ pub struct TestRPC<'a> { pub tip_hash: BlockHeaderHash, /// block height of the chain tip pub tip_height: u64, + /// block header hash of the chain tip + pub tip_hash: BlockHeaderHash, + /// block height of the chain tip + pub tip_height: u64, /// consensus hash of the chain tip pub consensus_hash: ConsensusHash, /// hash of last microblock @@ -818,7 +824,7 @@ impl<'a> TestRPC<'a> { 32, ); - let tip_height: u64 = 1; + let tip_height : u64 = 1; TestRPC { privk1, @@ -921,6 +927,8 @@ impl<'a> TestRPC<'a> { consensus_hash: nakamoto_tip.consensus_hash.clone(), tip_hash: nakamoto_tip.anchored_header.block_hash(), tip_height: nakamoto_tip.stacks_block_height, + tip_hash: nakamoto_tip.anchored_header.block_hash(), + tip_height: nakamoto_tip.stacks_block_height, microblock_tip_hash: BlockHeaderHash([0x00; 32]), mempool_txids: vec![], microblock_txids: vec![], From fc2e4c3c14d1525ced8a87f4d9b44a19b388de5d Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Mon, 11 Nov 2024 11:21:07 +0100 Subject: [PATCH 24/33] removed useless get_nakamoto_block_by_hash_rowid and added notes about height test value --- stackslib/src/chainstate/nakamoto/staging_blocks.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/staging_blocks.rs b/stackslib/src/chainstate/nakamoto/staging_blocks.rs index 972e83633a..ee295607a9 100644 --- a/stackslib/src/chainstate/nakamoto/staging_blocks.rs +++ b/stackslib/src/chainstate/nakamoto/staging_blocks.rs @@ -282,17 +282,6 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { Ok(res) } - /// Get the rowid of a staging Nakamoto block by block_hash - pub fn get_nakamoto_block_by_hash_rowid( - &self, - block_hash: &StacksBlockId, - ) -> Result, ChainstateError> { - let sql = "SELECT rowid FROM nakamoto_staging_blocks WHERE block_hash = ?1"; - let args = params![block_hash]; - let res: Option = query_row(self, sql, args)?; - Ok(res) - } - /// Get the tenure and parent block ID of a staging block. /// Used for downloads pub fn get_tenure_and_parent_block_id( From 1535f9bb98f87d837d7ac02625ac621e04e500be Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Mon, 11 Nov 2024 13:33:37 +0100 Subject: [PATCH 25/33] reformat unit tests --- stackslib/src/net/api/tests/getblockbyhash.rs | 11 +++++------ stackslib/src/net/api/tests/mod.rs | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/stackslib/src/net/api/tests/getblockbyhash.rs b/stackslib/src/net/api/tests/getblockbyhash.rs index 443587d83b..3abc1c8ea6 100644 --- a/stackslib/src/net/api/tests/getblockbyhash.rs +++ b/stackslib/src/net/api/tests/getblockbyhash.rs @@ -93,8 +93,10 @@ fn test_try_make_response() { let mut requests = vec![]; // query existing block - let request = - StacksHttpRequest::new_get_nakamoto_block_by_hash(addr.into(), nakamoto_chain_tip_hash.clone()); + let request = StacksHttpRequest::new_get_nakamoto_block_by_hash( + addr.into(), + nakamoto_chain_tip_hash.clone(), + ); requests.push(request); // query non-existant block @@ -108,10 +110,7 @@ fn test_try_make_response() { let response = responses.remove(0); let resp = response.decode_nakamoto_block().unwrap(); - assert_eq!( - resp.header.block_hash(), - nakamoto_chain_tip_hash - ); + assert_eq!(resp.header.block_hash(), nakamoto_chain_tip_hash); assert_eq!(resp.header.consensus_hash, consensus_hash); diff --git a/stackslib/src/net/api/tests/mod.rs b/stackslib/src/net/api/tests/mod.rs index f3e41d44bf..207beebde2 100644 --- a/stackslib/src/net/api/tests/mod.rs +++ b/stackslib/src/net/api/tests/mod.rs @@ -824,7 +824,7 @@ impl<'a> TestRPC<'a> { 32, ); - let tip_height : u64 = 1; + let tip_height: u64 = 1; TestRPC { privk1, From 5a812faf24e977ffa2e8cfecdb9c1e0e25056350 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Tue, 12 Nov 2024 16:51:53 +0100 Subject: [PATCH 26/33] fixed blockbyheight to use the marf --- stackslib/src/net/api/getblockbyheight.rs | 10 ++++++++++ stackslib/src/net/api/tests/getblockbyheight.rs | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/stackslib/src/net/api/getblockbyheight.rs b/stackslib/src/net/api/getblockbyheight.rs index 9a17589c5d..4e657dfdfc 100644 --- a/stackslib/src/net/api/getblockbyheight.rs +++ b/stackslib/src/net/api/getblockbyheight.rs @@ -25,6 +25,9 @@ use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId}; use stacks_common::types::net::PeerHost; use {serde, serde_json}; +use crate::chainstate::nakamoto::{ + NakamotoBlock, NakamotoChainState, NakamotoStagingBlocksConn, StacksDBIndexed, +}; use crate::chainstate::nakamoto::{ NakamotoBlock, NakamotoChainState, NakamotoStagingBlocksConn, StacksDBIndexed, }; @@ -124,6 +127,13 @@ impl RPCRequestHandler for RPCNakamotoBlockByHeightRequestHandler { } }; + let tip = match node.load_stacks_chain_tip(&preamble, &contents) { + Ok(tip) => tip, + Err(error_resp) => { + return error_resp.try_into_contents().map_err(NetError::from); + } + }; + let index_block_hash_res = node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { chainstate diff --git a/stackslib/src/net/api/tests/getblockbyheight.rs b/stackslib/src/net/api/tests/getblockbyheight.rs index f030197b46..0f608c4e13 100644 --- a/stackslib/src/net/api/tests/getblockbyheight.rs +++ b/stackslib/src/net/api/tests/getblockbyheight.rs @@ -51,6 +51,12 @@ fn test_try_parse_request() { let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default()); + // NOTE: MARF enforces the height to be a u32 value + let request = StacksHttpRequest::new_get_nakamoto_block_by_height( + addr.into(), + 0xfffffffe, + TipRequest::UseLatestAnchoredTip, + ); // NOTE: MARF enforces the height to be a u32 value let request = StacksHttpRequest::new_get_nakamoto_block_by_height( addr.into(), @@ -133,6 +139,15 @@ fn test_try_make_response() { let mut dummy_tip = rpc_test.canonical_tip.clone(); dummy_tip.0[0] = dummy_tip.0[0].wrapping_add(1); + let request = StacksHttpRequest::new_get_nakamoto_block_by_height( + addr.into(), + nakamoto_chain_tip_height, + TipRequest::SpecificTip(dummy_tip), + ); + // dummy hack for generating an invalid tip + let mut dummy_tip = rpc_test.canonical_tip.clone(); + dummy_tip.0[0] = dummy_tip.0[0].wrapping_add(1); + let request = StacksHttpRequest::new_get_nakamoto_block_by_height( addr.into(), nakamoto_chain_tip_height, From 553ec68195860633d812f69eeb28d7aefd18c33a Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Tue, 12 Nov 2024 18:35:57 +0100 Subject: [PATCH 27/33] removed get_block_id_by_block_height over staging db --- .../src/chainstate/nakamoto/staging_blocks.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/staging_blocks.rs b/stackslib/src/chainstate/nakamoto/staging_blocks.rs index ee295607a9..335b304a45 100644 --- a/stackslib/src/chainstate/nakamoto/staging_blocks.rs +++ b/stackslib/src/chainstate/nakamoto/staging_blocks.rs @@ -321,25 +321,6 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { .optional()?) } - /// Get the block ID of a staging block from block height. - /// Used for downloads - pub fn get_block_id_by_block_height( - &self, - block_height: u64, - ) -> Result, ChainstateError> { - let sql = "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE height = ?1"; - let args = params![block_height]; - - let mut stmt = self.deref().prepare(sql)?; - Ok(stmt - .query_row(args, |row| { - let block_id: StacksBlockId = row.get(0)?; - - Ok(block_id) - }) - .optional()?) - } - /// Get a Nakamoto block by index block hash, as well as its size. /// Verifies its integrity. /// Returns Ok(Some(block, size)) if the block was present From cdf6879249da6d2f5acce1dedb2bf2a334bc937f Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Wed, 13 Nov 2024 12:00:39 +0100 Subject: [PATCH 28/33] removed getblockbyhash --- .../src/chainstate/nakamoto/staging_blocks.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/staging_blocks.rs b/stackslib/src/chainstate/nakamoto/staging_blocks.rs index 335b304a45..382c708850 100644 --- a/stackslib/src/chainstate/nakamoto/staging_blocks.rs +++ b/stackslib/src/chainstate/nakamoto/staging_blocks.rs @@ -302,25 +302,6 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { .optional()?) } - /// Get the block ID of a staging block from block hash. - /// Used for downloads - pub fn get_block_id_by_block_hash( - &self, - block_hash: &BlockHeaderHash, - ) -> Result, ChainstateError> { - let sql = "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE block_hash = ?1"; - let args = params![block_hash]; - - let mut stmt = self.deref().prepare(sql)?; - Ok(stmt - .query_row(args, |row| { - let block_id: StacksBlockId = row.get(0)?; - - Ok(block_id) - }) - .optional()?) - } - /// Get a Nakamoto block by index block hash, as well as its size. /// Verifies its integrity. /// Returns Ok(Some(block, size)) if the block was present From bc0fc2e4cc91cd4d0f6fbe1374c639872d567f98 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Wed, 13 Nov 2024 12:01:23 +0100 Subject: [PATCH 29/33] removed files for getblockbyhash --- stackslib/src/net/api/getblockbyhash.rs | 210 ------------------ stackslib/src/net/api/tests/getblockbyhash.rs | 122 ---------- 2 files changed, 332 deletions(-) delete mode 100644 stackslib/src/net/api/getblockbyhash.rs delete mode 100644 stackslib/src/net/api/tests/getblockbyhash.rs diff --git a/stackslib/src/net/api/getblockbyhash.rs b/stackslib/src/net/api/getblockbyhash.rs deleted file mode 100644 index e27e34ab42..0000000000 --- a/stackslib/src/net/api/getblockbyhash.rs +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation -// Copyright (C) 2020-2024 Stacks Open Internet Foundation -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::io::{Read, Seek, SeekFrom, Write}; -use std::{fs, io}; - -use regex::{Captures, Regex}; -use rusqlite::Connection; -use serde::de::Error as de_Error; -use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; -use stacks_common::types::chainstate::{BlockHeaderHash, ConsensusHash, StacksBlockId}; -use stacks_common::types::net::PeerHost; -use {serde, serde_json}; - -use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState, NakamotoStagingBlocksConn}; -use crate::chainstate::stacks::db::StacksChainState; -use crate::chainstate::stacks::Error as ChainError; -use crate::net::api::getblock_v3::{NakamotoBlockStream, RPCNakamotoBlockRequestHandler}; -use crate::net::http::{ - parse_bytes, Error, HttpBadRequest, HttpChunkGenerator, HttpContentType, HttpNotFound, - HttpRequest, HttpRequestContents, HttpRequestPreamble, HttpResponse, HttpResponseContents, - HttpResponsePayload, HttpResponsePreamble, HttpServerError, HttpVersion, -}; -use crate::net::httpcore::{ - HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp, StacksHttpRequest, - StacksHttpResponse, -}; -use crate::net::{Error as NetError, StacksNodeState, TipRequest, MAX_HEADERS}; -use crate::util_lib::db::{DBConn, Error as DBError}; - -#[derive(Clone)] -pub struct RPCNakamotoBlockByHashRequestHandler { - pub block_hash: Option, -} - -impl RPCNakamotoBlockByHashRequestHandler { - pub fn new() -> Self { - Self { block_hash: None } - } -} - -/// Decode the HTTP request -impl HttpRequest for RPCNakamotoBlockByHashRequestHandler { - fn verb(&self) -> &'static str { - "GET" - } - - fn path_regex(&self) -> Regex { - Regex::new(r#"^/v3/blockbyhash/(?P[0-9a-f]{64})$"#).unwrap() - } - - fn metrics_identifier(&self) -> &str { - "/v3/blockbyhash/:block_hash" - } - - /// Try to decode this request. - /// There's nothing to load here, so just make sure the request is well-formed. - fn try_parse_request( - &mut self, - preamble: &HttpRequestPreamble, - captures: &Captures, - query: Option<&str>, - _body: &[u8], - ) -> Result { - if preamble.get_content_length() != 0 { - return Err(Error::DecodeError( - "Invalid Http request: expected 0-length body".to_string(), - )); - } - - let block_hash_str = captures - .name("block_hash") - .ok_or_else(|| { - Error::DecodeError("Failed to match path to block hash group".to_string()) - })? - .as_str(); - - let block_hash = BlockHeaderHash::from_hex(block_hash_str).map_err(|_| { - Error::DecodeError("Invalid path: unparseable consensus hash".to_string()) - })?; - self.block_hash = Some(block_hash); - - Ok(HttpRequestContents::new().query_string(query)) - } -} - -impl RPCRequestHandler for RPCNakamotoBlockByHashRequestHandler { - /// Reset internal state - fn restart(&mut self) { - self.block_hash = None; - } - - /// Make the response - fn try_handle_request( - &mut self, - preamble: HttpRequestPreamble, - _contents: HttpRequestContents, - node: &mut StacksNodeState, - ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> { - let block_hash = self - .block_hash - .take() - .ok_or(NetError::SendError("Missing `block_hash`".into()))?; - - let index_block_hash_res = - node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { - chainstate - .nakamoto_blocks_db() - .get_block_id_by_block_hash(&block_hash) - }); - - let block_id = match index_block_hash_res { - Ok(index_block_hash_opt) => match index_block_hash_opt { - Some(index_block_hash) => index_block_hash, - None => { - // block hash not found - let msg = format!("No such block {:?}\n", &block_hash); - warn!("{}", &msg); - return StacksHttpResponse::new_error(&preamble, &HttpNotFound::new(msg)) - .try_into_contents() - .map_err(NetError::from); - } - }, - Err(e) => { - // error querying the db - let msg = format!("Failed to load block {}: {:?}\n", &block_hash, &e); - warn!("{}", &msg); - return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg)) - .try_into_contents() - .map_err(NetError::from); - } - }; - - // start loading up the block - let stream = match RPCNakamotoBlockRequestHandler::get_stream_by_node(&block_id, node) { - Ok(stream) => stream, - Err(ChainError::NoSuchBlockError) => { - return StacksHttpResponse::new_error( - &preamble, - &HttpNotFound::new(format!("No such block {:?}\n", &block_hash)), - ) - .try_into_contents() - .map_err(NetError::from) - } - Err(e) => { - // nope -- error trying to check - let msg = format!("Failed to load block {}: {:?}\n", &block_hash, &e); - warn!("{}", &msg); - return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg)) - .try_into_contents() - .map_err(NetError::from); - } - }; - - let resp_preamble = HttpResponsePreamble::from_http_request_preamble( - &preamble, - 200, - "OK", - None, - HttpContentType::Bytes, - ); - - Ok(( - resp_preamble, - HttpResponseContents::from_stream(Box::new(stream)), - )) - } -} - -/// Decode the HTTP response -impl HttpResponse for RPCNakamotoBlockByHashRequestHandler { - /// Decode this response from a byte stream. This is called by the client to decode this - /// message - fn try_parse_response( - &self, - preamble: &HttpResponsePreamble, - body: &[u8], - ) -> Result { - let bytes = parse_bytes(preamble, body, MAX_MESSAGE_LEN.into())?; - Ok(HttpResponsePayload::Bytes(bytes)) - } -} - -impl StacksHttpRequest { - pub fn new_get_nakamoto_block_by_hash( - host: PeerHost, - block_hash: BlockHeaderHash, - ) -> StacksHttpRequest { - StacksHttpRequest::new_for_peer( - host, - "GET".into(), - format!("/v3/blockbyhash/{}", &block_hash), - HttpRequestContents::new(), - ) - .expect("FATAL: failed to construct request from infallible data") - } -} diff --git a/stackslib/src/net/api/tests/getblockbyhash.rs b/stackslib/src/net/api/tests/getblockbyhash.rs deleted file mode 100644 index 3abc1c8ea6..0000000000 --- a/stackslib/src/net/api/tests/getblockbyhash.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation -// Copyright (C) 2020-2024 Stacks Open Internet Foundation -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - -use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions}; -use clarity::vm::{ClarityName, ContractName}; -use stacks_common::codec::StacksMessageCodec; -use stacks_common::types::chainstate::{ - BlockHeaderHash, ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, -}; -use stacks_common::types::net::PeerHost; -use stacks_common::types::Address; - -use super::TestRPC; -use crate::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandle}; -use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState}; -use crate::chainstate::stacks::db::blocks::test::*; -use crate::chainstate::stacks::db::StacksChainState; -use crate::chainstate::stacks::{ - Error as chainstate_error, StacksBlock, StacksBlockHeader, StacksMicroblock, -}; -use crate::net::api::getblock_v3::NakamotoBlockStream; -use crate::net::api::*; -use crate::net::connection::ConnectionOptions; -use crate::net::http::HttpChunkGenerator; -use crate::net::httpcore::{ - HttpPreambleExtensions, HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp, - StacksHttpRequest, -}; -use crate::net::test::TestEventObserver; -use crate::net::tests::inv::nakamoto::make_nakamoto_peer_from_invs; -use crate::net::{ProtocolFamily, TipRequest}; -use crate::util_lib::db::DBConn; - -#[test] -fn test_try_parse_request() { - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); - let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default()); - - let request = - StacksHttpRequest::new_get_nakamoto_block_by_hash(addr.into(), BlockHeaderHash([0x11; 32])); - let bytes = request.try_serialize().unwrap(); - - debug!("Request:\n{}\n", std::str::from_utf8(&bytes).unwrap()); - - let (parsed_preamble, offset) = http.read_preamble(&bytes).unwrap(); - let mut handler = getblockbyhash::RPCNakamotoBlockByHashRequestHandler::new(); - let mut parsed_request = http - .handle_try_parse_request( - &mut handler, - &parsed_preamble.expect_request(), - &bytes[offset..], - ) - .unwrap(); - - // parsed request consumes headers that would not be in a constructed reqeuest - parsed_request.clear_headers(); - let (preamble, contents) = parsed_request.destruct(); - - // consumed path args - assert_eq!(handler.block_hash, Some(BlockHeaderHash([0x11; 32]))); - - assert_eq!(&preamble, request.preamble()); - - handler.restart(); - assert!(handler.block_hash.is_none()); -} - -#[test] -fn test_try_make_response() { - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); - - let test_observer = TestEventObserver::new(); - let rpc_test = TestRPC::setup_nakamoto(function_name!(), &test_observer); - - let nakamoto_chain_tip_hash = rpc_test.tip_hash.clone(); - let consensus_hash = rpc_test.consensus_hash.clone(); - - let mut requests = vec![]; - - // query existing block - let request = StacksHttpRequest::new_get_nakamoto_block_by_hash( - addr.into(), - nakamoto_chain_tip_hash.clone(), - ); - requests.push(request); - - // query non-existant block - let request = - StacksHttpRequest::new_get_nakamoto_block_by_hash(addr.into(), BlockHeaderHash([0x11; 32])); - requests.push(request); - - let mut responses = rpc_test.run(requests); - - // got the block - let response = responses.remove(0); - let resp = response.decode_nakamoto_block().unwrap(); - - assert_eq!(resp.header.block_hash(), nakamoto_chain_tip_hash); - - assert_eq!(resp.header.consensus_hash, consensus_hash); - - // no block - let response = responses.remove(0); - let (preamble, body) = response.destruct(); - - assert_eq!(preamble.status_code, 404); -} From 648c9a9fe8ad8cc75b768992c85c661f76ddced9 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Wed, 13 Nov 2024 12:07:52 +0100 Subject: [PATCH 30/33] reverted getblock_v3 patch --- stackslib/src/net/api/getblock_v3.rs | 40 +++++++++++++--------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/stackslib/src/net/api/getblock_v3.rs b/stackslib/src/net/api/getblock_v3.rs index 058e39dd1a..56e8063dda 100644 --- a/stackslib/src/net/api/getblock_v3.rs +++ b/stackslib/src/net/api/getblock_v3.rs @@ -46,6 +46,12 @@ pub struct RPCNakamotoBlockRequestHandler { pub block_id: Option, } +impl RPCNakamotoBlockRequestHandler { + pub fn new() -> Self { + Self { block_id: None } + } +} + pub struct NakamotoBlockStream { /// index block hash of the block to download pub index_block_hash: StacksBlockId, @@ -63,27 +69,6 @@ pub struct NakamotoBlockStream { pub rowid: i64, } -impl RPCNakamotoBlockRequestHandler { - pub fn new() -> Self { - Self { block_id: None } - } - - pub fn get_stream_by_node( - block_id: &StacksBlockId, - node: &mut StacksNodeState, - ) -> Result { - node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { - let Some((tenure_id, parent_block_id)) = chainstate - .nakamoto_blocks_db() - .get_tenure_and_parent_block_id(&block_id)? - else { - return Err(ChainError::NoSuchBlockError); - }; - NakamotoBlockStream::new(chainstate, block_id.clone(), tenure_id, parent_block_id) - }) - } -} - impl NakamotoBlockStream { pub fn new( chainstate: &StacksChainState, @@ -194,8 +179,19 @@ impl RPCRequestHandler for RPCNakamotoBlockRequestHandler { .take() .ok_or(NetError::SendError("Missing `block_id`".into()))?; + let stream_res = + node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { + let Some((tenure_id, parent_block_id)) = chainstate + .nakamoto_blocks_db() + .get_tenure_and_parent_block_id(&block_id)? + else { + return Err(ChainError::NoSuchBlockError); + }; + NakamotoBlockStream::new(chainstate, block_id.clone(), tenure_id, parent_block_id) + }); + // start loading up the block - let stream = match Self::get_stream_by_node(&block_id, node) { + let stream = match stream_res { Ok(stream) => stream, Err(ChainError::NoSuchBlockError) => { return StacksHttpResponse::new_error( From 5647bc9deda5929f9a26074ee2a422c741818428 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Tue, 19 Nov 2024 22:09:56 +0100 Subject: [PATCH 31/33] fixed rebasing for CHANGELOG and bitcoin-tests --- .github/workflows/bitcoin-tests.yml | 1 - CHANGELOG.md | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/bitcoin-tests.yml b/.github/workflows/bitcoin-tests.yml index 9f586c18ff..5b10594c54 100644 --- a/.github/workflows/bitcoin-tests.yml +++ b/.github/workflows/bitcoin-tests.yml @@ -143,7 +143,6 @@ jobs: - tests::nakamoto_integrations::clarity_cost_spend_down - tests::nakamoto_integrations::v3_blockbyhash_api_endpoint - tests::nakamoto_integrations::v3_blockbyheight_api_endpoint - - tests::nakamoto_integrations::clarity_cost_spend_down # TODO: enable these once v1 signer is supported by a new nakamoto epoch # - tests::signer::v1::dkg # - tests::signer::v1::sign_request_rejected diff --git a/CHANGELOG.md b/CHANGELOG.md index 2da6463b30..0298a24802 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,6 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE - Add `tenure_cost_limit_per_block_percentage` to the miner config file to control the percentage remaining tenure cost limit to consume per nakamoto block. - Add `/v3/blockbyhash` and `/v3/blockbyheight` rpc endpoints - Add `/v3/blocks/height/:block_height` rpc endpoint -- Add `tenure_cost_limit_per_block_percentage` to the miner config file to control the percentage remaining tenure cost limit to consume per nakamoto block. ## [3.0.0.0.1] From 0a4252f9add8702bb2b0ce0dc6ed11652834b5fc Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Tue, 19 Nov 2024 22:45:01 +0100 Subject: [PATCH 32/33] upodated CHANGELOG and integration tests --- .github/workflows/bitcoin-tests.yml | 1 - CHANGELOG.md | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/bitcoin-tests.yml b/.github/workflows/bitcoin-tests.yml index 5b10594c54..ebe9f433a9 100644 --- a/.github/workflows/bitcoin-tests.yml +++ b/.github/workflows/bitcoin-tests.yml @@ -141,7 +141,6 @@ jobs: - tests::nakamoto_integrations::v3_signer_api_endpoint - tests::nakamoto_integrations::signer_chainstate - tests::nakamoto_integrations::clarity_cost_spend_down - - tests::nakamoto_integrations::v3_blockbyhash_api_endpoint - tests::nakamoto_integrations::v3_blockbyheight_api_endpoint # TODO: enable these once v1 signer is supported by a new nakamoto epoch # - tests::signer::v1::dkg diff --git a/CHANGELOG.md b/CHANGELOG.md index 0298a24802..2f9d50c249 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,6 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE - Add index to `metadata_table` in Clarity DB on `blockhash` - Add `block_commit_delay_ms` to the config file to control the time to wait after seeing a new burn block, before submitting a block commit, to allow time for the first Nakamoto block of the new tenure to be mined, allowing this miner to avoid the need to RBF the block commit. - Add `tenure_cost_limit_per_block_percentage` to the miner config file to control the percentage remaining tenure cost limit to consume per nakamoto block. -- Add `/v3/blockbyhash` and `/v3/blockbyheight` rpc endpoints - Add `/v3/blocks/height/:block_height` rpc endpoint ## [3.0.0.0.1] From cd391dce4c96e66f5bda6d658ae263abb5d37ee0 Mon Sep 17 00:00:00 2001 From: Roberto De Ioris Date: Tue, 19 Nov 2024 22:52:40 +0100 Subject: [PATCH 33/33] completed rebase to develop --- stackslib/src/net/api/getblockbyheight.rs | 10 ---------- stackslib/src/net/api/tests/mod.rs | 8 -------- 2 files changed, 18 deletions(-) diff --git a/stackslib/src/net/api/getblockbyheight.rs b/stackslib/src/net/api/getblockbyheight.rs index 4e657dfdfc..9a17589c5d 100644 --- a/stackslib/src/net/api/getblockbyheight.rs +++ b/stackslib/src/net/api/getblockbyheight.rs @@ -25,9 +25,6 @@ use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId}; use stacks_common::types::net::PeerHost; use {serde, serde_json}; -use crate::chainstate::nakamoto::{ - NakamotoBlock, NakamotoChainState, NakamotoStagingBlocksConn, StacksDBIndexed, -}; use crate::chainstate::nakamoto::{ NakamotoBlock, NakamotoChainState, NakamotoStagingBlocksConn, StacksDBIndexed, }; @@ -127,13 +124,6 @@ impl RPCRequestHandler for RPCNakamotoBlockByHeightRequestHandler { } }; - let tip = match node.load_stacks_chain_tip(&preamble, &contents) { - Ok(tip) => tip, - Err(error_resp) => { - return error_resp.try_into_contents().map_err(NetError::from); - } - }; - let index_block_hash_res = node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| { chainstate diff --git a/stackslib/src/net/api/tests/mod.rs b/stackslib/src/net/api/tests/mod.rs index 207beebde2..40d329686d 100644 --- a/stackslib/src/net/api/tests/mod.rs +++ b/stackslib/src/net/api/tests/mod.rs @@ -61,8 +61,6 @@ mod getattachmentsinv; mod getblock; mod getblock_v3; mod getblockbyheight; -mod getblockbyhash; -mod getblockbyheight; mod getconstantval; mod getcontractabi; mod getcontractsrc; @@ -206,10 +204,6 @@ pub struct TestRPC<'a> { pub tip_hash: BlockHeaderHash, /// block height of the chain tip pub tip_height: u64, - /// block header hash of the chain tip - pub tip_hash: BlockHeaderHash, - /// block height of the chain tip - pub tip_height: u64, /// consensus hash of the chain tip pub consensus_hash: ConsensusHash, /// hash of last microblock @@ -927,8 +921,6 @@ impl<'a> TestRPC<'a> { consensus_hash: nakamoto_tip.consensus_hash.clone(), tip_hash: nakamoto_tip.anchored_header.block_hash(), tip_height: nakamoto_tip.stacks_block_height, - tip_hash: nakamoto_tip.anchored_header.block_hash(), - tip_height: nakamoto_tip.stacks_block_height, microblock_tip_hash: BlockHeaderHash([0x00; 32]), mempool_txids: vec![], microblock_txids: vec![],