diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index 8807eda6..3a77f16c 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -35,10 +35,10 @@ use zebra_state::{OutputIndex, OutputLocation, TransactionLocation}; use crate::queue::Queue; #[cfg(feature = "getblocktemplate-rpcs")] -mod get_block_template; +mod get_block_template_rpcs; #[cfg(feature = "getblocktemplate-rpcs")] -pub use get_block_template::{GetBlockTemplateRpc, GetBlockTemplateRpcImpl}; +pub use get_block_template_rpcs::{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_rpcs.rs similarity index 75% rename from zebra-rpc/src/methods/get_block_template.rs rename to zebra-rpc/src/methods/get_block_template_rpcs.rs index de7d3f45..8af354c1 100644 --- a/zebra-rpc/src/methods/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -6,7 +6,14 @@ use jsonrpc_core::{self, BoxFuture, Error, ErrorCode, Result}; use jsonrpc_derive::rpc; use tower::{Service, ServiceExt}; -use crate::methods::{GetBlockHash, MISSING_BLOCK_ERROR_CODE}; +pub(crate) mod types; + +use crate::methods::{ + get_block_template_rpcs::types::{ + coinbase::Coinbase, default_roots::DefaultRoots, get_block_template::GetBlockTemplate, + }, + GetBlockHash, MISSING_BLOCK_ERROR_CODE, +}; /// getblocktemplate RPC method signatures. #[rpc(server)] @@ -35,8 +42,19 @@ pub trait GetBlockTemplateRpc { /// /// - If `index` is positive then index = block height. /// - If `index` is negative then -1 is the last known valid block. + /// - This rpc method is available only if zebra is built with `--features getblocktemplate-rpcs`. #[rpc(name = "getblockhash")] fn get_block_hash(&self, index: i32) -> BoxFuture>; + + /// Documentation to be filled as we go. + /// + /// zcashd reference: [`getblocktemplate`](https://zcash-rpc.github.io/getblocktemplate.html) + /// + /// # Notes + /// + /// - This rpc method is available only if zebra is built with `--features getblocktemplate-rpcs`. + #[rpc(name = "getblocktemplate")] + fn get_block_template(&self) -> BoxFuture>; } /// RPC method implementations. @@ -139,6 +157,40 @@ where } .boxed() } + + fn get_block_template(&self) -> BoxFuture> { + async move { + let empty_string = String::from(""); + + // Returns empty `GetBlockTemplate` + Ok(GetBlockTemplate { + capabilities: vec![], + version: 0, + previous_block_hash: empty_string.clone(), + block_commitments_hash: empty_string.clone(), + light_client_root_hash: empty_string.clone(), + final_sapling_root_hash: empty_string.clone(), + default_roots: DefaultRoots { + merkle_root: empty_string.clone(), + chain_history_root: empty_string.clone(), + auth_data_root: empty_string.clone(), + block_commitments_hash: empty_string.clone(), + }, + transactions: vec![], + coinbase_txn: Coinbase {}, + target: empty_string.clone(), + min_time: 0, + mutable: vec![], + nonce_range: empty_string.clone(), + sigop_limit: 0, + size_limit: 0, + cur_time: 0, + bits: empty_string, + height: 0, + }) + } + .boxed() + } } /// Given a potentially negative index, find the corresponding `Height`. diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs new file mode 100644 index 00000000..61093910 --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs @@ -0,0 +1,6 @@ +//! Types used in mining RPC methods. + +pub(crate) mod coinbase; +pub(crate) mod default_roots; +pub(crate) mod get_block_template; +pub(crate) mod transaction; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/coinbase.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/coinbase.rs new file mode 100644 index 00000000..323f50ca --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/coinbase.rs @@ -0,0 +1,5 @@ +//! The `Coinbase` type is part of the `getblocktemplate` RPC method output. + +/// documentation and fields to be added in #5453. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Coinbase {} diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/default_roots.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/default_roots.rs new file mode 100644 index 00000000..b8bf3a49 --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/default_roots.rs @@ -0,0 +1,18 @@ +//! The `DefaultRoots` type is part of the `getblocktemplate` RPC method output. + +/// Documentation to be added in #5452 or #5455. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct DefaultRoots { + /// Add documentation. + #[serde(rename = "merkleroot")] + pub merkle_root: String, + /// Add documentation. + #[serde(rename = "chainhistoryroot")] + pub chain_history_root: String, + /// Add documentation. + #[serde(rename = "authdataroot")] + pub auth_data_root: String, + /// Add documentation. + #[serde(rename = "blockcommitmentshash")] + pub block_commitments_hash: String, +} diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs new file mode 100644 index 00000000..76a9bb8b --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs @@ -0,0 +1,57 @@ +//! The `GetBlockTempate` type is the output of the `getblocktemplate` RPC method. + +use crate::methods::get_block_template_rpcs::types::{ + coinbase::Coinbase, default_roots::DefaultRoots, transaction::Transaction, +}; + +/// Documentation to be added after we document all the individual fields. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct GetBlockTemplate { + /// Add documentation. + pub capabilities: Vec, + /// Add documentation. + pub version: usize, + /// Add documentation. + #[serde(rename = "previousblockhash")] + pub previous_block_hash: String, + /// Add documentation. + #[serde(rename = "blockcommitmentshash")] + pub block_commitments_hash: String, + /// Add documentation. + #[serde(rename = "lightclientroothash")] + pub light_client_root_hash: String, + /// Add documentation. + #[serde(rename = "finalsaplingroothash")] + pub final_sapling_root_hash: String, + /// Add documentation. + #[serde(rename = "defaultroots")] + pub default_roots: DefaultRoots, + /// Add documentation. + pub transactions: Vec, + /// Add documentation. + #[serde(rename = "coinbasetxn")] + pub coinbase_txn: Coinbase, + /// Add documentation. + pub target: String, + /// Add documentation. + #[serde(rename = "mintime")] + pub min_time: u32, + /// Add documentation. + pub mutable: Vec, + /// Add documentation. + #[serde(rename = "noncerange")] + pub nonce_range: String, + /// Add documentation. + #[serde(rename = "sigoplimit")] + pub sigop_limit: u32, + /// Add documentation. + #[serde(rename = "sizelimit")] + pub size_limit: u32, + /// Add documentation. + #[serde(rename = "curtime")] + pub cur_time: u32, + /// Add documentation. + pub bits: String, + /// Add documentation. + pub height: u32, +} diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/transaction.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/transaction.rs new file mode 100644 index 00000000..5ee0c528 --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/transaction.rs @@ -0,0 +1,5 @@ +//! The `Transaction` type is part of the `getblocktemplate` RPC method output. + +/// Documentation and fields to be added in #5454. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Transaction {} diff --git a/zebra-rpc/src/methods/tests/snapshot.rs b/zebra-rpc/src/methods/tests/snapshot.rs index 044910e6..3211cd92 100644 --- a/zebra-rpc/src/methods/tests/snapshot.rs +++ b/zebra-rpc/src/methods/tests/snapshot.rs @@ -191,6 +191,13 @@ async fn test_rpc_response_data_for_network(network: Network) { .expect("We should have a GetBlockHash struct"); snapshot_rpc_getblockhash(get_block_hash, &settings); + + // `getblocktemplate` + let get_block_template = get_block_template_rpc + .get_block_template() + .await + .expect("We should have a GetBlockTemplate struct"); + snapshot_rpc_getblocktemplate(get_block_template, &settings); } } @@ -292,6 +299,15 @@ fn snapshot_rpc_getblockhash(block_hash: GetBlockHash, settings: &insta::Setting settings.bind(|| insta::assert_json_snapshot!("get_block_hash", block_hash)); } +#[cfg(feature = "getblocktemplate-rpcs")] +/// Snapshot `getblocktemplate` response, using `cargo insta` and JSON serialization. +fn snapshot_rpc_getblocktemplate( + block_template: crate::methods::get_block_template_rpcs::types::get_block_template::GetBlockTemplate, + settings: &insta::Settings, +) { + settings.bind(|| insta::assert_json_snapshot!("get_block_template", block_template)); +} + /// 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_template@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_template@mainnet_10.snap new file mode 100644 index 00000000..e3a0491f --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshots/get_block_template@mainnet_10.snap @@ -0,0 +1,30 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot.rs +assertion_line: 308 +expression: block_template +--- +{ + "capabilities": [], + "version": 0, + "previousblockhash": "", + "blockcommitmentshash": "", + "lightclientroothash": "", + "finalsaplingroothash": "", + "defaultroots": { + "merkleroot": "", + "chainhistoryroot": "", + "authdataroot": "", + "blockcommitmentshash": "" + }, + "transactions": [], + "coinbasetxn": {}, + "target": "", + "mintime": 0, + "mutable": [], + "noncerange": "", + "sigoplimit": 0, + "sizelimit": 0, + "curtime": 0, + "bits": "", + "height": 0 +} diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_template@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_template@testnet_10.snap new file mode 100644 index 00000000..e3a0491f --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshots/get_block_template@testnet_10.snap @@ -0,0 +1,30 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot.rs +assertion_line: 308 +expression: block_template +--- +{ + "capabilities": [], + "version": 0, + "previousblockhash": "", + "blockcommitmentshash": "", + "lightclientroothash": "", + "finalsaplingroothash": "", + "defaultroots": { + "merkleroot": "", + "chainhistoryroot": "", + "authdataroot": "", + "blockcommitmentshash": "" + }, + "transactions": [], + "coinbasetxn": {}, + "target": "", + "mintime": 0, + "mutable": [], + "noncerange": "", + "sigoplimit": 0, + "sizelimit": 0, + "curtime": 0, + "bits": "", + "height": 0 +} diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 514646b4..07b89909 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -647,7 +647,7 @@ async fn rpc_getblockcount() { // Init RPC let get_block_template_rpc = - get_block_template::GetBlockTemplateRpcImpl::new(latest_chain_tip.clone(), read_state); + get_block_template_rpcs::GetBlockTemplateRpcImpl::new(latest_chain_tip.clone(), read_state); // Get the tip height using RPC method `get_block_count` let get_block_count = get_block_template_rpc @@ -686,7 +686,7 @@ async fn rpc_getblockcount_empty_state() { ); let get_block_template_rpc = - get_block_template::GetBlockTemplateRpcImpl::new(latest_chain_tip.clone(), read_state); + get_block_template_rpcs::GetBlockTemplateRpcImpl::new(latest_chain_tip.clone(), read_state); // Get the tip height using RPC method `get_block_count let get_block_count = get_block_template_rpc.get_block_count(); @@ -730,7 +730,7 @@ async fn rpc_getblockhash() { latest_chain_tip.clone(), ); let get_block_template_rpc = - get_block_template::GetBlockTemplateRpcImpl::new(latest_chain_tip, read_state); + get_block_template_rpcs::GetBlockTemplateRpcImpl::new(latest_chain_tip, read_state); // Query the hashes using positive indexes for (i, block) in blocks.iter().enumerate() { @@ -757,3 +757,70 @@ async fn rpc_getblockhash() { mempool.expect_no_requests().await; } + +#[cfg(feature = "getblocktemplate-rpcs")] +#[tokio::test(flavor = "multi_thread")] +async fn rpc_getblocktemplate() { + 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(); + + let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); + // Create a populated state service + let (_state, read_state, latest_chain_tip, _chain_tip_change) = + zebra_state::populated_state(blocks.clone(), Mainnet).await; + + // Init RPCs + let _rpc = RpcImpl::new( + "RPC test", + Mainnet, + false, + Buffer::new(mempool.clone(), 1), + Buffer::new(read_state.clone(), 1), + latest_chain_tip.clone(), + ); + let get_block_template_rpc = + get_block_template_rpcs::GetBlockTemplateRpcImpl::new(latest_chain_tip, read_state); + + let get_block_template = get_block_template_rpc + .get_block_template() + .await + .expect("We should have a GetBlockTemplate struct"); + + assert!(get_block_template.capabilities.is_empty()); + assert_eq!(get_block_template.version, 0); + assert!(get_block_template.previous_block_hash.is_empty()); + assert!(get_block_template.block_commitments_hash.is_empty()); + assert!(get_block_template.light_client_root_hash.is_empty()); + assert!(get_block_template.final_sapling_root_hash.is_empty()); + assert!(get_block_template.default_roots.merkle_root.is_empty()); + assert!(get_block_template + .default_roots + .chain_history_root + .is_empty()); + assert!(get_block_template.default_roots.auth_data_root.is_empty()); + assert!(get_block_template + .default_roots + .block_commitments_hash + .is_empty()); + assert!(get_block_template.transactions.is_empty()); + assert_eq!( + get_block_template.coinbase_txn, + get_block_template_rpcs::types::coinbase::Coinbase {} + ); + assert!(get_block_template.target.is_empty()); + assert_eq!(get_block_template.min_time, 0); + assert!(get_block_template.mutable.is_empty()); + assert!(get_block_template.nonce_range.is_empty()); + assert_eq!(get_block_template.sigop_limit, 0); + assert_eq!(get_block_template.size_limit, 0); + assert_eq!(get_block_template.cur_time, 0); + assert!(get_block_template.bits.is_empty()); + assert_eq!(get_block_template.height, 0); + + mempool.expect_no_requests().await; +}