change(rpc): Add proposal capability to getblocktemplate (#5870)

* adds ValidateBlock request to state

* adds `Request` enum in block verifier

skips solution check for BlockProposal requests

calls CheckBlockValidity instead of Commit block for BlockProposal requests

* uses new Request in references to chain verifier

* adds getblocktemplate proposal mode response type

* makes getblocktemplate-rpcs feature in zebra-consensus select getblocktemplate-rpcs in zebra-state

* Adds PR review revisions

* adds info log in CheckBlockProposalValidity

* Reverts replacement of match statement

* adds `GetBlockTemplate::capabilities` fn

* conditions calling checkpoint verifier on !request.is_proposal

* updates references to validate_and_commit_non_finalized

* adds snapshot test, updates test vectors

* adds `should_count_metrics` to NonFinalizedState

* Returns an error from chain verifier for block proposal requests below checkpoint height

adds feature flags

* adds "proposal" to GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD

* adds back block::Request to zebra-consensus lib

* updates snapshots

* Removes unnecessary network arg

* skips req in tracing intstrument for read state

* Moves out block proposal validation to its own fn

* corrects `difficulty_threshold_is_valid` docs

adds/fixes some comments, adds TODOs

general cleanup from a self-review.

* Update zebra-state/src/service.rs

* Apply suggestions from code review

Co-authored-by: teor <teor@riseup.net>

* Update zebra-rpc/src/methods/get_block_template_rpcs.rs

Co-authored-by: teor <teor@riseup.net>

* check best chain tip

* Update zebra-state/src/service.rs

Co-authored-by: teor <teor@riseup.net>

* Applies cleanup suggestions from code review

Co-authored-by: teor <teor@riseup.net>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Arya 2023-01-11 18:39:51 -05:00 committed by GitHub
parent 6e61066c7e
commit 3cbee9465a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 823 additions and 254 deletions

View File

@ -7,7 +7,10 @@ edition = "2021"
[features]
default = []
getblocktemplate-rpcs = []
getblocktemplate-rpcs = [
"zebra-state/getblocktemplate-rpcs",
"zebra-chain/getblocktemplate-rpcs",
]
proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"]
[dependencies]

View File

@ -21,20 +21,17 @@ use thiserror::Error;
use tower::{Service, ServiceExt};
use tracing::Instrument;
use zebra_chain::{
amount::Amount,
block::{self, Block},
parameters::Network,
transparent,
work::equihash,
};
use zebra_chain::{amount::Amount, block, parameters::Network, transparent, work::equihash};
use zebra_state as zs;
use crate::{error::*, transaction as tx, BoxError};
pub mod check;
pub mod request;
pub mod subsidy;
pub use request::Request;
#[cfg(test)]
mod tests;
@ -74,6 +71,11 @@ pub enum VerifyBlockError {
// TODO: make this into a concrete type, and add it to is_duplicate_request() (#2908)
Commit(#[source] BoxError),
#[cfg(feature = "getblocktemplate-rpcs")]
#[error("unable to validate block proposal: failed semantic verification (proof of work is not checked for proposals)")]
// TODO: make this into a concrete type (see #5732)
ValidateProposal(#[source] BoxError),
#[error("invalid transaction")]
Transaction(#[from] TransactionError),
}
@ -115,7 +117,7 @@ where
}
}
impl<S, V> Service<Arc<Block>> for BlockVerifier<S, V>
impl<S, V> Service<Request> for BlockVerifier<S, V>
where
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
S::Future: Send + 'static,
@ -134,11 +136,13 @@ where
Poll::Ready(Ok(()))
}
fn call(&mut self, block: Arc<Block>) -> Self::Future {
fn call(&mut self, request: Request) -> Self::Future {
let mut state_service = self.state_service.clone();
let mut transaction_verifier = self.transaction_verifier.clone();
let network = self.network;
let block = request.block();
// We don't include the block hash, because it's likely already in a parent span
let span = tracing::debug_span!("block", height = ?block.coinbase_height());
@ -172,10 +176,17 @@ where
Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
}
// Do the difficulty checks first, to raise the threshold for
// attacks that use any other fields.
check::difficulty_is_valid(&block.header, network, &height, &hash)?;
check::equihash_solution_is_valid(&block.header)?;
// > The block data MUST be validated and checked against the server's usual
// > acceptance rules (excluding the check for a valid proof-of-work).
// <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
if request.is_proposal() {
check::difficulty_threshold_is_valid(&block.header, network, &height, &hash)?;
} else {
// Do the difficulty checks first, to raise the threshold for
// attacks that use any other fields.
check::difficulty_is_valid(&block.header, network, &height, &hash)?;
check::equihash_solution_is_valid(&block.header)?;
}
// Next, check the Merkle root validity, to ensure that
// the header binds to the transactions in the blocks.
@ -279,6 +290,23 @@ where
new_outputs,
transaction_hashes,
};
// Return early for proposal requests when getblocktemplate-rpcs feature is enabled
#[cfg(feature = "getblocktemplate-rpcs")]
if request.is_proposal() {
return match state_service
.ready()
.await
.map_err(VerifyBlockError::ValidateProposal)?
.call(zs::Request::CheckBlockProposalValidity(prepared_block))
.await
.map_err(VerifyBlockError::ValidateProposal)?
{
zs::Response::ValidBlockProposal => Ok(hash),
_ => unreachable!("wrong response for CheckBlockProposalValidity"),
};
}
match state_service
.ready()
.await

View File

@ -58,24 +58,22 @@ pub fn coinbase_is_first(block: &Block) -> Result<Arc<transaction::Transaction>,
Ok(first.clone())
}
/// Returns `Ok(())` if `hash` passes:
/// - the target difficulty limit for `network` (PoWLimit), and
/// - the difficulty filter,
/// based on the fields in `header`.
/// Returns `Ok(ExpandedDifficulty)` if the`difficulty_threshold` of `header` is at least as difficult as
/// the target difficulty limit for `network` (PoWLimit)
///
/// If the block is invalid, returns an error containing `height` and `hash`.
pub fn difficulty_is_valid(
/// If the header difficulty threshold is invalid, returns an error containing `height` and `hash`.
pub fn difficulty_threshold_is_valid(
header: &Header,
network: Network,
height: &Height,
hash: &Hash,
) -> Result<(), BlockError> {
) -> Result<ExpandedDifficulty, BlockError> {
let difficulty_threshold = header
.difficulty_threshold
.to_expanded()
.ok_or(BlockError::InvalidDifficulty(*height, *hash))?;
// Note: the comparisons in this function are u256 integer comparisons, like
// Note: the comparison in this function is a u256 integer comparison, like
// zcashd and bitcoin. Greater values represent *less* work.
// The PowLimit check is part of `Threshold()` in the spec, but it doesn't
@ -90,6 +88,26 @@ pub fn difficulty_is_valid(
))?;
}
Ok(difficulty_threshold)
}
/// Returns `Ok(())` if `hash` passes:
/// - the target difficulty limit for `network` (PoWLimit), and
/// - the difficulty filter,
/// based on the fields in `header`.
///
/// If the block is invalid, returns an error containing `height` and `hash`.
pub fn difficulty_is_valid(
header: &Header,
network: Network,
height: &Height,
hash: &Hash,
) -> Result<(), BlockError> {
let difficulty_threshold = difficulty_threshold_is_valid(header, network, height, hash)?;
// Note: the comparison in this function is a u256 integer comparison, like
// zcashd and bitcoin. Greater values represent *less* work.
// # Consensus
//
// > The block MUST pass the difficulty filter.

View File

@ -0,0 +1,40 @@
//! Block verifier request type.
use std::sync::Arc;
use zebra_chain::block::Block;
#[derive(Debug, Clone, PartialEq, Eq)]
/// A request to the chain or block verifier
pub enum Request {
/// Performs semantic validation, then asks the state to perform contextual validation and commit the block
Commit(Arc<Block>),
#[cfg(feature = "getblocktemplate-rpcs")]
/// Performs semantic validation but skips checking proof of work,
/// then asks the state to perform contextual validation.
/// Does not commit the block to the state.
CheckProposal(Arc<Block>),
}
impl Request {
/// Returns inner block
pub fn block(&self) -> Arc<Block> {
Arc::clone(match self {
Request::Commit(block) => block,
#[cfg(feature = "getblocktemplate-rpcs")]
Request::CheckProposal(block) => block,
})
}
/// Returns `true` if the request is a proposal
pub fn is_proposal(&self) -> bool {
match self {
Request::Commit(_) => false,
#[cfg(feature = "getblocktemplate-rpcs")]
Request::CheckProposal(_) => true,
}
}
}

View File

@ -28,19 +28,18 @@ use crate::{parameters::SLOW_START_SHIFT, transaction};
use super::*;
static VALID_BLOCK_TRANSCRIPT: Lazy<
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
> = Lazy::new(|| {
let block: Arc<_> =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
.unwrap()
.into();
let hash = Ok(block.as_ref().into());
vec![(block, hash)]
});
static VALID_BLOCK_TRANSCRIPT: Lazy<Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>> =
Lazy::new(|| {
let block: Arc<_> =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
.unwrap()
.into();
let hash = Ok(block.as_ref().into());
vec![(Request::Commit(block), hash)]
});
static INVALID_TIME_BLOCK_TRANSCRIPT: Lazy<
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
> = Lazy::new(|| {
let mut block: Block =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..]).unwrap();
@ -55,11 +54,14 @@ static INVALID_TIME_BLOCK_TRANSCRIPT: Lazy<
.unwrap();
Arc::make_mut(&mut block.header).time = three_hours_in_the_future;
vec![(Arc::new(block), Err(ExpectedTranscriptError::Any))]
vec![(
Request::Commit(Arc::new(block)),
Err(ExpectedTranscriptError::Any),
)]
});
static INVALID_HEADER_SOLUTION_TRANSCRIPT: Lazy<
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
> = Lazy::new(|| {
let mut block: Block =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..]).unwrap();
@ -67,11 +69,14 @@ static INVALID_HEADER_SOLUTION_TRANSCRIPT: Lazy<
// Change nonce to something invalid
Arc::make_mut(&mut block.header).nonce = [0; 32];
vec![(Arc::new(block), Err(ExpectedTranscriptError::Any))]
vec![(
Request::Commit(Arc::new(block)),
Err(ExpectedTranscriptError::Any),
)]
});
static INVALID_COINBASE_TRANSCRIPT: Lazy<
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
> = Lazy::new(|| {
let header = block::Header::zcash_deserialize(&zebra_test::vectors::DUMMY_HEADER[..]).unwrap();
@ -105,9 +110,18 @@ static INVALID_COINBASE_TRANSCRIPT: Lazy<
assert_eq!(block3.transactions.len(), 2);
vec![
(Arc::new(block1), Err(ExpectedTranscriptError::Any)),
(Arc::new(block2), Err(ExpectedTranscriptError::Any)),
(Arc::new(block3), Err(ExpectedTranscriptError::Any)),
(
Request::Commit(Arc::new(block1)),
Err(ExpectedTranscriptError::Any),
),
(
Request::Commit(Arc::new(block2)),
Err(ExpectedTranscriptError::Any),
),
(
Request::Commit(Arc::new(block3)),
Err(ExpectedTranscriptError::Any),
),
]
});

View File

@ -15,7 +15,6 @@
use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
@ -27,15 +26,14 @@ use tower::{buffer::Buffer, util::BoxService, Service, ServiceExt};
use tracing::{instrument, Span};
use zebra_chain::{
block::{self, Block, Height},
block::{self, Height},
parameters::Network,
};
use zebra_state as zs;
use crate::{
block::BlockVerifier,
block::VerifyBlockError,
block::{BlockVerifier, Request, VerifyBlockError},
checkpoint::{CheckpointList, CheckpointVerifier, VerifyCheckpointError},
error::TransactionError,
transaction, BoxError, Config,
@ -129,7 +127,7 @@ impl VerifyChainError {
}
}
impl<S, V> Service<Arc<Block>> for ChainVerifier<S, V>
impl<S, V> Service<Request> for ChainVerifier<S, V>
where
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
S::Future: Send + 'static,
@ -167,14 +165,29 @@ where
Poll::Ready(Ok(()))
}
fn call(&mut self, block: Arc<Block>) -> Self::Future {
fn call(&mut self, request: Request) -> Self::Future {
let block = request.block();
match block.coinbase_height() {
#[cfg(feature = "getblocktemplate-rpcs")]
// There's currently no known use case for block proposals below the checkpoint height,
// so it's okay to immediately return an error here.
Some(height) if height <= self.max_checkpoint_height && request.is_proposal() => {
async {
// TODO: Add a `ValidateProposalError` enum with a `BelowCheckpoint` variant?
Err(VerifyBlockError::ValidateProposal(
"block proposals must be above checkpoint height".into(),
))?
}
.boxed()
}
Some(height) if height <= self.max_checkpoint_height => {
self.checkpoint.call(block).map_err(Into::into).boxed()
}
// This also covers blocks with no height, which the block verifier
// will reject immediately.
_ => self.block.call(block).map_err(Into::into).boxed(),
_ => self.block.call(request).map_err(Into::into).boxed(),
}
}
}
@ -219,7 +232,7 @@ pub async fn init<S>(
mut state_service: S,
debug_skip_parameter_preload: bool,
) -> (
Buffer<BoxService<Arc<Block>, block::Hash, VerifyChainError>, Arc<Block>>,
Buffer<BoxService<Request, block::Hash, VerifyChainError>, Request>,
Buffer<
BoxService<transaction::Request, transaction::Response, TransactionError>,
transaction::Request,

View File

@ -49,7 +49,7 @@ async fn verifiers_from_network(
network: Network,
) -> (
impl Service<
Arc<Block>,
Request,
Response = block::Hash,
Error = BoxError,
Future = impl Future<Output = Result<block::Hash, BoxError>>,
@ -77,7 +77,7 @@ async fn verifiers_from_network(
}
static BLOCK_VERIFY_TRANSCRIPT_GENESIS: Lazy<
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
> = Lazy::new(|| {
let block: Arc<_> =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
@ -85,27 +85,29 @@ static BLOCK_VERIFY_TRANSCRIPT_GENESIS: Lazy<
.into();
let hash = Ok(block.hash());
vec![(block, hash)]
vec![(Request::Commit(block), hash)]
});
static BLOCK_VERIFY_TRANSCRIPT_GENESIS_FAIL: Lazy<
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
> = Lazy::new(|| {
let block: Arc<_> =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
.unwrap()
.into();
vec![(block, Err(ExpectedTranscriptError::Any))]
vec![(Request::Commit(block), Err(ExpectedTranscriptError::Any))]
});
static NO_COINBASE_TRANSCRIPT: Lazy<
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
> = Lazy::new(|| {
let block = block_no_transactions();
static NO_COINBASE_TRANSCRIPT: Lazy<Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>> =
Lazy::new(|| {
let block = block_no_transactions();
vec![(Arc::new(block), Err(ExpectedTranscriptError::Any))]
});
vec![(
Request::Commit(Arc::new(block)),
Err(ExpectedTranscriptError::Any),
)]
});
static NO_COINBASE_STATE_TRANSCRIPT: Lazy<
Vec<(zs::Request, Result<zs::Response, ExpectedTranscriptError>)>,

View File

@ -51,7 +51,7 @@ pub use block::{
funding_streams::{funding_stream_address, funding_stream_values, new_coinbase_script},
general::miner_subsidy,
},
VerifyBlockError, MAX_BLOCK_SIGOPS,
Request, VerifyBlockError, MAX_BLOCK_SIGOPS,
};
pub use chain::VerifyChainError;
pub use checkpoint::{

View File

@ -26,9 +26,8 @@ use crate::methods::{
DEFAULT_SOLUTION_RATE_WINDOW_SIZE, GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL,
},
get_block_template::{
check_block_template_parameters, check_miner_address, check_synced_to_tip,
fetch_mempool_transactions, fetch_state_tip_and_local_time,
generate_coinbase_and_roots,
check_miner_address, check_synced_to_tip, fetch_mempool_transactions,
fetch_state_tip_and_local_time, generate_coinbase_and_roots, validate_block_proposal,
},
types::{
get_block_template::GetBlockTemplate, get_mining_info, hex_data::HexData,
@ -101,7 +100,7 @@ pub trait GetBlockTemplateRpc {
fn get_block_template(
&self,
parameters: Option<get_block_template::JsonParameters>,
) -> BoxFuture<Result<GetBlockTemplate>>;
) -> BoxFuture<Result<get_block_template::Response>>;
/// Submits block to the node to be validated and committed.
/// Returns the [`submit_block::Response`] for the operation, as a JSON string.
@ -165,7 +164,7 @@ where
Response = zebra_state::ReadResponse,
Error = zebra_state::BoxError,
>,
ChainVerifier: Service<Arc<Block>, Response = block::Hash, Error = zebra_consensus::BoxError>
ChainVerifier: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
+ Clone
+ Send
+ Sync
@ -217,7 +216,7 @@ where
+ Sync
+ 'static,
Tip: ChainTip + Clone + Send + Sync + 'static,
ChainVerifier: Service<Arc<Block>, Response = block::Hash, Error = zebra_consensus::BoxError>
ChainVerifier: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
+ Clone
+ Send
+ Sync
@ -265,12 +264,12 @@ where
+ 'static,
<State as Service<zebra_state::ReadRequest>>::Future: Send,
Tip: ChainTip + Clone + Send + Sync + 'static,
ChainVerifier: Service<Arc<Block>, Response = block::Hash, Error = zebra_consensus::BoxError>
ChainVerifier: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
+ Clone
+ Send
+ Sync
+ 'static,
<ChainVerifier as Service<Arc<Block>>>::Future: Send,
<ChainVerifier as Service<zebra_consensus::Request>>::Future: Send,
SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
{
fn get_block_count(&self) -> Result<u32> {
@ -312,11 +311,11 @@ where
.boxed()
}
// TODO: use HexData to handle block proposal data, and a generic error constructor (#5548)
// TODO: use a generic error constructor (#5548)
fn get_block_template(
&self,
parameters: Option<get_block_template::JsonParameters>,
) -> BoxFuture<Result<GetBlockTemplate>> {
) -> BoxFuture<Result<get_block_template::Response>> {
// Should we generate coinbase transactions that are exactly like zcashd's?
//
// This is useful for testing, but either way Zebra should obey the consensus rules.
@ -332,21 +331,28 @@ where
let sync_status = self.sync_status.clone();
let state = self.state.clone();
if let Some(HexData(block_proposal_bytes)) = parameters
.as_ref()
.and_then(get_block_template::JsonParameters::block_proposal_data)
{
return validate_block_proposal(self.chain_verifier.clone(), block_proposal_bytes)
.boxed();
}
// To implement long polling correctly, we split this RPC into multiple phases.
async move {
get_block_template::check_parameters(&parameters)?;
let client_long_poll_id = parameters
.as_ref()
.and_then(|params| params.long_poll_id.clone());
// - One-off checks
// Check config and parameters.
// These checks always have the same result during long polling.
let miner_address = check_miner_address(miner_address)?;
let mut client_long_poll_id = None;
if let Some(parameters) = parameters {
check_block_template_parameters(&parameters)?;
client_long_poll_id = parameters.long_poll_id;
}
// - Checks and fetches that can change during long polling
//
// Set up the loop.
@ -506,7 +512,7 @@ where
?server_long_poll_id,
?client_long_poll_id,
"returning from long poll due to a state error.\
Is Zebra shutting down?"
Is Zebra shutting down?"
);
return Err(Error {
@ -582,7 +588,7 @@ where
COINBASE_LIKE_ZCASHD,
);
Ok(response)
Ok(response.into())
}
.boxed()
}
@ -608,7 +614,7 @@ where
message: error.to_string(),
data: None,
})?
.call(Arc::new(block))
.call(zebra_consensus::Request::Commit(Arc::new(block)))
.await;
let chain_error = match chain_verifier_response {

View File

@ -18,6 +18,8 @@ pub const GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL: u64 = 5;
pub const GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD: &str = "00000000ffffffff";
/// A hardcoded list of fields that the miner can change from the block template.
///
/// <https://en.bitcoin.it/wiki/BIP_0023#Mutations>
pub const GET_BLOCK_TEMPLATE_MUTABLE_FIELD: &[&str] = &[
// Standard mutations, copied from zcashd
"time",
@ -26,8 +28,9 @@ pub const GET_BLOCK_TEMPLATE_MUTABLE_FIELD: &[&str] = &[
];
/// A hardcoded list of Zebra's getblocktemplate RPC capabilities.
/// Currently empty.
pub const GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD: &[&str] = &[];
///
/// <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
pub const GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD: &[&str] = &["proposal"];
/// The max estimated distance to the chain tip for the getblocktemplate method.
///
@ -35,6 +38,8 @@ pub const GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD: &[&str] = &[];
/// > A full validator MUST NOT accept blocks with nTime more than two hours in the future
/// > according to its clock. This is not strictly a consensus rule because it is nondeterministic,
/// > and clock time varies between nodes.
/// >
/// > <https://zips.z.cash/protocol/protocol.pdf#blockheader>
pub const MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP: i32 = 100;
/// The RPC error code used by `zcashd` for when it's still downloading initial blocks.

View File

@ -8,12 +8,14 @@ use tower::{Service, ServiceExt};
use zebra_chain::{
amount::{self, Amount, NegativeOrZero, NonNegative},
block::{
self,
merkle::{self, AuthDataRoot},
ChainHistoryBlockTxAuthCommitmentHash, Height,
},
chain_sync_status::ChainSyncStatus,
chain_tip::ChainTip,
parameters::Network,
serialization::ZcashDeserializeInto,
transaction::{Transaction, UnminedTx, VerifiedUnminedTx},
transparent,
};
@ -25,26 +27,57 @@ use zebra_state::GetBlockTemplateChainInfo;
use crate::methods::get_block_template_rpcs::{
constants::{MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP, NOT_SYNCED_ERROR_CODE},
types::{default_roots::DefaultRoots, get_block_template, transaction::TransactionTemplate},
types::{default_roots::DefaultRoots, transaction::TransactionTemplate},
};
pub use crate::methods::get_block_template_rpcs::types::get_block_template::*;
// - Parameter checks
/// Returns an error if the get block template RPC `parameters` are invalid.
pub fn check_block_template_parameters(
parameters: &get_block_template::JsonParameters,
) -> Result<()> {
if parameters.data.is_some() || parameters.mode == GetBlockTemplateRequestMode::Proposal {
return Err(Error {
code: ErrorCode::InvalidParams,
message: "\"proposal\" mode is currently unsupported by Zebra".to_string(),
data: None,
});
}
/// Checks that `data` is omitted in `Template` mode or provided in `Proposal` mode,
///
/// Returns an error if there's a mismatch between the mode and whether `data` is provided.
pub fn check_parameters(parameters: &Option<JsonParameters>) -> Result<()> {
let Some(parameters) = parameters else {
return Ok(())
};
Ok(())
match parameters {
JsonParameters {
mode: GetBlockTemplateRequestMode::Template,
data: None,
..
}
| JsonParameters {
mode: GetBlockTemplateRequestMode::Proposal,
data: Some(_),
..
} => Ok(()),
JsonParameters {
mode: GetBlockTemplateRequestMode::Proposal,
data: None,
..
} => Err(Error {
code: ErrorCode::InvalidParams,
message: "\"data\" parameter must be \
provided in \"proposal\" mode"
.to_string(),
data: None,
}),
JsonParameters {
mode: GetBlockTemplateRequestMode::Template,
data: Some(_),
..
} => Err(Error {
code: ErrorCode::InvalidParams,
message: "\"data\" parameter must be \
omitted in \"template\" mode"
.to_string(),
data: None,
}),
}
}
/// Returns the miner address, or an error if it is invalid.
@ -60,6 +93,42 @@ pub fn check_miner_address(
})
}
/// Attempts to validate block proposal against all of the server's
/// usual acceptance rules (except proof-of-work).
///
/// Returns a `getblocktemplate` [`Response`].
pub async fn validate_block_proposal<ChainVerifier>(
mut chain_verifier: ChainVerifier,
block_proposal_bytes: Vec<u8>,
) -> Result<Response>
where
ChainVerifier: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
+ Clone
+ Send
+ Sync
+ 'static,
{
let Ok(block) = block_proposal_bytes.zcash_deserialize_into() else {
return Ok(ProposalRejectReason::Rejected.into())
};
let chain_verifier_response = chain_verifier
.ready()
.await
.map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?
.call(zebra_consensus::Request::CheckProposal(Arc::new(block)))
.await;
Ok(chain_verifier_response
.map(|_hash| ProposalResponse::Valid)
.unwrap_or_else(|_| ProposalRejectReason::Rejected.into())
.into())
}
// - State and syncer checks
/// Returns an error if Zebra is not synced to the consensus chain tip.

View File

@ -27,7 +27,7 @@ pub mod parameters;
pub use parameters::*;
/// A serialized `getblocktemplate` RPC response.
/// A serialized `getblocktemplate` RPC response in template mode.
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct GetBlockTemplate {
/// The getblocktemplate RPC capabilities supported by Zebra.
@ -162,6 +162,14 @@ pub struct GetBlockTemplate {
}
impl GetBlockTemplate {
/// Returns a `Vec` of capabilities supported by the `getblocktemplate` RPC
pub fn capabilities() -> Vec<String> {
GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD
.iter()
.map(ToString::to_string)
.collect()
}
/// Returns a new [`GetBlockTemplate`] struct, based on the supplied arguments and defaults.
///
/// The result of this method only depends on the supplied arguments and constants.
@ -203,10 +211,7 @@ impl GetBlockTemplate {
.expect("state always returns a valid difficulty value");
// Convert default values
let capabilities: Vec<String> = GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD
.iter()
.map(ToString::to_string)
.collect();
let capabilities: Vec<String> = Self::capabilities();
let mutable: Vec<String> = GET_BLOCK_TEMPLATE_MUTABLE_FIELD
.iter()
.map(ToString::to_string)
@ -253,3 +258,68 @@ impl GetBlockTemplate {
}
}
}
/// Error response to a `getblocktemplate` RPC request in proposal mode.
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ProposalRejectReason {
/// Block proposal rejected as invalid.
Rejected,
}
/// Response to a `getblocktemplate` RPC request in proposal mode.
///
/// See <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(untagged, rename_all = "kebab-case")]
pub enum ProposalResponse {
/// Block proposal was rejected as invalid, returns `reject-reason` and server `capabilities`.
ErrorResponse {
/// Reason the proposal was invalid as-is.
reject_reason: ProposalRejectReason,
/// The getblocktemplate RPC capabilities supported by Zebra.
capabilities: Vec<String>,
},
/// Block proposal was successfully validated, returns null.
Valid,
}
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
/// A `getblocktemplate` RPC response.
pub enum Response {
/// `getblocktemplate` RPC request in template mode.
TemplateMode(Box<GetBlockTemplate>),
/// `getblocktemplate` RPC request in proposal mode.
ProposalMode(ProposalResponse),
}
impl From<ProposalRejectReason> for ProposalResponse {
fn from(reject_reason: ProposalRejectReason) -> Self {
Self::ErrorResponse {
reject_reason,
capabilities: GetBlockTemplate::capabilities(),
}
}
}
impl From<ProposalRejectReason> for Response {
fn from(error_response: ProposalRejectReason) -> Self {
Self::ProposalMode(ProposalResponse::from(error_response))
}
}
impl From<ProposalResponse> for Response {
fn from(proposal_response: ProposalResponse) -> Self {
Self::ProposalMode(proposal_response)
}
}
impl From<GetBlockTemplate> for Response {
fn from(template: GetBlockTemplate) -> Self {
Self::TemplateMode(Box::new(template))
}
}

View File

@ -3,7 +3,6 @@
use crate::methods::get_block_template_rpcs::types::{hex_data::HexData, long_poll::LongPollId};
/// Defines whether the RPC method should generate a block template or attempt to validate a block proposal.
/// `Proposal` mode is currently unsupported and will return an error.
#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum GetBlockTemplateRequestMode {
@ -11,7 +10,6 @@ pub enum GetBlockTemplateRequestMode {
Template,
/// Indicates a request to validate block data.
/// Currently unsupported and will return an error.
Proposal,
}
@ -63,19 +61,17 @@ pub enum GetBlockTemplateCapability {
/// All other fields are optional.
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, Default)]
pub struct JsonParameters {
/// Must be set to "template" or omitted, as "proposal" mode is currently unsupported.
///
/// Defines whether the RPC method should generate a block template or attempt to
/// validate block data, checking against all of the server's usual acceptance rules
/// (excluding the check for a valid proof-of-work).
// TODO: Support `proposal` mode.
#[serde(default)]
pub mode: GetBlockTemplateRequestMode,
/// Must be omitted as "proposal" mode is currently unsupported.
/// Must be omitted when `getblocktemplate` RPC is called in "template" mode (or when `mode` is omitted).
/// Must be provided when `getblocktemplate` RPC is called in "proposal" mode.
///
/// Hex-encoded block data to be validated and checked against the server's usual acceptance rules
/// (excluding the check for a valid proof-of-work) when `mode` is set to `proposal`.
/// (excluding the check for a valid proof-of-work).
pub data: Option<HexData>,
/// A list of client-side supported capability features
@ -87,4 +83,29 @@ pub struct JsonParameters {
/// In Zebra, the ID represents the chain tip, max time, and mempool contents.
#[serde(rename = "longpollid")]
pub long_poll_id: Option<LongPollId>,
/// The workid for the block template.
///
/// currently unused.
#[serde(rename = "workid")]
pub _work_id: Option<String>,
}
impl JsonParameters {
/// Returns Some(data) with the block proposal hexdata if in `Proposal` mode and `data` is provided.
pub fn block_proposal_data(&self) -> Option<HexData> {
match self {
Self { data: None, .. }
| Self {
mode: GetBlockTemplateRequestMode::Template,
..
} => None,
Self {
mode: GetBlockTemplateRequestMode::Proposal,
data,
..
} => data.clone(),
}
}
}

View File

@ -23,13 +23,16 @@ use zebra_node_services::mempool;
use zebra_state::{GetBlockTemplateChainInfo, ReadRequest, ReadResponse};
use zebra_test::mock_service::{MockService, PanicAssertion};
use zebra_test::{
mock_service::{MockService, PanicAssertion},
vectors::BLOCK_MAINNET_1_BYTES,
};
use crate::methods::{
get_block_template_rpcs::{
self,
types::{
get_block_template::{self, GetBlockTemplate},
get_block_template::{self, GetBlockTemplateRequestMode},
get_mining_info,
hex_data::HexData,
long_poll::{LongPollId, LONG_POLL_ID_LENGTH},
@ -159,12 +162,12 @@ pub async fn test_responses<State, ReadState>(
// create a new rpc instance with new state and mock
let get_block_template_rpc = GetBlockTemplateRpcImpl::new(
network,
mining_config,
mining_config.clone(),
Buffer::new(mempool.clone(), 1),
new_read_state.clone(),
mock_chain_tip,
mock_chain_tip.clone(),
chain_verifier,
mock_sync_status,
mock_sync_status.clone(),
);
// Basic variant (default mode and no extra features)
@ -195,10 +198,12 @@ pub async fn test_responses<State, ReadState>(
.await
.respond(mempool::Response::FullTransactions(vec![]));
let get_block_template = get_block_template
let get_block_template::Response::TemplateMode(get_block_template) = get_block_template
.await
.expect("unexpected panic in getblocktemplate RPC task")
.expect("unexpected error in getblocktemplate RPC call");
.expect("unexpected error in getblocktemplate RPC call") else {
panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response")
};
let coinbase_tx: Transaction = get_block_template
.coinbase_txn
@ -207,7 +212,12 @@ pub async fn test_responses<State, ReadState>(
.zcash_deserialize_into()
.expect("coinbase bytes are valid");
snapshot_rpc_getblocktemplate("basic", get_block_template, coinbase_tx, &settings);
snapshot_rpc_getblocktemplate(
"basic",
(*get_block_template).into(),
Some(coinbase_tx),
&settings,
);
// long polling feature with submit old field
@ -250,10 +260,12 @@ pub async fn test_responses<State, ReadState>(
.await
.respond(mempool::Response::FullTransactions(vec![]));
let get_block_template = get_block_template
let get_block_template::Response::TemplateMode(get_block_template) = get_block_template
.await
.expect("unexpected panic in getblocktemplate RPC task")
.expect("unexpected error in getblocktemplate RPC call");
.expect("unexpected error in getblocktemplate RPC call") else {
panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response")
};
let coinbase_tx: Transaction = get_block_template
.coinbase_txn
@ -262,7 +274,62 @@ pub async fn test_responses<State, ReadState>(
.zcash_deserialize_into()
.expect("coinbase bytes are valid");
snapshot_rpc_getblocktemplate("long_poll", get_block_template, coinbase_tx, &settings);
snapshot_rpc_getblocktemplate(
"long_poll",
(*get_block_template).into(),
Some(coinbase_tx),
&settings,
);
// `getblocktemplate` proposal mode variant
let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template(Some(
get_block_template::JsonParameters {
mode: GetBlockTemplateRequestMode::Proposal,
data: Some(HexData("".into())),
..Default::default()
},
)));
let get_block_template = get_block_template
.await
.expect("unexpected panic in getblocktemplate RPC task")
.expect("unexpected error in getblocktemplate RPC call");
snapshot_rpc_getblocktemplate("invalid-proposal", get_block_template, None, &settings);
let mut mock_chain_verifier = MockService::build().for_unit_tests();
let get_block_template_rpc = GetBlockTemplateRpcImpl::new(
network,
mining_config,
Buffer::new(mempool.clone(), 1),
new_read_state.clone(),
mock_chain_tip,
mock_chain_verifier.clone(),
mock_sync_status,
);
let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template(Some(
get_block_template::JsonParameters {
mode: GetBlockTemplateRequestMode::Proposal,
data: Some(HexData(BLOCK_MAINNET_1_BYTES.to_vec())),
..Default::default()
},
)));
tokio::spawn(async move {
mock_chain_verifier
.expect_request_that(|req| matches!(req, zebra_consensus::Request::CheckProposal(_)))
.await
.respond(Hash::from([0; 32]));
});
let get_block_template = get_block_template
.await
.expect("unexpected panic in getblocktemplate RPC task")
.expect("unexpected error in getblocktemplate RPC call");
snapshot_rpc_getblocktemplate("proposal", get_block_template, None, &settings);
// `submitblock`
@ -287,19 +354,22 @@ fn snapshot_rpc_getblockhash(block_hash: GetBlockHash, settings: &insta::Setting
/// Snapshot `getblocktemplate` response, using `cargo insta` and JSON serialization.
fn snapshot_rpc_getblocktemplate(
variant: &'static str,
block_template: GetBlockTemplate,
coinbase_tx: Transaction,
block_template: get_block_template::Response,
coinbase_tx: Option<Transaction>,
settings: &insta::Settings,
) {
settings.bind(|| {
insta::assert_json_snapshot!(format!("get_block_template_{variant}"), block_template)
});
settings.bind(|| {
insta::assert_ron_snapshot!(
format!("get_block_template_{variant}.coinbase_tx"),
coinbase_tx
)
});
if let Some(coinbase_tx) = coinbase_tx {
settings.bind(|| {
insta::assert_ron_snapshot!(
format!("get_block_template_{variant}.coinbase_tx"),
coinbase_tx
)
});
};
}
/// Snapshot `submitblock` response, using `cargo insta` and JSON serialization.

View File

@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_template
---
{
"capabilities": [],
"capabilities": [
"proposal"
],
"version": 4,
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
"blockcommitmentshash": "02990723c6b62a724651322d141b4a72a4ffd66518167d809badbd5117d5518a",

View File

@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_template
---
{
"capabilities": [],
"capabilities": [
"proposal"
],
"version": 4,
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
"blockcommitmentshash": "3b25791957f9383b6ce851d728a78309664d5d7a82ca87b6a9125a2f2c529792",

View File

@ -0,0 +1,10 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_template
---
{
"reject_reason": "rejected",
"capabilities": [
"proposal"
]
}

View File

@ -0,0 +1,10 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_template
---
{
"reject_reason": "rejected",
"capabilities": [
"proposal"
]
}

View File

@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_template
---
{
"capabilities": [],
"capabilities": [
"proposal"
],
"version": 4,
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
"blockcommitmentshash": "02990723c6b62a724651322d141b4a72a4ffd66518167d809badbd5117d5518a",

View File

@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_template
---
{
"capabilities": [],
"capabilities": [
"proposal"
],
"version": 4,
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
"blockcommitmentshash": "3b25791957f9383b6ce851d728a78309664d5d7a82ca87b6a9125a2f2c529792",

View File

@ -0,0 +1,5 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_template
---
null

View File

@ -0,0 +1,5 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_template
---
null

View File

@ -997,15 +997,16 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
};
let get_block_template_fut = get_block_template_rpc.get_block_template(None);
let (get_block_template, ..) = tokio::join!(
get_block_template_fut,
mock_mempool_request_handler,
mock_read_state_request_handler,
);
let get_block_template =
get_block_template.expect("unexpected error in getblocktemplate RPC call");
let get_block_template::Response::TemplateMode(get_block_template) = get_block_template
.expect("unexpected error in getblocktemplate RPC call") else {
panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response")
};
assert_eq!(
get_block_template.capabilities,
@ -1099,7 +1100,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
..Default::default()
}))
.await
.expect_err("needs an error when using unsupported mode");
.expect_err("needs an error when called in proposal mode without data");
assert_eq!(get_block_template_sync_error.code, ErrorCode::InvalidParams);
@ -1109,7 +1110,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
..Default::default()
}))
.await
.expect_err("needs an error when passing in block data");
.expect_err("needs an error when passing in block data in template mode");
assert_eq!(get_block_template_sync_error.code, ErrorCode::InvalidParams);

View File

@ -7,7 +7,7 @@
//! 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::{fmt, panic, sync::Arc};
use std::{fmt, panic};
use jsonrpc_core::{Compatibility, MetaIoHandler};
use jsonrpc_http_server::{CloseHandle, ServerBuilder};
@ -17,10 +17,7 @@ use tower::{buffer::Buffer, Service};
use tracing::{Instrument, *};
use zebra_chain::{
block::{self, Block},
chain_sync_status::ChainSyncStatus,
chain_tip::ChainTip,
parameters::Network,
block, chain_sync_status::ChainSyncStatus, chain_tip::ChainTip, parameters::Network,
};
use zebra_node_services::mempool;
@ -107,12 +104,15 @@ impl RpcServer {
+ 'static,
State::Future: Send,
Tip: ChainTip + Clone + Send + Sync + 'static,
ChainVerifier: Service<Arc<Block>, Response = block::Hash, Error = zebra_consensus::BoxError>
+ Clone
ChainVerifier: Service<
zebra_consensus::Request,
Response = block::Hash,
Error = zebra_consensus::BoxError,
> + Clone
+ Send
+ Sync
+ 'static,
<ChainVerifier as Service<Arc<Block>>>::Future: Send,
<ChainVerifier as Service<zebra_consensus::Request>>::Future: Send,
SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
{
if let Some(listen_addr) = config.listen_addr {

View File

@ -558,6 +558,13 @@ pub enum Request {
///
/// Returns [`Response::ValidBestChainTipNullifiersAndAnchors`]
CheckBestChainTipNullifiersAndAnchors(UnminedTx),
#[cfg(feature = "getblocktemplate-rpcs")]
/// Performs contextual validation of the given block, but does not commit it to the state.
///
/// Returns [`Response::ValidBlockProposal`] when successful.
/// See `[ReadRequest::CheckBlockProposalValidity]` for details.
CheckBlockProposalValidity(PreparedBlock),
}
impl Request {
@ -577,6 +584,8 @@ impl Request {
Request::CheckBestChainTipNullifiersAndAnchors(_) => {
"best_chain_tip_nullifiers_anchors"
}
#[cfg(feature = "getblocktemplate-rpcs")]
Request::CheckBlockProposalValidity(_) => "check_block_proposal_validity",
}
}
@ -790,6 +799,16 @@ pub enum ReadRequest {
/// Optionally estimate the network speed at the time when a certain block was found
height: Option<block::Height>,
},
#[cfg(feature = "getblocktemplate-rpcs")]
/// Performs contextual validation of the given block, but does not commit it to the state.
///
/// It is the caller's responsibility to perform semantic validation.
/// (The caller does not need to check proof of work for block proposals.)
///
/// Returns [`ReadResponse::ValidBlockProposal`] when successful, or an error if
/// the block fails contextual validation.
CheckBlockProposalValidity(PreparedBlock),
}
impl ReadRequest {
@ -819,6 +838,8 @@ impl ReadRequest {
ReadRequest::ChainInfo => "chain_info",
#[cfg(feature = "getblocktemplate-rpcs")]
ReadRequest::SolutionRate { .. } => "solution_rate",
#[cfg(feature = "getblocktemplate-rpcs")]
ReadRequest::CheckBlockProposalValidity(_) => "check_block_proposal_validity",
}
}
@ -866,6 +887,11 @@ impl TryFrom<Request> for ReadRequest {
Err("ReadService does not write blocks")
}
#[cfg(feature = "getblocktemplate-rpcs")]
Request::CheckBlockProposalValidity(prepared) => {
Ok(ReadRequest::CheckBlockProposalValidity(prepared))
}
Request::AwaitUtxo(_) => Err("ReadService does not track pending UTXOs. \
Manually convert the request to ReadRequest::AnyChainUtxo, \
and handle pending UTXOs"),

View File

@ -59,6 +59,10 @@ pub enum Response {
///
/// Does not check transparent UTXO inputs
ValidBestChainTipNullifiersAndAnchors,
#[cfg(feature = "getblocktemplate-rpcs")]
/// Response to [`Request::CheckBlockProposalValidity`](crate::Request::CheckBlockProposalValidity)
ValidBlockProposal,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -137,6 +141,10 @@ pub enum ReadResponse {
#[cfg(feature = "getblocktemplate-rpcs")]
/// Response to [`ReadRequest::SolutionRate`](crate::ReadRequest::SolutionRate)
SolutionRate(Option<u128>),
#[cfg(feature = "getblocktemplate-rpcs")]
/// Response to [`ReadRequest::CheckBlockProposalValidity`](crate::ReadRequest::CheckBlockProposalValidity)
ValidBlockProposal,
}
/// A structure with the information needed from the state to build a `getblocktemplate` RPC response.
@ -215,6 +223,9 @@ impl TryFrom<ReadResponse> for Response {
Err("there is no corresponding Response for this ReadResponse")
}
#[cfg(feature = "getblocktemplate-rpcs")]
ReadResponse::ValidBlockProposal => Ok(Response::ValidBlockProposal),
#[cfg(feature = "getblocktemplate-rpcs")]
ReadResponse::BlockHash(_) => {
Err("there is no corresponding Response for this ReadResponse")

View File

@ -1047,6 +1047,24 @@ impl Service<Request> for StateService {
}
.boxed()
}
#[cfg(feature = "getblocktemplate-rpcs")]
Request::CheckBlockProposalValidity(_) => {
// Redirect the request to the concurrent ReadStateService
let read_service = self.read_service.clone();
async move {
let req = req
.try_into()
.expect("ReadRequest conversion should not fail");
let rsp = read_service.oneshot(req).await?;
let rsp = rsp.try_into().expect("Response conversion should not fail");
Ok(rsp)
}
.boxed()
}
}
}
}
@ -1082,7 +1100,7 @@ impl Service<ReadRequest> for ReadStateService {
Poll::Ready(Ok(()))
}
#[instrument(name = "read_state", skip(self))]
#[instrument(name = "read_state", skip(self, req))]
fn call(&mut self, req: ReadRequest) -> Self::Future {
req.count_metric();
@ -1688,6 +1706,58 @@ impl Service<ReadRequest> for ReadStateService {
.map(|join_result| join_result.expect("panic in ReadRequest::ChainInfo"))
.boxed()
}
#[cfg(feature = "getblocktemplate-rpcs")]
ReadRequest::CheckBlockProposalValidity(prepared) => {
let timer = CodeTimer::start();
let state = self.clone();
// # Performance
//
// Allow other async tasks to make progress while concurrently reading blocks from disk.
let span = Span::current();
tokio::task::spawn_blocking(move || {
span.in_scope(move || {
tracing::info!("attempting to validate and commit block proposal onto a cloned non-finalized state");
let mut latest_non_finalized_state = state.latest_non_finalized_state();
// The previous block of a valid proposal must be on the best chain tip.
let Some((_best_tip_height, best_tip_hash)) = read::best_tip(&latest_non_finalized_state, &state.db) else {
return Err("state is empty: wait for Zebra to sync before submitting a proposal".into());
};
if prepared.block.header.previous_block_hash != best_tip_hash {
return Err("proposal is not based on the current best chain tip: previous block hash must be the best chain tip".into());
}
// This clone of the non-finalized state is dropped when this closure returns.
// The non-finalized state that's used in the rest of the state (including finalizing
// blocks into the db) is not mutated here.
//
// TODO: Convert `CommitBlockError` to a new `ValidateProposalError`?
latest_non_finalized_state.should_count_metrics = false;
write::validate_and_commit_non_finalized(
&state.db,
&mut latest_non_finalized_state,
prepared,
)?;
// The work is done in the future.
timer.finish(
module_path!(),
line!(),
"ReadRequest::CheckBlockProposalValidity",
);
Ok(ReadResponse::ValidBlockProposal)
})
})
.map(|join_result| {
join_result.expect("panic in ReadRequest::CheckBlockProposalValidity")
})
.boxed()
}
}
}
}

View File

@ -14,7 +14,7 @@ use zebra_chain::{
use crate::{
service::{
block_iter::any_ancestor_blocks, check::difficulty::POW_ADJUSTMENT_BLOCK_SPAN,
finalized_state::FinalizedState, non_finalized_state::NonFinalizedState,
finalized_state::ZebraDb, non_finalized_state::NonFinalizedState,
},
BoxError, PreparedBlock, ValidateContextError,
};
@ -365,25 +365,25 @@ where
///
/// Additional contextual validity checks are performed by the non-finalized [`Chain`].
pub(crate) fn initial_contextual_validity(
finalized_state: &FinalizedState,
finalized_state: &ZebraDb,
non_finalized_state: &NonFinalizedState,
prepared: &PreparedBlock,
) -> Result<(), ValidateContextError> {
let relevant_chain = any_ancestor_blocks(
non_finalized_state,
&finalized_state.db,
finalized_state,
prepared.block.header.previous_block_hash,
);
// Security: check proof of work before any other checks
check::block_is_valid_for_recent_chain(
prepared,
finalized_state.network(),
finalized_state.db.finalized_tip_height(),
non_finalized_state.network,
finalized_state.finalized_tip_height(),
relevant_chain,
)?;
check::nullifier::no_duplicates_in_finalized_chain(prepared, &finalized_state.db)?;
check::nullifier::no_duplicates_in_finalized_chain(prepared, finalized_state)?;
Ok(())
}

View File

@ -81,10 +81,12 @@ fn check_sprout_anchors() {
// Validate and commit [`block_1`]. This will add an anchor referencing the
// empty note commitment tree to the state.
assert!(
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block_1)
.is_ok()
);
assert!(validate_and_commit_non_finalized(
&finalized_state.db,
&mut non_finalized_state,
block_1
)
.is_ok());
let check_unmined_tx_anchors_result = unmined_txs.iter().try_for_each(|unmined_tx| {
tx_anchors_refer_to_final_treestates(
@ -98,7 +100,7 @@ fn check_sprout_anchors() {
// Validate and commit [`block_2`]. This will also check the anchors.
assert_eq!(
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block_2),
validate_and_commit_non_finalized(&finalized_state.db, &mut non_finalized_state, block_2),
Ok(())
);
}
@ -288,10 +290,12 @@ fn check_sapling_anchors() {
Err(ValidateContextError::UnknownSaplingAnchor { .. })
));
assert!(
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block1)
.is_ok()
);
assert!(validate_and_commit_non_finalized(
&finalized_state.db,
&mut non_finalized_state,
block1
)
.is_ok());
let check_unmined_tx_anchors_result = unmined_txs.iter().try_for_each(|unmined_tx| {
tx_anchors_refer_to_final_treestates(
@ -304,7 +308,7 @@ fn check_sapling_anchors() {
assert!(check_unmined_tx_anchors_result.is_ok());
assert_eq!(
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block2),
validate_and_commit_non_finalized(&finalized_state.db, &mut non_finalized_state, block2),
Ok(())
);
}

View File

@ -102,7 +102,7 @@ proptest! {
} else {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&finalized_state.db,
&mut non_finalized_state,
block1.clone()
);
@ -156,7 +156,7 @@ proptest! {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&finalized_state.db,
&mut non_finalized_state,
block1
);
@ -217,7 +217,7 @@ proptest! {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&finalized_state.db,
&mut non_finalized_state,
block1
);
@ -278,8 +278,10 @@ proptest! {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block1);
&finalized_state.db,
&mut non_finalized_state,
block1
);
prop_assert_eq!(
commit_result,
@ -364,7 +366,7 @@ proptest! {
} else {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&finalized_state.db,
&mut non_finalized_state,
block1.clone()
);
@ -383,7 +385,7 @@ proptest! {
let block2 = Arc::new(block2).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&finalized_state.db,
&mut non_finalized_state,
block2
);
@ -459,8 +461,10 @@ proptest! {
} else {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block1.clone());
&finalized_state.db,
&mut non_finalized_state,
block1.clone()
);
prop_assert_eq!(commit_result, Ok(()));
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
@ -506,8 +510,10 @@ proptest! {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block1);
&finalized_state.db,
&mut non_finalized_state,
block1
);
prop_assert_eq!(
commit_result,
@ -560,7 +566,7 @@ proptest! {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&finalized_state.db,
&mut non_finalized_state,
block1
);
@ -639,8 +645,10 @@ proptest! {
} else {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block1.clone());
&finalized_state.db,
&mut non_finalized_state,
block1.clone()
);
prop_assert_eq!(commit_result, Ok(()));
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
@ -655,8 +663,10 @@ proptest! {
let block2 = Arc::new(block2).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block2);
&finalized_state.db,
&mut non_finalized_state,
block2
);
prop_assert_eq!(
commit_result,
@ -731,8 +741,10 @@ proptest! {
} else {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block1.clone());
&finalized_state.db,
&mut non_finalized_state,
block1.clone()
);
prop_assert_eq!(commit_result, Ok(()));
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
@ -779,8 +791,10 @@ proptest! {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block1);
&finalized_state.db,
&mut non_finalized_state,
block1
);
prop_assert_eq!(
commit_result,
@ -837,8 +851,10 @@ proptest! {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block1);
&finalized_state.db,
&mut non_finalized_state,
block1
);
prop_assert_eq!(
commit_result,
@ -918,8 +934,10 @@ proptest! {
} else {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block1.clone());
&finalized_state.db,
&mut non_finalized_state,
block1.clone()
);
prop_assert_eq!(commit_result, Ok(()));
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
@ -933,8 +951,10 @@ proptest! {
let block2 = Arc::new(block2).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block2);
&finalized_state.db,
&mut non_finalized_state,
block2
);
prop_assert_eq!(
commit_result,

View File

@ -194,8 +194,10 @@ proptest! {
} else {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block1.clone());
&finalized_state.db,
&mut non_finalized_state,
block1.clone()
);
// the block was committed
prop_assert_eq!(commit_result, Ok(()));
@ -279,8 +281,10 @@ proptest! {
} else {
let block2 = Arc::new(block2).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block2.clone());
&finalized_state.db,
&mut non_finalized_state,
block2.clone()
);
// the block was committed
prop_assert_eq!(commit_result, Ok(()));
@ -357,8 +361,10 @@ proptest! {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block1);
&finalized_state.db,
&mut non_finalized_state,
block1
);
// the block was rejected
prop_assert_eq!(
@ -419,8 +425,10 @@ proptest! {
let block2 = Arc::new(block2).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block2);
&finalized_state.db,
&mut non_finalized_state,
block2
);
// the block was rejected
prop_assert_eq!(
@ -503,8 +511,10 @@ proptest! {
let block2 = Arc::new(block2).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block2);
&finalized_state.db,
&mut non_finalized_state,
block2
);
// the block was rejected
prop_assert_eq!(
@ -615,8 +625,10 @@ proptest! {
} else {
let block2 = block2.clone().prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block2.clone());
&finalized_state.db,
&mut non_finalized_state,
block2.clone()
);
// the block was committed
prop_assert_eq!(commit_result, Ok(()));
@ -651,8 +663,10 @@ proptest! {
let block3 = Arc::new(block3).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block3);
&finalized_state.db,
&mut non_finalized_state,
block3
);
// the block was rejected
if use_finalized_state_spend {
@ -725,8 +739,10 @@ proptest! {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block1);
&finalized_state.db,
&mut non_finalized_state,
block1
);
// the block was rejected
prop_assert_eq!(
@ -790,8 +806,10 @@ proptest! {
let block1 = Arc::new(block1).prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&mut non_finalized_state, block1);
&finalized_state.db,
&mut non_finalized_state,
block1
);
// the block was rejected
prop_assert_eq!(
@ -885,7 +903,7 @@ fn new_state_with_mainnet_transparent_data(
} else {
let block1 = block1.clone().prepare();
let commit_result = validate_and_commit_non_finalized(
&finalized_state,
&finalized_state.db,
&mut non_finalized_state,
block1.clone(),
);

View File

@ -44,9 +44,14 @@ pub struct NonFinalizedState {
pub chain_set: BTreeSet<Arc<Chain>>,
/// The configured Zcash network.
//
// Note: this field is currently unused, but it's useful for debugging.
pub network: Network,
#[cfg(feature = "getblocktemplate-rpcs")]
/// Configures the non-finalized state to count metrics.
///
/// Used for skipping metrics counting when testing block proposals
/// with a commit to a cloned non-finalized state.
pub should_count_metrics: bool,
}
impl NonFinalizedState {
@ -55,6 +60,8 @@ impl NonFinalizedState {
NonFinalizedState {
chain_set: Default::default(),
network,
#[cfg(feature = "getblocktemplate-rpcs")]
should_count_metrics: true,
}
}
@ -494,6 +501,11 @@ impl NonFinalizedState {
/// Update the metrics after `block` is committed
fn update_metrics_for_committed_block(&self, height: block::Height, hash: block::Hash) {
#[cfg(feature = "getblocktemplate-rpcs")]
if !self.should_count_metrics {
return;
}
metrics::counter!("state.memory.committed.block.count", 1);
metrics::gauge!("state.memory.committed.block.height", height.0 as f64);
@ -517,6 +529,11 @@ impl NonFinalizedState {
/// Update the metrics after `self.chain_set` is modified
fn update_metrics_for_chains(&self) {
#[cfg(feature = "getblocktemplate-rpcs")]
if !self.should_count_metrics {
return;
}
metrics::gauge!("state.memory.chain.count", self.chain_set.len() as f64);
metrics::gauge!(
"state.memory.best.chain.length",

View File

@ -12,7 +12,7 @@ use crate::{
constants::MAX_BLOCK_REORG_HEIGHT,
service::{
check,
finalized_state::FinalizedState,
finalized_state::{FinalizedState, ZebraDb},
non_finalized_state::NonFinalizedState,
queued_blocks::{QueuedFinalized, QueuedNonFinalized},
BoxError, ChainTipBlock, ChainTipSender, CloneError,
@ -36,17 +36,17 @@ const PARENT_ERROR_MAP_LIMIT: usize = MAX_BLOCK_REORG_HEIGHT as usize * 2;
/// non-finalized state if it is contextually valid.
#[tracing::instrument(level = "debug", skip(prepared), fields(height = ?prepared.height, hash = %prepared.hash))]
pub(crate) fn validate_and_commit_non_finalized(
finalized_state: &FinalizedState,
finalized_state: &ZebraDb,
non_finalized_state: &mut NonFinalizedState,
prepared: PreparedBlock,
) -> Result<(), CommitBlockError> {
check::initial_contextual_validity(finalized_state, non_finalized_state, &prepared)?;
let parent_hash = prepared.block.header.previous_block_hash;
if finalized_state.db.finalized_tip_hash() == parent_hash {
non_finalized_state.commit_new_chain(prepared, &finalized_state.db)?;
if finalized_state.finalized_tip_hash() == parent_hash {
non_finalized_state.commit_new_chain(prepared, finalized_state)?;
} else {
non_finalized_state.commit_block(prepared, &finalized_state.db)?;
non_finalized_state.commit_block(prepared, finalized_state)?;
}
Ok(())
@ -205,7 +205,7 @@ pub fn write_blocks_from_channels(
} else {
tracing::trace!(?child_hash, "validating queued child");
result = validate_and_commit_non_finalized(
&finalized_state,
&finalized_state.db,
&mut non_finalized_state,
queued_child,
)

View File

@ -24,10 +24,7 @@ use tower::{buffer::Buffer, timeout::Timeout, util::BoxService, Service, Service
use zebra_network as zn;
use zebra_state as zs;
use zebra_chain::{
block::{self, Block},
transaction::UnminedTxId,
};
use zebra_chain::{block, transaction::UnminedTxId};
use zebra_consensus::chain::VerifyChainError;
use zebra_network::{
constants::{ADDR_RESPONSE_LIMIT_DENOMINATOR, MAX_ADDRS_IN_MESSAGE},
@ -53,7 +50,10 @@ type BlockDownloadPeerSet =
Buffer<BoxService<zn::Request, zn::Response, zn::BoxError>, zn::Request>;
type State = Buffer<BoxService<zs::Request, zs::Response, zs::BoxError>, zs::Request>;
type Mempool = Buffer<BoxService<mempool::Request, mempool::Response, BoxError>, mempool::Request>;
type BlockVerifier = Buffer<BoxService<Arc<Block>, block::Hash, VerifyChainError>, Arc<Block>>;
type BlockVerifier = Buffer<
BoxService<zebra_consensus::Request, block::Hash, VerifyChainError>,
zebra_consensus::Request,
>;
type GossipedBlockDownloads =
BlockDownloads<Timeout<BlockDownloadPeerSet>, Timeout<BlockVerifier>, State>;

View File

@ -4,7 +4,6 @@ use std::{
collections::HashMap,
convert::TryFrom,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
@ -18,10 +17,7 @@ use tokio::{sync::oneshot, task::JoinHandle};
use tower::{Service, ServiceExt};
use tracing_futures::Instrument;
use zebra_chain::{
block::{self, Block},
chain_tip::ChainTip,
};
use zebra_chain::{block, chain_tip::ChainTip};
use zebra_network as zn;
use zebra_state as zs;
@ -77,7 +73,10 @@ pub struct Downloads<ZN, ZV, ZS>
where
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Clone + 'static,
ZN::Future: Send,
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError> + Send + Clone + 'static,
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
+ Send
+ Clone
+ 'static,
ZV::Future: Send,
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
ZS::Future: Send,
@ -117,7 +116,10 @@ impl<ZN, ZV, ZS> Stream for Downloads<ZN, ZV, ZS>
where
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Clone + 'static,
ZN::Future: Send,
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError> + Send + Clone + 'static,
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
+ Send
+ Clone
+ 'static,
ZV::Future: Send,
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
ZS::Future: Send,
@ -160,7 +162,10 @@ impl<ZN, ZV, ZS> Downloads<ZN, ZV, ZS>
where
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Clone + 'static,
ZN::Future: Send,
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError> + Send + Clone + 'static,
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
+ Send
+ Clone
+ 'static,
ZV::Future: Send,
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
ZS::Future: Send,
@ -338,7 +343,7 @@ where
}
verifier
.oneshot(block)
.oneshot(zebra_consensus::Request::Commit(block))
.await
.map(|hash| (hash, block_height))
}

View File

@ -1,6 +1,6 @@
//! Inbound service tests with a real peer set.
use std::{iter, net::SocketAddr, sync::Arc};
use std::{iter, net::SocketAddr};
use futures::FutureExt;
use indexmap::IndexSet;
@ -13,7 +13,7 @@ use tower::{
};
use zebra_chain::{
block::{self, Block, Height},
block::{self, Height},
parameters::Network,
serialization::ZcashDeserializeInto,
transaction::{AuthDigest, Hash as TxHash, Transaction, UnminedTx, UnminedTxId, WtxId},
@ -603,7 +603,7 @@ async fn setup(
Buffer<BoxService<mempool::Request, mempool::Response, BoxError>, mempool::Request>,
Buffer<BoxService<zebra_state::Request, zebra_state::Response, BoxError>, zebra_state::Request>,
// mocked services
MockService<Arc<Block>, block::Hash, PanicAssertion, VerifyChainError>,
MockService<zebra_consensus::Request, block::Hash, PanicAssertion, VerifyChainError>,
MockService<transaction::Request, transaction::Response, PanicAssertion, TransactionError>,
// real tasks
JoinHandle<Result<(), BlockGossipError>>,

View File

@ -2,7 +2,7 @@
//!
//! It is used when Zebra is a long way behind the current chain tip.
use std::{cmp::max, collections::HashSet, pin::Pin, sync::Arc, task::Poll, time::Duration};
use std::{cmp::max, collections::HashSet, pin::Pin, task::Poll, time::Duration};
use color_eyre::eyre::{eyre, Report};
use futures::stream::{FuturesUnordered, StreamExt};
@ -15,7 +15,7 @@ use tower::{
};
use zebra_chain::{
block::{self, Block, Height},
block::{self, Height},
chain_tip::ChainTip,
parameters::genesis_hash,
};
@ -300,7 +300,7 @@ where
+ Clone
+ 'static,
ZS::Future: Send,
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
+ Send
+ Sync
+ Clone
@ -381,7 +381,7 @@ where
+ Clone
+ 'static,
ZS::Future: Send,
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
+ Send
+ Sync
+ Clone

View File

@ -24,7 +24,7 @@ use tower::{hedge, Service, ServiceExt};
use tracing_futures::Instrument;
use zebra_chain::{
block::{self, Block, Height},
block::{self, Height},
chain_tip::ChainTip,
};
use zebra_network as zn;
@ -163,7 +163,7 @@ pub struct Downloads<ZN, ZV, ZSTip>
where
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Sync + 'static,
ZN::Future: Send,
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
+ Send
+ Sync
+ Clone
@ -217,7 +217,7 @@ impl<ZN, ZV, ZSTip> Stream for Downloads<ZN, ZV, ZSTip>
where
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Sync + 'static,
ZN::Future: Send,
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
+ Send
+ Sync
+ Clone
@ -264,7 +264,7 @@ impl<ZN, ZV, ZSTip> Downloads<ZN, ZV, ZSTip>
where
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Sync + 'static,
ZN::Future: Send,
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
+ Send
+ Sync
+ Clone
@ -516,7 +516,7 @@ where
// Verify the block.
let mut rsp = verifier
.map_err(|error| BlockDownloadVerifyError::VerifierServiceError { error })?
.call(block).boxed();
.call(zebra_consensus::Request::Commit(block)).boxed();
// Add a shorter timeout to workaround a known bug (#5125)
let short_timeout_max = (max_checkpoint_height + FINAL_CHECKPOINT_BLOCK_VERIFY_TIMEOUT_LIMIT).expect("checkpoint block height is in valid range");

View File

@ -89,7 +89,7 @@ async fn sync_blocks_ok() -> Result<(), crate::BoxError> {
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
chain_verifier
.expect_request(block0)
.expect_request(zebra_consensus::Request::Commit(block0))
.await
.respond(block0_hash);
@ -175,9 +175,9 @@ async fn sync_blocks_ok() -> Result<(), crate::BoxError> {
for _ in 1..=2 {
chain_verifier
.expect_request_that(|req| remaining_blocks.remove(&req.hash()).is_some())
.expect_request_that(|req| remaining_blocks.remove(&req.block().hash()).is_some())
.await
.respond_with(|req| req.hash());
.respond_with(|req| req.block().hash());
}
assert_eq!(
remaining_blocks,
@ -239,9 +239,9 @@ async fn sync_blocks_ok() -> Result<(), crate::BoxError> {
for _ in 3..=4 {
chain_verifier
.expect_request_that(|req| remaining_blocks.remove(&req.hash()).is_some())
.expect_request_that(|req| remaining_blocks.remove(&req.block().hash()).is_some())
.await
.respond_with(|req| req.hash());
.respond_with(|req| req.block().hash());
}
assert_eq!(
remaining_blocks,
@ -316,7 +316,7 @@ async fn sync_blocks_duplicate_hashes_ok() -> Result<(), crate::BoxError> {
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
chain_verifier
.expect_request(block0)
.expect_request(zebra_consensus::Request::Commit(block0))
.await
.respond(block0_hash);
@ -404,9 +404,9 @@ async fn sync_blocks_duplicate_hashes_ok() -> Result<(), crate::BoxError> {
for _ in 1..=2 {
chain_verifier
.expect_request_that(|req| remaining_blocks.remove(&req.hash()).is_some())
.expect_request_that(|req| remaining_blocks.remove(&req.block().hash()).is_some())
.await
.respond_with(|req| req.hash());
.respond_with(|req| req.block().hash());
}
assert_eq!(
remaining_blocks,
@ -470,9 +470,9 @@ async fn sync_blocks_duplicate_hashes_ok() -> Result<(), crate::BoxError> {
for _ in 3..=4 {
chain_verifier
.expect_request_that(|req| remaining_blocks.remove(&req.hash()).is_some())
.expect_request_that(|req| remaining_blocks.remove(&req.block().hash()).is_some())
.await
.respond_with(|req| req.hash());
.respond_with(|req| req.block().hash());
}
assert_eq!(
remaining_blocks,
@ -598,7 +598,7 @@ async fn sync_block_too_high_obtain_tips() -> Result<(), crate::BoxError> {
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
chain_verifier
.expect_request(block0)
.expect_request(zebra_consensus::Request::Commit(block0))
.await
.respond(block0_hash);
@ -759,7 +759,7 @@ async fn sync_block_too_high_extend_tips() -> Result<(), crate::BoxError> {
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
chain_verifier
.expect_request(block0)
.expect_request(zebra_consensus::Request::Commit(block0))
.await
.respond(block0_hash);
@ -845,9 +845,9 @@ async fn sync_block_too_high_extend_tips() -> Result<(), crate::BoxError> {
for _ in 1..=2 {
chain_verifier
.expect_request_that(|req| remaining_blocks.remove(&req.hash()).is_some())
.expect_request_that(|req| remaining_blocks.remove(&req.block().hash()).is_some())
.await
.respond_with(|req| req.hash());
.respond_with(|req| req.block().hash());
}
assert_eq!(
remaining_blocks,
@ -927,7 +927,7 @@ fn setup() -> (
impl Future<Output = Result<(), Report>> + Send,
SyncStatus,
// ChainVerifier
MockService<Arc<Block>, block::Hash, PanicAssertion>,
MockService<zebra_consensus::Request, block::Hash, PanicAssertion>,
// PeerSet
MockService<zebra_network::Request, zebra_network::Response, PanicAssertion>,
// StateService