Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change(rpc): add submitblock RPC method #5526

Merged
merged 20 commits into from
Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d7f6798
adds submitblock rpc method
arya2 Nov 1, 2022
fc5e77a
re-orders imports
arya2 Nov 1, 2022
2cf2f45
replaces thread::yield_now with async yield_now
arya2 Nov 1, 2022
02f0613
Fix doc warnings and unused variable warnings, add missing docs
teor2345 Nov 2, 2022
894c482
Mark work_id as optional
teor2345 Nov 2, 2022
136eae9
Use the same ChainVerifier for downloaded and submitted blocks
teor2345 Nov 2, 2022
7ea79d7
Revert unused changes & minor cleanups
teor2345 Nov 2, 2022
d04ad71
Document currently-unreachable code
teor2345 Nov 2, 2022
2160fbb
updates tests and submit_block response for AlreadyVerified error
arya2 Nov 2, 2022
daffc8f
Update zebra-rpc/src/methods/get_block_template_rpcs.rs
arya2 Nov 2, 2022
6595f07
changes names from BlockVerifier to ChainVerifier and block_verifier …
arya2 Nov 2, 2022
3635525
move how to run the submit_block test example to acceptance.rs
arya2 Nov 2, 2022
e9e7a82
updates snapshot tests
arya2 Nov 2, 2022
6b68c07
moved acceptance test to a separate file
arya2 Nov 3, 2022
0267fb4
removes extra tower::ServiceBuilder::new(), updates docs
arya2 Nov 3, 2022
2640b3d
updates vectors and snapshot tests, changes hex decoding error in sub…
arya2 Nov 3, 2022
dd181c1
hides errors module in zebra-rpc behind a feature flag and adds docs.
arya2 Nov 3, 2022
cf4e937
Updates snapshot test, adds mod docs, moves HexData to its own mod, a…
arya2 Nov 3, 2022
4959a78
update submit block acceptance test mod doc
arya2 Nov 3, 2022
2ebe202
Merge branch 'main' into submitblock
mergify[bot] Nov 4, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
1 change: 1 addition & 0 deletions zebra-consensus/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,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
1 change: 1 addition & 0 deletions zebra-consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
4 changes: 3 additions & 1 deletion zebra-rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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" }
zebra-network = { path = "../zebra-network" }
zebra-node-services = { path = "../zebra-node-services" }
zebra-state = { path = "../zebra-state" }
Expand All @@ -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/" }
9 changes: 9 additions & 0 deletions zebra-rpc/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use jsonrpc_core::types::error::{Error, ErrorCode};
teor2345 marked this conversation as resolved.
Show resolved Hide resolved

pub(crate) fn make_server_error(message: impl std::fmt::Display) -> Error {
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
Error {
code: ErrorCode::ServerError(0),
message: message.to_string(),
data: None,
}
}
1 change: 1 addition & 0 deletions zebra-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
139 changes: 129 additions & 10 deletions zebra-rpc/src/methods/get_block_template_rpcs.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
//! RPC methods related to mining only available with `getblocktemplate-rpcs` rust feature.

use std::sync::Arc;

use futures::{FutureExt, TryFutureExt};
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, VerifyChainError, VerifyCheckpointError};
use zebra_node_services::mempool;

use crate::methods::{
get_block_template_rpcs::types::{
default_roots::DefaultRoots, get_block_template::GetBlockTemplate,
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;
Expand Down Expand Up @@ -74,10 +86,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 [`submit_block::Response`] for the operation, 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: submit_block::HexData,
_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, ChainVerifier>
where
Mempool: Service<
mempool::Request,
Expand All @@ -89,7 +117,11 @@ where
Response = zebra_state::ReadResponse,
Error = zebra_state::BoxError,
>,
Tip: ChainTip,
ChainVerifier: Service<Arc<Block>, Response = block::Hash, Error = zebra_consensus::BoxError>
+ Clone
+ Send
+ Sync
+ 'static,
{
// TODO: Add the other fields from the [`Rpc`] struct as-needed

Expand All @@ -107,9 +139,12 @@ where

/// Allows efficient access to the best tip of the blockchain.
latest_chain_tip: Tip,

/// The chain verifier, used for submitting blocks.
chain_verifier: ChainVerifier,
}

impl<Mempool, State, Tip> GetBlockTemplateRpcImpl<Mempool, State, Tip>
impl<Mempool, State, Tip, ChainVerifier> GetBlockTemplateRpcImpl<Mempool, State, Tip, ChainVerifier>
where
Mempool: Service<
mempool::Request,
Expand All @@ -125,22 +160,30 @@ where
+ Sync
+ 'static,
Tip: ChainTip + Clone + Send + Sync + 'static,
ChainVerifier: Service<Arc<Block>, Response = block::Hash, Error = zebra_consensus::BoxError>
+ 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,
chain_verifier: ChainVerifier,
) -> Self {
Self {
mempool,
state,
latest_chain_tip,
chain_verifier,
}
}
}

impl<Mempool, State, Tip> GetBlockTemplateRpc for GetBlockTemplateRpcImpl<Mempool, State, Tip>
impl<Mempool, State, Tip, ChainVerifier> GetBlockTemplateRpc
for GetBlockTemplateRpcImpl<Mempool, State, Tip, ChainVerifier>
where
Mempool: Service<
mempool::Request,
Expand All @@ -158,6 +201,12 @@ where
+ 'static,
<State as Service<zebra_state::ReadRequest>>::Future: Send,
Tip: ChainTip + Send + Sync + 'static,
ChainVerifier: Service<Arc<Block>, Response = block::Hash, Error = zebra_consensus::BoxError>
+ Clone
+ Send
+ Sync
+ 'static,
<ChainVerifier as Service<Arc<Block>>>::Future: Send,
{
fn get_block_count(&self) -> Result<u32> {
self.latest_chain_tip
Expand Down Expand Up @@ -302,6 +351,76 @@ where
}
.boxed()
}

fn submit_block(
&self,
submit_block::HexData(block_bytes): submit_block::HexData,
_options: Option<submit_block::JsonParameters>,
) -> BoxFuture<Result<submit_block::Response>> {
let mut chain_verifier = self.chain_verifier.clone();

async move {
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(make_server_error)?
.call(Arc::new(block))
.await;

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.
//
// 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<VerifyChainError, BoxError>,
// by downcasting from Any to VerifyChainError.
Err(box_error) => box_error
.downcast::<VerifyChainError>()
.map(|boxed_chain_error| *boxed_chain_error),
};

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.
//
// 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) => 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) => submit_block::ErrorResponse::Rejected,
}
.into())
}
.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 @@ -2,4 +2,5 @@

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,49 @@
//! 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 [`GetBlockTemplateRpc::submit_block`] method
#[derive(Debug, serde::Deserialize)]
pub struct JsonParameters {
pub(crate) _work_id: Option<String>,
}

/// 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(rename_all = "kebab-case")]
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
DuplicateInconclusive,
/// Block was already committed to the non-finalized state, but not on the best chain
Inconclusive,
/// Block rejected as invalid
Rejected,
}

/// 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(untagged)]
pub enum Response {
/// Block was not successfully submitted, return error
ErrorResponse(ErrorResponse),
/// Block successfully submitted, returns null
Accepted,
}

impl From<ErrorResponse> for Response {
fn from(error_response: ErrorResponse) -> Self {
Self::ErrorResponse(error_response)
}
}
#[derive(Debug, PartialEq, Eq, serde::Deserialize)]
pub struct HexData(#[serde(with = "hex")] pub Vec<u8>);
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 4 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,8 @@ 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) =
#[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;

// Start snapshots of RPC responses.
Expand All @@ -56,7 +57,9 @@ 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(),
latest_chain_tip.clone(),
settings.clone(),
Expand Down
Loading