From d7f679807213926f4d5e5322ce3b28678f7b78bf Mon Sep 17 00:00:00 2001 From: arya2 Date: Tue, 1 Nov 2022 16:13:29 -0400 Subject: [PATCH 01/19] adds submitblock rpc method --- Cargo.lock | 1 + zebra-consensus/Cargo.toml | 1 + zebra-consensus/src/block.rs | 2 + zebra-consensus/src/chain.rs | 2 +- zebra-consensus/src/lib.rs | 5 + zebra-rpc/Cargo.toml | 4 +- .../src/methods/get_block_template_rpcs.rs | 102 ++++++++++- .../methods/get_block_template_rpcs/types.rs | 1 + .../types/submit_block.rs | 24 +++ zebra-rpc/src/methods/tests/snapshot.rs | 3 +- .../tests/snapshot/get_block_template_rpcs.rs | 27 ++- zebra-rpc/src/methods/tests/vectors.rs | 87 +++++++++- zebra-rpc/src/server.rs | 45 ++++- zebra-rpc/src/server/tests/vectors.rs | 12 ++ zebrad/src/commands/start.rs | 9 +- zebrad/tests/acceptance.rs | 10 ++ zebrad/tests/common/getblocktemplate.rs | 164 ++++++++++++++++++ zebrad/tests/common/mod.rs | 2 + 18 files changed, 476 insertions(+), 25 deletions(-) create mode 100644 zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs create mode 100644 zebrad/tests/common/getblocktemplate.rs diff --git a/Cargo.lock b/Cargo.lock index 5788314eaac..798321f462e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5396,6 +5396,7 @@ dependencies = [ "tracing", "tracing-futures", "zebra-chain", + "zebra-consensus", "zebra-network", "zebra-node-services", "zebra-state", diff --git a/zebra-consensus/Cargo.toml b/zebra-consensus/Cargo.toml index e48f8982344..94055e09cb1 100644 --- a/zebra-consensus/Cargo.toml +++ b/zebra-consensus/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [features] default = [] +getblocktemplate-rpcs = [] proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"] [dependencies] diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index 6c8bd05ef89..90045c12106 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -39,6 +39,7 @@ mod subsidy; mod tests; /// Asynchronous block verification. +#[cfg_attr(feature = "getblocktemplate-rpcs", derive(Clone))] #[derive(Debug)] pub struct BlockVerifier { /// The network to be verified. @@ -93,6 +94,7 @@ where V: Service + Send + Clone + 'static, V::Future: Send + 'static, { + /// Creates a new BlockVerifier pub fn new(network: Network, state_service: S, transaction_verifier: V) -> Self { Self { network, diff --git a/zebra-consensus/src/chain.rs b/zebra-consensus/src/chain.rs index 9baef5b8399..05b5e0a8ab7 100644 --- a/zebra-consensus/src/chain.rs +++ b/zebra-consensus/src/chain.rs @@ -56,7 +56,7 @@ mod tests; /// /// We deliberately add extra slots, because they only cost a small amount of /// memory, but missing slots can significantly slow down Zebra. -const VERIFIER_BUFFER_BOUND: usize = 5; +pub const VERIFIER_BUFFER_BOUND: usize = 5; /// The chain verifier routes requests to either the checkpoint verifier or the /// block verifier, depending on the maximum checkpoint height. diff --git a/zebra-consensus/src/lib.rs b/zebra-consensus/src/lib.rs index 97e7075c6fb..2fec3c1ebae 100644 --- a/zebra-consensus/src/lib.rs +++ b/zebra-consensus/src/lib.rs @@ -56,3 +56,8 @@ pub use primitives::{ed25519, groth16, halo2, redjubjub, redpallas}; /// A boxed [`std::error::Error`]. pub type BoxError = Box; + +#[cfg(feature = "getblocktemplate-rpcs")] +pub use block::BlockVerifier; +#[cfg(feature = "getblocktemplate-rpcs")] +pub use transaction::Verifier as TransactionVerifier; diff --git a/zebra-rpc/Cargo.toml b/zebra-rpc/Cargo.toml index ae200557696..a962fb892e3 100644 --- a/zebra-rpc/Cargo.toml +++ b/zebra-rpc/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" [features] default = [] -getblocktemplate-rpcs = ["zebra-state/getblocktemplate-rpcs", "zebra-node-services/getblocktemplate-rpcs"] +getblocktemplate-rpcs = ["zebra-state/getblocktemplate-rpcs", "zebra-node-services/getblocktemplate-rpcs", "zebra-consensus/getblocktemplate-rpcs"] # Test-only features proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"] @@ -43,6 +43,7 @@ proptest = { version = "0.10.1", optional = true } proptest-derive = { version = "0.3.0", optional = true } zebra-chain = { path = "../zebra-chain" } +zebra-consensus = { path = "../zebra-consensus", optional = true } zebra-network = { path = "../zebra-network" } zebra-node-services = { path = "../zebra-node-services" } zebra-state = { path = "../zebra-state" } @@ -57,5 +58,6 @@ thiserror = "1.0.37" tokio = { version = "1.21.2", features = ["full", "tracing", "test-util"] } zebra-chain = { path = "../zebra-chain", features = ["proptest-impl"] } +zebra-consensus = { path = "../zebra-consensus" } zebra-state = { path = "../zebra-state", features = ["proptest-impl"] } zebra-test = { path = "../zebra-test/" } diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 16717ced2f3..5c4aac08c6b 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -1,16 +1,26 @@ //! RPC methods related to mining only available with `getblocktemplate-rpcs` rust feature. +use std::sync::Arc; + use futures::{FutureExt, TryFutureExt}; +use zebra_chain::{ + block::{self, Block, Height}, + chain_tip::ChainTip, + serialization::ZcashDeserializeInto, +}; + use jsonrpc_core::{self, BoxFuture, Error, ErrorCode, Result}; use jsonrpc_derive::rpc; use tower::{buffer::Buffer, Service, ServiceExt}; use zebra_chain::{amount::Amount, block::Height, chain_tip::ChainTip}; +use zebra_consensus::{BlockError, VerifyBlockError}; use zebra_node_services::mempool; use crate::methods::{ get_block_template_rpcs::types::{ - default_roots::DefaultRoots, get_block_template::GetBlockTemplate, + coinbase::Coinbase, default_roots::DefaultRoots, default_roots::DefaultRoots, + get_block_template::GetBlockTemplate, get_block_template::GetBlockTemplate, submit_block, transaction::TransactionTemplate, }, GetBlockHash, MISSING_BLOCK_ERROR_CODE, @@ -74,10 +84,26 @@ pub trait GetBlockTemplateRpc { /// This rpc method is available only if zebra is built with `--features getblocktemplate-rpcs`. #[rpc(name = "getblocktemplate")] fn get_block_template(&self) -> BoxFuture>; + + /// Submits block to the node to be validated and committed. + /// Returns the [`SentTransactionHash`] for the transaction, as a JSON string. + /// + /// zcashd reference: [`submitblock`](https://zcash.github.io/rpc/submitblock.html) + /// + /// # Parameters + /// - `hexdata` (string, required) + /// - `jsonparametersobject` (string, optional) - currently ignored + /// - holds a single field, workid, that must be included in submissions if provided by the server. + #[rpc(name = "submitblock")] + fn submit_block( + &self, + hex_data: String, + _options: Option, + ) -> BoxFuture>; } /// RPC method implementations. -pub struct GetBlockTemplateRpcImpl +pub struct GetBlockTemplateRpcImpl where Mempool: Service< mempool::Request, @@ -89,7 +115,11 @@ where Response = zebra_state::ReadResponse, Error = zebra_state::BoxError, >, - Tip: ChainTip, + BlockVerifier: Service, Response = block::Hash, Error = VerifyBlockError> + + Clone + + Send + + Sync + + 'static, { // TODO: Add the other fields from the [`Rpc`] struct as-needed @@ -107,9 +137,12 @@ where /// Allows efficient access to the best tip of the blockchain. latest_chain_tip: Tip, + + /// The full block verifier, used for submitting blocks. + block_verifier: BlockVerifier, } -impl GetBlockTemplateRpcImpl +impl GetBlockTemplateRpcImpl where Mempool: Service< mempool::Request, @@ -125,22 +158,30 @@ where + Sync + 'static, Tip: ChainTip + Clone + Send + Sync + 'static, + BlockVerifier: Service, Response = block::Hash, Error = VerifyBlockError> + + Clone + + Send + + Sync + + 'static, { /// Create a new instance of the handler for getblocktemplate RPCs. pub fn new( mempool: Buffer, state: State, latest_chain_tip: Tip, + block_verifier: BlockVerifier, ) -> Self { Self { mempool, state, latest_chain_tip, + block_verifier, } } } -impl GetBlockTemplateRpc for GetBlockTemplateRpcImpl +impl GetBlockTemplateRpc + for GetBlockTemplateRpcImpl where Mempool: Service< mempool::Request, @@ -158,6 +199,12 @@ where + 'static, >::Future: Send, Tip: ChainTip + Send + Sync + 'static, + BlockVerifier: Service, Response = block::Hash, Error = VerifyBlockError> + + Clone + + Send + + Sync + + 'static, + >>::Future: Send, { fn get_block_count(&self) -> Result { self.latest_chain_tip @@ -302,6 +349,51 @@ where } .boxed() } + + fn submit_block( + &self, + hex_data: String, + _options: Option, + ) -> BoxFuture> { + let mut block_verifier = self.block_verifier.clone(); + + async move { + let block = hex::decode(hex_data).map_err(|error| Error { + code: ErrorCode::ServerError(0), + message: format!("failed to decode hexdata, error msg: {error}"), + data: None, + })?; + + let block: Block = block.zcash_deserialize_into().map_err(|error| Error { + code: ErrorCode::ServerError(0), + message: format!("failed to deserialize into block, error msg: {error}"), + data: None, + })?; + + let block_verifier_response: std::result::Result = + block_verifier + .ready() + .await + .map_err(|error| Error { + code: ErrorCode::ServerError(0), + message: error.to_string(), + data: None, + })? + .call(Arc::new(block)) + .await; + + let submit_block_response = match block_verifier_response { + Ok(_block_hash) => submit_block::Response::Accepted, + Err(VerifyBlockError::Block { + source: BlockError::AlreadyInChain(..), + }) => submit_block::Response::Duplicate, + Err(_) => submit_block::Response::Rejected, + }; + + Ok(submit_block_response) + } + .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 index e2477ab2c91..78a55fd92c1 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs @@ -2,4 +2,5 @@ pub(crate) mod default_roots; pub(crate) mod get_block_template; +pub(crate) mod submit_block; pub(crate) mod transaction; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs new file mode 100644 index 00000000000..faf4dea91ea --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs @@ -0,0 +1,24 @@ +/// Optional argument `jsonparametersobject` for `submitblock` RPC request +/// +/// See notes for [`Rpc::submit_block`] method +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct JsonParameters { + pub(crate) work_id: String, +} + +/// Response to a `submitblock` RPC request +#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum Response { + /// Block was already committed to the non-finalized or finalized state + Duplicate, + /// Block was already added to the state queue or channel, but not yet committed to the non-finalized state + DuplicateInconclusive, + /// Block was already committed to the non-finalized state, but not on the best chain + Inconclusive, + /// Block rejected as invalid + Rejected, + /// Block successfully submitted, return null + #[serde(rename = "null")] + Accepted, +} diff --git a/zebra-rpc/src/methods/tests/snapshot.rs b/zebra-rpc/src/methods/tests/snapshot.rs index 132cd16d0f7..b2b72a7fdc9 100644 --- a/zebra-rpc/src/methods/tests/snapshot.rs +++ b/zebra-rpc/src/methods/tests/snapshot.rs @@ -46,7 +46,7 @@ async fn test_rpc_response_data_for_network(network: Network) { let mut mempool: MockService<_, _, _, zebra_node_services::BoxError> = MockService::build().for_unit_tests(); // Create a populated state service - let (_state, read_state, latest_chain_tip, _chain_tip_change) = + let (state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::populated_state(blocks.clone(), network).await; // Start snapshots of RPC responses. @@ -57,6 +57,7 @@ async fn test_rpc_response_data_for_network(network: Network) { #[cfg(feature = "getblocktemplate-rpcs")] get_block_template_rpcs::test_responses( mempool.clone(), + state.clone(), read_state.clone(), latest_chain_tip.clone(), settings.clone(), diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index d20d1b1ee62..3de7da21e46 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -8,6 +8,8 @@ use insta::Settings; use tower::{buffer::Buffer, Service}; +use zebra_chain::parameters::Network; +use zebra_consensus::{chain::VERIFIER_BUFFER_BOUND, BlockVerifier, TransactionVerifier}; use zebra_node_services::mempool; use zebra_state::LatestChainTip; @@ -15,18 +17,28 @@ use zebra_test::mock_service::{MockService, PanicAssertion}; use crate::methods::{GetBlockHash, GetBlockTemplateRpc, GetBlockTemplateRpcImpl}; -pub async fn test_responses( - mut mempool: MockService< +pub async fn test_responses( + mempool: MockService< mempool::Request, mempool::Response, PanicAssertion, zebra_node_services::BoxError, >, - read_state: State, + state: State, + read_state: ReadState, latest_chain_tip: LatestChainTip, settings: Settings, ) where State: Service< + zebra_state::Request, + Response = zebra_state::Response, + Error = zebra_state::BoxError, + > + Clone + + Send + + Sync + + 'static, + >::Future: Send, + ReadState: Service< zebra_state::ReadRequest, Response = zebra_state::ReadResponse, Error = zebra_state::BoxError, @@ -34,12 +46,19 @@ pub async fn test_responses( + Send + Sync + 'static, - >::Future: Send, + >::Future: Send, { + let tx_verifier = TransactionVerifier::new(Network::Mainnet, state.clone()); + let tx_verifier = Buffer::new( + tower::util::BoxService::new(tx_verifier), + VERIFIER_BUFFER_BOUND, + ); + let block_verifier = BlockVerifier::new(Network::Mainnet, state, tx_verifier); let get_block_template_rpc = GetBlockTemplateRpcImpl::new( Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip, + tower::ServiceBuilder::new().service(block_verifier), ); // `getblockcount` diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 0e80ec04655..8645bbb087b 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -5,6 +5,9 @@ use std::{ops::RangeInclusive, sync::Arc}; use jsonrpc_core::ErrorCode; use tower::buffer::Buffer; +#[cfg(feature = "getblocktemplate-rpcs")] +use tower::util::BoxService; + use zebra_chain::{ block::Block, chain_tip::NoChainTip, @@ -18,6 +21,9 @@ use zebra_node_services::BoxError; use zebra_test::mock_service::MockService; +#[cfg(feature = "getblocktemplate-rpcs")] +use zebra_consensus::{chain::VERIFIER_BUFFER_BOUND, BlockVerifier, TransactionVerifier}; + use super::super::*; #[tokio::test(flavor = "multi_thread")] @@ -632,14 +638,18 @@ async fn rpc_getblockcount() { // 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) = + let (state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::populated_state(blocks.clone(), Mainnet).await; // Init RPC + let tx_verifier = TransactionVerifier::new(Mainnet, state.clone()); + let tx_verifier = Buffer::new(BoxService::new(tx_verifier), VERIFIER_BUFFER_BOUND); + let block_verifier = BlockVerifier::new(Mainnet, state, tx_verifier); let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), + tower::ServiceBuilder::new().service(block_verifier), ); // Get the tip height using RPC method `get_block_count` @@ -661,14 +671,18 @@ async fn rpc_getblockcount_empty_state() { // 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) = + let (state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::init_test_services(Mainnet); // Init RPC + let tx_verifier = TransactionVerifier::new(Mainnet, state.clone()); + let tx_verifier = Buffer::new(BoxService::new(tx_verifier), VERIFIER_BUFFER_BOUND); + let block_verifier = BlockVerifier::new(Mainnet, state, tx_verifier); let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), + tower::ServiceBuilder::new().service(block_verifier), ); // Get the tip height using RPC method `get_block_count @@ -696,14 +710,18 @@ async fn rpc_getblockhash() { 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) = + let (state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::populated_state(blocks.clone(), Mainnet).await; // Init RPC + let tx_verifier = TransactionVerifier::new(Mainnet, state.clone()); + let tx_verifier = Buffer::new(BoxService::new(tx_verifier), VERIFIER_BUFFER_BOUND); + let block_verifier = BlockVerifier::new(Mainnet, state, tx_verifier); let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), + tower::ServiceBuilder::new().service(block_verifier), ); // Query the hashes using positive indexes @@ -745,14 +763,18 @@ async fn rpc_getblocktemplate() { 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) = + let (state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::populated_state(blocks.clone(), Mainnet).await; // Init RPC + let tx_verifier = TransactionVerifier::new(Mainnet, state.clone()); + let tx_verifier = Buffer::new(BoxService::new(tx_verifier), VERIFIER_BUFFER_BOUND); + let block_verifier = BlockVerifier::new(Mainnet, state, tx_verifier); let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), + tower::ServiceBuilder::new().service(block_verifier), ); let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template()); @@ -782,3 +804,60 @@ async fn rpc_getblocktemplate() { mempool.expect_no_requests().await; } + +#[cfg(feature = "getblocktemplate-rpcs")] +#[tokio::test(flavor = "multi_thread")] +async fn rpc_submitblock_errors() { + 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, 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(), + ); + + // Init RPC + let tx_verifier = TransactionVerifier::new(Mainnet, state.clone()); + let tx_verifier = Buffer::new(BoxService::new(tx_verifier), VERIFIER_BUFFER_BOUND); + let block_verifier = BlockVerifier::new(Mainnet, state, tx_verifier); + let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( + Buffer::new(mempool.clone(), 1), + read_state, + latest_chain_tip.clone(), + tower::ServiceBuilder::new().service(block_verifier), + ); + + // Try to submit pre-populated blocks and assert that it responds with duplicate. + for (_height, block_bytes) in zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS.iter() { + let hex_data = hex::encode(block_bytes); + + let submit_block_response = get_block_template_rpc + .submit_block(hex_data, None) + .await + .expect("We should have a GetBlockHash struct"); + + assert_eq!( + submit_block_response, + get_block_template_rpcs::types::submit_block::Response::Duplicate + ); + } + + mempool.expect_no_requests().await; + + // See zebrad::tests::acceptance::submit_block for success case. +} diff --git a/zebra-rpc/src/server.rs b/zebra-rpc/src/server.rs index 0c637428661..8d1db9947da 100644 --- a/zebra-rpc/src/server.rs +++ b/zebra-rpc/src/server.rs @@ -13,12 +13,19 @@ use jsonrpc_core::{Compatibility, MetaIoHandler}; use jsonrpc_http_server::ServerBuilder; use tokio::task::JoinHandle; use tower::{buffer::Buffer, Service}; + +#[cfg(feature = "getblocktemplate-rpcs")] +use tower::{util::BoxService, ServiceBuilder}; + use tracing::*; use tracing_futures::Instrument; use zebra_chain::{chain_tip::ChainTip, parameters::Network}; use zebra_node_services::{mempool, BoxError}; +#[cfg(feature = "getblocktemplate-rpcs")] +use zebra_consensus::{error::TransactionError, transaction, BlockVerifier}; + use crate::{ config::Config, methods::{Rpc, RpcImpl}, @@ -45,6 +52,23 @@ impl RpcServer { app_version: Version, mempool: Buffer, state: State, + // TODO: use cfg-if to apply trait constraints behind feature flag and remove the `Option`. + #[cfg(feature = "getblocktemplate-rpcs")] block_verifier: Option< + BlockVerifier< + Buffer< + BoxService< + zebra_state::Request, + zebra_state::Response, + Box, + >, + zebra_state::Request, + >, + Buffer< + BoxService, + transaction::Request, + >, + >, + >, latest_chain_tip: Tip, network: Network, ) -> (JoinHandle<()>, JoinHandle<()>) @@ -73,14 +97,19 @@ impl RpcServer { #[cfg(feature = "getblocktemplate-rpcs")] { - // Initialize the getblocktemplate rpc method handler - let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new( - mempool.clone(), - state.clone(), - latest_chain_tip.clone(), - ); - - io.extend_with(get_block_template_rpc_impl.to_delegate()); + if let Some(block_verifier) = block_verifier { + let block_verifier = ServiceBuilder::new().service(block_verifier); + + // Initialize the getblocktemplate rpc methods + let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new( + mempool.clone(), + state.clone(), + latest_chain_tip.clone(), + block_verifier, + ); + + io.extend_with(get_block_template_rpc_impl.to_delegate()); + } } // Initialize the rpc methods with the zebra version diff --git a/zebra-rpc/src/server/tests/vectors.rs b/zebra-rpc/src/server/tests/vectors.rs index c42c774e52d..f538281061c 100644 --- a/zebra-rpc/src/server/tests/vectors.rs +++ b/zebra-rpc/src/server/tests/vectors.rs @@ -54,6 +54,8 @@ fn rpc_server_spawn(parallel_cpu_threads: bool) { "RPC server test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), + #[cfg(feature = "getblocktemplate-rpcs")] + None, NoChainTip, Mainnet, ); @@ -121,6 +123,8 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool) { "RPC server test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), + #[cfg(feature = "getblocktemplate-rpcs")] + None, NoChainTip, Mainnet, ); @@ -175,6 +179,8 @@ fn rpc_server_spawn_port_conflict() { "RPC server 1 test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), + #[cfg(feature = "getblocktemplate-rpcs")] + None, NoChainTip, Mainnet, ); @@ -188,6 +194,8 @@ fn rpc_server_spawn_port_conflict() { "RPC server 2 conflict test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), + #[cfg(feature = "getblocktemplate-rpcs")] + None, NoChainTip, Mainnet, ); @@ -271,6 +279,8 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { "RPC server 1 test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), + #[cfg(feature = "getblocktemplate-rpcs")] + None, NoChainTip, Mainnet, ); @@ -284,6 +294,8 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { "RPC server 2 conflict test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), + #[cfg(feature = "getblocktemplate-rpcs")] + None, NoChainTip, Mainnet, ); diff --git a/zebrad/src/commands/start.rs b/zebrad/src/commands/start.rs index 3169ede3fbf..9be41c10df9 100644 --- a/zebrad/src/commands/start.rs +++ b/zebrad/src/commands/start.rs @@ -73,7 +73,8 @@ use futures::FutureExt; use tokio::{pin, select, sync::oneshot}; use tower::{builder::ServiceBuilder, util::BoxService}; use tracing_futures::Instrument; - +#[cfg(feature = "getblocktemplate-rpcs")] +use zebra_consensus::BlockVerifier; use zebra_rpc::server::RpcServer; use crate::{ @@ -150,6 +151,10 @@ impl StartCmd { ) .await; + #[cfg(feature = "getblocktemplate-rpcs")] + let block_verifier = + BlockVerifier::new(config.network.network, state.clone(), tx_verifier.clone()); + info!("initializing syncer"); let (syncer, sync_status) = ChainSync::new( &config, @@ -181,6 +186,8 @@ impl StartCmd { app_version(), mempool.clone(), read_only_state_service, + #[cfg(feature = "getblocktemplate-rpcs")] + Some(block_verifier), latest_chain_tip.clone(), config.network.network, ); diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 7313f681094..993e8422381 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -2157,3 +2157,13 @@ async fn sending_transactions_using_lightwalletd() -> Result<()> { async fn lightwalletd_wallet_grpc_tests() -> Result<()> { common::lightwalletd::wallet_grpc_test::run().await } + +/// Test successful submitblock rpc call +/// +/// See [`common::getblocktemplate`] for more information. +#[tokio::test] +#[ignore] +#[cfg(feature = "getblocktemplate-rpcs")] +async fn submit_block() -> Result<()> { + common::getblocktemplate::submit_block::run().await +} diff --git a/zebrad/tests/common/getblocktemplate.rs b/zebrad/tests/common/getblocktemplate.rs new file mode 100644 index 00000000000..b68bb9043b8 --- /dev/null +++ b/zebrad/tests/common/getblocktemplate.rs @@ -0,0 +1,164 @@ +use super::*; + +pub(crate) mod submit_block { + + use std::{path::PathBuf, thread}; + + use color_eyre::eyre::{eyre, Context, Result}; + + use futures::TryFutureExt; + use indexmap::IndexSet; + use reqwest::Client; + use tower::{Service, ServiceExt}; + use zebra_chain::{block::Height, parameters::Network, serialization::ZcashSerialize}; + use zebra_state::HashOrHeight; + use zebra_test::args; + + use crate::common::{ + cached_state::{copy_state_directory, start_state_service_with_cache_dir}, + config::{persistent_test_config, testdir}, + launch::ZebradTestDirExt, + lightwalletd::random_known_rpc_port_config, + }; + + use super::cached_state::{load_tip_height_from_state_directory, ZEBRA_CACHED_STATE_DIR}; + + async fn get_future_block_hex_data( + network: Network, + zebrad_state_path: &PathBuf, + ) -> Result> { + tracing::info!( + ?zebrad_state_path, + "getting cached sync height from ZEBRA_CACHED_STATE_DIR path" + ); + + let cached_sync_height = + load_tip_height_from_state_directory(network, zebrad_state_path.as_ref()).await?; + + let future_block_height = Height(cached_sync_height.0 + 1); + + tracing::info!( + ?cached_sync_height, + ?future_block_height, + "got cached sync height, copying state dir to tempdir" + ); + + let copied_state_path = copy_state_directory(network, &zebrad_state_path).await?; + + let mut config = persistent_test_config()?; + config.state.debug_stop_at_height = Some(future_block_height.0); + + let mut child = copied_state_path + .with_config(&mut config)? + .spawn_child(args!["start"])? + .bypass_test_capture(true); + + while child.is_running() { + thread::yield_now(); + } + + let _ = child.kill(true); + let copied_state_path = child.dir.take().unwrap(); + + let (_read_write_state_service, mut state, _latest_chain_tip, _chain_tip_change) = + start_state_service_with_cache_dir(network, copied_state_path.as_ref()).await?; + let request = zebra_state::ReadRequest::Block(HashOrHeight::Height(future_block_height)); + + let response = state + .ready() + .and_then(|ready_service| ready_service.call(request)) + .map_err(|error| eyre!(error)) + .await?; + + let block_hex_data = match response { + zebra_state::ReadResponse::Block(Some(block)) => { + hex::encode(block.zcash_serialize_to_vec()?) + } + zebra_state::ReadResponse::Block(None) => { + tracing::info!( + "Reached the end of the finalized chain, state is missing block at {future_block_height:?}", + ); + return Ok(None); + } + _ => unreachable!("Incorrect response from state service: {response:?}"), + }; + + Ok(Some(block_hex_data)) + } + + #[allow(clippy::print_stderr)] + pub(crate) async fn run() -> Result<(), color_eyre::Report> { + let _init_guard = zebra_test::init(); + + let mut config = random_known_rpc_port_config(true)?; + let network = config.network.network; + let rpc_address = config.rpc.listen_addr.unwrap(); + + config.state.cache_dir = match std::env::var_os(ZEBRA_CACHED_STATE_DIR) { + Some(path) => path.into(), + None => { + eprintln!( + "skipped submitblock test, \ + set the {ZEBRA_CACHED_STATE_DIR:?} environment variable to run the test", + ); + + return Ok(()); + } + }; + + // TODO: As part of or as a pre-cursor to issue #5015, + // - Use only original cached state, + // - sync until the tip + // - get first 3 blocks in non-finalized state via getblock rpc calls + // - restart zebra without peers + // - submit block(s) above the finalized tip height + let block_hex_data = get_future_block_hex_data(network, &config.state.cache_dir) + .await? + .expect("spawned zebrad in get_future_block_hex_data should live until it gets the next block"); + + // Runs the rest of this test without an internet connection + config.network.initial_mainnet_peers = IndexSet::new(); + config.network.initial_testnet_peers = IndexSet::new(); + config.mempool.debug_enable_at_height = Some(0); + + // We're using the cached state + config.state.ephemeral = false; + + let mut child = testdir()? + .with_exact_config(&config)? + .spawn_child(args!["start"])? + .bypass_test_capture(true); + + child.expect_stdout_line_matches(&format!("Opened RPC endpoint at {rpc_address}"))?; + + // Create an http client + let client = Client::new(); + + let res = client + .post(format!("http://{}", &rpc_address)) + .body(format!( + r#"{{"jsonrpc": "2.0", "method": "submitblock", "params": ["{block_hex_data}"], "id":123 }}"# + )) + .header("Content-Type", "application/json") + .send() + .await?; + + assert!(res.status().is_success()); + let res_text = res.text().await?; + + // Test rpc endpoint response + assert!(res_text.contains(r#""result":"null""#)); + + child.kill(false)?; + + let output = child.wait_with_output()?; + let output = output.assert_failure()?; + + // [Note on port conflict](#Note on port conflict) + output + .assert_was_killed() + .wrap_err("Possible port conflict. Are there other acceptance tests running?")?; + + Ok(()) + } +} diff --git a/zebrad/tests/common/mod.rs b/zebrad/tests/common/mod.rs index 3fa04adb0c6..c909cae05e5 100644 --- a/zebrad/tests/common/mod.rs +++ b/zebrad/tests/common/mod.rs @@ -13,6 +13,8 @@ pub mod cached_state; pub mod check; pub mod config; pub mod failure_messages; +#[cfg(feature = "getblocktemplate-rpcs")] +pub mod getblocktemplate; pub mod launch; pub mod lightwalletd; pub mod sync; From fc5e77a272277ff3a43aba329d0901eae569c155 Mon Sep 17 00:00:00 2001 From: arya2 Date: Tue, 1 Nov 2022 16:19:29 -0400 Subject: [PATCH 02/19] re-orders imports --- .../src/methods/get_block_template_rpcs.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 5c4aac08c6b..df96b166379 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -3,24 +3,23 @@ use std::sync::Arc; use futures::{FutureExt, TryFutureExt}; -use zebra_chain::{ - block::{self, Block, Height}, - chain_tip::ChainTip, - serialization::ZcashDeserializeInto, -}; - use jsonrpc_core::{self, BoxFuture, Error, ErrorCode, Result}; use jsonrpc_derive::rpc; use tower::{buffer::Buffer, Service, ServiceExt}; -use zebra_chain::{amount::Amount, block::Height, chain_tip::ChainTip}; +use zebra_chain::{ + amount::Amount, + block::Height, + block::{self, Block}, + chain_tip::ChainTip, + serialization::ZcashDeserializeInto, +}; use zebra_consensus::{BlockError, VerifyBlockError}; use zebra_node_services::mempool; use crate::methods::{ get_block_template_rpcs::types::{ - coinbase::Coinbase, default_roots::DefaultRoots, default_roots::DefaultRoots, - get_block_template::GetBlockTemplate, get_block_template::GetBlockTemplate, submit_block, + default_roots::DefaultRoots, get_block_template::GetBlockTemplate, submit_block, transaction::TransactionTemplate, }, GetBlockHash, MISSING_BLOCK_ERROR_CODE, From 2cf2f455b30c826e0e5492004a69a0d8a7fc0a1b Mon Sep 17 00:00:00 2001 From: arya2 Date: Tue, 1 Nov 2022 16:26:38 -0400 Subject: [PATCH 03/19] replaces thread::yield_now with async yield_now --- zebrad/tests/common/getblocktemplate.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zebrad/tests/common/getblocktemplate.rs b/zebrad/tests/common/getblocktemplate.rs index b68bb9043b8..a8697187965 100644 --- a/zebrad/tests/common/getblocktemplate.rs +++ b/zebrad/tests/common/getblocktemplate.rs @@ -2,7 +2,7 @@ use super::*; pub(crate) mod submit_block { - use std::{path::PathBuf, thread}; + use std::path::PathBuf; use color_eyre::eyre::{eyre, Context, Result}; @@ -54,7 +54,7 @@ pub(crate) mod submit_block { .bypass_test_capture(true); while child.is_running() { - thread::yield_now(); + tokio::task::yield_now().await; } let _ = child.kill(true); From 02f0613cfe9ac26622e70dac5997e98f35045bfd Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 2 Nov 2022 12:12:14 +1000 Subject: [PATCH 04/19] Fix doc warnings and unused variable warnings, add missing docs --- zebra-rpc/src/methods/get_block_template_rpcs.rs | 2 +- .../get_block_template_rpcs/types/submit_block.rs | 12 ++++++++++-- zebra-rpc/src/methods/tests/snapshot.rs | 3 ++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index df96b166379..220b35949da 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -85,7 +85,7 @@ pub trait GetBlockTemplateRpc { fn get_block_template(&self) -> BoxFuture>; /// Submits block to the node to be validated and committed. - /// Returns the [`SentTransactionHash`] for the transaction, as a JSON string. + /// Returns the [`submit_block::Response`] for the transaction, as a JSON string. /// /// zcashd reference: [`submitblock`](https://zcash.github.io/rpc/submitblock.html) /// diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs index faf4dea91ea..d8f79ad6de7 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs @@ -1,12 +1,20 @@ +//! Parameter and response types for the `submitblock` RPC. + +// Allow doc links to these imports. +#[allow(unused_imports)] +use crate::methods::get_block_template_rpcs::GetBlockTemplateRpc; + /// Optional argument `jsonparametersobject` for `submitblock` RPC request /// -/// See notes for [`Rpc::submit_block`] method +/// See notes for [`GetBlockTemplateRpc::submit_block`] method #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct JsonParameters { pub(crate) work_id: String, } -/// Response to a `submitblock` RPC request +/// Response to a `submitblock` RPC request. +/// +/// Zebra never returns "duplicate-invalid", because it does not store invalid blocks. #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "kebab-case")] pub enum Response { diff --git a/zebra-rpc/src/methods/tests/snapshot.rs b/zebra-rpc/src/methods/tests/snapshot.rs index b2b72a7fdc9..a37f55b9dfc 100644 --- a/zebra-rpc/src/methods/tests/snapshot.rs +++ b/zebra-rpc/src/methods/tests/snapshot.rs @@ -46,6 +46,7 @@ async fn test_rpc_response_data_for_network(network: Network) { let mut mempool: MockService<_, _, _, zebra_node_services::BoxError> = MockService::build().for_unit_tests(); // Create a populated state service + #[allow(unused_variables)] let (state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::populated_state(blocks.clone(), network).await; @@ -57,7 +58,7 @@ async fn test_rpc_response_data_for_network(network: Network) { #[cfg(feature = "getblocktemplate-rpcs")] get_block_template_rpcs::test_responses( mempool.clone(), - state.clone(), + state, read_state.clone(), latest_chain_tip.clone(), settings.clone(), From 894c482ca337a7bb5fcbdab66a81092ceaf4cfc3 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 2 Nov 2022 12:12:51 +1000 Subject: [PATCH 05/19] Mark work_id as optional --- .../src/methods/get_block_template_rpcs/types/submit_block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs index d8f79ad6de7..ce23cfba86e 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs @@ -9,7 +9,7 @@ use crate::methods::get_block_template_rpcs::GetBlockTemplateRpc; /// See notes for [`GetBlockTemplateRpc::submit_block`] method #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct JsonParameters { - pub(crate) work_id: String, + pub(crate) work_id: Option, } /// Response to a `submitblock` RPC request. From 136eae9ba62c975a8897a902603f4a9c93b13f10 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 2 Nov 2022 14:25:55 +1000 Subject: [PATCH 06/19] Use the same ChainVerifier for downloaded and submitted blocks --- zebra-consensus/src/lib.rs | 1 + zebra-rpc/Cargo.toml | 2 +- .../src/methods/get_block_template_rpcs.rs | 75 +++++++++++----- zebra-rpc/src/methods/tests/snapshot.rs | 1 + .../tests/snapshot/get_block_template_rpcs.rs | 23 +++-- zebra-rpc/src/methods/tests/vectors.rs | 88 ++++++++++++++----- zebra-rpc/src/server.rs | 71 ++++++--------- zebra-rpc/src/server/tests/vectors.rs | 30 ++++--- zebrad/src/commands/start.rs | 10 +-- 9 files changed, 185 insertions(+), 116 deletions(-) diff --git a/zebra-consensus/src/lib.rs b/zebra-consensus/src/lib.rs index 2fec3c1ebae..e222423963c 100644 --- a/zebra-consensus/src/lib.rs +++ b/zebra-consensus/src/lib.rs @@ -47,6 +47,7 @@ pub mod chain; pub mod error; pub use block::VerifyBlockError; +pub use chain::VerifyChainError; pub use checkpoint::{ CheckpointList, VerifyCheckpointError, MAX_CHECKPOINT_BYTE_COUNT, MAX_CHECKPOINT_HEIGHT_GAP, }; diff --git a/zebra-rpc/Cargo.toml b/zebra-rpc/Cargo.toml index a962fb892e3..67b3ee3696a 100644 --- a/zebra-rpc/Cargo.toml +++ b/zebra-rpc/Cargo.toml @@ -43,7 +43,7 @@ proptest = { version = "0.10.1", optional = true } proptest-derive = { version = "0.3.0", optional = true } zebra-chain = { path = "../zebra-chain" } -zebra-consensus = { path = "../zebra-consensus", optional = true } +zebra-consensus = { path = "../zebra-consensus" } zebra-network = { path = "../zebra-network" } zebra-node-services = { path = "../zebra-node-services" } zebra-state = { path = "../zebra-state" } diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 220b35949da..8dd9b1923e7 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -14,7 +14,7 @@ use zebra_chain::{ chain_tip::ChainTip, serialization::ZcashDeserializeInto, }; -use zebra_consensus::{BlockError, VerifyBlockError}; +use zebra_consensus::{BlockError, VerifyBlockError, VerifyChainError}; use zebra_node_services::mempool; use crate::methods::{ @@ -114,7 +114,7 @@ where Response = zebra_state::ReadResponse, Error = zebra_state::BoxError, >, - BlockVerifier: Service, Response = block::Hash, Error = VerifyBlockError> + BlockVerifier: Service, Response = block::Hash, Error = zebra_consensus::BoxError> + Clone + Send + Sync @@ -157,7 +157,7 @@ where + Sync + 'static, Tip: ChainTip + Clone + Send + Sync + 'static, - BlockVerifier: Service, Response = block::Hash, Error = VerifyBlockError> + BlockVerifier: Service, Response = block::Hash, Error = zebra_consensus::BoxError> + Clone + Send + Sync @@ -198,7 +198,7 @@ where + 'static, >::Future: Send, Tip: ChainTip + Send + Sync + 'static, - BlockVerifier: Service, Response = block::Hash, Error = VerifyBlockError> + BlockVerifier: Service, Response = block::Hash, Error = zebra_consensus::BoxError> + Clone + Send + Sync @@ -369,27 +369,56 @@ where data: None, })?; - let block_verifier_response: std::result::Result = - block_verifier - .ready() - .await - .map_err(|error| Error { - code: ErrorCode::ServerError(0), - message: error.to_string(), - data: None, - })? - .call(Arc::new(block)) - .await; - - let submit_block_response = match block_verifier_response { - Ok(_block_hash) => submit_block::Response::Accepted, - Err(VerifyBlockError::Block { - source: BlockError::AlreadyInChain(..), - }) => submit_block::Response::Duplicate, - Err(_) => submit_block::Response::Rejected, + let block_verifier_response = block_verifier + .ready() + .await + .map_err(|error| Error { + code: ErrorCode::ServerError(0), + message: error.to_string(), + data: None, + })? + .call(Arc::new(block)) + .await; + + let chain_error = match block_verifier_response { + // Currently, this match arm returns `null` (Accepted) for blocks committed + // to any chain, but Accepted is only for blocks in the best chain. + // + // TODO (#5487): + // - Inconclusive: check if the block is on a side-chain + // The difference is important to miners, because they want to mine on the best chain. + Ok(_block_hash) => return Ok(submit_block::Response::Accepted), + + // Turns BoxError into Result, + // by downcasting from Any to VerifyChainError. + Err(box_error) => box_error + .downcast::() + .map(|boxed_chain_error| *boxed_chain_error), }; - Ok(submit_block_response) + match chain_error { + Ok(VerifyChainError::Block(VerifyBlockError::Block { + source: BlockError::AlreadyInChain(..), + })) => Ok(submit_block::Response::Duplicate), + + // Currently, these match arms return Reject for the older duplicate in a queue, + // but queued duplicates should be DuplicateInconclusive. + // + // Optional TODO (#5487): + // - DuplicateInconclusive: turn these non-finalized state duplicate block errors + // into BlockError enum variants, and handle them as DuplicateInconclusive: + // - "block already sent to be committed to the state" + // - "replaced by newer request" + // - keep the older request in the queue, + // and return a duplicate error for the newer request immediately. + // This improves the speed of the RPC response. + // + // Checking the download queues and ChainVerifier buffer for duplicates + // might require architectural changes to Zebra, so we should only do it + // if mining pools really need it. + Ok(_verify_chain_error) => Ok(submit_block::Response::Rejected), + Err(_boxed_string_error) => Ok(submit_block::Response::Rejected), + } } .boxed() } diff --git a/zebra-rpc/src/methods/tests/snapshot.rs b/zebra-rpc/src/methods/tests/snapshot.rs index a37f55b9dfc..83c39bacaa1 100644 --- a/zebra-rpc/src/methods/tests/snapshot.rs +++ b/zebra-rpc/src/methods/tests/snapshot.rs @@ -57,6 +57,7 @@ async fn test_rpc_response_data_for_network(network: Network) { // Test getblocktemplate-rpcs snapshots #[cfg(feature = "getblocktemplate-rpcs")] get_block_template_rpcs::test_responses( + network, mempool.clone(), state, read_state.clone(), diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index 3de7da21e46..5ae283293e1 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -9,7 +9,6 @@ use insta::Settings; use tower::{buffer::Buffer, Service}; use zebra_chain::parameters::Network; -use zebra_consensus::{chain::VERIFIER_BUFFER_BOUND, BlockVerifier, TransactionVerifier}; use zebra_node_services::mempool; use zebra_state::LatestChainTip; @@ -18,6 +17,7 @@ use zebra_test::mock_service::{MockService, PanicAssertion}; use crate::methods::{GetBlockHash, GetBlockTemplateRpc, GetBlockTemplateRpcImpl}; pub async fn test_responses( + network: Network, mempool: MockService< mempool::Request, mempool::Response, @@ -48,17 +48,24 @@ pub async fn test_responses( + 'static, >::Future: Send, { - let tx_verifier = TransactionVerifier::new(Network::Mainnet, state.clone()); - let tx_verifier = Buffer::new( - tower::util::BoxService::new(tx_verifier), - VERIFIER_BUFFER_BOUND, - ); - let block_verifier = BlockVerifier::new(Network::Mainnet, state, tx_verifier); + let ( + block_verifier, + _transaction_verifier, + _parameter_download_task_handle, + _max_checkpoint_height, + ) = zebra_consensus::chain::init( + zebra_consensus::Config::default(), + network, + state.clone(), + true, + ) + .await; + let get_block_template_rpc = GetBlockTemplateRpcImpl::new( Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip, - tower::ServiceBuilder::new().service(block_verifier), + block_verifier, ); // `getblockcount` diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 8645bbb087b..a0a95faf2d1 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -5,9 +5,6 @@ use std::{ops::RangeInclusive, sync::Arc}; use jsonrpc_core::ErrorCode; use tower::buffer::Buffer; -#[cfg(feature = "getblocktemplate-rpcs")] -use tower::util::BoxService; - use zebra_chain::{ block::Block, chain_tip::NoChainTip, @@ -21,9 +18,6 @@ use zebra_node_services::BoxError; use zebra_test::mock_service::MockService; -#[cfg(feature = "getblocktemplate-rpcs")] -use zebra_consensus::{chain::VERIFIER_BUFFER_BOUND, BlockVerifier, TransactionVerifier}; - use super::super::*; #[tokio::test(flavor = "multi_thread")] @@ -641,15 +635,25 @@ async fn rpc_getblockcount() { let (state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::populated_state(blocks.clone(), Mainnet).await; + let ( + block_verifier, + _transaction_verifier, + _parameter_download_task_handle, + _max_checkpoint_height, + ) = zebra_consensus::chain::init( + zebra_consensus::Config::default(), + Mainnet, + state.clone(), + true, + ) + .await; + // Init RPC - let tx_verifier = TransactionVerifier::new(Mainnet, state.clone()); - let tx_verifier = Buffer::new(BoxService::new(tx_verifier), VERIFIER_BUFFER_BOUND); - let block_verifier = BlockVerifier::new(Mainnet, state, tx_verifier); let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), - tower::ServiceBuilder::new().service(block_verifier), + block_verifier, ); // Get the tip height using RPC method `get_block_count` @@ -674,10 +678,20 @@ async fn rpc_getblockcount_empty_state() { let (state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::init_test_services(Mainnet); + let ( + block_verifier, + _transaction_verifier, + _parameter_download_task_handle, + _max_checkpoint_height, + ) = zebra_consensus::chain::init( + zebra_consensus::Config::default(), + Mainnet, + state.clone(), + true, + ) + .await; + // Init RPC - let tx_verifier = TransactionVerifier::new(Mainnet, state.clone()); - let tx_verifier = Buffer::new(BoxService::new(tx_verifier), VERIFIER_BUFFER_BOUND); - let block_verifier = BlockVerifier::new(Mainnet, state, tx_verifier); let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Buffer::new(mempool.clone(), 1), read_state, @@ -713,10 +727,20 @@ async fn rpc_getblockhash() { let (state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::populated_state(blocks.clone(), Mainnet).await; + let ( + block_verifier, + _transaction_verifier, + _parameter_download_task_handle, + _max_checkpoint_height, + ) = zebra_consensus::chain::init( + zebra_consensus::Config::default(), + Mainnet, + state.clone(), + true, + ) + .await; + // Init RPC - let tx_verifier = TransactionVerifier::new(Mainnet, state.clone()); - let tx_verifier = Buffer::new(BoxService::new(tx_verifier), VERIFIER_BUFFER_BOUND); - let block_verifier = BlockVerifier::new(Mainnet, state, tx_verifier); let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Buffer::new(mempool.clone(), 1), read_state, @@ -766,10 +790,20 @@ async fn rpc_getblocktemplate() { let (state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::populated_state(blocks.clone(), Mainnet).await; + let ( + block_verifier, + _transaction_verifier, + _parameter_download_task_handle, + _max_checkpoint_height, + ) = zebra_consensus::chain::init( + zebra_consensus::Config::default(), + Mainnet, + state.clone(), + true, + ) + .await; + // Init RPC - let tx_verifier = TransactionVerifier::new(Mainnet, state.clone()); - let tx_verifier = Buffer::new(BoxService::new(tx_verifier), VERIFIER_BUFFER_BOUND); - let block_verifier = BlockVerifier::new(Mainnet, state, tx_verifier); let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Buffer::new(mempool.clone(), 1), read_state, @@ -831,10 +865,20 @@ async fn rpc_submitblock_errors() { latest_chain_tip.clone(), ); + let ( + block_verifier, + _transaction_verifier, + _parameter_download_task_handle, + _max_checkpoint_height, + ) = zebra_consensus::chain::init( + zebra_consensus::Config::default(), + Mainnet, + state.clone(), + true, + ) + .await; + // Init RPC - let tx_verifier = TransactionVerifier::new(Mainnet, state.clone()); - let tx_verifier = Buffer::new(BoxService::new(tx_verifier), VERIFIER_BUFFER_BOUND); - let block_verifier = BlockVerifier::new(Mainnet, state, tx_verifier); let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Buffer::new(mempool.clone(), 1), read_state, diff --git a/zebra-rpc/src/server.rs b/zebra-rpc/src/server.rs index 8d1db9947da..9e9ed9b52ba 100644 --- a/zebra-rpc/src/server.rs +++ b/zebra-rpc/src/server.rs @@ -7,24 +7,22 @@ //! See the full list of //! [Differences between JSON-RPC 1.0 and 2.0.](https://www.simple-is-better.org/rpc/#differences-between-1-0-and-2-0) -use std::panic; +use std::{panic, sync::Arc}; use jsonrpc_core::{Compatibility, MetaIoHandler}; use jsonrpc_http_server::ServerBuilder; use tokio::task::JoinHandle; use tower::{buffer::Buffer, Service}; -#[cfg(feature = "getblocktemplate-rpcs")] -use tower::{util::BoxService, ServiceBuilder}; - use tracing::*; use tracing_futures::Instrument; -use zebra_chain::{chain_tip::ChainTip, parameters::Network}; -use zebra_node_services::{mempool, BoxError}; - -#[cfg(feature = "getblocktemplate-rpcs")] -use zebra_consensus::{error::TransactionError, transaction, BlockVerifier}; +use zebra_chain::{ + block::{self, Block}, + chain_tip::ChainTip, + parameters::Network, +}; +use zebra_node_services::mempool; use crate::{ config::Config, @@ -47,35 +45,22 @@ pub struct RpcServer; impl RpcServer { /// Start a new RPC server endpoint - pub fn spawn( + pub fn spawn( config: Config, app_version: Version, mempool: Buffer, state: State, - // TODO: use cfg-if to apply trait constraints behind feature flag and remove the `Option`. - #[cfg(feature = "getblocktemplate-rpcs")] block_verifier: Option< - BlockVerifier< - Buffer< - BoxService< - zebra_state::Request, - zebra_state::Response, - Box, - >, - zebra_state::Request, - >, - Buffer< - BoxService, - transaction::Request, - >, - >, - >, + #[allow(unused_variables)] block_verifier: BlockVerifier, latest_chain_tip: Tip, network: Network, ) -> (JoinHandle<()>, JoinHandle<()>) where Version: ToString + Clone, - Mempool: tower::Service - + 'static, + Mempool: tower::Service< + mempool::Request, + Response = mempool::Response, + Error = zebra_node_services::BoxError, + > + 'static, Mempool::Future: Send, State: Service< zebra_state::ReadRequest, @@ -87,6 +72,12 @@ impl RpcServer { + 'static, State::Future: Send, Tip: ChainTip + Clone + Send + Sync + 'static, + BlockVerifier: Service, Response = block::Hash, Error = zebra_consensus::BoxError> + + Clone + + Send + + Sync + + 'static, + >>::Future: Send, { if let Some(listen_addr) = config.listen_addr { info!("Trying to open RPC endpoint at {}...", listen_addr,); @@ -97,19 +88,15 @@ impl RpcServer { #[cfg(feature = "getblocktemplate-rpcs")] { - if let Some(block_verifier) = block_verifier { - let block_verifier = ServiceBuilder::new().service(block_verifier); - - // Initialize the getblocktemplate rpc methods - let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new( - mempool.clone(), - state.clone(), - latest_chain_tip.clone(), - block_verifier, - ); - - io.extend_with(get_block_template_rpc_impl.to_delegate()); - } + // Initialize the getblocktemplate rpc methods + let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new( + mempool.clone(), + state.clone(), + latest_chain_tip.clone(), + block_verifier, + ); + + io.extend_with(get_block_template_rpc_impl.to_delegate()); } // Initialize the rpc methods with the zebra version diff --git a/zebra-rpc/src/server/tests/vectors.rs b/zebra-rpc/src/server/tests/vectors.rs index f538281061c..2ba956e8017 100644 --- a/zebra-rpc/src/server/tests/vectors.rs +++ b/zebra-rpc/src/server/tests/vectors.rs @@ -46,6 +46,8 @@ fn rpc_server_spawn(parallel_cpu_threads: bool) { rt.block_on(async { let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); + let mut block_verifier: MockService<_, _, _, BoxError> = + MockService::build().for_unit_tests(); info!("spawning RPC server..."); @@ -54,8 +56,7 @@ fn rpc_server_spawn(parallel_cpu_threads: bool) { "RPC server test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), - #[cfg(feature = "getblocktemplate-rpcs")] - None, + Buffer::new(block_verifier.clone(), 1), NoChainTip, Mainnet, ); @@ -64,6 +65,7 @@ fn rpc_server_spawn(parallel_cpu_threads: bool) { mempool.expect_no_requests().await; state.expect_no_requests().await; + block_verifier.expect_no_requests().await; // The server and queue tasks should continue without errors or panics let rpc_server_task_result = rpc_server_task_handle.now_or_never(); @@ -115,6 +117,8 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool) { rt.block_on(async { let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); + let mut block_verifier: MockService<_, _, _, BoxError> = + MockService::build().for_unit_tests(); info!("spawning RPC server..."); @@ -123,8 +127,7 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool) { "RPC server test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), - #[cfg(feature = "getblocktemplate-rpcs")] - None, + Buffer::new(block_verifier.clone(), 1), NoChainTip, Mainnet, ); @@ -133,6 +136,7 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool) { mempool.expect_no_requests().await; state.expect_no_requests().await; + block_verifier.expect_no_requests().await; // The server and queue tasks should continue without errors or panics let rpc_server_task_result = rpc_server_task_handle.now_or_never(); @@ -171,6 +175,8 @@ fn rpc_server_spawn_port_conflict() { let test_task_handle = rt.spawn(async { let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); + let mut block_verifier: MockService<_, _, _, BoxError> = + MockService::build().for_unit_tests(); info!("spawning RPC server 1..."); @@ -179,8 +185,7 @@ fn rpc_server_spawn_port_conflict() { "RPC server 1 test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), - #[cfg(feature = "getblocktemplate-rpcs")] - None, + Buffer::new(block_verifier.clone(), 1), NoChainTip, Mainnet, ); @@ -194,8 +199,7 @@ fn rpc_server_spawn_port_conflict() { "RPC server 2 conflict test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), - #[cfg(feature = "getblocktemplate-rpcs")] - None, + Buffer::new(block_verifier.clone(), 1), NoChainTip, Mainnet, ); @@ -204,6 +208,7 @@ fn rpc_server_spawn_port_conflict() { mempool.expect_no_requests().await; state.expect_no_requests().await; + block_verifier.expect_no_requests().await; // Because there is a panic inside a multi-threaded executor, // we can't depend on the exact behaviour of the other tasks, @@ -271,6 +276,8 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { let test_task_handle = rt.spawn(async { let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); + let mut block_verifier: MockService<_, _, _, BoxError> = + MockService::build().for_unit_tests(); info!("spawning parallel RPC server 1..."); @@ -279,8 +286,7 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { "RPC server 1 test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), - #[cfg(feature = "getblocktemplate-rpcs")] - None, + Buffer::new(block_verifier.clone(), 1), NoChainTip, Mainnet, ); @@ -294,8 +300,7 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { "RPC server 2 conflict test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), - #[cfg(feature = "getblocktemplate-rpcs")] - None, + Buffer::new(block_verifier.clone(), 1), NoChainTip, Mainnet, ); @@ -304,6 +309,7 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { mempool.expect_no_requests().await; state.expect_no_requests().await; + block_verifier.expect_no_requests().await; // Because there might be a panic inside a multi-threaded executor, // we can't depend on the exact behaviour of the other tasks, diff --git a/zebrad/src/commands/start.rs b/zebrad/src/commands/start.rs index 9be41c10df9..5fdfb3e7026 100644 --- a/zebrad/src/commands/start.rs +++ b/zebrad/src/commands/start.rs @@ -73,8 +73,7 @@ use futures::FutureExt; use tokio::{pin, select, sync::oneshot}; use tower::{builder::ServiceBuilder, util::BoxService}; use tracing_futures::Instrument; -#[cfg(feature = "getblocktemplate-rpcs")] -use zebra_consensus::BlockVerifier; + use zebra_rpc::server::RpcServer; use crate::{ @@ -151,10 +150,6 @@ impl StartCmd { ) .await; - #[cfg(feature = "getblocktemplate-rpcs")] - let block_verifier = - BlockVerifier::new(config.network.network, state.clone(), tx_verifier.clone()); - info!("initializing syncer"); let (syncer, sync_status) = ChainSync::new( &config, @@ -186,8 +181,7 @@ impl StartCmd { app_version(), mempool.clone(), read_only_state_service, - #[cfg(feature = "getblocktemplate-rpcs")] - Some(block_verifier), + chain_verifier.clone(), latest_chain_tip.clone(), config.network.network, ); From 7ea79d75ee7a9762f799b7bc9cff72d82d207305 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 2 Nov 2022 14:26:29 +1000 Subject: [PATCH 07/19] Revert unused changes & minor cleanups --- zebra-consensus/src/block.rs | 1 - zebra-consensus/src/chain.rs | 2 +- zebra-consensus/src/lib.rs | 5 ----- zebra-rpc/Cargo.toml | 3 +++ 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index 90045c12106..3788f768041 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -39,7 +39,6 @@ mod subsidy; mod tests; /// Asynchronous block verification. -#[cfg_attr(feature = "getblocktemplate-rpcs", derive(Clone))] #[derive(Debug)] pub struct BlockVerifier { /// The network to be verified. diff --git a/zebra-consensus/src/chain.rs b/zebra-consensus/src/chain.rs index 05b5e0a8ab7..9baef5b8399 100644 --- a/zebra-consensus/src/chain.rs +++ b/zebra-consensus/src/chain.rs @@ -56,7 +56,7 @@ mod tests; /// /// We deliberately add extra slots, because they only cost a small amount of /// memory, but missing slots can significantly slow down Zebra. -pub const VERIFIER_BUFFER_BOUND: usize = 5; +const VERIFIER_BUFFER_BOUND: usize = 5; /// The chain verifier routes requests to either the checkpoint verifier or the /// block verifier, depending on the maximum checkpoint height. diff --git a/zebra-consensus/src/lib.rs b/zebra-consensus/src/lib.rs index e222423963c..1c5fa20886b 100644 --- a/zebra-consensus/src/lib.rs +++ b/zebra-consensus/src/lib.rs @@ -57,8 +57,3 @@ pub use primitives::{ed25519, groth16, halo2, redjubjub, redpallas}; /// A boxed [`std::error::Error`]. pub type BoxError = Box; - -#[cfg(feature = "getblocktemplate-rpcs")] -pub use block::BlockVerifier; -#[cfg(feature = "getblocktemplate-rpcs")] -pub use transaction::Verifier as TransactionVerifier; diff --git a/zebra-rpc/Cargo.toml b/zebra-rpc/Cargo.toml index 67b3ee3696a..be2522d51a3 100644 --- a/zebra-rpc/Cargo.toml +++ b/zebra-rpc/Cargo.toml @@ -14,6 +14,9 @@ getblocktemplate-rpcs = ["zebra-state/getblocktemplate-rpcs", "zebra-node-servic # Test-only features proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"] +# Test-only features +proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"] + [dependencies] chrono = { version = "0.4.22", default-features = false, features = ["clock", "std"] } futures = "0.3.25" From d04ad71e6f64064b1d60c9b0586aa5230421d1ef Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 2 Nov 2022 14:53:31 +1000 Subject: [PATCH 08/19] Document currently-unreachable code --- zebra-rpc/src/methods/get_block_template_rpcs.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 8dd9b1923e7..52fc7315dee 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -417,7 +417,10 @@ where // might require architectural changes to Zebra, so we should only do it // if mining pools really need it. Ok(_verify_chain_error) => Ok(submit_block::Response::Rejected), - Err(_boxed_string_error) => Ok(submit_block::Response::Rejected), + + // This match arm is currently unreachable, but if future changes add extra error types, + // we want to turn them into `Rejected`. + Err(_unknown_error_type) => Ok(submit_block::Response::Rejected), } } .boxed() From 2160fbb03fd4b8ce63ee3a3f6e72e94039512486 Mon Sep 17 00:00:00 2001 From: arya2 Date: Wed, 2 Nov 2022 15:58:39 -0400 Subject: [PATCH 09/19] updates tests and submit_block response for AlreadyVerified error --- .../src/methods/get_block_template_rpcs.rs | 18 +++++++++------- .../types/submit_block.rs | 21 ++++++++++++++++--- zebra-rpc/src/methods/tests/snapshot.rs | 2 +- .../tests/snapshot/get_block_template_rpcs.rs | 20 +++++++++++++++++- .../snapshots/submit_block@testnet_10.snap | 10 +++++++++ zebra-rpc/src/methods/tests/vectors.rs | 7 ++----- zebra-rpc/src/server.rs | 3 ++- zebrad/tests/acceptance.rs | 2 +- ...template.rs => get_block_template_rpcs.rs} | 3 +++ zebrad/tests/common/mod.rs | 2 +- 10 files changed, 68 insertions(+), 20 deletions(-) create mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block@testnet_10.snap rename zebrad/tests/common/{getblocktemplate.rs => get_block_template_rpcs.rs} (96%) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 52fc7315dee..b88ad08080a 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -14,7 +14,7 @@ use zebra_chain::{ chain_tip::ChainTip, serialization::ZcashDeserializeInto, }; -use zebra_consensus::{BlockError, VerifyBlockError, VerifyChainError}; +use zebra_consensus::{BlockError, VerifyBlockError, VerifyChainError, VerifyCheckpointError}; use zebra_node_services::mempool; use crate::methods::{ @@ -396,10 +396,13 @@ where .map(|boxed_chain_error| *boxed_chain_error), }; - match chain_error { - Ok(VerifyChainError::Block(VerifyBlockError::Block { - source: BlockError::AlreadyInChain(..), - })) => Ok(submit_block::Response::Duplicate), + Ok(match chain_error { + Ok( + VerifyChainError::Checkpoint(VerifyCheckpointError::AlreadyVerified { .. }) + | VerifyChainError::Block(VerifyBlockError::Block { + source: BlockError::AlreadyInChain(..), + }), + ) => submit_block::ErrorResponse::Duplicate, // Currently, these match arms return Reject for the older duplicate in a queue, // but queued duplicates should be DuplicateInconclusive. @@ -416,12 +419,13 @@ where // Checking the download queues and ChainVerifier buffer for duplicates // might require architectural changes to Zebra, so we should only do it // if mining pools really need it. - Ok(_verify_chain_error) => Ok(submit_block::Response::Rejected), + Ok(_verify_chain_error) => submit_block::ErrorResponse::Rejected, // This match arm is currently unreachable, but if future changes add extra error types, // we want to turn them into `Rejected`. - Err(_unknown_error_type) => Ok(submit_block::Response::Rejected), + Err(_unknown_error_type) => submit_block::ErrorResponse::Rejected, } + .into()) } .boxed() } diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs index ce23cfba86e..7633fe4cb6c 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs @@ -17,7 +17,7 @@ pub struct JsonParameters { /// Zebra never returns "duplicate-invalid", because it does not store invalid blocks. #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "kebab-case")] -pub enum Response { +pub enum ErrorResponse { /// Block was already committed to the non-finalized or finalized state Duplicate, /// Block was already added to the state queue or channel, but not yet committed to the non-finalized state @@ -26,7 +26,22 @@ pub enum Response { Inconclusive, /// Block rejected as invalid Rejected, - /// Block successfully submitted, return null - #[serde(rename = "null")] +} + +/// Response to a `submitblock` RPC request. +/// +/// Zebra never returns "duplicate-invalid", because it does not store invalid blocks. +#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(untagged)] +pub enum Response { + /// Block was not successfully submitted, return error + ErrorResponse(ErrorResponse), + /// Block successfully submitted, returns null Accepted, } + +impl From for Response { + fn from(error_response: ErrorResponse) -> Self { + Self::ErrorResponse(error_response) + } +} diff --git a/zebra-rpc/src/methods/tests/snapshot.rs b/zebra-rpc/src/methods/tests/snapshot.rs index 83c39bacaa1..c7cb6fcd6e7 100644 --- a/zebra-rpc/src/methods/tests/snapshot.rs +++ b/zebra-rpc/src/methods/tests/snapshot.rs @@ -46,7 +46,7 @@ async fn test_rpc_response_data_for_network(network: Network) { let mut mempool: MockService<_, _, _, zebra_node_services::BoxError> = MockService::build().for_unit_tests(); // Create a populated state service - #[allow(unused_variables)] + #[cfg_attr(not(feature = "getblocktemplate-rpcs"), allow(unused_variables))] let (state, read_state, latest_chain_tip, _chain_tip_change) = zebra_state::populated_state(blocks.clone(), network).await; diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index 5ae283293e1..83478db39da 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -14,7 +14,10 @@ use zebra_state::LatestChainTip; use zebra_test::mock_service::{MockService, PanicAssertion}; -use crate::methods::{GetBlockHash, GetBlockTemplateRpc, GetBlockTemplateRpcImpl}; +use crate::methods::{ + get_block_template_rpcs::types::submit_block, GetBlockHash, GetBlockTemplateRpc, + GetBlockTemplateRpcImpl, +}; pub async fn test_responses( network: Network, @@ -97,6 +100,13 @@ pub async fn test_responses( .expect("unexpected error in getblocktemplate RPC call"); snapshot_rpc_getblocktemplate(get_block_template, &settings); + + // `submitblock` + let submit_block = get_block_template_rpc + .submit_block("".to_string(), None) + .await; + + snapshot_rpc_submitblock(submit_block, &settings); } /// Snapshot `getblockcount` response, using `cargo insta` and JSON serialization. @@ -116,3 +126,11 @@ fn snapshot_rpc_getblocktemplate( ) { settings.bind(|| insta::assert_json_snapshot!("get_block_template", block_template)); } + +/// Snapshot `submitblock` response, using `cargo insta` and JSON serialization. +fn snapshot_rpc_submitblock( + submit_block_response: jsonrpc_core::Result, + settings: &insta::Settings, +) { + settings.bind(|| insta::assert_json_snapshot!("submit_block", submit_block_response)); +} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block@testnet_10.snap new file mode 100644 index 00000000000..3ea8656b9cf --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block@testnet_10.snap @@ -0,0 +1,10 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: submit_block_response +--- +{ + "Err": { + "code": 0, + "message": "failed to deserialize into block, error msg: io error: failed to fill whole buffer" + } +} diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index a0a95faf2d1..84de1f540c6 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -890,14 +890,11 @@ async fn rpc_submitblock_errors() { for (_height, block_bytes) in zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS.iter() { let hex_data = hex::encode(block_bytes); - let submit_block_response = get_block_template_rpc - .submit_block(hex_data, None) - .await - .expect("We should have a GetBlockHash struct"); + let submit_block_response = get_block_template_rpc.submit_block(hex_data, None).await; assert_eq!( submit_block_response, - get_block_template_rpcs::types::submit_block::Response::Duplicate + Ok(get_block_template_rpcs::types::submit_block::ErrorResponse::Duplicate.into()) ); } diff --git a/zebra-rpc/src/server.rs b/zebra-rpc/src/server.rs index 9e9ed9b52ba..51640fe85b6 100644 --- a/zebra-rpc/src/server.rs +++ b/zebra-rpc/src/server.rs @@ -50,7 +50,8 @@ impl RpcServer { app_version: Version, mempool: Buffer, state: State, - #[allow(unused_variables)] block_verifier: BlockVerifier, + #[cfg_attr(not(feature = "getblocktemplate-rpcs"), allow(unused_variables))] + block_verifier: BlockVerifier, latest_chain_tip: Tip, network: Network, ) -> (JoinHandle<()>, JoinHandle<()>) diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 993e8422381..c8c6eefb268 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -2165,5 +2165,5 @@ async fn lightwalletd_wallet_grpc_tests() -> Result<()> { #[ignore] #[cfg(feature = "getblocktemplate-rpcs")] async fn submit_block() -> Result<()> { - common::getblocktemplate::submit_block::run().await + common::get_block_template_rpcs::submit_block::run().await } diff --git a/zebrad/tests/common/getblocktemplate.rs b/zebrad/tests/common/get_block_template_rpcs.rs similarity index 96% rename from zebrad/tests/common/getblocktemplate.rs rename to zebrad/tests/common/get_block_template_rpcs.rs index a8697187965..b5f42fe60cf 100644 --- a/zebrad/tests/common/getblocktemplate.rs +++ b/zebrad/tests/common/get_block_template_rpcs.rs @@ -1,3 +1,6 @@ +//! Example of how to run the submit_block test: +//! ZEBRA_CACHED_STATE_DIR=/path/to/zebra/chain cargo test submit_block --features getblocktemplate-rpcs --release -- --ignored --nocapture + use super::*; pub(crate) mod submit_block { diff --git a/zebrad/tests/common/mod.rs b/zebrad/tests/common/mod.rs index c909cae05e5..fe318719e55 100644 --- a/zebrad/tests/common/mod.rs +++ b/zebrad/tests/common/mod.rs @@ -14,7 +14,7 @@ pub mod check; pub mod config; pub mod failure_messages; #[cfg(feature = "getblocktemplate-rpcs")] -pub mod getblocktemplate; +pub mod get_block_template_rpcs; pub mod launch; pub mod lightwalletd; pub mod sync; From daffc8fec76e0112246402e76b1774eb28df12ec Mon Sep 17 00:00:00 2001 From: Arya Date: Wed, 2 Nov 2022 15:23:49 -0400 Subject: [PATCH 10/19] Update zebra-rpc/src/methods/get_block_template_rpcs.rs Co-authored-by: Alfredo Garcia --- zebra-rpc/src/methods/get_block_template_rpcs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index b88ad08080a..6bcfcd7efa6 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -85,7 +85,7 @@ pub trait GetBlockTemplateRpc { fn get_block_template(&self) -> BoxFuture>; /// Submits block to the node to be validated and committed. - /// Returns the [`submit_block::Response`] for the transaction, as a JSON string. + /// Returns the [`submit_block::Response`] for the operation, as a JSON string. /// /// zcashd reference: [`submitblock`](https://zcash.github.io/rpc/submitblock.html) /// From 6595f077bf8cee35005e9e894720ede46f916787 Mon Sep 17 00:00:00 2001 From: arya2 Date: Wed, 2 Nov 2022 16:18:10 -0400 Subject: [PATCH 11/19] changes names from BlockVerifier to ChainVerifier and block_verifier to chain_verifier to keep it consistent with naming in zebra-consensus --- .../src/methods/get_block_template_rpcs.rs | 28 +++++++++---------- .../tests/snapshot/get_block_template_rpcs.rs | 4 +-- zebra-rpc/src/methods/tests/vectors.rs | 20 ++++++------- zebra-rpc/src/server.rs | 10 +++---- zebra-rpc/src/server/tests/vectors.rs | 28 +++++++++---------- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 6bcfcd7efa6..9067f1e10ed 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -102,7 +102,7 @@ pub trait GetBlockTemplateRpc { } /// RPC method implementations. -pub struct GetBlockTemplateRpcImpl +pub struct GetBlockTemplateRpcImpl where Mempool: Service< mempool::Request, @@ -114,7 +114,7 @@ where Response = zebra_state::ReadResponse, Error = zebra_state::BoxError, >, - BlockVerifier: Service, Response = block::Hash, Error = zebra_consensus::BoxError> + ChainVerifier: Service, Response = block::Hash, Error = zebra_consensus::BoxError> + Clone + Send + Sync @@ -138,10 +138,10 @@ where latest_chain_tip: Tip, /// The full block verifier, used for submitting blocks. - block_verifier: BlockVerifier, + chain_verifier: ChainVerifier, } -impl GetBlockTemplateRpcImpl +impl GetBlockTemplateRpcImpl where Mempool: Service< mempool::Request, @@ -157,7 +157,7 @@ where + Sync + 'static, Tip: ChainTip + Clone + Send + Sync + 'static, - BlockVerifier: Service, Response = block::Hash, Error = zebra_consensus::BoxError> + ChainVerifier: Service, Response = block::Hash, Error = zebra_consensus::BoxError> + Clone + Send + Sync @@ -168,19 +168,19 @@ where mempool: Buffer, state: State, latest_chain_tip: Tip, - block_verifier: BlockVerifier, + chain_verifier: ChainVerifier, ) -> Self { Self { mempool, state, latest_chain_tip, - block_verifier, + chain_verifier, } } } -impl GetBlockTemplateRpc - for GetBlockTemplateRpcImpl +impl GetBlockTemplateRpc + for GetBlockTemplateRpcImpl where Mempool: Service< mempool::Request, @@ -198,12 +198,12 @@ where + 'static, >::Future: Send, Tip: ChainTip + Send + Sync + 'static, - BlockVerifier: Service, Response = block::Hash, Error = zebra_consensus::BoxError> + ChainVerifier: Service, Response = block::Hash, Error = zebra_consensus::BoxError> + Clone + Send + Sync + 'static, - >>::Future: Send, + >>::Future: Send, { fn get_block_count(&self) -> Result { self.latest_chain_tip @@ -354,7 +354,7 @@ where hex_data: String, _options: Option, ) -> BoxFuture> { - let mut block_verifier = self.block_verifier.clone(); + let mut chain_verifier = self.chain_verifier.clone(); async move { let block = hex::decode(hex_data).map_err(|error| Error { @@ -369,7 +369,7 @@ where data: None, })?; - let block_verifier_response = block_verifier + let chain_verifier_response = chain_verifier .ready() .await .map_err(|error| Error { @@ -380,7 +380,7 @@ where .call(Arc::new(block)) .await; - let chain_error = match block_verifier_response { + let chain_error = match chain_verifier_response { // Currently, this match arm returns `null` (Accepted) for blocks committed // to any chain, but Accepted is only for blocks in the best chain. // diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index 83478db39da..13ddac6db30 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -52,7 +52,7 @@ pub async fn test_responses( >::Future: Send, { let ( - block_verifier, + chain_verifier, _transaction_verifier, _parameter_download_task_handle, _max_checkpoint_height, @@ -68,7 +68,7 @@ pub async fn test_responses( Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip, - block_verifier, + chain_verifier, ); // `getblockcount` diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 84de1f540c6..e8f2fc02f8b 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -636,7 +636,7 @@ async fn rpc_getblockcount() { zebra_state::populated_state(blocks.clone(), Mainnet).await; let ( - block_verifier, + chain_verifier, _transaction_verifier, _parameter_download_task_handle, _max_checkpoint_height, @@ -653,7 +653,7 @@ async fn rpc_getblockcount() { Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), - block_verifier, + chain_verifier, ); // Get the tip height using RPC method `get_block_count` @@ -679,7 +679,7 @@ async fn rpc_getblockcount_empty_state() { zebra_state::init_test_services(Mainnet); let ( - block_verifier, + chain_verifier, _transaction_verifier, _parameter_download_task_handle, _max_checkpoint_height, @@ -696,7 +696,7 @@ async fn rpc_getblockcount_empty_state() { Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), - tower::ServiceBuilder::new().service(block_verifier), + tower::ServiceBuilder::new().service(chain_verifier), ); // Get the tip height using RPC method `get_block_count @@ -728,7 +728,7 @@ async fn rpc_getblockhash() { zebra_state::populated_state(blocks.clone(), Mainnet).await; let ( - block_verifier, + chain_verifier, _transaction_verifier, _parameter_download_task_handle, _max_checkpoint_height, @@ -745,7 +745,7 @@ async fn rpc_getblockhash() { Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), - tower::ServiceBuilder::new().service(block_verifier), + tower::ServiceBuilder::new().service(chain_verifier), ); // Query the hashes using positive indexes @@ -791,7 +791,7 @@ async fn rpc_getblocktemplate() { zebra_state::populated_state(blocks.clone(), Mainnet).await; let ( - block_verifier, + chain_verifier, _transaction_verifier, _parameter_download_task_handle, _max_checkpoint_height, @@ -808,7 +808,7 @@ async fn rpc_getblocktemplate() { Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), - tower::ServiceBuilder::new().service(block_verifier), + tower::ServiceBuilder::new().service(chain_verifier), ); let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template()); @@ -866,7 +866,7 @@ async fn rpc_submitblock_errors() { ); let ( - block_verifier, + chain_verifier, _transaction_verifier, _parameter_download_task_handle, _max_checkpoint_height, @@ -883,7 +883,7 @@ async fn rpc_submitblock_errors() { Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), - tower::ServiceBuilder::new().service(block_verifier), + tower::ServiceBuilder::new().service(chain_verifier), ); // Try to submit pre-populated blocks and assert that it responds with duplicate. diff --git a/zebra-rpc/src/server.rs b/zebra-rpc/src/server.rs index 51640fe85b6..866996017cc 100644 --- a/zebra-rpc/src/server.rs +++ b/zebra-rpc/src/server.rs @@ -45,13 +45,13 @@ pub struct RpcServer; impl RpcServer { /// Start a new RPC server endpoint - pub fn spawn( + pub fn spawn( config: Config, app_version: Version, mempool: Buffer, state: State, #[cfg_attr(not(feature = "getblocktemplate-rpcs"), allow(unused_variables))] - block_verifier: BlockVerifier, + chain_verifier: ChainVerifier, latest_chain_tip: Tip, network: Network, ) -> (JoinHandle<()>, JoinHandle<()>) @@ -73,12 +73,12 @@ impl RpcServer { + 'static, State::Future: Send, Tip: ChainTip + Clone + Send + Sync + 'static, - BlockVerifier: Service, Response = block::Hash, Error = zebra_consensus::BoxError> + ChainVerifier: Service, Response = block::Hash, Error = zebra_consensus::BoxError> + Clone + Send + Sync + 'static, - >>::Future: Send, + >>::Future: Send, { if let Some(listen_addr) = config.listen_addr { info!("Trying to open RPC endpoint at {}...", listen_addr,); @@ -94,7 +94,7 @@ impl RpcServer { mempool.clone(), state.clone(), latest_chain_tip.clone(), - block_verifier, + chain_verifier, ); io.extend_with(get_block_template_rpc_impl.to_delegate()); diff --git a/zebra-rpc/src/server/tests/vectors.rs b/zebra-rpc/src/server/tests/vectors.rs index 2ba956e8017..9db82385751 100644 --- a/zebra-rpc/src/server/tests/vectors.rs +++ b/zebra-rpc/src/server/tests/vectors.rs @@ -46,7 +46,7 @@ fn rpc_server_spawn(parallel_cpu_threads: bool) { rt.block_on(async { let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); - let mut block_verifier: MockService<_, _, _, BoxError> = + let mut chain_verifier: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); info!("spawning RPC server..."); @@ -56,7 +56,7 @@ fn rpc_server_spawn(parallel_cpu_threads: bool) { "RPC server test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), - Buffer::new(block_verifier.clone(), 1), + Buffer::new(chain_verifier.clone(), 1), NoChainTip, Mainnet, ); @@ -65,7 +65,7 @@ fn rpc_server_spawn(parallel_cpu_threads: bool) { mempool.expect_no_requests().await; state.expect_no_requests().await; - block_verifier.expect_no_requests().await; + chain_verifier.expect_no_requests().await; // The server and queue tasks should continue without errors or panics let rpc_server_task_result = rpc_server_task_handle.now_or_never(); @@ -117,7 +117,7 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool) { rt.block_on(async { let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); - let mut block_verifier: MockService<_, _, _, BoxError> = + let mut chain_verifier: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); info!("spawning RPC server..."); @@ -127,7 +127,7 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool) { "RPC server test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), - Buffer::new(block_verifier.clone(), 1), + Buffer::new(chain_verifier.clone(), 1), NoChainTip, Mainnet, ); @@ -136,7 +136,7 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool) { mempool.expect_no_requests().await; state.expect_no_requests().await; - block_verifier.expect_no_requests().await; + chain_verifier.expect_no_requests().await; // The server and queue tasks should continue without errors or panics let rpc_server_task_result = rpc_server_task_handle.now_or_never(); @@ -175,7 +175,7 @@ fn rpc_server_spawn_port_conflict() { let test_task_handle = rt.spawn(async { let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); - let mut block_verifier: MockService<_, _, _, BoxError> = + let mut chain_verifier: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); info!("spawning RPC server 1..."); @@ -185,7 +185,7 @@ fn rpc_server_spawn_port_conflict() { "RPC server 1 test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), - Buffer::new(block_verifier.clone(), 1), + Buffer::new(chain_verifier.clone(), 1), NoChainTip, Mainnet, ); @@ -199,7 +199,7 @@ fn rpc_server_spawn_port_conflict() { "RPC server 2 conflict test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), - Buffer::new(block_verifier.clone(), 1), + Buffer::new(chain_verifier.clone(), 1), NoChainTip, Mainnet, ); @@ -208,7 +208,7 @@ fn rpc_server_spawn_port_conflict() { mempool.expect_no_requests().await; state.expect_no_requests().await; - block_verifier.expect_no_requests().await; + chain_verifier.expect_no_requests().await; // Because there is a panic inside a multi-threaded executor, // we can't depend on the exact behaviour of the other tasks, @@ -276,7 +276,7 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { let test_task_handle = rt.spawn(async { let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); - let mut block_verifier: MockService<_, _, _, BoxError> = + let mut chain_verifier: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); info!("spawning parallel RPC server 1..."); @@ -286,7 +286,7 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { "RPC server 1 test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), - Buffer::new(block_verifier.clone(), 1), + Buffer::new(chain_verifier.clone(), 1), NoChainTip, Mainnet, ); @@ -300,7 +300,7 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { "RPC server 2 conflict test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), - Buffer::new(block_verifier.clone(), 1), + Buffer::new(chain_verifier.clone(), 1), NoChainTip, Mainnet, ); @@ -309,7 +309,7 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { mempool.expect_no_requests().await; state.expect_no_requests().await; - block_verifier.expect_no_requests().await; + chain_verifier.expect_no_requests().await; // Because there might be a panic inside a multi-threaded executor, // we can't depend on the exact behaviour of the other tasks, From 3635525faefa026d1fbfe45e59345f6479068232 Mon Sep 17 00:00:00 2001 From: arya2 Date: Wed, 2 Nov 2022 16:24:34 -0400 Subject: [PATCH 12/19] move how to run the submit_block test example to acceptance.rs --- zebrad/tests/acceptance.rs | 5 +++++ zebrad/tests/common/get_block_template_rpcs.rs | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index c8c6eefb268..2f8991efcdd 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -101,6 +101,11 @@ //! $ cargo test lightwalletd_wallet_grpc_tests --features lightwalletd-grpc-tests -- --ignored --nocapture //! ``` //! +//! ## Getblocktemplate tests +//! +//! Example of how to run the submit_block test: +//! ZEBRA_CACHED_STATE_DIR=/path/to/zebra/chain cargo test submit_block --features getblocktemplate-rpcs --release -- --ignored --nocapture +//! //! Please refer to the documentation of each test for more information. //! //! ## Disk Space for Testing diff --git a/zebrad/tests/common/get_block_template_rpcs.rs b/zebrad/tests/common/get_block_template_rpcs.rs index b5f42fe60cf..a8697187965 100644 --- a/zebrad/tests/common/get_block_template_rpcs.rs +++ b/zebrad/tests/common/get_block_template_rpcs.rs @@ -1,6 +1,3 @@ -//! Example of how to run the submit_block test: -//! ZEBRA_CACHED_STATE_DIR=/path/to/zebra/chain cargo test submit_block --features getblocktemplate-rpcs --release -- --ignored --nocapture - use super::*; pub(crate) mod submit_block { From e9e7a824fd93873dd9838c3fb2fe3b1f2bf3cfaa Mon Sep 17 00:00:00 2001 From: arya2 Date: Wed, 2 Nov 2022 18:05:06 -0400 Subject: [PATCH 13/19] updates snapshot tests --- .../methods/tests/snapshot/get_block_template_rpcs.rs | 9 +++++---- ...submit_block_deserialization_error@mainnet_10.snap} | 0 .../submit_block_deserialization_error@testnet_10.snap | 10 ++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) rename zebra-rpc/src/methods/tests/snapshot/snapshots/{submit_block@testnet_10.snap => submit_block_deserialization_error@mainnet_10.snap} (100%) create mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@testnet_10.snap diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index 13ddac6db30..901ae6e06b2 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -105,8 +105,7 @@ pub async fn test_responses( let submit_block = get_block_template_rpc .submit_block("".to_string(), None) .await; - - snapshot_rpc_submitblock(submit_block, &settings); + snapshot_rpc_submit_block_deserialization_error(submit_block, &settings); } /// Snapshot `getblockcount` response, using `cargo insta` and JSON serialization. @@ -128,9 +127,11 @@ fn snapshot_rpc_getblocktemplate( } /// Snapshot `submitblock` response, using `cargo insta` and JSON serialization. -fn snapshot_rpc_submitblock( +fn snapshot_rpc_submit_block_deserialization_error( submit_block_response: jsonrpc_core::Result, settings: &insta::Settings, ) { - settings.bind(|| insta::assert_json_snapshot!("submit_block", submit_block_response)); + settings.bind(|| { + insta::assert_json_snapshot!("submit_block_deserialization_error", submit_block_response) + }); } diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@mainnet_10.snap similarity index 100% rename from zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block@testnet_10.snap rename to zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@mainnet_10.snap diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@testnet_10.snap new file mode 100644 index 00000000000..3ea8656b9cf --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@testnet_10.snap @@ -0,0 +1,10 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: submit_block_response +--- +{ + "Err": { + "code": 0, + "message": "failed to deserialize into block, error msg: io error: failed to fill whole buffer" + } +} From 6b68c076ed6bfd82aaab6811f634b9fdf5d67b77 Mon Sep 17 00:00:00 2001 From: arya2 Date: Thu, 3 Nov 2022 12:43:17 -0400 Subject: [PATCH 14/19] moved acceptance test to a separate file --- .../tests/common/get_block_template_rpcs.rs | 163 +----------------- .../get_block_template_rpcs/submit_block.rs | 161 +++++++++++++++++ 2 files changed, 162 insertions(+), 162 deletions(-) create mode 100644 zebrad/tests/common/get_block_template_rpcs/submit_block.rs diff --git a/zebrad/tests/common/get_block_template_rpcs.rs b/zebrad/tests/common/get_block_template_rpcs.rs index a8697187965..b6c4b372061 100644 --- a/zebrad/tests/common/get_block_template_rpcs.rs +++ b/zebrad/tests/common/get_block_template_rpcs.rs @@ -1,164 +1,3 @@ use super::*; -pub(crate) mod submit_block { - - use std::path::PathBuf; - - use color_eyre::eyre::{eyre, Context, Result}; - - use futures::TryFutureExt; - use indexmap::IndexSet; - use reqwest::Client; - use tower::{Service, ServiceExt}; - use zebra_chain::{block::Height, parameters::Network, serialization::ZcashSerialize}; - use zebra_state::HashOrHeight; - use zebra_test::args; - - use crate::common::{ - cached_state::{copy_state_directory, start_state_service_with_cache_dir}, - config::{persistent_test_config, testdir}, - launch::ZebradTestDirExt, - lightwalletd::random_known_rpc_port_config, - }; - - use super::cached_state::{load_tip_height_from_state_directory, ZEBRA_CACHED_STATE_DIR}; - - async fn get_future_block_hex_data( - network: Network, - zebrad_state_path: &PathBuf, - ) -> Result> { - tracing::info!( - ?zebrad_state_path, - "getting cached sync height from ZEBRA_CACHED_STATE_DIR path" - ); - - let cached_sync_height = - load_tip_height_from_state_directory(network, zebrad_state_path.as_ref()).await?; - - let future_block_height = Height(cached_sync_height.0 + 1); - - tracing::info!( - ?cached_sync_height, - ?future_block_height, - "got cached sync height, copying state dir to tempdir" - ); - - let copied_state_path = copy_state_directory(network, &zebrad_state_path).await?; - - let mut config = persistent_test_config()?; - config.state.debug_stop_at_height = Some(future_block_height.0); - - let mut child = copied_state_path - .with_config(&mut config)? - .spawn_child(args!["start"])? - .bypass_test_capture(true); - - while child.is_running() { - tokio::task::yield_now().await; - } - - let _ = child.kill(true); - let copied_state_path = child.dir.take().unwrap(); - - let (_read_write_state_service, mut state, _latest_chain_tip, _chain_tip_change) = - start_state_service_with_cache_dir(network, copied_state_path.as_ref()).await?; - let request = zebra_state::ReadRequest::Block(HashOrHeight::Height(future_block_height)); - - let response = state - .ready() - .and_then(|ready_service| ready_service.call(request)) - .map_err(|error| eyre!(error)) - .await?; - - let block_hex_data = match response { - zebra_state::ReadResponse::Block(Some(block)) => { - hex::encode(block.zcash_serialize_to_vec()?) - } - zebra_state::ReadResponse::Block(None) => { - tracing::info!( - "Reached the end of the finalized chain, state is missing block at {future_block_height:?}", - ); - return Ok(None); - } - _ => unreachable!("Incorrect response from state service: {response:?}"), - }; - - Ok(Some(block_hex_data)) - } - - #[allow(clippy::print_stderr)] - pub(crate) async fn run() -> Result<(), color_eyre::Report> { - let _init_guard = zebra_test::init(); - - let mut config = random_known_rpc_port_config(true)?; - let network = config.network.network; - let rpc_address = config.rpc.listen_addr.unwrap(); - - config.state.cache_dir = match std::env::var_os(ZEBRA_CACHED_STATE_DIR) { - Some(path) => path.into(), - None => { - eprintln!( - "skipped submitblock test, \ - set the {ZEBRA_CACHED_STATE_DIR:?} environment variable to run the test", - ); - - return Ok(()); - } - }; - - // TODO: As part of or as a pre-cursor to issue #5015, - // - Use only original cached state, - // - sync until the tip - // - get first 3 blocks in non-finalized state via getblock rpc calls - // - restart zebra without peers - // - submit block(s) above the finalized tip height - let block_hex_data = get_future_block_hex_data(network, &config.state.cache_dir) - .await? - .expect("spawned zebrad in get_future_block_hex_data should live until it gets the next block"); - - // Runs the rest of this test without an internet connection - config.network.initial_mainnet_peers = IndexSet::new(); - config.network.initial_testnet_peers = IndexSet::new(); - config.mempool.debug_enable_at_height = Some(0); - - // We're using the cached state - config.state.ephemeral = false; - - let mut child = testdir()? - .with_exact_config(&config)? - .spawn_child(args!["start"])? - .bypass_test_capture(true); - - child.expect_stdout_line_matches(&format!("Opened RPC endpoint at {rpc_address}"))?; - - // Create an http client - let client = Client::new(); - - let res = client - .post(format!("http://{}", &rpc_address)) - .body(format!( - r#"{{"jsonrpc": "2.0", "method": "submitblock", "params": ["{block_hex_data}"], "id":123 }}"# - )) - .header("Content-Type", "application/json") - .send() - .await?; - - assert!(res.status().is_success()); - let res_text = res.text().await?; - - // Test rpc endpoint response - assert!(res_text.contains(r#""result":"null""#)); - - child.kill(false)?; - - let output = child.wait_with_output()?; - let output = output.assert_failure()?; - - // [Note on port conflict](#Note on port conflict) - output - .assert_was_killed() - .wrap_err("Possible port conflict. Are there other acceptance tests running?")?; - - Ok(()) - } -} +pub(crate) mod submit_block; diff --git a/zebrad/tests/common/get_block_template_rpcs/submit_block.rs b/zebrad/tests/common/get_block_template_rpcs/submit_block.rs new file mode 100644 index 00000000000..1618e0387e3 --- /dev/null +++ b/zebrad/tests/common/get_block_template_rpcs/submit_block.rs @@ -0,0 +1,161 @@ +use std::path::PathBuf; + +use color_eyre::eyre::{eyre, Context, Result}; + +use futures::TryFutureExt; +use indexmap::IndexSet; +use reqwest::Client; +use tower::{Service, ServiceExt}; +use zebra_chain::{block::Height, parameters::Network, serialization::ZcashSerialize}; +use zebra_state::HashOrHeight; +use zebra_test::args; + +use crate::common::{ + cached_state::{copy_state_directory, start_state_service_with_cache_dir}, + config::{persistent_test_config, testdir}, + launch::ZebradTestDirExt, + lightwalletd::random_known_rpc_port_config, +}; + +use super::cached_state::{load_tip_height_from_state_directory, ZEBRA_CACHED_STATE_DIR}; + +async fn get_future_block_hex_data( + network: Network, + zebrad_state_path: &PathBuf, +) -> Result> { + tracing::info!( + ?zebrad_state_path, + "getting cached sync height from ZEBRA_CACHED_STATE_DIR path" + ); + + let cached_sync_height = + load_tip_height_from_state_directory(network, zebrad_state_path.as_ref()).await?; + + let future_block_height = Height(cached_sync_height.0 + 1); + + tracing::info!( + ?cached_sync_height, + ?future_block_height, + "got cached sync height, copying state dir to tempdir" + ); + + let copied_state_path = copy_state_directory(network, &zebrad_state_path).await?; + + let mut config = persistent_test_config()?; + config.state.debug_stop_at_height = Some(future_block_height.0); + + let mut child = copied_state_path + .with_config(&mut config)? + .spawn_child(args!["start"])? + .bypass_test_capture(true); + + while child.is_running() { + tokio::task::yield_now().await; + } + + let _ = child.kill(true); + let copied_state_path = child.dir.take().unwrap(); + + let (_read_write_state_service, mut state, _latest_chain_tip, _chain_tip_change) = + start_state_service_with_cache_dir(network, copied_state_path.as_ref()).await?; + let request = zebra_state::ReadRequest::Block(HashOrHeight::Height(future_block_height)); + + let response = state + .ready() + .and_then(|ready_service| ready_service.call(request)) + .map_err(|error| eyre!(error)) + .await?; + + let block_hex_data = match response { + zebra_state::ReadResponse::Block(Some(block)) => { + hex::encode(block.zcash_serialize_to_vec()?) + } + zebra_state::ReadResponse::Block(None) => { + tracing::info!( + "Reached the end of the finalized chain, state is missing block at {future_block_height:?}", + ); + return Ok(None); + } + _ => unreachable!("Incorrect response from state service: {response:?}"), + }; + + Ok(Some(block_hex_data)) +} + +#[allow(clippy::print_stderr)] +pub(crate) async fn run() -> Result<(), color_eyre::Report> { + let _init_guard = zebra_test::init(); + + let mut config = random_known_rpc_port_config(true)?; + let network = config.network.network; + let rpc_address = config.rpc.listen_addr.unwrap(); + + config.state.cache_dir = match std::env::var_os(ZEBRA_CACHED_STATE_DIR) { + Some(path) => path.into(), + None => { + eprintln!( + "skipped submitblock test, \ + set the {ZEBRA_CACHED_STATE_DIR:?} environment variable to run the test", + ); + + return Ok(()); + } + }; + + // TODO: As part of or as a pre-cursor to issue #5015, + // - Use only original cached state, + // - sync until the tip + // - get first 3 blocks in non-finalized state via getblock rpc calls + // - restart zebra without peers + // - submit block(s) above the finalized tip height + let block_hex_data = get_future_block_hex_data(network, &config.state.cache_dir) + .await? + .expect( + "spawned zebrad in get_future_block_hex_data should live until it gets the next block", + ); + + // Runs the rest of this test without an internet connection + config.network.initial_mainnet_peers = IndexSet::new(); + config.network.initial_testnet_peers = IndexSet::new(); + config.mempool.debug_enable_at_height = Some(0); + + // We're using the cached state + config.state.ephemeral = false; + + let mut child = testdir()? + .with_exact_config(&config)? + .spawn_child(args!["start"])? + .bypass_test_capture(true); + + child.expect_stdout_line_matches(&format!("Opened RPC endpoint at {rpc_address}"))?; + + // Create an http client + let client = Client::new(); + + let res = client + .post(format!("http://{}", &rpc_address)) + .body(format!( + r#"{{"jsonrpc": "2.0", "method": "submitblock", "params": ["{block_hex_data}"], "id":123 }}"# + )) + .header("Content-Type", "application/json") + .send() + .await?; + + assert!(res.status().is_success()); + let res_text = res.text().await?; + + // Test rpc endpoint response + assert!(res_text.contains(r#""result":"null""#)); + + child.kill(false)?; + + let output = child.wait_with_output()?; + let output = output.assert_failure()?; + + // [Note on port conflict](#Note on port conflict) + output + .assert_was_killed() + .wrap_err("Possible port conflict. Are there other acceptance tests running?")?; + + Ok(()) +} From 0267fb475801175840f011c61224ad71fb93b00c Mon Sep 17 00:00:00 2001 From: arya2 Date: Thu, 3 Nov 2022 12:47:58 -0400 Subject: [PATCH 15/19] removes extra tower::ServiceBuilder::new(), updates docs --- zebra-rpc/src/methods/tests/vectors.rs | 2 +- zebra-rpc/src/server.rs | 2 +- zebrad/tests/acceptance.rs | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index e8f2fc02f8b..21b416276e6 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -696,7 +696,7 @@ async fn rpc_getblockcount_empty_state() { Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), - tower::ServiceBuilder::new().service(chain_verifier), + chain_verifier, ); // Get the tip height using RPC method `get_block_count diff --git a/zebra-rpc/src/server.rs b/zebra-rpc/src/server.rs index 866996017cc..b0843413709 100644 --- a/zebra-rpc/src/server.rs +++ b/zebra-rpc/src/server.rs @@ -89,7 +89,7 @@ impl RpcServer { #[cfg(feature = "getblocktemplate-rpcs")] { - // Initialize the getblocktemplate rpc methods + // Initialize the getblocktemplate rpc method handler let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new( mempool.clone(), state.clone(), diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 2f8991efcdd..e3e365fa87c 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -104,7 +104,10 @@ //! ## Getblocktemplate tests //! //! Example of how to run the submit_block test: +//! +//! ```console //! ZEBRA_CACHED_STATE_DIR=/path/to/zebra/chain cargo test submit_block --features getblocktemplate-rpcs --release -- --ignored --nocapture +//! ``` //! //! Please refer to the documentation of each test for more information. //! From 2640b3d0f87573981af13def28d9114b33a717d3 Mon Sep 17 00:00:00 2001 From: arya2 Date: Thu, 3 Nov 2022 14:25:07 -0400 Subject: [PATCH 16/19] updates vectors and snapshot tests, changes hex decoding error in submit_block method from server error to parse error --- zebra-rpc/Cargo.toml | 3 -- zebra-rpc/src/errors.rs | 9 +++++ zebra-rpc/src/lib.rs | 1 + .../src/methods/get_block_template_rpcs.rs | 40 ++++++++----------- .../types/submit_block.rs | 10 +++-- .../tests/snapshot/get_block_template_rpcs.rs | 10 ++--- ...t_rpc_submit_block_invalid@mainnet_10.snap | 7 ++++ ...t_rpc_submit_block_invalid@testnet_10.snap | 7 ++++ ...lock_deserialization_error@mainnet_10.snap | 10 ----- ...lock_deserialization_error@testnet_10.snap | 10 ----- zebra-rpc/src/methods/tests/vectors.rs | 25 ++++++++++-- 11 files changed, 72 insertions(+), 60 deletions(-) create mode 100644 zebra-rpc/src/errors.rs create mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@mainnet_10.snap create mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@testnet_10.snap delete mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@mainnet_10.snap delete mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@testnet_10.snap diff --git a/zebra-rpc/Cargo.toml b/zebra-rpc/Cargo.toml index be2522d51a3..67b3ee3696a 100644 --- a/zebra-rpc/Cargo.toml +++ b/zebra-rpc/Cargo.toml @@ -14,9 +14,6 @@ getblocktemplate-rpcs = ["zebra-state/getblocktemplate-rpcs", "zebra-node-servic # Test-only features proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"] -# Test-only features -proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"] - [dependencies] chrono = { version = "0.4.22", default-features = false, features = ["clock", "std"] } futures = "0.3.25" diff --git a/zebra-rpc/src/errors.rs b/zebra-rpc/src/errors.rs new file mode 100644 index 00000000000..ff65a6176a2 --- /dev/null +++ b/zebra-rpc/src/errors.rs @@ -0,0 +1,9 @@ +use jsonrpc_core::types::error::{Error, ErrorCode}; + +pub(crate) fn make_server_error(message: impl std::fmt::Display) -> Error { + Error { + code: ErrorCode::ServerError(0), + message: message.to_string(), + data: None, + } +} diff --git a/zebra-rpc/src/lib.rs b/zebra-rpc/src/lib.rs index 0bb4b072e2e..10e253f02d7 100644 --- a/zebra-rpc/src/lib.rs +++ b/zebra-rpc/src/lib.rs @@ -5,6 +5,7 @@ #![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_rpc")] pub mod config; +mod errors; pub mod methods; pub mod queue; pub mod server; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 9067f1e10ed..82baec908c0 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -17,12 +17,15 @@ use zebra_chain::{ use zebra_consensus::{BlockError, VerifyBlockError, VerifyChainError, VerifyCheckpointError}; use zebra_node_services::mempool; -use crate::methods::{ - get_block_template_rpcs::types::{ - default_roots::DefaultRoots, get_block_template::GetBlockTemplate, submit_block, - transaction::TransactionTemplate, +use crate::{ + errors::make_server_error, + methods::{ + get_block_template_rpcs::types::{ + default_roots::DefaultRoots, get_block_template::GetBlockTemplate, submit_block, + transaction::TransactionTemplate, + }, + GetBlockHash, MISSING_BLOCK_ERROR_CODE, }, - GetBlockHash, MISSING_BLOCK_ERROR_CODE, }; pub mod config; @@ -96,7 +99,7 @@ pub trait GetBlockTemplateRpc { #[rpc(name = "submitblock")] fn submit_block( &self, - hex_data: String, + hex_data: submit_block::HexData, _options: Option, ) -> BoxFuture>; } @@ -137,7 +140,7 @@ where /// Allows efficient access to the best tip of the blockchain. latest_chain_tip: Tip, - /// The full block verifier, used for submitting blocks. + /// The chain verifier, used for submitting blocks. chain_verifier: ChainVerifier, } @@ -351,32 +354,21 @@ where fn submit_block( &self, - hex_data: String, + submit_block::HexData(block_bytes): submit_block::HexData, _options: Option, ) -> BoxFuture> { let mut chain_verifier = self.chain_verifier.clone(); async move { - let block = hex::decode(hex_data).map_err(|error| Error { - code: ErrorCode::ServerError(0), - message: format!("failed to decode hexdata, error msg: {error}"), - data: None, - })?; - - let block: Block = block.zcash_deserialize_into().map_err(|error| Error { - code: ErrorCode::ServerError(0), - message: format!("failed to deserialize into block, error msg: {error}"), - data: None, - })?; + let block: Block = match block_bytes.zcash_deserialize_into() { + Ok(block_bytes) => block_bytes, + Err(_) => return Ok(submit_block::ErrorResponse::Rejected.into()), + }; let chain_verifier_response = chain_verifier .ready() .await - .map_err(|error| Error { - code: ErrorCode::ServerError(0), - message: error.to_string(), - data: None, - })? + .map_err(make_server_error)? .call(Arc::new(block)) .await; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs index 7633fe4cb6c..80edb1d66ea 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs @@ -7,15 +7,15 @@ use crate::methods::get_block_template_rpcs::GetBlockTemplateRpc; /// Optional argument `jsonparametersobject` for `submitblock` RPC request /// /// See notes for [`GetBlockTemplateRpc::submit_block`] method -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, serde::Deserialize)] pub struct JsonParameters { - pub(crate) work_id: Option, + pub(crate) _work_id: Option, } /// Response to a `submitblock` RPC request. /// /// Zebra never returns "duplicate-invalid", because it does not store invalid blocks. -#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, PartialEq, Eq, serde::Serialize)] #[serde(rename_all = "kebab-case")] pub enum ErrorResponse { /// Block was already committed to the non-finalized or finalized state @@ -31,7 +31,7 @@ pub enum ErrorResponse { /// Response to a `submitblock` RPC request. /// /// Zebra never returns "duplicate-invalid", because it does not store invalid blocks. -#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, PartialEq, Eq, serde::Serialize)] #[serde(untagged)] pub enum Response { /// Block was not successfully submitted, return error @@ -45,3 +45,5 @@ impl From for Response { Self::ErrorResponse(error_response) } } +#[derive(Debug, PartialEq, Eq, serde::Deserialize)] +pub struct HexData(#[serde(with = "hex")] pub Vec); diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index 901ae6e06b2..949d5e05ee5 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -21,7 +21,7 @@ use crate::methods::{ pub async fn test_responses( network: Network, - mempool: MockService< + mut mempool: MockService< mempool::Request, mempool::Response, PanicAssertion, @@ -103,9 +103,9 @@ pub async fn test_responses( // `submitblock` let submit_block = get_block_template_rpc - .submit_block("".to_string(), None) + .submit_block(submit_block::HexData("".into()), None) .await; - snapshot_rpc_submit_block_deserialization_error(submit_block, &settings); + snapshot_rpc_submit_block_invalid(submit_block, &settings); } /// Snapshot `getblockcount` response, using `cargo insta` and JSON serialization. @@ -127,11 +127,11 @@ fn snapshot_rpc_getblocktemplate( } /// Snapshot `submitblock` response, using `cargo insta` and JSON serialization. -fn snapshot_rpc_submit_block_deserialization_error( +fn snapshot_rpc_submit_block_invalid( submit_block_response: jsonrpc_core::Result, settings: &insta::Settings, ) { settings.bind(|| { - insta::assert_json_snapshot!("submit_block_deserialization_error", submit_block_response) + insta::assert_json_snapshot!("snapshot_rpc_submit_block_invalid", submit_block_response) }); } diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@mainnet_10.snap new file mode 100644 index 00000000000..418e3aec224 --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@mainnet_10.snap @@ -0,0 +1,7 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: submit_block_response +--- +{ + "Ok": "rejected" +} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@testnet_10.snap new file mode 100644 index 00000000000..418e3aec224 --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@testnet_10.snap @@ -0,0 +1,7 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: submit_block_response +--- +{ + "Ok": "rejected" +} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@mainnet_10.snap deleted file mode 100644 index 3ea8656b9cf..00000000000 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@mainnet_10.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs -expression: submit_block_response ---- -{ - "Err": { - "code": 0, - "message": "failed to deserialize into block, error msg: io error: failed to fill whole buffer" - } -} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@testnet_10.snap deleted file mode 100644 index 3ea8656b9cf..00000000000 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/submit_block_deserialization_error@testnet_10.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs -expression: submit_block_response ---- -{ - "Err": { - "code": 0, - "message": "failed to deserialize into block, error msg: io error: failed to fill whole buffer" - } -} diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 21b416276e6..2b7604265c6 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -887,10 +887,13 @@ async fn rpc_submitblock_errors() { ); // Try to submit pre-populated blocks and assert that it responds with duplicate. - for (_height, block_bytes) in zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS.iter() { - let hex_data = hex::encode(block_bytes); - - let submit_block_response = get_block_template_rpc.submit_block(hex_data, None).await; + for (_height, &block_bytes) in zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS.iter() { + let submit_block_response = get_block_template_rpc + .submit_block( + get_block_template_rpcs::types::submit_block::HexData(block_bytes.into()), + None, + ) + .await; assert_eq!( submit_block_response, @@ -898,6 +901,20 @@ async fn rpc_submitblock_errors() { ); } + let submit_block_response = get_block_template_rpc + .submit_block( + get_block_template_rpcs::types::submit_block::HexData( + zebra_test::vectors::BAD_BLOCK_MAINNET_202_BYTES.to_vec(), + ), + None, + ) + .await; + + assert_eq!( + submit_block_response, + Ok(get_block_template_rpcs::types::submit_block::ErrorResponse::Rejected.into()) + ); + mempool.expect_no_requests().await; // See zebrad::tests::acceptance::submit_block for success case. From dd181c1c33b6d3d0842d6c6452bc20cc184a1d15 Mon Sep 17 00:00:00 2001 From: arya2 Date: Thu, 3 Nov 2022 16:05:23 -0400 Subject: [PATCH 17/19] hides errors module in zebra-rpc behind a feature flag and adds docs. --- zebra-rpc/src/errors.rs | 4 ++++ zebra-rpc/src/lib.rs | 1 + 2 files changed, 5 insertions(+) diff --git a/zebra-rpc/src/errors.rs b/zebra-rpc/src/errors.rs index ff65a6176a2..a6f8ff3f73e 100644 --- a/zebra-rpc/src/errors.rs +++ b/zebra-rpc/src/errors.rs @@ -1,5 +1,9 @@ use jsonrpc_core::types::error::{Error, ErrorCode}; +/// Returns a jsonrpc_core [`Error`] with an [`ErrorCode::ServerError(0)`] +/// with the provided message. +// TODO: Remove the feature flag and replace repetitive closures passed to `map_err` +// in rpc methods. pub(crate) fn make_server_error(message: impl std::fmt::Display) -> Error { Error { code: ErrorCode::ServerError(0), diff --git a/zebra-rpc/src/lib.rs b/zebra-rpc/src/lib.rs index 10e253f02d7..43ee932294f 100644 --- a/zebra-rpc/src/lib.rs +++ b/zebra-rpc/src/lib.rs @@ -5,6 +5,7 @@ #![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_rpc")] pub mod config; +#[cfg(feature = "getblocktemplate-rpcs")] mod errors; pub mod methods; pub mod queue; From cf4e9376e4af691f2dc1183f53b1c95cdb91bcd5 Mon Sep 17 00:00:00 2001 From: arya2 Date: Thu, 3 Nov 2022 19:33:31 -0400 Subject: [PATCH 18/19] Updates snapshot test, adds mod docs, moves HexData to its own mod, and removes the unrelated make_server_error refactor for now --- zebra-rpc/src/errors.rs | 13 ----------- zebra-rpc/src/lib.rs | 2 -- .../src/methods/get_block_template_rpcs.rs | 23 ++++++++++--------- .../methods/get_block_template_rpcs/types.rs | 1 + .../get_block_template_rpcs/types/hex_data.rs | 6 +++++ .../types/submit_block.rs | 2 -- .../tests/snapshot/get_block_template_rpcs.rs | 12 ++++++---- ...t_rpc_submit_block_invalid@mainnet_10.snap | 4 +--- ...t_rpc_submit_block_invalid@testnet_10.snap | 4 +--- zebra-rpc/src/methods/tests/vectors.rs | 4 ++-- .../tests/common/get_block_template_rpcs.rs | 2 ++ .../get_block_template_rpcs/submit_block.rs | 10 ++++++++ 12 files changed, 42 insertions(+), 41 deletions(-) delete mode 100644 zebra-rpc/src/errors.rs create mode 100644 zebra-rpc/src/methods/get_block_template_rpcs/types/hex_data.rs diff --git a/zebra-rpc/src/errors.rs b/zebra-rpc/src/errors.rs deleted file mode 100644 index a6f8ff3f73e..00000000000 --- a/zebra-rpc/src/errors.rs +++ /dev/null @@ -1,13 +0,0 @@ -use jsonrpc_core::types::error::{Error, ErrorCode}; - -/// Returns a jsonrpc_core [`Error`] with an [`ErrorCode::ServerError(0)`] -/// with the provided message. -// TODO: Remove the feature flag and replace repetitive closures passed to `map_err` -// in rpc methods. -pub(crate) fn make_server_error(message: impl std::fmt::Display) -> Error { - Error { - code: ErrorCode::ServerError(0), - message: message.to_string(), - data: None, - } -} diff --git a/zebra-rpc/src/lib.rs b/zebra-rpc/src/lib.rs index 43ee932294f..0bb4b072e2e 100644 --- a/zebra-rpc/src/lib.rs +++ b/zebra-rpc/src/lib.rs @@ -5,8 +5,6 @@ #![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_rpc")] pub mod config; -#[cfg(feature = "getblocktemplate-rpcs")] -mod errors; pub mod methods; pub mod queue; pub mod server; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 82baec908c0..ae9c004e885 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -17,15 +17,12 @@ use zebra_chain::{ use zebra_consensus::{BlockError, VerifyBlockError, VerifyChainError, VerifyCheckpointError}; use zebra_node_services::mempool; -use crate::{ - errors::make_server_error, - methods::{ - get_block_template_rpcs::types::{ - default_roots::DefaultRoots, get_block_template::GetBlockTemplate, submit_block, - transaction::TransactionTemplate, - }, - GetBlockHash, MISSING_BLOCK_ERROR_CODE, +use crate::methods::{ + get_block_template_rpcs::types::{ + default_roots::DefaultRoots, get_block_template::GetBlockTemplate, hex_data::HexData, + submit_block, transaction::TransactionTemplate, }, + GetBlockHash, MISSING_BLOCK_ERROR_CODE, }; pub mod config; @@ -99,7 +96,7 @@ pub trait GetBlockTemplateRpc { #[rpc(name = "submitblock")] fn submit_block( &self, - hex_data: submit_block::HexData, + hex_data: HexData, _options: Option, ) -> BoxFuture>; } @@ -354,7 +351,7 @@ where fn submit_block( &self, - submit_block::HexData(block_bytes): submit_block::HexData, + HexData(block_bytes): HexData, _options: Option, ) -> BoxFuture> { let mut chain_verifier = self.chain_verifier.clone(); @@ -368,7 +365,11 @@ where let chain_verifier_response = chain_verifier .ready() .await - .map_err(make_server_error)? + .map_err(|error| Error { + code: ErrorCode::ServerError(0), + message: error.to_string(), + data: None, + })? .call(Arc::new(block)) .await; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs index 78a55fd92c1..848ea9ae686 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs @@ -2,5 +2,6 @@ pub(crate) mod default_roots; pub(crate) mod get_block_template; +pub(crate) mod hex_data; pub(crate) mod submit_block; pub(crate) mod transaction; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/hex_data.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/hex_data.rs new file mode 100644 index 00000000000..bd203b615f3 --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/hex_data.rs @@ -0,0 +1,6 @@ +//! Deserializes hex-encoded inputs such as the one required +//! for the `submitblock` RPC method. + +/// Deserialize hex-encoded strings to bytes. +#[derive(Debug, PartialEq, Eq, serde::Deserialize)] +pub struct HexData(#[serde(with = "hex")] pub Vec); diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs index 80edb1d66ea..65eb7744c40 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs @@ -45,5 +45,3 @@ impl From for Response { Self::ErrorResponse(error_response) } } -#[derive(Debug, PartialEq, Eq, serde::Deserialize)] -pub struct HexData(#[serde(with = "hex")] pub Vec); diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index 949d5e05ee5..37b43927c25 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -15,8 +15,8 @@ use zebra_state::LatestChainTip; use zebra_test::mock_service::{MockService, PanicAssertion}; use crate::methods::{ - get_block_template_rpcs::types::submit_block, GetBlockHash, GetBlockTemplateRpc, - GetBlockTemplateRpcImpl, + get_block_template_rpcs::types::{hex_data::HexData, submit_block}, + GetBlockHash, GetBlockTemplateRpc, GetBlockTemplateRpcImpl, }; pub async fn test_responses( @@ -103,8 +103,10 @@ pub async fn test_responses( // `submitblock` let submit_block = get_block_template_rpc - .submit_block(submit_block::HexData("".into()), None) - .await; + .submit_block(HexData("".into()), None) + .await + .expect("unexpected error in submitblock RPC call"); + snapshot_rpc_submit_block_invalid(submit_block, &settings); } @@ -128,7 +130,7 @@ fn snapshot_rpc_getblocktemplate( /// Snapshot `submitblock` response, using `cargo insta` and JSON serialization. fn snapshot_rpc_submit_block_invalid( - submit_block_response: jsonrpc_core::Result, + submit_block_response: submit_block::Response, settings: &insta::Settings, ) { settings.bind(|| { diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@mainnet_10.snap index 418e3aec224..7a2529392f4 100644 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@mainnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@mainnet_10.snap @@ -2,6 +2,4 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs expression: submit_block_response --- -{ - "Ok": "rejected" -} +"rejected" diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@testnet_10.snap index 418e3aec224..7a2529392f4 100644 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@testnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/snapshot_rpc_submit_block_invalid@testnet_10.snap @@ -2,6 +2,4 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs expression: submit_block_response --- -{ - "Ok": "rejected" -} +"rejected" diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 2b7604265c6..be2a04416aa 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -890,7 +890,7 @@ async fn rpc_submitblock_errors() { for (_height, &block_bytes) in zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS.iter() { let submit_block_response = get_block_template_rpc .submit_block( - get_block_template_rpcs::types::submit_block::HexData(block_bytes.into()), + get_block_template_rpcs::types::hex_data::HexData(block_bytes.into()), None, ) .await; @@ -903,7 +903,7 @@ async fn rpc_submitblock_errors() { let submit_block_response = get_block_template_rpc .submit_block( - get_block_template_rpcs::types::submit_block::HexData( + get_block_template_rpcs::types::hex_data::HexData( zebra_test::vectors::BAD_BLOCK_MAINNET_202_BYTES.to_vec(), ), None, diff --git a/zebrad/tests/common/get_block_template_rpcs.rs b/zebrad/tests/common/get_block_template_rpcs.rs index b6c4b372061..716c91005c0 100644 --- a/zebrad/tests/common/get_block_template_rpcs.rs +++ b/zebrad/tests/common/get_block_template_rpcs.rs @@ -1,3 +1,5 @@ +//! Acceptance tests for getblocktemplate RPC methods in Zebra. + use super::*; pub(crate) mod submit_block; diff --git a/zebrad/tests/common/get_block_template_rpcs/submit_block.rs b/zebrad/tests/common/get_block_template_rpcs/submit_block.rs index 1618e0387e3..bc4f31622c0 100644 --- a/zebrad/tests/common/get_block_template_rpcs/submit_block.rs +++ b/zebrad/tests/common/get_block_template_rpcs/submit_block.rs @@ -1,3 +1,13 @@ +//! Test submitblock RPC method. +//! +//! This test requires a cached chain state that is partially synchronized close to the +//! network chain tip height, and will finish the sync and update the cached chain state. +//! +//! After finishing the sync, it will get the first 20 blocks in the non-finalized state +//! (past the MAX_BLOCK_REORG_HEIGHT) via getblock rpc calls, get the finalized tip height +//! of the updated cached state, restart zebra without peers, and submit blocks above the +//! finalized tip height. + use std::path::PathBuf; use color_eyre::eyre::{eyre, Context, Result}; From 4959a78f3c206192ec402f65b29fa5e39ebb9ec2 Mon Sep 17 00:00:00 2001 From: arya2 Date: Thu, 3 Nov 2022 19:51:20 -0400 Subject: [PATCH 19/19] update submit block acceptance test mod doc --- .../get_block_template_rpcs/submit_block.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/zebrad/tests/common/get_block_template_rpcs/submit_block.rs b/zebrad/tests/common/get_block_template_rpcs/submit_block.rs index bc4f31622c0..6f07f0dae5a 100644 --- a/zebrad/tests/common/get_block_template_rpcs/submit_block.rs +++ b/zebrad/tests/common/get_block_template_rpcs/submit_block.rs @@ -1,12 +1,17 @@ //! Test submitblock RPC method. //! -//! This test requires a cached chain state that is partially synchronized close to the -//! network chain tip height, and will finish the sync and update the cached chain state. -//! -//! After finishing the sync, it will get the first 20 blocks in the non-finalized state -//! (past the MAX_BLOCK_REORG_HEIGHT) via getblock rpc calls, get the finalized tip height -//! of the updated cached state, restart zebra without peers, and submit blocks above the -//! finalized tip height. +//! This test requires a cached chain state that is synchronized past the max checkpoint height, +//! and will sync to the next block without updating the cached chain state. + +// TODO: Update this test and the doc to: +// +// This test requires a cached chain state that is partially synchronized close to the +// network chain tip height, and will finish the sync and update the cached chain state. +// +// After finishing the sync, it will get the first 20 blocks in the non-finalized state +// (past the MAX_BLOCK_REORG_HEIGHT) via getblock rpc calls, get the finalized tip height +// of the updated cached state, restart zebra without peers, and submit blocks above the +// finalized tip height. use std::path::PathBuf;