From d81a5fc5765a68043199aa413a448a36c035e19f Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Wed, 19 Oct 2022 09:01:13 -0300 Subject: [PATCH] feat(rpc): introduce `getblocktemplate-rpcs` feature (#5357) * introduce `getblocktemplate-rpcs` feature * reorder imports * highlight the problem * add docs * add additional empty state test * add snapshot test * remove getblocktemplate trait * use `cfg-if` * leave server as it was * add a missing space to the docs * fix typo in test Co-authored-by: Arya * suggestion for introduce `getblocktemplate-rpcs` feature (#5418) * adds minimal new object for the get block template methods * updated TODO comment Co-authored-by: Arya --- zebra-rpc/Cargo.toml | 1 + zebra-rpc/src/methods.rs | 6 ++ zebra-rpc/src/methods/get_block_template.rs | 56 ++++++++++++ zebra-rpc/src/methods/tests/snapshot.rs | 20 ++++ .../snapshots/get_block_count@mainnet_10.snap | 6 ++ .../snapshots/get_block_count@testnet_10.snap | 6 ++ zebra-rpc/src/methods/tests/vectors.rs | 91 +++++++++++++++++++ zebra-rpc/src/server.rs | 21 ++++- 8 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 zebra-rpc/src/methods/get_block_template.rs create mode 100644 zebra-rpc/src/methods/tests/snapshots/get_block_count@mainnet_10.snap create mode 100644 zebra-rpc/src/methods/tests/snapshots/get_block_count@testnet_10.snap diff --git a/zebra-rpc/Cargo.toml b/zebra-rpc/Cargo.toml index eccf362d4..67b28233f 100644 --- a/zebra-rpc/Cargo.toml +++ b/zebra-rpc/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" [features] default = [] proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"] +getblocktemplate-rpcs = [] [dependencies] chrono = { version = "0.4.22", default-features = false, features = ["clock", "std"] } diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index 79f2d34f6..e39e5aa54 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -34,6 +34,12 @@ use zebra_state::{OutputIndex, OutputLocation, TransactionLocation}; use crate::queue::Queue; +#[cfg(feature = "getblocktemplate-rpcs")] +mod get_block_template; + +#[cfg(feature = "getblocktemplate-rpcs")] +pub use get_block_template::{GetBlockTemplateRpc, GetBlockTemplateRpcImpl}; + #[cfg(test)] mod tests; diff --git a/zebra-rpc/src/methods/get_block_template.rs b/zebra-rpc/src/methods/get_block_template.rs new file mode 100644 index 000000000..7856df4c2 --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template.rs @@ -0,0 +1,56 @@ +//! RPC methods related to mining only available with `getblocktemplate-rpcs` rust feature. +use zebra_chain::chain_tip::ChainTip; + +use jsonrpc_core::{self, Error, ErrorCode, Result}; +use jsonrpc_derive::rpc; + +/// getblocktemplate RPC method signatures. +#[rpc(server)] +pub trait GetBlockTemplateRpc { + /// Returns the height of the most recent block in the best valid block chain (equivalently, + /// the number of blocks in this chain excluding the genesis block). + /// + /// zcashd reference: [`getblockcount`](https://zcash.github.io/rpc/getblockcount.html) + /// + /// # Notes + /// + /// This rpc method is available only if zebra is built with `--features getblocktemplate-rpcs`. + #[rpc(name = "getblockcount")] + fn get_block_count(&self) -> Result; +} + +/// RPC method implementations. +pub struct GetBlockTemplateRpcImpl +where + Tip: ChainTip, +{ + // TODO: Add the other fields from the [`Rpc`] struct as-needed + /// Allows efficient access to the best tip of the blockchain. + latest_chain_tip: Tip, +} + +impl GetBlockTemplateRpcImpl +where + Tip: ChainTip + Clone + Send + Sync + 'static, +{ + /// Create a new instance of the RPC handler. + pub fn new(latest_chain_tip: Tip) -> Self { + Self { latest_chain_tip } + } +} + +impl GetBlockTemplateRpc for GetBlockTemplateRpcImpl +where + Tip: ChainTip + Send + Sync + 'static, +{ + fn get_block_count(&self) -> Result { + self.latest_chain_tip + .best_tip_height() + .map(|height| height.0) + .ok_or(Error { + code: ErrorCode::ServerError(0), + message: "No blocks in state".to_string(), + data: None, + }) + } +} diff --git a/zebra-rpc/src/methods/tests/snapshot.rs b/zebra-rpc/src/methods/tests/snapshot.rs index d9e1a8f42..2f860ff93 100644 --- a/zebra-rpc/src/methods/tests/snapshot.rs +++ b/zebra-rpc/src/methods/tests/snapshot.rs @@ -41,6 +41,9 @@ async fn test_rpc_response_data_for_network(network: Network) { let (_state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::populated_state(blocks.clone(), network).await; + #[cfg(feature = "getblocktemplate-rpcs")] + let latest_chain_tip_gbt_clone = latest_chain_tip.clone(); + // Init RPC let (rpc, _rpc_tx_queue_task_handle) = RpcImpl::new( "RPC test", @@ -166,6 +169,17 @@ async fn test_rpc_response_data_for_network(network: Network) { .await .expect("We should have a vector of strings"); snapshot_rpc_getaddressutxos(get_address_utxos, &settings); + + #[cfg(feature = "getblocktemplate-rpcs")] + { + let get_block_template_rpc = GetBlockTemplateRpcImpl::new(latest_chain_tip_gbt_clone); + + // `getblockcount` + let get_block_count = get_block_template_rpc + .get_block_count() + .expect("We should have a number"); + snapshot_rpc_getblockcount(get_block_count, &settings); + } } /// Snapshot `getinfo` response, using `cargo insta` and JSON serialization. @@ -254,6 +268,12 @@ fn snapshot_rpc_getaddressutxos(utxos: Vec, settings: &insta::S settings.bind(|| insta::assert_json_snapshot!("get_address_utxos", utxos)); } +#[cfg(feature = "getblocktemplate-rpcs")] +/// Snapshot `getblockcount` response, using `cargo insta` and JSON serialization. +fn snapshot_rpc_getblockcount(block_count: u32, settings: &insta::Settings) { + settings.bind(|| insta::assert_json_snapshot!("get_block_count", block_count)); +} + /// Utility function to convert a `Network` to a lowercase string. fn network_string(network: Network) -> String { let mut net_suffix = network.to_string(); diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_count@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_count@mainnet_10.snap new file mode 100644 index 000000000..e4e1509f8 --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshots/get_block_count@mainnet_10.snap @@ -0,0 +1,6 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot.rs +assertion_line: 273 +expression: block_count +--- +10 diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_count@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_count@testnet_10.snap new file mode 100644 index 000000000..e4e1509f8 --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshots/get_block_count@testnet_10.snap @@ -0,0 +1,6 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot.rs +assertion_line: 273 +expression: block_count +--- +10 diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index ab09932d8..4d22831d0 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -612,3 +612,94 @@ async fn rpc_getaddressutxos_response() { mempool.expect_no_requests().await; } + +#[tokio::test(flavor = "multi_thread")] +#[cfg(feature = "getblocktemplate-rpcs")] +async fn rpc_getblockcount() { + let _init_guard = zebra_test::init(); + + // Create a continuous chain of mainnet blocks from genesis + let blocks: Vec> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS + .iter() + .map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap()) + .collect(); + + // Get the height of the block at the tip using hardcoded block tip bytes. + // We want to test the RPC response is equal to this hash + let tip_block = blocks.last().unwrap(); + let tip_block_height = tip_block.coinbase_height().unwrap(); + + // Get a mempool handle + let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); + // Create a populated state service, the tip will be in `NUMBER_OF_BLOCKS`. + let (_state, read_state, latest_chain_tip, _chain_tip_change) = + zebra_state::populated_state(blocks.clone(), Mainnet).await; + + // Init RPC + let (_rpc, rpc_tx_queue_task_handle) = RpcImpl::new( + "RPC test", + Mainnet, + false, + Buffer::new(mempool.clone(), 1), + read_state, + latest_chain_tip.clone(), + ); + + // Init RPC + let get_block_template_rpc = + get_block_template::GetBlockTemplateRpcImpl::new(latest_chain_tip.clone()); + + // Get the tip height using RPC method `get_block_count` + let get_block_count = get_block_template_rpc + .get_block_count() + .expect("We should have a number"); + + // Check if response is equal to block 10 hash. + assert_eq!(get_block_count, tip_block_height.0); + + mempool.expect_no_requests().await; + + // The queue task should continue without errors or panics + let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never(); + assert!(matches!(rpc_tx_queue_task_result, None)); +} + +#[cfg(feature = "getblocktemplate-rpcs")] +#[tokio::test(flavor = "multi_thread")] +async fn rpc_getblockcount_empty_state() { + let _init_guard = zebra_test::init(); + + // Get a mempool handle + let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); + // Create an empty state + let (_state, read_state, latest_chain_tip, _chain_tip_change) = + zebra_state::init_test_services(Mainnet); + + // Init RPC + let (_rpc, rpc_tx_queue_task_handle) = RpcImpl::new( + "RPC test", + Mainnet, + false, + Buffer::new(mempool.clone(), 1), + read_state, + latest_chain_tip.clone(), + ); + + let get_block_template_rpc = + get_block_template::GetBlockTemplateRpcImpl::new(latest_chain_tip.clone()); + + // Get the tip height using RPC method `get_block_count + let get_block_count = get_block_template_rpc.get_block_count(); + + // state an empty so we should get an error + assert!(get_block_count.is_err()); + + // Check the error we got is the correct one + assert_eq!(get_block_count.err().unwrap().message, "No blocks in state"); + + mempool.expect_no_requests().await; + + // The queue task should continue without errors or panics + let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never(); + assert!(matches!(rpc_tx_queue_task_result, None)); +} diff --git a/zebra-rpc/src/server.rs b/zebra-rpc/src/server.rs index b49460502..4647b72e1 100644 --- a/zebra-rpc/src/server.rs +++ b/zebra-rpc/src/server.rs @@ -25,6 +25,9 @@ use crate::{ server::{compatibility::FixHttpRequestMiddleware, tracing_middleware::TracingMiddleware}, }; +#[cfg(feature = "getblocktemplate-rpcs")] +use crate::methods::{GetBlockTemplateRpc, GetBlockTemplateRpcImpl}; + pub mod compatibility; mod tracing_middleware; @@ -46,7 +49,7 @@ impl RpcServer { network: Network, ) -> (JoinHandle<()>, JoinHandle<()>) where - Version: ToString, + Version: ToString + Clone, Mempool: tower::Service + 'static, Mempool::Future: Send, @@ -64,6 +67,19 @@ impl RpcServer { if let Some(listen_addr) = config.listen_addr { info!("Trying to open RPC endpoint at {}...", listen_addr,); + // Create handler compatible with V1 and V2 RPC protocols + let mut io: MetaIoHandler<(), _> = + MetaIoHandler::new(Compatibility::Both, TracingMiddleware); + + #[cfg(feature = "getblocktemplate-rpcs")] + { + // Initialize the getblocktemplate rpc methods + let get_block_template_rpc_impl = + GetBlockTemplateRpcImpl::new(latest_chain_tip.clone()); + + io.extend_with(get_block_template_rpc_impl.to_delegate()); + } + // Initialize the rpc methods with the zebra version let (rpc_impl, rpc_tx_queue_task_handle) = RpcImpl::new( app_version, @@ -74,9 +90,6 @@ impl RpcServer { latest_chain_tip, ); - // Create handler compatible with V1 and V2 RPC protocols - let mut io: MetaIoHandler<(), _> = - MetaIoHandler::new(Compatibility::Both, TracingMiddleware); io.extend_with(rpc_impl.to_delegate()); // If zero, automatically scale threads to the number of CPU cores