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:
parent
6e61066c7e
commit
3cbee9465a
|
|
@ -7,7 +7,10 @@ edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
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"]
|
proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
|
|
@ -21,20 +21,17 @@ use thiserror::Error;
|
||||||
use tower::{Service, ServiceExt};
|
use tower::{Service, ServiceExt};
|
||||||
use tracing::Instrument;
|
use tracing::Instrument;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{amount::Amount, block, parameters::Network, transparent, work::equihash};
|
||||||
amount::Amount,
|
|
||||||
block::{self, Block},
|
|
||||||
parameters::Network,
|
|
||||||
transparent,
|
|
||||||
work::equihash,
|
|
||||||
};
|
|
||||||
use zebra_state as zs;
|
use zebra_state as zs;
|
||||||
|
|
||||||
use crate::{error::*, transaction as tx, BoxError};
|
use crate::{error::*, transaction as tx, BoxError};
|
||||||
|
|
||||||
pub mod check;
|
pub mod check;
|
||||||
|
pub mod request;
|
||||||
pub mod subsidy;
|
pub mod subsidy;
|
||||||
|
|
||||||
|
pub use request::Request;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
|
@ -74,6 +71,11 @@ pub enum VerifyBlockError {
|
||||||
// TODO: make this into a concrete type, and add it to is_duplicate_request() (#2908)
|
// TODO: make this into a concrete type, and add it to is_duplicate_request() (#2908)
|
||||||
Commit(#[source] BoxError),
|
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")]
|
#[error("invalid transaction")]
|
||||||
Transaction(#[from] TransactionError),
|
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
|
where
|
||||||
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
||||||
S::Future: Send + 'static,
|
S::Future: Send + 'static,
|
||||||
|
|
@ -134,11 +136,13 @@ where
|
||||||
Poll::Ready(Ok(()))
|
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 state_service = self.state_service.clone();
|
||||||
let mut transaction_verifier = self.transaction_verifier.clone();
|
let mut transaction_verifier = self.transaction_verifier.clone();
|
||||||
let network = self.network;
|
let network = self.network;
|
||||||
|
|
||||||
|
let block = request.block();
|
||||||
|
|
||||||
// We don't include the block hash, because it's likely already in a parent span
|
// 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());
|
let span = tracing::debug_span!("block", height = ?block.coinbase_height());
|
||||||
|
|
||||||
|
|
@ -172,10 +176,17 @@ where
|
||||||
Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
|
Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the difficulty checks first, to raise the threshold for
|
// > The block data MUST be validated and checked against the server's usual
|
||||||
// attacks that use any other fields.
|
// > acceptance rules (excluding the check for a valid proof-of-work).
|
||||||
check::difficulty_is_valid(&block.header, network, &height, &hash)?;
|
// <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
|
||||||
check::equihash_solution_is_valid(&block.header)?;
|
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
|
// Next, check the Merkle root validity, to ensure that
|
||||||
// the header binds to the transactions in the blocks.
|
// the header binds to the transactions in the blocks.
|
||||||
|
|
@ -279,6 +290,23 @@ where
|
||||||
new_outputs,
|
new_outputs,
|
||||||
transaction_hashes,
|
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
|
match state_service
|
||||||
.ready()
|
.ready()
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -58,24 +58,22 @@ pub fn coinbase_is_first(block: &Block) -> Result<Arc<transaction::Transaction>,
|
||||||
Ok(first.clone())
|
Ok(first.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Ok(())` if `hash` passes:
|
/// Returns `Ok(ExpandedDifficulty)` if the`difficulty_threshold` of `header` is at least as difficult as
|
||||||
/// - the target difficulty limit for `network` (PoWLimit), and
|
/// the target difficulty limit for `network` (PoWLimit)
|
||||||
/// - the difficulty filter,
|
|
||||||
/// based on the fields in `header`.
|
|
||||||
///
|
///
|
||||||
/// If the block is invalid, returns an error containing `height` and `hash`.
|
/// If the header difficulty threshold is invalid, returns an error containing `height` and `hash`.
|
||||||
pub fn difficulty_is_valid(
|
pub fn difficulty_threshold_is_valid(
|
||||||
header: &Header,
|
header: &Header,
|
||||||
network: Network,
|
network: Network,
|
||||||
height: &Height,
|
height: &Height,
|
||||||
hash: &Hash,
|
hash: &Hash,
|
||||||
) -> Result<(), BlockError> {
|
) -> Result<ExpandedDifficulty, BlockError> {
|
||||||
let difficulty_threshold = header
|
let difficulty_threshold = header
|
||||||
.difficulty_threshold
|
.difficulty_threshold
|
||||||
.to_expanded()
|
.to_expanded()
|
||||||
.ok_or(BlockError::InvalidDifficulty(*height, *hash))?;
|
.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.
|
// zcashd and bitcoin. Greater values represent *less* work.
|
||||||
|
|
||||||
// The PowLimit check is part of `Threshold()` in the spec, but it doesn't
|
// 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
|
// # Consensus
|
||||||
//
|
//
|
||||||
// > The block MUST pass the difficulty filter.
|
// > The block MUST pass the difficulty filter.
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,19 +28,18 @@ use crate::{parameters::SLOW_START_SHIFT, transaction};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
static VALID_BLOCK_TRANSCRIPT: Lazy<
|
static VALID_BLOCK_TRANSCRIPT: Lazy<Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>> =
|
||||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
Lazy::new(|| {
|
||||||
> = Lazy::new(|| {
|
let block: Arc<_> =
|
||||||
let block: Arc<_> =
|
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
|
||||||
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
|
.unwrap()
|
||||||
.unwrap()
|
.into();
|
||||||
.into();
|
let hash = Ok(block.as_ref().into());
|
||||||
let hash = Ok(block.as_ref().into());
|
vec![(Request::Commit(block), hash)]
|
||||||
vec![(block, hash)]
|
});
|
||||||
});
|
|
||||||
|
|
||||||
static INVALID_TIME_BLOCK_TRANSCRIPT: Lazy<
|
static INVALID_TIME_BLOCK_TRANSCRIPT: Lazy<
|
||||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||||
> = Lazy::new(|| {
|
> = Lazy::new(|| {
|
||||||
let mut block: Block =
|
let mut block: Block =
|
||||||
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..]).unwrap();
|
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..]).unwrap();
|
||||||
|
|
@ -55,11 +54,14 @@ static INVALID_TIME_BLOCK_TRANSCRIPT: Lazy<
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Arc::make_mut(&mut block.header).time = three_hours_in_the_future;
|
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<
|
static INVALID_HEADER_SOLUTION_TRANSCRIPT: Lazy<
|
||||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||||
> = Lazy::new(|| {
|
> = Lazy::new(|| {
|
||||||
let mut block: Block =
|
let mut block: Block =
|
||||||
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..]).unwrap();
|
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
|
// Change nonce to something invalid
|
||||||
Arc::make_mut(&mut block.header).nonce = [0; 32];
|
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<
|
static INVALID_COINBASE_TRANSCRIPT: Lazy<
|
||||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||||
> = Lazy::new(|| {
|
> = Lazy::new(|| {
|
||||||
let header = block::Header::zcash_deserialize(&zebra_test::vectors::DUMMY_HEADER[..]).unwrap();
|
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);
|
assert_eq!(block3.transactions.len(), 2);
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
(Arc::new(block1), Err(ExpectedTranscriptError::Any)),
|
(
|
||||||
(Arc::new(block2), Err(ExpectedTranscriptError::Any)),
|
Request::Commit(Arc::new(block1)),
|
||||||
(Arc::new(block3), Err(ExpectedTranscriptError::Any)),
|
Err(ExpectedTranscriptError::Any),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Request::Commit(Arc::new(block2)),
|
||||||
|
Err(ExpectedTranscriptError::Any),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Request::Commit(Arc::new(block3)),
|
||||||
|
Err(ExpectedTranscriptError::Any),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::Arc,
|
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -27,15 +26,14 @@ use tower::{buffer::Buffer, util::BoxService, Service, ServiceExt};
|
||||||
use tracing::{instrument, Span};
|
use tracing::{instrument, Span};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Block, Height},
|
block::{self, Height},
|
||||||
parameters::Network,
|
parameters::Network,
|
||||||
};
|
};
|
||||||
|
|
||||||
use zebra_state as zs;
|
use zebra_state as zs;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
block::BlockVerifier,
|
block::{BlockVerifier, Request, VerifyBlockError},
|
||||||
block::VerifyBlockError,
|
|
||||||
checkpoint::{CheckpointList, CheckpointVerifier, VerifyCheckpointError},
|
checkpoint::{CheckpointList, CheckpointVerifier, VerifyCheckpointError},
|
||||||
error::TransactionError,
|
error::TransactionError,
|
||||||
transaction, BoxError, Config,
|
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
|
where
|
||||||
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
||||||
S::Future: Send + 'static,
|
S::Future: Send + 'static,
|
||||||
|
|
@ -167,14 +165,29 @@ where
|
||||||
Poll::Ready(Ok(()))
|
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() {
|
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 => {
|
Some(height) if height <= self.max_checkpoint_height => {
|
||||||
self.checkpoint.call(block).map_err(Into::into).boxed()
|
self.checkpoint.call(block).map_err(Into::into).boxed()
|
||||||
}
|
}
|
||||||
// This also covers blocks with no height, which the block verifier
|
// This also covers blocks with no height, which the block verifier
|
||||||
// will reject immediately.
|
// 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,
|
mut state_service: S,
|
||||||
debug_skip_parameter_preload: bool,
|
debug_skip_parameter_preload: bool,
|
||||||
) -> (
|
) -> (
|
||||||
Buffer<BoxService<Arc<Block>, block::Hash, VerifyChainError>, Arc<Block>>,
|
Buffer<BoxService<Request, block::Hash, VerifyChainError>, Request>,
|
||||||
Buffer<
|
Buffer<
|
||||||
BoxService<transaction::Request, transaction::Response, TransactionError>,
|
BoxService<transaction::Request, transaction::Response, TransactionError>,
|
||||||
transaction::Request,
|
transaction::Request,
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ async fn verifiers_from_network(
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> (
|
) -> (
|
||||||
impl Service<
|
impl Service<
|
||||||
Arc<Block>,
|
Request,
|
||||||
Response = block::Hash,
|
Response = block::Hash,
|
||||||
Error = BoxError,
|
Error = BoxError,
|
||||||
Future = impl Future<Output = Result<block::Hash, BoxError>>,
|
Future = impl Future<Output = Result<block::Hash, BoxError>>,
|
||||||
|
|
@ -77,7 +77,7 @@ async fn verifiers_from_network(
|
||||||
}
|
}
|
||||||
|
|
||||||
static BLOCK_VERIFY_TRANSCRIPT_GENESIS: Lazy<
|
static BLOCK_VERIFY_TRANSCRIPT_GENESIS: Lazy<
|
||||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||||
> = Lazy::new(|| {
|
> = Lazy::new(|| {
|
||||||
let block: Arc<_> =
|
let block: Arc<_> =
|
||||||
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
|
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
|
||||||
|
|
@ -85,27 +85,29 @@ static BLOCK_VERIFY_TRANSCRIPT_GENESIS: Lazy<
|
||||||
.into();
|
.into();
|
||||||
let hash = Ok(block.hash());
|
let hash = Ok(block.hash());
|
||||||
|
|
||||||
vec![(block, hash)]
|
vec![(Request::Commit(block), hash)]
|
||||||
});
|
});
|
||||||
|
|
||||||
static BLOCK_VERIFY_TRANSCRIPT_GENESIS_FAIL: Lazy<
|
static BLOCK_VERIFY_TRANSCRIPT_GENESIS_FAIL: Lazy<
|
||||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||||
> = Lazy::new(|| {
|
> = Lazy::new(|| {
|
||||||
let block: Arc<_> =
|
let block: Arc<_> =
|
||||||
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
|
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
vec![(block, Err(ExpectedTranscriptError::Any))]
|
vec![(Request::Commit(block), Err(ExpectedTranscriptError::Any))]
|
||||||
});
|
});
|
||||||
|
|
||||||
static NO_COINBASE_TRANSCRIPT: Lazy<
|
static NO_COINBASE_TRANSCRIPT: Lazy<Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>> =
|
||||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
Lazy::new(|| {
|
||||||
> = Lazy::new(|| {
|
let block = block_no_transactions();
|
||||||
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<
|
static NO_COINBASE_STATE_TRANSCRIPT: Lazy<
|
||||||
Vec<(zs::Request, Result<zs::Response, ExpectedTranscriptError>)>,
|
Vec<(zs::Request, Result<zs::Response, ExpectedTranscriptError>)>,
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ pub use block::{
|
||||||
funding_streams::{funding_stream_address, funding_stream_values, new_coinbase_script},
|
funding_streams::{funding_stream_address, funding_stream_values, new_coinbase_script},
|
||||||
general::miner_subsidy,
|
general::miner_subsidy,
|
||||||
},
|
},
|
||||||
VerifyBlockError, MAX_BLOCK_SIGOPS,
|
Request, VerifyBlockError, MAX_BLOCK_SIGOPS,
|
||||||
};
|
};
|
||||||
pub use chain::VerifyChainError;
|
pub use chain::VerifyChainError;
|
||||||
pub use checkpoint::{
|
pub use checkpoint::{
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,8 @@ use crate::methods::{
|
||||||
DEFAULT_SOLUTION_RATE_WINDOW_SIZE, GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL,
|
DEFAULT_SOLUTION_RATE_WINDOW_SIZE, GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL,
|
||||||
},
|
},
|
||||||
get_block_template::{
|
get_block_template::{
|
||||||
check_block_template_parameters, check_miner_address, check_synced_to_tip,
|
check_miner_address, check_synced_to_tip, fetch_mempool_transactions,
|
||||||
fetch_mempool_transactions, fetch_state_tip_and_local_time,
|
fetch_state_tip_and_local_time, generate_coinbase_and_roots, validate_block_proposal,
|
||||||
generate_coinbase_and_roots,
|
|
||||||
},
|
},
|
||||||
types::{
|
types::{
|
||||||
get_block_template::GetBlockTemplate, get_mining_info, hex_data::HexData,
|
get_block_template::GetBlockTemplate, get_mining_info, hex_data::HexData,
|
||||||
|
|
@ -101,7 +100,7 @@ pub trait GetBlockTemplateRpc {
|
||||||
fn get_block_template(
|
fn get_block_template(
|
||||||
&self,
|
&self,
|
||||||
parameters: Option<get_block_template::JsonParameters>,
|
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.
|
/// Submits block to the node to be validated and committed.
|
||||||
/// Returns the [`submit_block::Response`] for the operation, as a JSON string.
|
/// Returns the [`submit_block::Response`] for the operation, as a JSON string.
|
||||||
|
|
@ -165,7 +164,7 @@ where
|
||||||
Response = zebra_state::ReadResponse,
|
Response = zebra_state::ReadResponse,
|
||||||
Error = zebra_state::BoxError,
|
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
|
+ Clone
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
|
|
@ -217,7 +216,7 @@ where
|
||||||
+ Sync
|
+ Sync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
Tip: ChainTip + Clone + Send + 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
|
+ Clone
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
|
|
@ -265,12 +264,12 @@ where
|
||||||
+ 'static,
|
+ 'static,
|
||||||
<State as Service<zebra_state::ReadRequest>>::Future: Send,
|
<State as Service<zebra_state::ReadRequest>>::Future: Send,
|
||||||
Tip: ChainTip + Clone + Send + 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
|
+ Clone
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
<ChainVerifier as Service<Arc<Block>>>::Future: Send,
|
<ChainVerifier as Service<zebra_consensus::Request>>::Future: Send,
|
||||||
SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
|
SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
fn get_block_count(&self) -> Result<u32> {
|
fn get_block_count(&self) -> Result<u32> {
|
||||||
|
|
@ -312,11 +311,11 @@ where
|
||||||
.boxed()
|
.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(
|
fn get_block_template(
|
||||||
&self,
|
&self,
|
||||||
parameters: Option<get_block_template::JsonParameters>,
|
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?
|
// 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.
|
// 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 sync_status = self.sync_status.clone();
|
||||||
let state = self.state.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.
|
// To implement long polling correctly, we split this RPC into multiple phases.
|
||||||
async move {
|
async move {
|
||||||
|
get_block_template::check_parameters(¶meters)?;
|
||||||
|
|
||||||
|
let client_long_poll_id = parameters
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|params| params.long_poll_id.clone());
|
||||||
|
|
||||||
// - One-off checks
|
// - One-off checks
|
||||||
|
|
||||||
// Check config and parameters.
|
// Check config and parameters.
|
||||||
// These checks always have the same result during long polling.
|
// These checks always have the same result during long polling.
|
||||||
let miner_address = check_miner_address(miner_address)?;
|
let miner_address = check_miner_address(miner_address)?;
|
||||||
|
|
||||||
let mut client_long_poll_id = None;
|
|
||||||
if let Some(parameters) = parameters {
|
|
||||||
check_block_template_parameters(¶meters)?;
|
|
||||||
|
|
||||||
client_long_poll_id = parameters.long_poll_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// - Checks and fetches that can change during long polling
|
// - Checks and fetches that can change during long polling
|
||||||
//
|
//
|
||||||
// Set up the loop.
|
// Set up the loop.
|
||||||
|
|
@ -506,7 +512,7 @@ where
|
||||||
?server_long_poll_id,
|
?server_long_poll_id,
|
||||||
?client_long_poll_id,
|
?client_long_poll_id,
|
||||||
"returning from long poll due to a state error.\
|
"returning from long poll due to a state error.\
|
||||||
Is Zebra shutting down?"
|
Is Zebra shutting down?"
|
||||||
);
|
);
|
||||||
|
|
||||||
return Err(Error {
|
return Err(Error {
|
||||||
|
|
@ -582,7 +588,7 @@ where
|
||||||
COINBASE_LIKE_ZCASHD,
|
COINBASE_LIKE_ZCASHD,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(response)
|
Ok(response.into())
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
@ -608,7 +614,7 @@ where
|
||||||
message: error.to_string(),
|
message: error.to_string(),
|
||||||
data: None,
|
data: None,
|
||||||
})?
|
})?
|
||||||
.call(Arc::new(block))
|
.call(zebra_consensus::Request::Commit(Arc::new(block)))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let chain_error = match chain_verifier_response {
|
let chain_error = match chain_verifier_response {
|
||||||
|
|
|
||||||
|
|
@ -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";
|
pub const GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD: &str = "00000000ffffffff";
|
||||||
|
|
||||||
/// A hardcoded list of fields that the miner can change from the block template.
|
/// 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] = &[
|
pub const GET_BLOCK_TEMPLATE_MUTABLE_FIELD: &[&str] = &[
|
||||||
// Standard mutations, copied from zcashd
|
// Standard mutations, copied from zcashd
|
||||||
"time",
|
"time",
|
||||||
|
|
@ -26,8 +28,9 @@ pub const GET_BLOCK_TEMPLATE_MUTABLE_FIELD: &[&str] = &[
|
||||||
];
|
];
|
||||||
|
|
||||||
/// A hardcoded list of Zebra's getblocktemplate RPC capabilities.
|
/// 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.
|
/// 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
|
/// > 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,
|
/// > according to its clock. This is not strictly a consensus rule because it is nondeterministic,
|
||||||
/// > and clock time varies between nodes.
|
/// > 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;
|
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.
|
/// The RPC error code used by `zcashd` for when it's still downloading initial blocks.
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,14 @@ use tower::{Service, ServiceExt};
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::{self, Amount, NegativeOrZero, NonNegative},
|
amount::{self, Amount, NegativeOrZero, NonNegative},
|
||||||
block::{
|
block::{
|
||||||
|
self,
|
||||||
merkle::{self, AuthDataRoot},
|
merkle::{self, AuthDataRoot},
|
||||||
ChainHistoryBlockTxAuthCommitmentHash, Height,
|
ChainHistoryBlockTxAuthCommitmentHash, Height,
|
||||||
},
|
},
|
||||||
chain_sync_status::ChainSyncStatus,
|
chain_sync_status::ChainSyncStatus,
|
||||||
chain_tip::ChainTip,
|
chain_tip::ChainTip,
|
||||||
parameters::Network,
|
parameters::Network,
|
||||||
|
serialization::ZcashDeserializeInto,
|
||||||
transaction::{Transaction, UnminedTx, VerifiedUnminedTx},
|
transaction::{Transaction, UnminedTx, VerifiedUnminedTx},
|
||||||
transparent,
|
transparent,
|
||||||
};
|
};
|
||||||
|
|
@ -25,26 +27,57 @@ use zebra_state::GetBlockTemplateChainInfo;
|
||||||
|
|
||||||
use crate::methods::get_block_template_rpcs::{
|
use crate::methods::get_block_template_rpcs::{
|
||||||
constants::{MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP, NOT_SYNCED_ERROR_CODE},
|
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::*;
|
pub use crate::methods::get_block_template_rpcs::types::get_block_template::*;
|
||||||
|
|
||||||
// - Parameter checks
|
// - Parameter checks
|
||||||
|
|
||||||
/// Returns an error if the get block template RPC `parameters` are invalid.
|
/// Checks that `data` is omitted in `Template` mode or provided in `Proposal` mode,
|
||||||
pub fn check_block_template_parameters(
|
///
|
||||||
parameters: &get_block_template::JsonParameters,
|
/// Returns an error if there's a mismatch between the mode and whether `data` is provided.
|
||||||
) -> Result<()> {
|
pub fn check_parameters(parameters: &Option<JsonParameters>) -> Result<()> {
|
||||||
if parameters.data.is_some() || parameters.mode == GetBlockTemplateRequestMode::Proposal {
|
let Some(parameters) = parameters else {
|
||||||
return Err(Error {
|
return Ok(())
|
||||||
code: ErrorCode::InvalidParams,
|
};
|
||||||
message: "\"proposal\" mode is currently unsupported by Zebra".to_string(),
|
|
||||||
data: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
/// 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
|
// - State and syncer checks
|
||||||
|
|
||||||
/// Returns an error if Zebra is not synced to the consensus chain tip.
|
/// Returns an error if Zebra is not synced to the consensus chain tip.
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ pub mod parameters;
|
||||||
|
|
||||||
pub use 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)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct GetBlockTemplate {
|
pub struct GetBlockTemplate {
|
||||||
/// The getblocktemplate RPC capabilities supported by Zebra.
|
/// The getblocktemplate RPC capabilities supported by Zebra.
|
||||||
|
|
@ -162,6 +162,14 @@ pub struct GetBlockTemplate {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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.
|
/// 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.
|
/// 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");
|
.expect("state always returns a valid difficulty value");
|
||||||
|
|
||||||
// Convert default values
|
// Convert default values
|
||||||
let capabilities: Vec<String> = GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD
|
let capabilities: Vec<String> = Self::capabilities();
|
||||||
.iter()
|
|
||||||
.map(ToString::to_string)
|
|
||||||
.collect();
|
|
||||||
let mutable: Vec<String> = GET_BLOCK_TEMPLATE_MUTABLE_FIELD
|
let mutable: Vec<String> = GET_BLOCK_TEMPLATE_MUTABLE_FIELD
|
||||||
.iter()
|
.iter()
|
||||||
.map(ToString::to_string)
|
.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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
use crate::methods::get_block_template_rpcs::types::{hex_data::HexData, long_poll::LongPollId};
|
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.
|
/// 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)]
|
#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum GetBlockTemplateRequestMode {
|
pub enum GetBlockTemplateRequestMode {
|
||||||
|
|
@ -11,7 +10,6 @@ pub enum GetBlockTemplateRequestMode {
|
||||||
Template,
|
Template,
|
||||||
|
|
||||||
/// Indicates a request to validate block data.
|
/// Indicates a request to validate block data.
|
||||||
/// Currently unsupported and will return an error.
|
|
||||||
Proposal,
|
Proposal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,19 +61,17 @@ pub enum GetBlockTemplateCapability {
|
||||||
/// All other fields are optional.
|
/// All other fields are optional.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, Default)]
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, Default)]
|
||||||
pub struct JsonParameters {
|
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
|
/// 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
|
/// validate block data, checking against all of the server's usual acceptance rules
|
||||||
/// (excluding the check for a valid proof-of-work).
|
/// (excluding the check for a valid proof-of-work).
|
||||||
// TODO: Support `proposal` mode.
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub mode: GetBlockTemplateRequestMode,
|
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
|
/// 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>,
|
pub data: Option<HexData>,
|
||||||
|
|
||||||
/// A list of client-side supported capability features
|
/// 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.
|
/// In Zebra, the ID represents the chain tip, max time, and mempool contents.
|
||||||
#[serde(rename = "longpollid")]
|
#[serde(rename = "longpollid")]
|
||||||
pub long_poll_id: Option<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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,16 @@ use zebra_node_services::mempool;
|
||||||
|
|
||||||
use zebra_state::{GetBlockTemplateChainInfo, ReadRequest, ReadResponse};
|
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::{
|
use crate::methods::{
|
||||||
get_block_template_rpcs::{
|
get_block_template_rpcs::{
|
||||||
self,
|
self,
|
||||||
types::{
|
types::{
|
||||||
get_block_template::{self, GetBlockTemplate},
|
get_block_template::{self, GetBlockTemplateRequestMode},
|
||||||
get_mining_info,
|
get_mining_info,
|
||||||
hex_data::HexData,
|
hex_data::HexData,
|
||||||
long_poll::{LongPollId, LONG_POLL_ID_LENGTH},
|
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
|
// create a new rpc instance with new state and mock
|
||||||
let get_block_template_rpc = GetBlockTemplateRpcImpl::new(
|
let get_block_template_rpc = GetBlockTemplateRpcImpl::new(
|
||||||
network,
|
network,
|
||||||
mining_config,
|
mining_config.clone(),
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
new_read_state.clone(),
|
new_read_state.clone(),
|
||||||
mock_chain_tip,
|
mock_chain_tip.clone(),
|
||||||
chain_verifier,
|
chain_verifier,
|
||||||
mock_sync_status,
|
mock_sync_status.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Basic variant (default mode and no extra features)
|
// Basic variant (default mode and no extra features)
|
||||||
|
|
@ -195,10 +198,12 @@ pub async fn test_responses<State, ReadState>(
|
||||||
.await
|
.await
|
||||||
.respond(mempool::Response::FullTransactions(vec![]));
|
.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
|
.await
|
||||||
.expect("unexpected panic in getblocktemplate RPC task")
|
.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
|
let coinbase_tx: Transaction = get_block_template
|
||||||
.coinbase_txn
|
.coinbase_txn
|
||||||
|
|
@ -207,7 +212,12 @@ pub async fn test_responses<State, ReadState>(
|
||||||
.zcash_deserialize_into()
|
.zcash_deserialize_into()
|
||||||
.expect("coinbase bytes are valid");
|
.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
|
// long polling feature with submit old field
|
||||||
|
|
||||||
|
|
@ -250,10 +260,12 @@ pub async fn test_responses<State, ReadState>(
|
||||||
.await
|
.await
|
||||||
.respond(mempool::Response::FullTransactions(vec![]));
|
.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
|
.await
|
||||||
.expect("unexpected panic in getblocktemplate RPC task")
|
.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
|
let coinbase_tx: Transaction = get_block_template
|
||||||
.coinbase_txn
|
.coinbase_txn
|
||||||
|
|
@ -262,7 +274,62 @@ pub async fn test_responses<State, ReadState>(
|
||||||
.zcash_deserialize_into()
|
.zcash_deserialize_into()
|
||||||
.expect("coinbase bytes are valid");
|
.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`
|
// `submitblock`
|
||||||
|
|
||||||
|
|
@ -287,19 +354,22 @@ fn snapshot_rpc_getblockhash(block_hash: GetBlockHash, settings: &insta::Setting
|
||||||
/// Snapshot `getblocktemplate` response, using `cargo insta` and JSON serialization.
|
/// Snapshot `getblocktemplate` response, using `cargo insta` and JSON serialization.
|
||||||
fn snapshot_rpc_getblocktemplate(
|
fn snapshot_rpc_getblocktemplate(
|
||||||
variant: &'static str,
|
variant: &'static str,
|
||||||
block_template: GetBlockTemplate,
|
block_template: get_block_template::Response,
|
||||||
coinbase_tx: Transaction,
|
coinbase_tx: Option<Transaction>,
|
||||||
settings: &insta::Settings,
|
settings: &insta::Settings,
|
||||||
) {
|
) {
|
||||||
settings.bind(|| {
|
settings.bind(|| {
|
||||||
insta::assert_json_snapshot!(format!("get_block_template_{variant}"), block_template)
|
insta::assert_json_snapshot!(format!("get_block_template_{variant}"), block_template)
|
||||||
});
|
});
|
||||||
settings.bind(|| {
|
|
||||||
insta::assert_ron_snapshot!(
|
if let Some(coinbase_tx) = coinbase_tx {
|
||||||
format!("get_block_template_{variant}.coinbase_tx"),
|
settings.bind(|| {
|
||||||
coinbase_tx
|
insta::assert_ron_snapshot!(
|
||||||
)
|
format!("get_block_template_{variant}.coinbase_tx"),
|
||||||
});
|
coinbase_tx
|
||||||
|
)
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Snapshot `submitblock` response, using `cargo insta` and JSON serialization.
|
/// Snapshot `submitblock` response, using `cargo insta` and JSON serialization.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||||
expression: block_template
|
expression: block_template
|
||||||
---
|
---
|
||||||
{
|
{
|
||||||
"capabilities": [],
|
"capabilities": [
|
||||||
|
"proposal"
|
||||||
|
],
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
|
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
|
||||||
"blockcommitmentshash": "02990723c6b62a724651322d141b4a72a4ffd66518167d809badbd5117d5518a",
|
"blockcommitmentshash": "02990723c6b62a724651322d141b4a72a4ffd66518167d809badbd5117d5518a",
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||||
expression: block_template
|
expression: block_template
|
||||||
---
|
---
|
||||||
{
|
{
|
||||||
"capabilities": [],
|
"capabilities": [
|
||||||
|
"proposal"
|
||||||
|
],
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
|
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
|
||||||
"blockcommitmentshash": "3b25791957f9383b6ce851d728a78309664d5d7a82ca87b6a9125a2f2c529792",
|
"blockcommitmentshash": "3b25791957f9383b6ce851d728a78309664d5d7a82ca87b6a9125a2f2c529792",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||||
|
expression: block_template
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"reject_reason": "rejected",
|
||||||
|
"capabilities": [
|
||||||
|
"proposal"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||||
|
expression: block_template
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"reject_reason": "rejected",
|
||||||
|
"capabilities": [
|
||||||
|
"proposal"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||||
expression: block_template
|
expression: block_template
|
||||||
---
|
---
|
||||||
{
|
{
|
||||||
"capabilities": [],
|
"capabilities": [
|
||||||
|
"proposal"
|
||||||
|
],
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
|
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
|
||||||
"blockcommitmentshash": "02990723c6b62a724651322d141b4a72a4ffd66518167d809badbd5117d5518a",
|
"blockcommitmentshash": "02990723c6b62a724651322d141b4a72a4ffd66518167d809badbd5117d5518a",
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||||
expression: block_template
|
expression: block_template
|
||||||
---
|
---
|
||||||
{
|
{
|
||||||
"capabilities": [],
|
"capabilities": [
|
||||||
|
"proposal"
|
||||||
|
],
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
|
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
|
||||||
"blockcommitmentshash": "3b25791957f9383b6ce851d728a78309664d5d7a82ca87b6a9125a2f2c529792",
|
"blockcommitmentshash": "3b25791957f9383b6ce851d728a78309664d5d7a82ca87b6a9125a2f2c529792",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||||
|
expression: block_template
|
||||||
|
---
|
||||||
|
null
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||||
|
expression: block_template
|
||||||
|
---
|
||||||
|
null
|
||||||
|
|
@ -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_fut = get_block_template_rpc.get_block_template(None);
|
||||||
|
|
||||||
let (get_block_template, ..) = tokio::join!(
|
let (get_block_template, ..) = tokio::join!(
|
||||||
get_block_template_fut,
|
get_block_template_fut,
|
||||||
mock_mempool_request_handler,
|
mock_mempool_request_handler,
|
||||||
mock_read_state_request_handler,
|
mock_read_state_request_handler,
|
||||||
);
|
);
|
||||||
|
|
||||||
let get_block_template =
|
let get_block_template::Response::TemplateMode(get_block_template) = get_block_template
|
||||||
get_block_template.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")
|
||||||
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_block_template.capabilities,
|
get_block_template.capabilities,
|
||||||
|
|
@ -1099,7 +1100,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
}))
|
||||||
.await
|
.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);
|
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()
|
..Default::default()
|
||||||
}))
|
}))
|
||||||
.await
|
.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);
|
assert_eq!(get_block_template_sync_error.code, ErrorCode::InvalidParams);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
//! See the full list of
|
//! 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)
|
//! [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_core::{Compatibility, MetaIoHandler};
|
||||||
use jsonrpc_http_server::{CloseHandle, ServerBuilder};
|
use jsonrpc_http_server::{CloseHandle, ServerBuilder};
|
||||||
|
|
@ -17,10 +17,7 @@ use tower::{buffer::Buffer, Service};
|
||||||
use tracing::{Instrument, *};
|
use tracing::{Instrument, *};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Block},
|
block, chain_sync_status::ChainSyncStatus, chain_tip::ChainTip, parameters::Network,
|
||||||
chain_sync_status::ChainSyncStatus,
|
|
||||||
chain_tip::ChainTip,
|
|
||||||
parameters::Network,
|
|
||||||
};
|
};
|
||||||
use zebra_node_services::mempool;
|
use zebra_node_services::mempool;
|
||||||
|
|
||||||
|
|
@ -107,12 +104,15 @@ impl RpcServer {
|
||||||
+ 'static,
|
+ 'static,
|
||||||
State::Future: Send,
|
State::Future: Send,
|
||||||
Tip: ChainTip + Clone + Send + Sync + 'static,
|
Tip: ChainTip + Clone + Send + Sync + 'static,
|
||||||
ChainVerifier: Service<Arc<Block>, Response = block::Hash, Error = zebra_consensus::BoxError>
|
ChainVerifier: Service<
|
||||||
+ Clone
|
zebra_consensus::Request,
|
||||||
|
Response = block::Hash,
|
||||||
|
Error = zebra_consensus::BoxError,
|
||||||
|
> + Clone
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
<ChainVerifier as Service<Arc<Block>>>::Future: Send,
|
<ChainVerifier as Service<zebra_consensus::Request>>::Future: Send,
|
||||||
SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
|
SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
if let Some(listen_addr) = config.listen_addr {
|
if let Some(listen_addr) = config.listen_addr {
|
||||||
|
|
|
||||||
|
|
@ -558,6 +558,13 @@ pub enum Request {
|
||||||
///
|
///
|
||||||
/// Returns [`Response::ValidBestChainTipNullifiersAndAnchors`]
|
/// Returns [`Response::ValidBestChainTipNullifiersAndAnchors`]
|
||||||
CheckBestChainTipNullifiersAndAnchors(UnminedTx),
|
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 {
|
impl Request {
|
||||||
|
|
@ -577,6 +584,8 @@ impl Request {
|
||||||
Request::CheckBestChainTipNullifiersAndAnchors(_) => {
|
Request::CheckBestChainTipNullifiersAndAnchors(_) => {
|
||||||
"best_chain_tip_nullifiers_anchors"
|
"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
|
/// Optionally estimate the network speed at the time when a certain block was found
|
||||||
height: Option<block::Height>,
|
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 {
|
impl ReadRequest {
|
||||||
|
|
@ -819,6 +838,8 @@ impl ReadRequest {
|
||||||
ReadRequest::ChainInfo => "chain_info",
|
ReadRequest::ChainInfo => "chain_info",
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
ReadRequest::SolutionRate { .. } => "solution_rate",
|
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")
|
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. \
|
Request::AwaitUtxo(_) => Err("ReadService does not track pending UTXOs. \
|
||||||
Manually convert the request to ReadRequest::AnyChainUtxo, \
|
Manually convert the request to ReadRequest::AnyChainUtxo, \
|
||||||
and handle pending UTXOs"),
|
and handle pending UTXOs"),
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@ pub enum Response {
|
||||||
///
|
///
|
||||||
/// Does not check transparent UTXO inputs
|
/// Does not check transparent UTXO inputs
|
||||||
ValidBestChainTipNullifiersAndAnchors,
|
ValidBestChainTipNullifiersAndAnchors,
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
/// Response to [`Request::CheckBlockProposalValidity`](crate::Request::CheckBlockProposalValidity)
|
||||||
|
ValidBlockProposal,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
@ -137,6 +141,10 @@ pub enum ReadResponse {
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
/// Response to [`ReadRequest::SolutionRate`](crate::ReadRequest::SolutionRate)
|
/// Response to [`ReadRequest::SolutionRate`](crate::ReadRequest::SolutionRate)
|
||||||
SolutionRate(Option<u128>),
|
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.
|
/// 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")
|
Err("there is no corresponding Response for this ReadResponse")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
ReadResponse::ValidBlockProposal => Ok(Response::ValidBlockProposal),
|
||||||
|
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
ReadResponse::BlockHash(_) => {
|
ReadResponse::BlockHash(_) => {
|
||||||
Err("there is no corresponding Response for this ReadResponse")
|
Err("there is no corresponding Response for this ReadResponse")
|
||||||
|
|
|
||||||
|
|
@ -1047,6 +1047,24 @@ impl Service<Request> for StateService {
|
||||||
}
|
}
|
||||||
.boxed()
|
.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(()))
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(name = "read_state", skip(self))]
|
#[instrument(name = "read_state", skip(self, req))]
|
||||||
fn call(&mut self, req: ReadRequest) -> Self::Future {
|
fn call(&mut self, req: ReadRequest) -> Self::Future {
|
||||||
req.count_metric();
|
req.count_metric();
|
||||||
|
|
||||||
|
|
@ -1688,6 +1706,58 @@ impl Service<ReadRequest> for ReadStateService {
|
||||||
.map(|join_result| join_result.expect("panic in ReadRequest::ChainInfo"))
|
.map(|join_result| join_result.expect("panic in ReadRequest::ChainInfo"))
|
||||||
.boxed()
|
.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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use zebra_chain::{
|
||||||
use crate::{
|
use crate::{
|
||||||
service::{
|
service::{
|
||||||
block_iter::any_ancestor_blocks, check::difficulty::POW_ADJUSTMENT_BLOCK_SPAN,
|
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,
|
BoxError, PreparedBlock, ValidateContextError,
|
||||||
};
|
};
|
||||||
|
|
@ -365,25 +365,25 @@ where
|
||||||
///
|
///
|
||||||
/// Additional contextual validity checks are performed by the non-finalized [`Chain`].
|
/// Additional contextual validity checks are performed by the non-finalized [`Chain`].
|
||||||
pub(crate) fn initial_contextual_validity(
|
pub(crate) fn initial_contextual_validity(
|
||||||
finalized_state: &FinalizedState,
|
finalized_state: &ZebraDb,
|
||||||
non_finalized_state: &NonFinalizedState,
|
non_finalized_state: &NonFinalizedState,
|
||||||
prepared: &PreparedBlock,
|
prepared: &PreparedBlock,
|
||||||
) -> Result<(), ValidateContextError> {
|
) -> Result<(), ValidateContextError> {
|
||||||
let relevant_chain = any_ancestor_blocks(
|
let relevant_chain = any_ancestor_blocks(
|
||||||
non_finalized_state,
|
non_finalized_state,
|
||||||
&finalized_state.db,
|
finalized_state,
|
||||||
prepared.block.header.previous_block_hash,
|
prepared.block.header.previous_block_hash,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Security: check proof of work before any other checks
|
// Security: check proof of work before any other checks
|
||||||
check::block_is_valid_for_recent_chain(
|
check::block_is_valid_for_recent_chain(
|
||||||
prepared,
|
prepared,
|
||||||
finalized_state.network(),
|
non_finalized_state.network,
|
||||||
finalized_state.db.finalized_tip_height(),
|
finalized_state.finalized_tip_height(),
|
||||||
relevant_chain,
|
relevant_chain,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
check::nullifier::no_duplicates_in_finalized_chain(prepared, &finalized_state.db)?;
|
check::nullifier::no_duplicates_in_finalized_chain(prepared, finalized_state)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,12 @@ fn check_sprout_anchors() {
|
||||||
|
|
||||||
// Validate and commit [`block_1`]. This will add an anchor referencing the
|
// Validate and commit [`block_1`]. This will add an anchor referencing the
|
||||||
// empty note commitment tree to the state.
|
// empty note commitment tree to the state.
|
||||||
assert!(
|
assert!(validate_and_commit_non_finalized(
|
||||||
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block_1)
|
&finalized_state.db,
|
||||||
.is_ok()
|
&mut non_finalized_state,
|
||||||
);
|
block_1
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
let check_unmined_tx_anchors_result = unmined_txs.iter().try_for_each(|unmined_tx| {
|
let check_unmined_tx_anchors_result = unmined_txs.iter().try_for_each(|unmined_tx| {
|
||||||
tx_anchors_refer_to_final_treestates(
|
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.
|
// Validate and commit [`block_2`]. This will also check the anchors.
|
||||||
assert_eq!(
|
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(())
|
Ok(())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -288,10 +290,12 @@ fn check_sapling_anchors() {
|
||||||
Err(ValidateContextError::UnknownSaplingAnchor { .. })
|
Err(ValidateContextError::UnknownSaplingAnchor { .. })
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(
|
assert!(validate_and_commit_non_finalized(
|
||||||
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block1)
|
&finalized_state.db,
|
||||||
.is_ok()
|
&mut non_finalized_state,
|
||||||
);
|
block1
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
let check_unmined_tx_anchors_result = unmined_txs.iter().try_for_each(|unmined_tx| {
|
let check_unmined_tx_anchors_result = unmined_txs.iter().try_for_each(|unmined_tx| {
|
||||||
tx_anchors_refer_to_final_treestates(
|
tx_anchors_refer_to_final_treestates(
|
||||||
|
|
@ -304,7 +308,7 @@ fn check_sapling_anchors() {
|
||||||
assert!(check_unmined_tx_anchors_result.is_ok());
|
assert!(check_unmined_tx_anchors_result.is_ok());
|
||||||
|
|
||||||
assert_eq!(
|
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(())
|
Ok(())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ proptest! {
|
||||||
} else {
|
} else {
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state,
|
&mut non_finalized_state,
|
||||||
block1.clone()
|
block1.clone()
|
||||||
);
|
);
|
||||||
|
|
@ -156,7 +156,7 @@ proptest! {
|
||||||
|
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state,
|
&mut non_finalized_state,
|
||||||
block1
|
block1
|
||||||
);
|
);
|
||||||
|
|
@ -217,7 +217,7 @@ proptest! {
|
||||||
|
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state,
|
&mut non_finalized_state,
|
||||||
block1
|
block1
|
||||||
);
|
);
|
||||||
|
|
@ -278,8 +278,10 @@ proptest! {
|
||||||
|
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block1);
|
&mut non_finalized_state,
|
||||||
|
block1
|
||||||
|
);
|
||||||
|
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
commit_result,
|
commit_result,
|
||||||
|
|
@ -364,7 +366,7 @@ proptest! {
|
||||||
} else {
|
} else {
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state,
|
&mut non_finalized_state,
|
||||||
block1.clone()
|
block1.clone()
|
||||||
);
|
);
|
||||||
|
|
@ -383,7 +385,7 @@ proptest! {
|
||||||
|
|
||||||
let block2 = Arc::new(block2).prepare();
|
let block2 = Arc::new(block2).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state,
|
&mut non_finalized_state,
|
||||||
block2
|
block2
|
||||||
);
|
);
|
||||||
|
|
@ -459,8 +461,10 @@ proptest! {
|
||||||
} else {
|
} else {
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block1.clone());
|
&mut non_finalized_state,
|
||||||
|
block1.clone()
|
||||||
|
);
|
||||||
|
|
||||||
prop_assert_eq!(commit_result, Ok(()));
|
prop_assert_eq!(commit_result, Ok(()));
|
||||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
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 block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block1);
|
&mut non_finalized_state,
|
||||||
|
block1
|
||||||
|
);
|
||||||
|
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
commit_result,
|
commit_result,
|
||||||
|
|
@ -560,7 +566,7 @@ proptest! {
|
||||||
|
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state,
|
&mut non_finalized_state,
|
||||||
block1
|
block1
|
||||||
);
|
);
|
||||||
|
|
@ -639,8 +645,10 @@ proptest! {
|
||||||
} else {
|
} else {
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block1.clone());
|
&mut non_finalized_state,
|
||||||
|
block1.clone()
|
||||||
|
);
|
||||||
|
|
||||||
prop_assert_eq!(commit_result, Ok(()));
|
prop_assert_eq!(commit_result, Ok(()));
|
||||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
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 block2 = Arc::new(block2).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block2);
|
&mut non_finalized_state,
|
||||||
|
block2
|
||||||
|
);
|
||||||
|
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
commit_result,
|
commit_result,
|
||||||
|
|
@ -731,8 +741,10 @@ proptest! {
|
||||||
} else {
|
} else {
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block1.clone());
|
&mut non_finalized_state,
|
||||||
|
block1.clone()
|
||||||
|
);
|
||||||
|
|
||||||
prop_assert_eq!(commit_result, Ok(()));
|
prop_assert_eq!(commit_result, Ok(()));
|
||||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
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 block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block1);
|
&mut non_finalized_state,
|
||||||
|
block1
|
||||||
|
);
|
||||||
|
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
commit_result,
|
commit_result,
|
||||||
|
|
@ -837,8 +851,10 @@ proptest! {
|
||||||
|
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block1);
|
&mut non_finalized_state,
|
||||||
|
block1
|
||||||
|
);
|
||||||
|
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
commit_result,
|
commit_result,
|
||||||
|
|
@ -918,8 +934,10 @@ proptest! {
|
||||||
} else {
|
} else {
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block1.clone());
|
&mut non_finalized_state,
|
||||||
|
block1.clone()
|
||||||
|
);
|
||||||
|
|
||||||
prop_assert_eq!(commit_result, Ok(()));
|
prop_assert_eq!(commit_result, Ok(()));
|
||||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
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 block2 = Arc::new(block2).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block2);
|
&mut non_finalized_state,
|
||||||
|
block2
|
||||||
|
);
|
||||||
|
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
commit_result,
|
commit_result,
|
||||||
|
|
|
||||||
|
|
@ -194,8 +194,10 @@ proptest! {
|
||||||
} else {
|
} else {
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block1.clone());
|
&mut non_finalized_state,
|
||||||
|
block1.clone()
|
||||||
|
);
|
||||||
|
|
||||||
// the block was committed
|
// the block was committed
|
||||||
prop_assert_eq!(commit_result, Ok(()));
|
prop_assert_eq!(commit_result, Ok(()));
|
||||||
|
|
@ -279,8 +281,10 @@ proptest! {
|
||||||
} else {
|
} else {
|
||||||
let block2 = Arc::new(block2).prepare();
|
let block2 = Arc::new(block2).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block2.clone());
|
&mut non_finalized_state,
|
||||||
|
block2.clone()
|
||||||
|
);
|
||||||
|
|
||||||
// the block was committed
|
// the block was committed
|
||||||
prop_assert_eq!(commit_result, Ok(()));
|
prop_assert_eq!(commit_result, Ok(()));
|
||||||
|
|
@ -357,8 +361,10 @@ proptest! {
|
||||||
|
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block1);
|
&mut non_finalized_state,
|
||||||
|
block1
|
||||||
|
);
|
||||||
|
|
||||||
// the block was rejected
|
// the block was rejected
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
|
|
@ -419,8 +425,10 @@ proptest! {
|
||||||
|
|
||||||
let block2 = Arc::new(block2).prepare();
|
let block2 = Arc::new(block2).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block2);
|
&mut non_finalized_state,
|
||||||
|
block2
|
||||||
|
);
|
||||||
|
|
||||||
// the block was rejected
|
// the block was rejected
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
|
|
@ -503,8 +511,10 @@ proptest! {
|
||||||
|
|
||||||
let block2 = Arc::new(block2).prepare();
|
let block2 = Arc::new(block2).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block2);
|
&mut non_finalized_state,
|
||||||
|
block2
|
||||||
|
);
|
||||||
|
|
||||||
// the block was rejected
|
// the block was rejected
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
|
|
@ -615,8 +625,10 @@ proptest! {
|
||||||
} else {
|
} else {
|
||||||
let block2 = block2.clone().prepare();
|
let block2 = block2.clone().prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block2.clone());
|
&mut non_finalized_state,
|
||||||
|
block2.clone()
|
||||||
|
);
|
||||||
|
|
||||||
// the block was committed
|
// the block was committed
|
||||||
prop_assert_eq!(commit_result, Ok(()));
|
prop_assert_eq!(commit_result, Ok(()));
|
||||||
|
|
@ -651,8 +663,10 @@ proptest! {
|
||||||
|
|
||||||
let block3 = Arc::new(block3).prepare();
|
let block3 = Arc::new(block3).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block3);
|
&mut non_finalized_state,
|
||||||
|
block3
|
||||||
|
);
|
||||||
|
|
||||||
// the block was rejected
|
// the block was rejected
|
||||||
if use_finalized_state_spend {
|
if use_finalized_state_spend {
|
||||||
|
|
@ -725,8 +739,10 @@ proptest! {
|
||||||
|
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block1);
|
&mut non_finalized_state,
|
||||||
|
block1
|
||||||
|
);
|
||||||
|
|
||||||
// the block was rejected
|
// the block was rejected
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
|
|
@ -790,8 +806,10 @@ proptest! {
|
||||||
|
|
||||||
let block1 = Arc::new(block1).prepare();
|
let block1 = Arc::new(block1).prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state, block1);
|
&mut non_finalized_state,
|
||||||
|
block1
|
||||||
|
);
|
||||||
|
|
||||||
// the block was rejected
|
// the block was rejected
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
|
|
@ -885,7 +903,7 @@ fn new_state_with_mainnet_transparent_data(
|
||||||
} else {
|
} else {
|
||||||
let block1 = block1.clone().prepare();
|
let block1 = block1.clone().prepare();
|
||||||
let commit_result = validate_and_commit_non_finalized(
|
let commit_result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state,
|
&mut non_finalized_state,
|
||||||
block1.clone(),
|
block1.clone(),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,14 @@ pub struct NonFinalizedState {
|
||||||
pub chain_set: BTreeSet<Arc<Chain>>,
|
pub chain_set: BTreeSet<Arc<Chain>>,
|
||||||
|
|
||||||
/// The configured Zcash network.
|
/// The configured Zcash network.
|
||||||
//
|
|
||||||
// Note: this field is currently unused, but it's useful for debugging.
|
|
||||||
pub network: Network,
|
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 {
|
impl NonFinalizedState {
|
||||||
|
|
@ -55,6 +60,8 @@ impl NonFinalizedState {
|
||||||
NonFinalizedState {
|
NonFinalizedState {
|
||||||
chain_set: Default::default(),
|
chain_set: Default::default(),
|
||||||
network,
|
network,
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
should_count_metrics: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -494,6 +501,11 @@ impl NonFinalizedState {
|
||||||
|
|
||||||
/// Update the metrics after `block` is committed
|
/// Update the metrics after `block` is committed
|
||||||
fn update_metrics_for_committed_block(&self, height: block::Height, hash: block::Hash) {
|
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::counter!("state.memory.committed.block.count", 1);
|
||||||
metrics::gauge!("state.memory.committed.block.height", height.0 as f64);
|
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
|
/// Update the metrics after `self.chain_set` is modified
|
||||||
fn update_metrics_for_chains(&self) {
|
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.chain.count", self.chain_set.len() as f64);
|
||||||
metrics::gauge!(
|
metrics::gauge!(
|
||||||
"state.memory.best.chain.length",
|
"state.memory.best.chain.length",
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
constants::MAX_BLOCK_REORG_HEIGHT,
|
constants::MAX_BLOCK_REORG_HEIGHT,
|
||||||
service::{
|
service::{
|
||||||
check,
|
check,
|
||||||
finalized_state::FinalizedState,
|
finalized_state::{FinalizedState, ZebraDb},
|
||||||
non_finalized_state::NonFinalizedState,
|
non_finalized_state::NonFinalizedState,
|
||||||
queued_blocks::{QueuedFinalized, QueuedNonFinalized},
|
queued_blocks::{QueuedFinalized, QueuedNonFinalized},
|
||||||
BoxError, ChainTipBlock, ChainTipSender, CloneError,
|
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.
|
/// non-finalized state if it is contextually valid.
|
||||||
#[tracing::instrument(level = "debug", skip(prepared), fields(height = ?prepared.height, hash = %prepared.hash))]
|
#[tracing::instrument(level = "debug", skip(prepared), fields(height = ?prepared.height, hash = %prepared.hash))]
|
||||||
pub(crate) fn validate_and_commit_non_finalized(
|
pub(crate) fn validate_and_commit_non_finalized(
|
||||||
finalized_state: &FinalizedState,
|
finalized_state: &ZebraDb,
|
||||||
non_finalized_state: &mut NonFinalizedState,
|
non_finalized_state: &mut NonFinalizedState,
|
||||||
prepared: PreparedBlock,
|
prepared: PreparedBlock,
|
||||||
) -> Result<(), CommitBlockError> {
|
) -> Result<(), CommitBlockError> {
|
||||||
check::initial_contextual_validity(finalized_state, non_finalized_state, &prepared)?;
|
check::initial_contextual_validity(finalized_state, non_finalized_state, &prepared)?;
|
||||||
let parent_hash = prepared.block.header.previous_block_hash;
|
let parent_hash = prepared.block.header.previous_block_hash;
|
||||||
|
|
||||||
if finalized_state.db.finalized_tip_hash() == parent_hash {
|
if finalized_state.finalized_tip_hash() == parent_hash {
|
||||||
non_finalized_state.commit_new_chain(prepared, &finalized_state.db)?;
|
non_finalized_state.commit_new_chain(prepared, finalized_state)?;
|
||||||
} else {
|
} else {
|
||||||
non_finalized_state.commit_block(prepared, &finalized_state.db)?;
|
non_finalized_state.commit_block(prepared, finalized_state)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -205,7 +205,7 @@ pub fn write_blocks_from_channels(
|
||||||
} else {
|
} else {
|
||||||
tracing::trace!(?child_hash, "validating queued child");
|
tracing::trace!(?child_hash, "validating queued child");
|
||||||
result = validate_and_commit_non_finalized(
|
result = validate_and_commit_non_finalized(
|
||||||
&finalized_state,
|
&finalized_state.db,
|
||||||
&mut non_finalized_state,
|
&mut non_finalized_state,
|
||||||
queued_child,
|
queued_child,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,7 @@ use tower::{buffer::Buffer, timeout::Timeout, util::BoxService, Service, Service
|
||||||
use zebra_network as zn;
|
use zebra_network as zn;
|
||||||
use zebra_state as zs;
|
use zebra_state as zs;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{block, transaction::UnminedTxId};
|
||||||
block::{self, Block},
|
|
||||||
transaction::UnminedTxId,
|
|
||||||
};
|
|
||||||
use zebra_consensus::chain::VerifyChainError;
|
use zebra_consensus::chain::VerifyChainError;
|
||||||
use zebra_network::{
|
use zebra_network::{
|
||||||
constants::{ADDR_RESPONSE_LIMIT_DENOMINATOR, MAX_ADDRS_IN_MESSAGE},
|
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>;
|
Buffer<BoxService<zn::Request, zn::Response, zn::BoxError>, zn::Request>;
|
||||||
type State = Buffer<BoxService<zs::Request, zs::Response, zs::BoxError>, zs::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 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 =
|
type GossipedBlockDownloads =
|
||||||
BlockDownloads<Timeout<BlockDownloadPeerSet>, Timeout<BlockVerifier>, State>;
|
BlockDownloads<Timeout<BlockDownloadPeerSet>, Timeout<BlockVerifier>, State>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::Arc,
|
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -18,10 +17,7 @@ use tokio::{sync::oneshot, task::JoinHandle};
|
||||||
use tower::{Service, ServiceExt};
|
use tower::{Service, ServiceExt};
|
||||||
use tracing_futures::Instrument;
|
use tracing_futures::Instrument;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{block, chain_tip::ChainTip};
|
||||||
block::{self, Block},
|
|
||||||
chain_tip::ChainTip,
|
|
||||||
};
|
|
||||||
use zebra_network as zn;
|
use zebra_network as zn;
|
||||||
use zebra_state as zs;
|
use zebra_state as zs;
|
||||||
|
|
||||||
|
|
@ -77,7 +73,10 @@ pub struct Downloads<ZN, ZV, ZS>
|
||||||
where
|
where
|
||||||
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Clone + 'static,
|
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Clone + 'static,
|
||||||
ZN::Future: Send,
|
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,
|
ZV::Future: Send,
|
||||||
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
||||||
ZS::Future: Send,
|
ZS::Future: Send,
|
||||||
|
|
@ -117,7 +116,10 @@ impl<ZN, ZV, ZS> Stream for Downloads<ZN, ZV, ZS>
|
||||||
where
|
where
|
||||||
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Clone + 'static,
|
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Clone + 'static,
|
||||||
ZN::Future: Send,
|
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,
|
ZV::Future: Send,
|
||||||
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
||||||
ZS::Future: Send,
|
ZS::Future: Send,
|
||||||
|
|
@ -160,7 +162,10 @@ impl<ZN, ZV, ZS> Downloads<ZN, ZV, ZS>
|
||||||
where
|
where
|
||||||
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Clone + 'static,
|
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Clone + 'static,
|
||||||
ZN::Future: Send,
|
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,
|
ZV::Future: Send,
|
||||||
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
||||||
ZS::Future: Send,
|
ZS::Future: Send,
|
||||||
|
|
@ -338,7 +343,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
verifier
|
verifier
|
||||||
.oneshot(block)
|
.oneshot(zebra_consensus::Request::Commit(block))
|
||||||
.await
|
.await
|
||||||
.map(|hash| (hash, block_height))
|
.map(|hash| (hash, block_height))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Inbound service tests with a real peer set.
|
//! Inbound service tests with a real peer set.
|
||||||
|
|
||||||
use std::{iter, net::SocketAddr, sync::Arc};
|
use std::{iter, net::SocketAddr};
|
||||||
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
|
@ -13,7 +13,7 @@ use tower::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Block, Height},
|
block::{self, Height},
|
||||||
parameters::Network,
|
parameters::Network,
|
||||||
serialization::ZcashDeserializeInto,
|
serialization::ZcashDeserializeInto,
|
||||||
transaction::{AuthDigest, Hash as TxHash, Transaction, UnminedTx, UnminedTxId, WtxId},
|
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<mempool::Request, mempool::Response, BoxError>, mempool::Request>,
|
||||||
Buffer<BoxService<zebra_state::Request, zebra_state::Response, BoxError>, zebra_state::Request>,
|
Buffer<BoxService<zebra_state::Request, zebra_state::Response, BoxError>, zebra_state::Request>,
|
||||||
// mocked services
|
// mocked services
|
||||||
MockService<Arc<Block>, block::Hash, PanicAssertion, VerifyChainError>,
|
MockService<zebra_consensus::Request, block::Hash, PanicAssertion, VerifyChainError>,
|
||||||
MockService<transaction::Request, transaction::Response, PanicAssertion, TransactionError>,
|
MockService<transaction::Request, transaction::Response, PanicAssertion, TransactionError>,
|
||||||
// real tasks
|
// real tasks
|
||||||
JoinHandle<Result<(), BlockGossipError>>,
|
JoinHandle<Result<(), BlockGossipError>>,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
//!
|
//!
|
||||||
//! It is used when Zebra is a long way behind the current chain tip.
|
//! 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 color_eyre::eyre::{eyre, Report};
|
||||||
use futures::stream::{FuturesUnordered, StreamExt};
|
use futures::stream::{FuturesUnordered, StreamExt};
|
||||||
|
|
@ -15,7 +15,7 @@ use tower::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Block, Height},
|
block::{self, Height},
|
||||||
chain_tip::ChainTip,
|
chain_tip::ChainTip,
|
||||||
parameters::genesis_hash,
|
parameters::genesis_hash,
|
||||||
};
|
};
|
||||||
|
|
@ -300,7 +300,7 @@ where
|
||||||
+ Clone
|
+ Clone
|
||||||
+ 'static,
|
+ 'static,
|
||||||
ZS::Future: Send,
|
ZS::Future: Send,
|
||||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
|
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
+ Clone
|
+ Clone
|
||||||
|
|
@ -381,7 +381,7 @@ where
|
||||||
+ Clone
|
+ Clone
|
||||||
+ 'static,
|
+ 'static,
|
||||||
ZS::Future: Send,
|
ZS::Future: Send,
|
||||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
|
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
+ Clone
|
+ Clone
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ use tower::{hedge, Service, ServiceExt};
|
||||||
use tracing_futures::Instrument;
|
use tracing_futures::Instrument;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Block, Height},
|
block::{self, Height},
|
||||||
chain_tip::ChainTip,
|
chain_tip::ChainTip,
|
||||||
};
|
};
|
||||||
use zebra_network as zn;
|
use zebra_network as zn;
|
||||||
|
|
@ -163,7 +163,7 @@ pub struct Downloads<ZN, ZV, ZSTip>
|
||||||
where
|
where
|
||||||
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Sync + 'static,
|
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Sync + 'static,
|
||||||
ZN::Future: Send,
|
ZN::Future: Send,
|
||||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
|
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
+ Clone
|
+ Clone
|
||||||
|
|
@ -217,7 +217,7 @@ impl<ZN, ZV, ZSTip> Stream for Downloads<ZN, ZV, ZSTip>
|
||||||
where
|
where
|
||||||
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Sync + 'static,
|
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Sync + 'static,
|
||||||
ZN::Future: Send,
|
ZN::Future: Send,
|
||||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
|
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
+ Clone
|
+ Clone
|
||||||
|
|
@ -264,7 +264,7 @@ impl<ZN, ZV, ZSTip> Downloads<ZN, ZV, ZSTip>
|
||||||
where
|
where
|
||||||
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Sync + 'static,
|
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Sync + 'static,
|
||||||
ZN::Future: Send,
|
ZN::Future: Send,
|
||||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
|
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
+ Clone
|
+ Clone
|
||||||
|
|
@ -516,7 +516,7 @@ where
|
||||||
// Verify the block.
|
// Verify the block.
|
||||||
let mut rsp = verifier
|
let mut rsp = verifier
|
||||||
.map_err(|error| BlockDownloadVerifyError::VerifierServiceError { error })?
|
.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)
|
// 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");
|
let short_timeout_max = (max_checkpoint_height + FINAL_CHECKPOINT_BLOCK_VERIFY_TIMEOUT_LIMIT).expect("checkpoint block height is in valid range");
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ async fn sync_blocks_ok() -> Result<(), crate::BoxError> {
|
||||||
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
|
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
|
||||||
|
|
||||||
chain_verifier
|
chain_verifier
|
||||||
.expect_request(block0)
|
.expect_request(zebra_consensus::Request::Commit(block0))
|
||||||
.await
|
.await
|
||||||
.respond(block0_hash);
|
.respond(block0_hash);
|
||||||
|
|
||||||
|
|
@ -175,9 +175,9 @@ async fn sync_blocks_ok() -> Result<(), crate::BoxError> {
|
||||||
|
|
||||||
for _ in 1..=2 {
|
for _ in 1..=2 {
|
||||||
chain_verifier
|
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
|
.await
|
||||||
.respond_with(|req| req.hash());
|
.respond_with(|req| req.block().hash());
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
remaining_blocks,
|
remaining_blocks,
|
||||||
|
|
@ -239,9 +239,9 @@ async fn sync_blocks_ok() -> Result<(), crate::BoxError> {
|
||||||
|
|
||||||
for _ in 3..=4 {
|
for _ in 3..=4 {
|
||||||
chain_verifier
|
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
|
.await
|
||||||
.respond_with(|req| req.hash());
|
.respond_with(|req| req.block().hash());
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
remaining_blocks,
|
remaining_blocks,
|
||||||
|
|
@ -316,7 +316,7 @@ async fn sync_blocks_duplicate_hashes_ok() -> Result<(), crate::BoxError> {
|
||||||
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
|
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
|
||||||
|
|
||||||
chain_verifier
|
chain_verifier
|
||||||
.expect_request(block0)
|
.expect_request(zebra_consensus::Request::Commit(block0))
|
||||||
.await
|
.await
|
||||||
.respond(block0_hash);
|
.respond(block0_hash);
|
||||||
|
|
||||||
|
|
@ -404,9 +404,9 @@ async fn sync_blocks_duplicate_hashes_ok() -> Result<(), crate::BoxError> {
|
||||||
|
|
||||||
for _ in 1..=2 {
|
for _ in 1..=2 {
|
||||||
chain_verifier
|
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
|
.await
|
||||||
.respond_with(|req| req.hash());
|
.respond_with(|req| req.block().hash());
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
remaining_blocks,
|
remaining_blocks,
|
||||||
|
|
@ -470,9 +470,9 @@ async fn sync_blocks_duplicate_hashes_ok() -> Result<(), crate::BoxError> {
|
||||||
|
|
||||||
for _ in 3..=4 {
|
for _ in 3..=4 {
|
||||||
chain_verifier
|
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
|
.await
|
||||||
.respond_with(|req| req.hash());
|
.respond_with(|req| req.block().hash());
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
remaining_blocks,
|
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())]));
|
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
|
||||||
|
|
||||||
chain_verifier
|
chain_verifier
|
||||||
.expect_request(block0)
|
.expect_request(zebra_consensus::Request::Commit(block0))
|
||||||
.await
|
.await
|
||||||
.respond(block0_hash);
|
.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())]));
|
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
|
||||||
|
|
||||||
chain_verifier
|
chain_verifier
|
||||||
.expect_request(block0)
|
.expect_request(zebra_consensus::Request::Commit(block0))
|
||||||
.await
|
.await
|
||||||
.respond(block0_hash);
|
.respond(block0_hash);
|
||||||
|
|
||||||
|
|
@ -845,9 +845,9 @@ async fn sync_block_too_high_extend_tips() -> Result<(), crate::BoxError> {
|
||||||
|
|
||||||
for _ in 1..=2 {
|
for _ in 1..=2 {
|
||||||
chain_verifier
|
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
|
.await
|
||||||
.respond_with(|req| req.hash());
|
.respond_with(|req| req.block().hash());
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
remaining_blocks,
|
remaining_blocks,
|
||||||
|
|
@ -927,7 +927,7 @@ fn setup() -> (
|
||||||
impl Future<Output = Result<(), Report>> + Send,
|
impl Future<Output = Result<(), Report>> + Send,
|
||||||
SyncStatus,
|
SyncStatus,
|
||||||
// ChainVerifier
|
// ChainVerifier
|
||||||
MockService<Arc<Block>, block::Hash, PanicAssertion>,
|
MockService<zebra_consensus::Request, block::Hash, PanicAssertion>,
|
||||||
// PeerSet
|
// PeerSet
|
||||||
MockService<zebra_network::Request, zebra_network::Response, PanicAssertion>,
|
MockService<zebra_network::Request, zebra_network::Response, PanicAssertion>,
|
||||||
// StateService
|
// StateService
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue