Skip to content

Commit

Permalink
adds submitblock rpc method
Browse files Browse the repository at this point in the history
  • Loading branch information
arya2 committed Nov 1, 2022
1 parent 75b2351 commit 60ec68d
Show file tree
Hide file tree
Showing 18 changed files with 479 additions and 24 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions zebra-consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2021"

[features]
default = []
getblocktemplate-rpcs = []
proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"]

[dependencies]
Expand Down
2 changes: 2 additions & 0 deletions zebra-consensus/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mod subsidy;
mod tests;

/// Asynchronous block verification.
#[cfg_attr(feature = "getblocktemplate-rpcs", derive(Clone))]
#[derive(Debug)]
pub struct BlockVerifier<S, V> {
/// The network to be verified.
Expand Down Expand Up @@ -93,6 +94,7 @@ where
V: Service<tx::Request, Response = tx::Response, Error = BoxError> + 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,
Expand Down
2 changes: 1 addition & 1 deletion zebra-consensus/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions zebra-consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,10 @@ pub use primitives::{ed25519, groth16, halo2, redjubjub, redpallas};

/// A boxed [`std::error::Error`].
pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;

#[cfg(feature = "getblocktemplate-rpcs")]
pub use block::BlockVerifier;
#[cfg(feature = "getblocktemplate-rpcs")]
pub use parameters::SLOW_START_INTERVAL;
#[cfg(feature = "getblocktemplate-rpcs")]
pub use transaction::Verifier as TransactionVerifier;
4 changes: 3 additions & 1 deletion zebra-rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ edition = "2021"
[features]
default = []
proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"]
getblocktemplate-rpcs = ["zebra-state/getblocktemplate-rpcs"]
getblocktemplate-rpcs = ["zebra-state/getblocktemplate-rpcs", "zebra-consensus/getblocktemplate-rpcs"]

[dependencies]
chrono = { version = "0.4.22", default-features = false, features = ["clock", "std"] }
Expand Down Expand Up @@ -41,6 +41,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" }
Expand All @@ -55,5 +56,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/" }
101 changes: 96 additions & 5 deletions zebra-rpc/src/methods/get_block_template_rpcs.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
//! 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::{block::Height, chain_tip::ChainTip};
use zebra_consensus::{BlockError, VerifyBlockError};
use zebra_node_services::mempool;

use crate::methods::{
get_block_template_rpcs::types::{
coinbase::Coinbase, default_roots::DefaultRoots, get_block_template::GetBlockTemplate,
submit_block,
},
GetBlockHash, MISSING_BLOCK_ERROR_CODE,
};
Expand Down Expand Up @@ -58,10 +67,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<Result<GetBlockTemplate>>;

/// 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<submit_block::JsonParameters>,
) -> BoxFuture<Result<submit_block::Response>>;
}

/// RPC method implementations.
pub struct GetBlockTemplateRpcImpl<Mempool, State, Tip>
pub struct GetBlockTemplateRpcImpl<Mempool, State, Tip, BlockVerifier>
where
Mempool: Service<
mempool::Request,
Expand All @@ -73,7 +98,11 @@ where
Response = zebra_state::ReadResponse,
Error = zebra_state::BoxError,
>,
Tip: ChainTip,
BlockVerifier: Service<Arc<Block>, Response = block::Hash, Error = VerifyBlockError>
+ Clone
+ Send
+ Sync
+ 'static,
{
// TODO: Add the other fields from the [`Rpc`] struct as-needed

Expand All @@ -92,9 +121,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<Mempool, State, Tip> GetBlockTemplateRpcImpl<Mempool, State, Tip>
impl<Mempool, State, Tip, BlockVerifier> GetBlockTemplateRpcImpl<Mempool, State, Tip, BlockVerifier>
where
Mempool: Service<
mempool::Request,
Expand All @@ -110,22 +142,30 @@ where
+ Sync
+ 'static,
Tip: ChainTip + Clone + Send + Sync + 'static,
BlockVerifier: Service<Arc<Block>, Response = block::Hash, Error = VerifyBlockError>
+ Clone
+ Send
+ Sync
+ 'static,
{
/// Create a new instance of the handler for getblocktemplate RPCs.
pub fn new(
mempool: Buffer<Mempool, mempool::Request>,
state: State,
latest_chain_tip: Tip,
block_verifier: BlockVerifier,
) -> Self {
Self {
mempool,
state,
latest_chain_tip,
block_verifier,
}
}
}

impl<Mempool, State, Tip> GetBlockTemplateRpc for GetBlockTemplateRpcImpl<Mempool, State, Tip>
impl<Mempool, State, Tip, BlockVerifier> GetBlockTemplateRpc
for GetBlockTemplateRpcImpl<Mempool, State, Tip, BlockVerifier>
where
Mempool: Service<
mempool::Request,
Expand All @@ -143,6 +183,12 @@ where
+ 'static,
<State as Service<zebra_state::ReadRequest>>::Future: Send,
Tip: ChainTip + Send + Sync + 'static,
BlockVerifier: Service<Arc<Block>, Response = block::Hash, Error = VerifyBlockError>
+ Clone
+ Send
+ Sync
+ 'static,
<BlockVerifier as Service<Arc<Block>>>::Future: Send,
{
fn get_block_count(&self) -> Result<u32> {
self.latest_chain_tip
Expand Down Expand Up @@ -226,6 +272,51 @@ where
}
.boxed()
}

fn submit_block(
&self,
hex_data: String,
_options: Option<submit_block::JsonParameters>,
) -> BoxFuture<Result<submit_block::Response>> {
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::Hash, VerifyBlockError> =
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`.
Expand Down
1 change: 1 addition & 0 deletions zebra-rpc/src/methods/get_block_template_rpcs/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
pub(crate) mod coinbase;
pub(crate) mod default_roots;
pub(crate) mod get_block_template;
pub(crate) mod submit_block;
pub(crate) mod transaction;
Original file line number Diff line number Diff line change
@@ -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,
}
3 changes: 2 additions & 1 deletion zebra-rpc/src/methods/tests/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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(),
Expand Down
25 changes: 22 additions & 3 deletions zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,57 @@
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;

use zebra_test::mock_service::{MockService, PanicAssertion};

use crate::methods::{GetBlockHash, GetBlockTemplateRpc, GetBlockTemplateRpcImpl};

pub async fn test_responses<State>(
pub async fn test_responses<State, ReadState>(
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,
<State as Service<zebra_state::Request>>::Future: Send,
ReadState: Service<
zebra_state::ReadRequest,
Response = zebra_state::ReadResponse,
Error = zebra_state::BoxError,
> + Clone
+ Send
+ Sync
+ 'static,
<State as Service<zebra_state::ReadRequest>>::Future: Send,
<ReadState as Service<zebra_state::ReadRequest>>::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`
Expand Down
Loading

0 comments on commit 60ec68d

Please sign in to comment.