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]
|
||||
default = []
|
||||
getblocktemplate-rpcs = []
|
||||
getblocktemplate-rpcs = [
|
||||
"zebra-state/getblocktemplate-rpcs",
|
||||
"zebra-chain/getblocktemplate-rpcs",
|
||||
]
|
||||
proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"]
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
|
|
@ -21,20 +21,17 @@ use thiserror::Error;
|
|||
use tower::{Service, ServiceExt};
|
||||
use tracing::Instrument;
|
||||
|
||||
use zebra_chain::{
|
||||
amount::Amount,
|
||||
block::{self, Block},
|
||||
parameters::Network,
|
||||
transparent,
|
||||
work::equihash,
|
||||
};
|
||||
use zebra_chain::{amount::Amount, block, parameters::Network, transparent, work::equihash};
|
||||
use zebra_state as zs;
|
||||
|
||||
use crate::{error::*, transaction as tx, BoxError};
|
||||
|
||||
pub mod check;
|
||||
pub mod request;
|
||||
pub mod subsidy;
|
||||
|
||||
pub use request::Request;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
|
|
@ -74,6 +71,11 @@ pub enum VerifyBlockError {
|
|||
// TODO: make this into a concrete type, and add it to is_duplicate_request() (#2908)
|
||||
Commit(#[source] BoxError),
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
#[error("unable to validate block proposal: failed semantic verification (proof of work is not checked for proposals)")]
|
||||
// TODO: make this into a concrete type (see #5732)
|
||||
ValidateProposal(#[source] BoxError),
|
||||
|
||||
#[error("invalid transaction")]
|
||||
Transaction(#[from] TransactionError),
|
||||
}
|
||||
|
|
@ -115,7 +117,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<S, V> Service<Arc<Block>> for BlockVerifier<S, V>
|
||||
impl<S, V> Service<Request> for BlockVerifier<S, V>
|
||||
where
|
||||
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
||||
S::Future: Send + 'static,
|
||||
|
|
@ -134,11 +136,13 @@ where
|
|||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, block: Arc<Block>) -> Self::Future {
|
||||
fn call(&mut self, request: Request) -> Self::Future {
|
||||
let mut state_service = self.state_service.clone();
|
||||
let mut transaction_verifier = self.transaction_verifier.clone();
|
||||
let network = self.network;
|
||||
|
||||
let block = request.block();
|
||||
|
||||
// We don't include the block hash, because it's likely already in a parent span
|
||||
let span = tracing::debug_span!("block", height = ?block.coinbase_height());
|
||||
|
||||
|
|
@ -172,10 +176,17 @@ where
|
|||
Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
|
||||
}
|
||||
|
||||
// Do the difficulty checks first, to raise the threshold for
|
||||
// attacks that use any other fields.
|
||||
check::difficulty_is_valid(&block.header, network, &height, &hash)?;
|
||||
check::equihash_solution_is_valid(&block.header)?;
|
||||
// > The block data MUST be validated and checked against the server's usual
|
||||
// > acceptance rules (excluding the check for a valid proof-of-work).
|
||||
// <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
|
||||
if request.is_proposal() {
|
||||
check::difficulty_threshold_is_valid(&block.header, network, &height, &hash)?;
|
||||
} else {
|
||||
// Do the difficulty checks first, to raise the threshold for
|
||||
// attacks that use any other fields.
|
||||
check::difficulty_is_valid(&block.header, network, &height, &hash)?;
|
||||
check::equihash_solution_is_valid(&block.header)?;
|
||||
}
|
||||
|
||||
// Next, check the Merkle root validity, to ensure that
|
||||
// the header binds to the transactions in the blocks.
|
||||
|
|
@ -279,6 +290,23 @@ where
|
|||
new_outputs,
|
||||
transaction_hashes,
|
||||
};
|
||||
|
||||
// Return early for proposal requests when getblocktemplate-rpcs feature is enabled
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
if request.is_proposal() {
|
||||
return match state_service
|
||||
.ready()
|
||||
.await
|
||||
.map_err(VerifyBlockError::ValidateProposal)?
|
||||
.call(zs::Request::CheckBlockProposalValidity(prepared_block))
|
||||
.await
|
||||
.map_err(VerifyBlockError::ValidateProposal)?
|
||||
{
|
||||
zs::Response::ValidBlockProposal => Ok(hash),
|
||||
_ => unreachable!("wrong response for CheckBlockProposalValidity"),
|
||||
};
|
||||
}
|
||||
|
||||
match state_service
|
||||
.ready()
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -58,24 +58,22 @@ pub fn coinbase_is_first(block: &Block) -> Result<Arc<transaction::Transaction>,
|
|||
Ok(first.clone())
|
||||
}
|
||||
|
||||
/// Returns `Ok(())` if `hash` passes:
|
||||
/// - the target difficulty limit for `network` (PoWLimit), and
|
||||
/// - the difficulty filter,
|
||||
/// based on the fields in `header`.
|
||||
/// Returns `Ok(ExpandedDifficulty)` if the`difficulty_threshold` of `header` is at least as difficult as
|
||||
/// the target difficulty limit for `network` (PoWLimit)
|
||||
///
|
||||
/// If the block is invalid, returns an error containing `height` and `hash`.
|
||||
pub fn difficulty_is_valid(
|
||||
/// If the header difficulty threshold is invalid, returns an error containing `height` and `hash`.
|
||||
pub fn difficulty_threshold_is_valid(
|
||||
header: &Header,
|
||||
network: Network,
|
||||
height: &Height,
|
||||
hash: &Hash,
|
||||
) -> Result<(), BlockError> {
|
||||
) -> Result<ExpandedDifficulty, BlockError> {
|
||||
let difficulty_threshold = header
|
||||
.difficulty_threshold
|
||||
.to_expanded()
|
||||
.ok_or(BlockError::InvalidDifficulty(*height, *hash))?;
|
||||
|
||||
// Note: the comparisons in this function are u256 integer comparisons, like
|
||||
// Note: the comparison in this function is a u256 integer comparison, like
|
||||
// zcashd and bitcoin. Greater values represent *less* work.
|
||||
|
||||
// The PowLimit check is part of `Threshold()` in the spec, but it doesn't
|
||||
|
|
@ -90,6 +88,26 @@ pub fn difficulty_is_valid(
|
|||
))?;
|
||||
}
|
||||
|
||||
Ok(difficulty_threshold)
|
||||
}
|
||||
|
||||
/// Returns `Ok(())` if `hash` passes:
|
||||
/// - the target difficulty limit for `network` (PoWLimit), and
|
||||
/// - the difficulty filter,
|
||||
/// based on the fields in `header`.
|
||||
///
|
||||
/// If the block is invalid, returns an error containing `height` and `hash`.
|
||||
pub fn difficulty_is_valid(
|
||||
header: &Header,
|
||||
network: Network,
|
||||
height: &Height,
|
||||
hash: &Hash,
|
||||
) -> Result<(), BlockError> {
|
||||
let difficulty_threshold = difficulty_threshold_is_valid(header, network, height, hash)?;
|
||||
|
||||
// Note: the comparison in this function is a u256 integer comparison, like
|
||||
// zcashd and bitcoin. Greater values represent *less* work.
|
||||
|
||||
// # Consensus
|
||||
//
|
||||
// > The block MUST pass the difficulty filter.
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
||||
static VALID_BLOCK_TRANSCRIPT: Lazy<
|
||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||
> = Lazy::new(|| {
|
||||
let block: Arc<_> =
|
||||
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
|
||||
.unwrap()
|
||||
.into();
|
||||
let hash = Ok(block.as_ref().into());
|
||||
vec![(block, hash)]
|
||||
});
|
||||
static VALID_BLOCK_TRANSCRIPT: Lazy<Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>> =
|
||||
Lazy::new(|| {
|
||||
let block: Arc<_> =
|
||||
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
|
||||
.unwrap()
|
||||
.into();
|
||||
let hash = Ok(block.as_ref().into());
|
||||
vec![(Request::Commit(block), hash)]
|
||||
});
|
||||
|
||||
static INVALID_TIME_BLOCK_TRANSCRIPT: Lazy<
|
||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||
> = Lazy::new(|| {
|
||||
let mut block: Block =
|
||||
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..]).unwrap();
|
||||
|
|
@ -55,11 +54,14 @@ static INVALID_TIME_BLOCK_TRANSCRIPT: Lazy<
|
|||
.unwrap();
|
||||
Arc::make_mut(&mut block.header).time = three_hours_in_the_future;
|
||||
|
||||
vec![(Arc::new(block), Err(ExpectedTranscriptError::Any))]
|
||||
vec![(
|
||||
Request::Commit(Arc::new(block)),
|
||||
Err(ExpectedTranscriptError::Any),
|
||||
)]
|
||||
});
|
||||
|
||||
static INVALID_HEADER_SOLUTION_TRANSCRIPT: Lazy<
|
||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||
> = Lazy::new(|| {
|
||||
let mut block: Block =
|
||||
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..]).unwrap();
|
||||
|
|
@ -67,11 +69,14 @@ static INVALID_HEADER_SOLUTION_TRANSCRIPT: Lazy<
|
|||
// Change nonce to something invalid
|
||||
Arc::make_mut(&mut block.header).nonce = [0; 32];
|
||||
|
||||
vec![(Arc::new(block), Err(ExpectedTranscriptError::Any))]
|
||||
vec![(
|
||||
Request::Commit(Arc::new(block)),
|
||||
Err(ExpectedTranscriptError::Any),
|
||||
)]
|
||||
});
|
||||
|
||||
static INVALID_COINBASE_TRANSCRIPT: Lazy<
|
||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||
> = Lazy::new(|| {
|
||||
let header = block::Header::zcash_deserialize(&zebra_test::vectors::DUMMY_HEADER[..]).unwrap();
|
||||
|
||||
|
|
@ -105,9 +110,18 @@ static INVALID_COINBASE_TRANSCRIPT: Lazy<
|
|||
assert_eq!(block3.transactions.len(), 2);
|
||||
|
||||
vec![
|
||||
(Arc::new(block1), Err(ExpectedTranscriptError::Any)),
|
||||
(Arc::new(block2), Err(ExpectedTranscriptError::Any)),
|
||||
(Arc::new(block3), Err(ExpectedTranscriptError::Any)),
|
||||
(
|
||||
Request::Commit(Arc::new(block1)),
|
||||
Err(ExpectedTranscriptError::Any),
|
||||
),
|
||||
(
|
||||
Request::Commit(Arc::new(block2)),
|
||||
Err(ExpectedTranscriptError::Any),
|
||||
),
|
||||
(
|
||||
Request::Commit(Arc::new(block3)),
|
||||
Err(ExpectedTranscriptError::Any),
|
||||
),
|
||||
]
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
|
|
@ -27,15 +26,14 @@ use tower::{buffer::Buffer, util::BoxService, Service, ServiceExt};
|
|||
use tracing::{instrument, Span};
|
||||
|
||||
use zebra_chain::{
|
||||
block::{self, Block, Height},
|
||||
block::{self, Height},
|
||||
parameters::Network,
|
||||
};
|
||||
|
||||
use zebra_state as zs;
|
||||
|
||||
use crate::{
|
||||
block::BlockVerifier,
|
||||
block::VerifyBlockError,
|
||||
block::{BlockVerifier, Request, VerifyBlockError},
|
||||
checkpoint::{CheckpointList, CheckpointVerifier, VerifyCheckpointError},
|
||||
error::TransactionError,
|
||||
transaction, BoxError, Config,
|
||||
|
|
@ -129,7 +127,7 @@ impl VerifyChainError {
|
|||
}
|
||||
}
|
||||
|
||||
impl<S, V> Service<Arc<Block>> for ChainVerifier<S, V>
|
||||
impl<S, V> Service<Request> for ChainVerifier<S, V>
|
||||
where
|
||||
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
||||
S::Future: Send + 'static,
|
||||
|
|
@ -167,14 +165,29 @@ where
|
|||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, block: Arc<Block>) -> Self::Future {
|
||||
fn call(&mut self, request: Request) -> Self::Future {
|
||||
let block = request.block();
|
||||
|
||||
match block.coinbase_height() {
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
// There's currently no known use case for block proposals below the checkpoint height,
|
||||
// so it's okay to immediately return an error here.
|
||||
Some(height) if height <= self.max_checkpoint_height && request.is_proposal() => {
|
||||
async {
|
||||
// TODO: Add a `ValidateProposalError` enum with a `BelowCheckpoint` variant?
|
||||
Err(VerifyBlockError::ValidateProposal(
|
||||
"block proposals must be above checkpoint height".into(),
|
||||
))?
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
Some(height) if height <= self.max_checkpoint_height => {
|
||||
self.checkpoint.call(block).map_err(Into::into).boxed()
|
||||
}
|
||||
// This also covers blocks with no height, which the block verifier
|
||||
// will reject immediately.
|
||||
_ => self.block.call(block).map_err(Into::into).boxed(),
|
||||
_ => self.block.call(request).map_err(Into::into).boxed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -219,7 +232,7 @@ pub async fn init<S>(
|
|||
mut state_service: S,
|
||||
debug_skip_parameter_preload: bool,
|
||||
) -> (
|
||||
Buffer<BoxService<Arc<Block>, block::Hash, VerifyChainError>, Arc<Block>>,
|
||||
Buffer<BoxService<Request, block::Hash, VerifyChainError>, Request>,
|
||||
Buffer<
|
||||
BoxService<transaction::Request, transaction::Response, TransactionError>,
|
||||
transaction::Request,
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ async fn verifiers_from_network(
|
|||
network: Network,
|
||||
) -> (
|
||||
impl Service<
|
||||
Arc<Block>,
|
||||
Request,
|
||||
Response = block::Hash,
|
||||
Error = BoxError,
|
||||
Future = impl Future<Output = Result<block::Hash, BoxError>>,
|
||||
|
|
@ -77,7 +77,7 @@ async fn verifiers_from_network(
|
|||
}
|
||||
|
||||
static BLOCK_VERIFY_TRANSCRIPT_GENESIS: Lazy<
|
||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||
> = Lazy::new(|| {
|
||||
let block: Arc<_> =
|
||||
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
|
||||
|
|
@ -85,27 +85,29 @@ static BLOCK_VERIFY_TRANSCRIPT_GENESIS: Lazy<
|
|||
.into();
|
||||
let hash = Ok(block.hash());
|
||||
|
||||
vec![(block, hash)]
|
||||
vec![(Request::Commit(block), hash)]
|
||||
});
|
||||
|
||||
static BLOCK_VERIFY_TRANSCRIPT_GENESIS_FAIL: Lazy<
|
||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||
> = Lazy::new(|| {
|
||||
let block: Arc<_> =
|
||||
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
vec![(block, Err(ExpectedTranscriptError::Any))]
|
||||
vec![(Request::Commit(block), Err(ExpectedTranscriptError::Any))]
|
||||
});
|
||||
|
||||
static NO_COINBASE_TRANSCRIPT: Lazy<
|
||||
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
|
||||
> = Lazy::new(|| {
|
||||
let block = block_no_transactions();
|
||||
static NO_COINBASE_TRANSCRIPT: Lazy<Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>> =
|
||||
Lazy::new(|| {
|
||||
let block = block_no_transactions();
|
||||
|
||||
vec![(Arc::new(block), Err(ExpectedTranscriptError::Any))]
|
||||
});
|
||||
vec![(
|
||||
Request::Commit(Arc::new(block)),
|
||||
Err(ExpectedTranscriptError::Any),
|
||||
)]
|
||||
});
|
||||
|
||||
static NO_COINBASE_STATE_TRANSCRIPT: Lazy<
|
||||
Vec<(zs::Request, Result<zs::Response, ExpectedTranscriptError>)>,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ pub use block::{
|
|||
funding_streams::{funding_stream_address, funding_stream_values, new_coinbase_script},
|
||||
general::miner_subsidy,
|
||||
},
|
||||
VerifyBlockError, MAX_BLOCK_SIGOPS,
|
||||
Request, VerifyBlockError, MAX_BLOCK_SIGOPS,
|
||||
};
|
||||
pub use chain::VerifyChainError;
|
||||
pub use checkpoint::{
|
||||
|
|
|
|||
|
|
@ -26,9 +26,8 @@ use crate::methods::{
|
|||
DEFAULT_SOLUTION_RATE_WINDOW_SIZE, GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL,
|
||||
},
|
||||
get_block_template::{
|
||||
check_block_template_parameters, check_miner_address, check_synced_to_tip,
|
||||
fetch_mempool_transactions, fetch_state_tip_and_local_time,
|
||||
generate_coinbase_and_roots,
|
||||
check_miner_address, check_synced_to_tip, fetch_mempool_transactions,
|
||||
fetch_state_tip_and_local_time, generate_coinbase_and_roots, validate_block_proposal,
|
||||
},
|
||||
types::{
|
||||
get_block_template::GetBlockTemplate, get_mining_info, hex_data::HexData,
|
||||
|
|
@ -101,7 +100,7 @@ pub trait GetBlockTemplateRpc {
|
|||
fn get_block_template(
|
||||
&self,
|
||||
parameters: Option<get_block_template::JsonParameters>,
|
||||
) -> BoxFuture<Result<GetBlockTemplate>>;
|
||||
) -> BoxFuture<Result<get_block_template::Response>>;
|
||||
|
||||
/// Submits block to the node to be validated and committed.
|
||||
/// Returns the [`submit_block::Response`] for the operation, as a JSON string.
|
||||
|
|
@ -165,7 +164,7 @@ where
|
|||
Response = zebra_state::ReadResponse,
|
||||
Error = zebra_state::BoxError,
|
||||
>,
|
||||
ChainVerifier: Service<Arc<Block>, Response = block::Hash, Error = zebra_consensus::BoxError>
|
||||
ChainVerifier: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
|
|
@ -217,7 +216,7 @@ where
|
|||
+ Sync
|
||||
+ 'static,
|
||||
Tip: ChainTip + Clone + Send + Sync + 'static,
|
||||
ChainVerifier: Service<Arc<Block>, Response = block::Hash, Error = zebra_consensus::BoxError>
|
||||
ChainVerifier: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
|
|
@ -265,12 +264,12 @@ where
|
|||
+ 'static,
|
||||
<State as Service<zebra_state::ReadRequest>>::Future: Send,
|
||||
Tip: ChainTip + Clone + Send + Sync + 'static,
|
||||
ChainVerifier: Service<Arc<Block>, Response = block::Hash, Error = zebra_consensus::BoxError>
|
||||
ChainVerifier: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
<ChainVerifier as Service<Arc<Block>>>::Future: Send,
|
||||
<ChainVerifier as Service<zebra_consensus::Request>>::Future: Send,
|
||||
SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
|
||||
{
|
||||
fn get_block_count(&self) -> Result<u32> {
|
||||
|
|
@ -312,11 +311,11 @@ where
|
|||
.boxed()
|
||||
}
|
||||
|
||||
// TODO: use HexData to handle block proposal data, and a generic error constructor (#5548)
|
||||
// TODO: use a generic error constructor (#5548)
|
||||
fn get_block_template(
|
||||
&self,
|
||||
parameters: Option<get_block_template::JsonParameters>,
|
||||
) -> BoxFuture<Result<GetBlockTemplate>> {
|
||||
) -> BoxFuture<Result<get_block_template::Response>> {
|
||||
// Should we generate coinbase transactions that are exactly like zcashd's?
|
||||
//
|
||||
// This is useful for testing, but either way Zebra should obey the consensus rules.
|
||||
|
|
@ -332,21 +331,28 @@ where
|
|||
let sync_status = self.sync_status.clone();
|
||||
let state = self.state.clone();
|
||||
|
||||
if let Some(HexData(block_proposal_bytes)) = parameters
|
||||
.as_ref()
|
||||
.and_then(get_block_template::JsonParameters::block_proposal_data)
|
||||
{
|
||||
return validate_block_proposal(self.chain_verifier.clone(), block_proposal_bytes)
|
||||
.boxed();
|
||||
}
|
||||
|
||||
// To implement long polling correctly, we split this RPC into multiple phases.
|
||||
async move {
|
||||
get_block_template::check_parameters(¶meters)?;
|
||||
|
||||
let client_long_poll_id = parameters
|
||||
.as_ref()
|
||||
.and_then(|params| params.long_poll_id.clone());
|
||||
|
||||
// - One-off checks
|
||||
|
||||
// Check config and parameters.
|
||||
// These checks always have the same result during long polling.
|
||||
let miner_address = check_miner_address(miner_address)?;
|
||||
|
||||
let mut client_long_poll_id = None;
|
||||
if let Some(parameters) = parameters {
|
||||
check_block_template_parameters(¶meters)?;
|
||||
|
||||
client_long_poll_id = parameters.long_poll_id;
|
||||
}
|
||||
|
||||
// - Checks and fetches that can change during long polling
|
||||
//
|
||||
// Set up the loop.
|
||||
|
|
@ -506,7 +512,7 @@ where
|
|||
?server_long_poll_id,
|
||||
?client_long_poll_id,
|
||||
"returning from long poll due to a state error.\
|
||||
Is Zebra shutting down?"
|
||||
Is Zebra shutting down?"
|
||||
);
|
||||
|
||||
return Err(Error {
|
||||
|
|
@ -582,7 +588,7 @@ where
|
|||
COINBASE_LIKE_ZCASHD,
|
||||
);
|
||||
|
||||
Ok(response)
|
||||
Ok(response.into())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
|
@ -608,7 +614,7 @@ where
|
|||
message: error.to_string(),
|
||||
data: None,
|
||||
})?
|
||||
.call(Arc::new(block))
|
||||
.call(zebra_consensus::Request::Commit(Arc::new(block)))
|
||||
.await;
|
||||
|
||||
let chain_error = match chain_verifier_response {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ pub const GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL: u64 = 5;
|
|||
pub const GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD: &str = "00000000ffffffff";
|
||||
|
||||
/// A hardcoded list of fields that the miner can change from the block template.
|
||||
///
|
||||
/// <https://en.bitcoin.it/wiki/BIP_0023#Mutations>
|
||||
pub const GET_BLOCK_TEMPLATE_MUTABLE_FIELD: &[&str] = &[
|
||||
// Standard mutations, copied from zcashd
|
||||
"time",
|
||||
|
|
@ -26,8 +28,9 @@ pub const GET_BLOCK_TEMPLATE_MUTABLE_FIELD: &[&str] = &[
|
|||
];
|
||||
|
||||
/// A hardcoded list of Zebra's getblocktemplate RPC capabilities.
|
||||
/// Currently empty.
|
||||
pub const GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD: &[&str] = &[];
|
||||
///
|
||||
/// <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
|
||||
pub const GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD: &[&str] = &["proposal"];
|
||||
|
||||
/// The max estimated distance to the chain tip for the getblocktemplate method.
|
||||
///
|
||||
|
|
@ -35,6 +38,8 @@ pub const GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD: &[&str] = &[];
|
|||
/// > A full validator MUST NOT accept blocks with nTime more than two hours in the future
|
||||
/// > according to its clock. This is not strictly a consensus rule because it is nondeterministic,
|
||||
/// > and clock time varies between nodes.
|
||||
/// >
|
||||
/// > <https://zips.z.cash/protocol/protocol.pdf#blockheader>
|
||||
pub const MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP: i32 = 100;
|
||||
|
||||
/// The RPC error code used by `zcashd` for when it's still downloading initial blocks.
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ use tower::{Service, ServiceExt};
|
|||
use zebra_chain::{
|
||||
amount::{self, Amount, NegativeOrZero, NonNegative},
|
||||
block::{
|
||||
self,
|
||||
merkle::{self, AuthDataRoot},
|
||||
ChainHistoryBlockTxAuthCommitmentHash, Height,
|
||||
},
|
||||
chain_sync_status::ChainSyncStatus,
|
||||
chain_tip::ChainTip,
|
||||
parameters::Network,
|
||||
serialization::ZcashDeserializeInto,
|
||||
transaction::{Transaction, UnminedTx, VerifiedUnminedTx},
|
||||
transparent,
|
||||
};
|
||||
|
|
@ -25,26 +27,57 @@ use zebra_state::GetBlockTemplateChainInfo;
|
|||
|
||||
use crate::methods::get_block_template_rpcs::{
|
||||
constants::{MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP, NOT_SYNCED_ERROR_CODE},
|
||||
types::{default_roots::DefaultRoots, get_block_template, transaction::TransactionTemplate},
|
||||
types::{default_roots::DefaultRoots, transaction::TransactionTemplate},
|
||||
};
|
||||
|
||||
pub use crate::methods::get_block_template_rpcs::types::get_block_template::*;
|
||||
|
||||
// - Parameter checks
|
||||
|
||||
/// Returns an error if the get block template RPC `parameters` are invalid.
|
||||
pub fn check_block_template_parameters(
|
||||
parameters: &get_block_template::JsonParameters,
|
||||
) -> Result<()> {
|
||||
if parameters.data.is_some() || parameters.mode == GetBlockTemplateRequestMode::Proposal {
|
||||
return Err(Error {
|
||||
code: ErrorCode::InvalidParams,
|
||||
message: "\"proposal\" mode is currently unsupported by Zebra".to_string(),
|
||||
data: None,
|
||||
});
|
||||
}
|
||||
/// Checks that `data` is omitted in `Template` mode or provided in `Proposal` mode,
|
||||
///
|
||||
/// Returns an error if there's a mismatch between the mode and whether `data` is provided.
|
||||
pub fn check_parameters(parameters: &Option<JsonParameters>) -> Result<()> {
|
||||
let Some(parameters) = parameters else {
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
Ok(())
|
||||
match parameters {
|
||||
JsonParameters {
|
||||
mode: GetBlockTemplateRequestMode::Template,
|
||||
data: None,
|
||||
..
|
||||
}
|
||||
| JsonParameters {
|
||||
mode: GetBlockTemplateRequestMode::Proposal,
|
||||
data: Some(_),
|
||||
..
|
||||
} => Ok(()),
|
||||
|
||||
JsonParameters {
|
||||
mode: GetBlockTemplateRequestMode::Proposal,
|
||||
data: None,
|
||||
..
|
||||
} => Err(Error {
|
||||
code: ErrorCode::InvalidParams,
|
||||
message: "\"data\" parameter must be \
|
||||
provided in \"proposal\" mode"
|
||||
.to_string(),
|
||||
data: None,
|
||||
}),
|
||||
|
||||
JsonParameters {
|
||||
mode: GetBlockTemplateRequestMode::Template,
|
||||
data: Some(_),
|
||||
..
|
||||
} => Err(Error {
|
||||
code: ErrorCode::InvalidParams,
|
||||
message: "\"data\" parameter must be \
|
||||
omitted in \"template\" mode"
|
||||
.to_string(),
|
||||
data: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the miner address, or an error if it is invalid.
|
||||
|
|
@ -60,6 +93,42 @@ pub fn check_miner_address(
|
|||
})
|
||||
}
|
||||
|
||||
/// Attempts to validate block proposal against all of the server's
|
||||
/// usual acceptance rules (except proof-of-work).
|
||||
///
|
||||
/// Returns a `getblocktemplate` [`Response`].
|
||||
pub async fn validate_block_proposal<ChainVerifier>(
|
||||
mut chain_verifier: ChainVerifier,
|
||||
block_proposal_bytes: Vec<u8>,
|
||||
) -> Result<Response>
|
||||
where
|
||||
ChainVerifier: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
{
|
||||
let Ok(block) = block_proposal_bytes.zcash_deserialize_into() else {
|
||||
return Ok(ProposalRejectReason::Rejected.into())
|
||||
};
|
||||
|
||||
let chain_verifier_response = chain_verifier
|
||||
.ready()
|
||||
.await
|
||||
.map_err(|error| Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
message: error.to_string(),
|
||||
data: None,
|
||||
})?
|
||||
.call(zebra_consensus::Request::CheckProposal(Arc::new(block)))
|
||||
.await;
|
||||
|
||||
Ok(chain_verifier_response
|
||||
.map(|_hash| ProposalResponse::Valid)
|
||||
.unwrap_or_else(|_| ProposalRejectReason::Rejected.into())
|
||||
.into())
|
||||
}
|
||||
|
||||
// - State and syncer checks
|
||||
|
||||
/// Returns an error if Zebra is not synced to the consensus chain tip.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ pub mod parameters;
|
|||
|
||||
pub use parameters::*;
|
||||
|
||||
/// A serialized `getblocktemplate` RPC response.
|
||||
/// A serialized `getblocktemplate` RPC response in template mode.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GetBlockTemplate {
|
||||
/// The getblocktemplate RPC capabilities supported by Zebra.
|
||||
|
|
@ -162,6 +162,14 @@ pub struct GetBlockTemplate {
|
|||
}
|
||||
|
||||
impl GetBlockTemplate {
|
||||
/// Returns a `Vec` of capabilities supported by the `getblocktemplate` RPC
|
||||
pub fn capabilities() -> Vec<String> {
|
||||
GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns a new [`GetBlockTemplate`] struct, based on the supplied arguments and defaults.
|
||||
///
|
||||
/// The result of this method only depends on the supplied arguments and constants.
|
||||
|
|
@ -203,10 +211,7 @@ impl GetBlockTemplate {
|
|||
.expect("state always returns a valid difficulty value");
|
||||
|
||||
// Convert default values
|
||||
let capabilities: Vec<String> = GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect();
|
||||
let capabilities: Vec<String> = Self::capabilities();
|
||||
let mutable: Vec<String> = GET_BLOCK_TEMPLATE_MUTABLE_FIELD
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
|
|
@ -253,3 +258,68 @@ impl GetBlockTemplate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error response to a `getblocktemplate` RPC request in proposal mode.
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ProposalRejectReason {
|
||||
/// Block proposal rejected as invalid.
|
||||
Rejected,
|
||||
}
|
||||
|
||||
/// Response to a `getblocktemplate` RPC request in proposal mode.
|
||||
///
|
||||
/// See <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged, rename_all = "kebab-case")]
|
||||
pub enum ProposalResponse {
|
||||
/// Block proposal was rejected as invalid, returns `reject-reason` and server `capabilities`.
|
||||
ErrorResponse {
|
||||
/// Reason the proposal was invalid as-is.
|
||||
reject_reason: ProposalRejectReason,
|
||||
|
||||
/// The getblocktemplate RPC capabilities supported by Zebra.
|
||||
capabilities: Vec<String>,
|
||||
},
|
||||
|
||||
/// Block proposal was successfully validated, returns null.
|
||||
Valid,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
/// A `getblocktemplate` RPC response.
|
||||
pub enum Response {
|
||||
/// `getblocktemplate` RPC request in template mode.
|
||||
TemplateMode(Box<GetBlockTemplate>),
|
||||
|
||||
/// `getblocktemplate` RPC request in proposal mode.
|
||||
ProposalMode(ProposalResponse),
|
||||
}
|
||||
|
||||
impl From<ProposalRejectReason> for ProposalResponse {
|
||||
fn from(reject_reason: ProposalRejectReason) -> Self {
|
||||
Self::ErrorResponse {
|
||||
reject_reason,
|
||||
capabilities: GetBlockTemplate::capabilities(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProposalRejectReason> for Response {
|
||||
fn from(error_response: ProposalRejectReason) -> Self {
|
||||
Self::ProposalMode(ProposalResponse::from(error_response))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProposalResponse> for Response {
|
||||
fn from(proposal_response: ProposalResponse) -> Self {
|
||||
Self::ProposalMode(proposal_response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetBlockTemplate> for Response {
|
||||
fn from(template: GetBlockTemplate) -> Self {
|
||||
Self::TemplateMode(Box::new(template))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
use crate::methods::get_block_template_rpcs::types::{hex_data::HexData, long_poll::LongPollId};
|
||||
|
||||
/// Defines whether the RPC method should generate a block template or attempt to validate a block proposal.
|
||||
/// `Proposal` mode is currently unsupported and will return an error.
|
||||
#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum GetBlockTemplateRequestMode {
|
||||
|
|
@ -11,7 +10,6 @@ pub enum GetBlockTemplateRequestMode {
|
|||
Template,
|
||||
|
||||
/// Indicates a request to validate block data.
|
||||
/// Currently unsupported and will return an error.
|
||||
Proposal,
|
||||
}
|
||||
|
||||
|
|
@ -63,19 +61,17 @@ pub enum GetBlockTemplateCapability {
|
|||
/// All other fields are optional.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, Default)]
|
||||
pub struct JsonParameters {
|
||||
/// Must be set to "template" or omitted, as "proposal" mode is currently unsupported.
|
||||
///
|
||||
/// Defines whether the RPC method should generate a block template or attempt to
|
||||
/// validate block data, checking against all of the server's usual acceptance rules
|
||||
/// (excluding the check for a valid proof-of-work).
|
||||
// TODO: Support `proposal` mode.
|
||||
#[serde(default)]
|
||||
pub mode: GetBlockTemplateRequestMode,
|
||||
|
||||
/// Must be omitted as "proposal" mode is currently unsupported.
|
||||
/// Must be omitted when `getblocktemplate` RPC is called in "template" mode (or when `mode` is omitted).
|
||||
/// Must be provided when `getblocktemplate` RPC is called in "proposal" mode.
|
||||
///
|
||||
/// Hex-encoded block data to be validated and checked against the server's usual acceptance rules
|
||||
/// (excluding the check for a valid proof-of-work) when `mode` is set to `proposal`.
|
||||
/// (excluding the check for a valid proof-of-work).
|
||||
pub data: Option<HexData>,
|
||||
|
||||
/// A list of client-side supported capability features
|
||||
|
|
@ -87,4 +83,29 @@ pub struct JsonParameters {
|
|||
/// In Zebra, the ID represents the chain tip, max time, and mempool contents.
|
||||
#[serde(rename = "longpollid")]
|
||||
pub long_poll_id: Option<LongPollId>,
|
||||
|
||||
/// The workid for the block template.
|
||||
///
|
||||
/// currently unused.
|
||||
#[serde(rename = "workid")]
|
||||
pub _work_id: Option<String>,
|
||||
}
|
||||
|
||||
impl JsonParameters {
|
||||
/// Returns Some(data) with the block proposal hexdata if in `Proposal` mode and `data` is provided.
|
||||
pub fn block_proposal_data(&self) -> Option<HexData> {
|
||||
match self {
|
||||
Self { data: None, .. }
|
||||
| Self {
|
||||
mode: GetBlockTemplateRequestMode::Template,
|
||||
..
|
||||
} => None,
|
||||
|
||||
Self {
|
||||
mode: GetBlockTemplateRequestMode::Proposal,
|
||||
data,
|
||||
..
|
||||
} => data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,16 @@ use zebra_node_services::mempool;
|
|||
|
||||
use zebra_state::{GetBlockTemplateChainInfo, ReadRequest, ReadResponse};
|
||||
|
||||
use zebra_test::mock_service::{MockService, PanicAssertion};
|
||||
use zebra_test::{
|
||||
mock_service::{MockService, PanicAssertion},
|
||||
vectors::BLOCK_MAINNET_1_BYTES,
|
||||
};
|
||||
|
||||
use crate::methods::{
|
||||
get_block_template_rpcs::{
|
||||
self,
|
||||
types::{
|
||||
get_block_template::{self, GetBlockTemplate},
|
||||
get_block_template::{self, GetBlockTemplateRequestMode},
|
||||
get_mining_info,
|
||||
hex_data::HexData,
|
||||
long_poll::{LongPollId, LONG_POLL_ID_LENGTH},
|
||||
|
|
@ -159,12 +162,12 @@ pub async fn test_responses<State, ReadState>(
|
|||
// create a new rpc instance with new state and mock
|
||||
let get_block_template_rpc = GetBlockTemplateRpcImpl::new(
|
||||
network,
|
||||
mining_config,
|
||||
mining_config.clone(),
|
||||
Buffer::new(mempool.clone(), 1),
|
||||
new_read_state.clone(),
|
||||
mock_chain_tip,
|
||||
mock_chain_tip.clone(),
|
||||
chain_verifier,
|
||||
mock_sync_status,
|
||||
mock_sync_status.clone(),
|
||||
);
|
||||
|
||||
// Basic variant (default mode and no extra features)
|
||||
|
|
@ -195,10 +198,12 @@ pub async fn test_responses<State, ReadState>(
|
|||
.await
|
||||
.respond(mempool::Response::FullTransactions(vec![]));
|
||||
|
||||
let get_block_template = get_block_template
|
||||
let get_block_template::Response::TemplateMode(get_block_template) = get_block_template
|
||||
.await
|
||||
.expect("unexpected panic in getblocktemplate RPC task")
|
||||
.expect("unexpected error in getblocktemplate RPC call");
|
||||
.expect("unexpected error in getblocktemplate RPC call") else {
|
||||
panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response")
|
||||
};
|
||||
|
||||
let coinbase_tx: Transaction = get_block_template
|
||||
.coinbase_txn
|
||||
|
|
@ -207,7 +212,12 @@ pub async fn test_responses<State, ReadState>(
|
|||
.zcash_deserialize_into()
|
||||
.expect("coinbase bytes are valid");
|
||||
|
||||
snapshot_rpc_getblocktemplate("basic", get_block_template, coinbase_tx, &settings);
|
||||
snapshot_rpc_getblocktemplate(
|
||||
"basic",
|
||||
(*get_block_template).into(),
|
||||
Some(coinbase_tx),
|
||||
&settings,
|
||||
);
|
||||
|
||||
// long polling feature with submit old field
|
||||
|
||||
|
|
@ -250,10 +260,12 @@ pub async fn test_responses<State, ReadState>(
|
|||
.await
|
||||
.respond(mempool::Response::FullTransactions(vec![]));
|
||||
|
||||
let get_block_template = get_block_template
|
||||
let get_block_template::Response::TemplateMode(get_block_template) = get_block_template
|
||||
.await
|
||||
.expect("unexpected panic in getblocktemplate RPC task")
|
||||
.expect("unexpected error in getblocktemplate RPC call");
|
||||
.expect("unexpected error in getblocktemplate RPC call") else {
|
||||
panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response")
|
||||
};
|
||||
|
||||
let coinbase_tx: Transaction = get_block_template
|
||||
.coinbase_txn
|
||||
|
|
@ -262,7 +274,62 @@ pub async fn test_responses<State, ReadState>(
|
|||
.zcash_deserialize_into()
|
||||
.expect("coinbase bytes are valid");
|
||||
|
||||
snapshot_rpc_getblocktemplate("long_poll", get_block_template, coinbase_tx, &settings);
|
||||
snapshot_rpc_getblocktemplate(
|
||||
"long_poll",
|
||||
(*get_block_template).into(),
|
||||
Some(coinbase_tx),
|
||||
&settings,
|
||||
);
|
||||
|
||||
// `getblocktemplate` proposal mode variant
|
||||
|
||||
let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template(Some(
|
||||
get_block_template::JsonParameters {
|
||||
mode: GetBlockTemplateRequestMode::Proposal,
|
||||
data: Some(HexData("".into())),
|
||||
..Default::default()
|
||||
},
|
||||
)));
|
||||
|
||||
let get_block_template = get_block_template
|
||||
.await
|
||||
.expect("unexpected panic in getblocktemplate RPC task")
|
||||
.expect("unexpected error in getblocktemplate RPC call");
|
||||
|
||||
snapshot_rpc_getblocktemplate("invalid-proposal", get_block_template, None, &settings);
|
||||
|
||||
let mut mock_chain_verifier = MockService::build().for_unit_tests();
|
||||
let get_block_template_rpc = GetBlockTemplateRpcImpl::new(
|
||||
network,
|
||||
mining_config,
|
||||
Buffer::new(mempool.clone(), 1),
|
||||
new_read_state.clone(),
|
||||
mock_chain_tip,
|
||||
mock_chain_verifier.clone(),
|
||||
mock_sync_status,
|
||||
);
|
||||
|
||||
let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template(Some(
|
||||
get_block_template::JsonParameters {
|
||||
mode: GetBlockTemplateRequestMode::Proposal,
|
||||
data: Some(HexData(BLOCK_MAINNET_1_BYTES.to_vec())),
|
||||
..Default::default()
|
||||
},
|
||||
)));
|
||||
|
||||
tokio::spawn(async move {
|
||||
mock_chain_verifier
|
||||
.expect_request_that(|req| matches!(req, zebra_consensus::Request::CheckProposal(_)))
|
||||
.await
|
||||
.respond(Hash::from([0; 32]));
|
||||
});
|
||||
|
||||
let get_block_template = get_block_template
|
||||
.await
|
||||
.expect("unexpected panic in getblocktemplate RPC task")
|
||||
.expect("unexpected error in getblocktemplate RPC call");
|
||||
|
||||
snapshot_rpc_getblocktemplate("proposal", get_block_template, None, &settings);
|
||||
|
||||
// `submitblock`
|
||||
|
||||
|
|
@ -287,19 +354,22 @@ fn snapshot_rpc_getblockhash(block_hash: GetBlockHash, settings: &insta::Setting
|
|||
/// Snapshot `getblocktemplate` response, using `cargo insta` and JSON serialization.
|
||||
fn snapshot_rpc_getblocktemplate(
|
||||
variant: &'static str,
|
||||
block_template: GetBlockTemplate,
|
||||
coinbase_tx: Transaction,
|
||||
block_template: get_block_template::Response,
|
||||
coinbase_tx: Option<Transaction>,
|
||||
settings: &insta::Settings,
|
||||
) {
|
||||
settings.bind(|| {
|
||||
insta::assert_json_snapshot!(format!("get_block_template_{variant}"), block_template)
|
||||
});
|
||||
settings.bind(|| {
|
||||
insta::assert_ron_snapshot!(
|
||||
format!("get_block_template_{variant}.coinbase_tx"),
|
||||
coinbase_tx
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(coinbase_tx) = coinbase_tx {
|
||||
settings.bind(|| {
|
||||
insta::assert_ron_snapshot!(
|
||||
format!("get_block_template_{variant}.coinbase_tx"),
|
||||
coinbase_tx
|
||||
)
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/// Snapshot `submitblock` response, using `cargo insta` and JSON serialization.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
|||
expression: block_template
|
||||
---
|
||||
{
|
||||
"capabilities": [],
|
||||
"capabilities": [
|
||||
"proposal"
|
||||
],
|
||||
"version": 4,
|
||||
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
|
||||
"blockcommitmentshash": "02990723c6b62a724651322d141b4a72a4ffd66518167d809badbd5117d5518a",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
|||
expression: block_template
|
||||
---
|
||||
{
|
||||
"capabilities": [],
|
||||
"capabilities": [
|
||||
"proposal"
|
||||
],
|
||||
"version": 4,
|
||||
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
|
||||
"blockcommitmentshash": "3b25791957f9383b6ce851d728a78309664d5d7a82ca87b6a9125a2f2c529792",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
{
|
||||
"capabilities": [],
|
||||
"capabilities": [
|
||||
"proposal"
|
||||
],
|
||||
"version": 4,
|
||||
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
|
||||
"blockcommitmentshash": "02990723c6b62a724651322d141b4a72a4ffd66518167d809badbd5117d5518a",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
|||
expression: block_template
|
||||
---
|
||||
{
|
||||
"capabilities": [],
|
||||
"capabilities": [
|
||||
"proposal"
|
||||
],
|
||||
"version": 4,
|
||||
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
|
||||
"blockcommitmentshash": "3b25791957f9383b6ce851d728a78309664d5d7a82ca87b6a9125a2f2c529792",
|
||||
|
|
|
|||
|
|
@ -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, ..) = tokio::join!(
|
||||
get_block_template_fut,
|
||||
mock_mempool_request_handler,
|
||||
mock_read_state_request_handler,
|
||||
);
|
||||
|
||||
let get_block_template =
|
||||
get_block_template.expect("unexpected error in getblocktemplate RPC call");
|
||||
let get_block_template::Response::TemplateMode(get_block_template) = get_block_template
|
||||
.expect("unexpected error in getblocktemplate RPC call") else {
|
||||
panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response")
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
get_block_template.capabilities,
|
||||
|
|
@ -1099,7 +1100,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
|
|||
..Default::default()
|
||||
}))
|
||||
.await
|
||||
.expect_err("needs an error when using unsupported mode");
|
||||
.expect_err("needs an error when called in proposal mode without data");
|
||||
|
||||
assert_eq!(get_block_template_sync_error.code, ErrorCode::InvalidParams);
|
||||
|
||||
|
|
@ -1109,7 +1110,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
|
|||
..Default::default()
|
||||
}))
|
||||
.await
|
||||
.expect_err("needs an error when passing in block data");
|
||||
.expect_err("needs an error when passing in block data in template mode");
|
||||
|
||||
assert_eq!(get_block_template_sync_error.code, ErrorCode::InvalidParams);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//! See the full list of
|
||||
//! [Differences between JSON-RPC 1.0 and 2.0.](https://www.simple-is-better.org/rpc/#differences-between-1-0-and-2-0)
|
||||
|
||||
use std::{fmt, panic, sync::Arc};
|
||||
use std::{fmt, panic};
|
||||
|
||||
use jsonrpc_core::{Compatibility, MetaIoHandler};
|
||||
use jsonrpc_http_server::{CloseHandle, ServerBuilder};
|
||||
|
|
@ -17,10 +17,7 @@ use tower::{buffer::Buffer, Service};
|
|||
use tracing::{Instrument, *};
|
||||
|
||||
use zebra_chain::{
|
||||
block::{self, Block},
|
||||
chain_sync_status::ChainSyncStatus,
|
||||
chain_tip::ChainTip,
|
||||
parameters::Network,
|
||||
block, chain_sync_status::ChainSyncStatus, chain_tip::ChainTip, parameters::Network,
|
||||
};
|
||||
use zebra_node_services::mempool;
|
||||
|
||||
|
|
@ -107,12 +104,15 @@ impl RpcServer {
|
|||
+ 'static,
|
||||
State::Future: Send,
|
||||
Tip: ChainTip + Clone + Send + Sync + 'static,
|
||||
ChainVerifier: Service<Arc<Block>, Response = block::Hash, Error = zebra_consensus::BoxError>
|
||||
+ Clone
|
||||
ChainVerifier: Service<
|
||||
zebra_consensus::Request,
|
||||
Response = block::Hash,
|
||||
Error = zebra_consensus::BoxError,
|
||||
> + Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
<ChainVerifier as Service<Arc<Block>>>::Future: Send,
|
||||
<ChainVerifier as Service<zebra_consensus::Request>>::Future: Send,
|
||||
SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
|
||||
{
|
||||
if let Some(listen_addr) = config.listen_addr {
|
||||
|
|
|
|||
|
|
@ -558,6 +558,13 @@ pub enum Request {
|
|||
///
|
||||
/// Returns [`Response::ValidBestChainTipNullifiersAndAnchors`]
|
||||
CheckBestChainTipNullifiersAndAnchors(UnminedTx),
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
/// Performs contextual validation of the given block, but does not commit it to the state.
|
||||
///
|
||||
/// Returns [`Response::ValidBlockProposal`] when successful.
|
||||
/// See `[ReadRequest::CheckBlockProposalValidity]` for details.
|
||||
CheckBlockProposalValidity(PreparedBlock),
|
||||
}
|
||||
|
||||
impl Request {
|
||||
|
|
@ -577,6 +584,8 @@ impl Request {
|
|||
Request::CheckBestChainTipNullifiersAndAnchors(_) => {
|
||||
"best_chain_tip_nullifiers_anchors"
|
||||
}
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
Request::CheckBlockProposalValidity(_) => "check_block_proposal_validity",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -790,6 +799,16 @@ pub enum ReadRequest {
|
|||
/// Optionally estimate the network speed at the time when a certain block was found
|
||||
height: Option<block::Height>,
|
||||
},
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
/// Performs contextual validation of the given block, but does not commit it to the state.
|
||||
///
|
||||
/// It is the caller's responsibility to perform semantic validation.
|
||||
/// (The caller does not need to check proof of work for block proposals.)
|
||||
///
|
||||
/// Returns [`ReadResponse::ValidBlockProposal`] when successful, or an error if
|
||||
/// the block fails contextual validation.
|
||||
CheckBlockProposalValidity(PreparedBlock),
|
||||
}
|
||||
|
||||
impl ReadRequest {
|
||||
|
|
@ -819,6 +838,8 @@ impl ReadRequest {
|
|||
ReadRequest::ChainInfo => "chain_info",
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
ReadRequest::SolutionRate { .. } => "solution_rate",
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
ReadRequest::CheckBlockProposalValidity(_) => "check_block_proposal_validity",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -866,6 +887,11 @@ impl TryFrom<Request> for ReadRequest {
|
|||
Err("ReadService does not write blocks")
|
||||
}
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
Request::CheckBlockProposalValidity(prepared) => {
|
||||
Ok(ReadRequest::CheckBlockProposalValidity(prepared))
|
||||
}
|
||||
|
||||
Request::AwaitUtxo(_) => Err("ReadService does not track pending UTXOs. \
|
||||
Manually convert the request to ReadRequest::AnyChainUtxo, \
|
||||
and handle pending UTXOs"),
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ pub enum Response {
|
|||
///
|
||||
/// Does not check transparent UTXO inputs
|
||||
ValidBestChainTipNullifiersAndAnchors,
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
/// Response to [`Request::CheckBlockProposalValidity`](crate::Request::CheckBlockProposalValidity)
|
||||
ValidBlockProposal,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
|
@ -137,6 +141,10 @@ pub enum ReadResponse {
|
|||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
/// Response to [`ReadRequest::SolutionRate`](crate::ReadRequest::SolutionRate)
|
||||
SolutionRate(Option<u128>),
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
/// Response to [`ReadRequest::CheckBlockProposalValidity`](crate::ReadRequest::CheckBlockProposalValidity)
|
||||
ValidBlockProposal,
|
||||
}
|
||||
|
||||
/// A structure with the information needed from the state to build a `getblocktemplate` RPC response.
|
||||
|
|
@ -215,6 +223,9 @@ impl TryFrom<ReadResponse> for Response {
|
|||
Err("there is no corresponding Response for this ReadResponse")
|
||||
}
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
ReadResponse::ValidBlockProposal => Ok(Response::ValidBlockProposal),
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
ReadResponse::BlockHash(_) => {
|
||||
Err("there is no corresponding Response for this ReadResponse")
|
||||
|
|
|
|||
|
|
@ -1047,6 +1047,24 @@ impl Service<Request> for StateService {
|
|||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
Request::CheckBlockProposalValidity(_) => {
|
||||
// Redirect the request to the concurrent ReadStateService
|
||||
let read_service = self.read_service.clone();
|
||||
|
||||
async move {
|
||||
let req = req
|
||||
.try_into()
|
||||
.expect("ReadRequest conversion should not fail");
|
||||
|
||||
let rsp = read_service.oneshot(req).await?;
|
||||
let rsp = rsp.try_into().expect("Response conversion should not fail");
|
||||
|
||||
Ok(rsp)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1082,7 +1100,7 @@ impl Service<ReadRequest> for ReadStateService {
|
|||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
#[instrument(name = "read_state", skip(self))]
|
||||
#[instrument(name = "read_state", skip(self, req))]
|
||||
fn call(&mut self, req: ReadRequest) -> Self::Future {
|
||||
req.count_metric();
|
||||
|
||||
|
|
@ -1688,6 +1706,58 @@ impl Service<ReadRequest> for ReadStateService {
|
|||
.map(|join_result| join_result.expect("panic in ReadRequest::ChainInfo"))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
ReadRequest::CheckBlockProposalValidity(prepared) => {
|
||||
let timer = CodeTimer::start();
|
||||
|
||||
let state = self.clone();
|
||||
|
||||
// # Performance
|
||||
//
|
||||
// Allow other async tasks to make progress while concurrently reading blocks from disk.
|
||||
let span = Span::current();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
span.in_scope(move || {
|
||||
tracing::info!("attempting to validate and commit block proposal onto a cloned non-finalized state");
|
||||
let mut latest_non_finalized_state = state.latest_non_finalized_state();
|
||||
|
||||
// The previous block of a valid proposal must be on the best chain tip.
|
||||
let Some((_best_tip_height, best_tip_hash)) = read::best_tip(&latest_non_finalized_state, &state.db) else {
|
||||
return Err("state is empty: wait for Zebra to sync before submitting a proposal".into());
|
||||
};
|
||||
|
||||
if prepared.block.header.previous_block_hash != best_tip_hash {
|
||||
return Err("proposal is not based on the current best chain tip: previous block hash must be the best chain tip".into());
|
||||
}
|
||||
|
||||
// This clone of the non-finalized state is dropped when this closure returns.
|
||||
// The non-finalized state that's used in the rest of the state (including finalizing
|
||||
// blocks into the db) is not mutated here.
|
||||
//
|
||||
// TODO: Convert `CommitBlockError` to a new `ValidateProposalError`?
|
||||
latest_non_finalized_state.should_count_metrics = false;
|
||||
write::validate_and_commit_non_finalized(
|
||||
&state.db,
|
||||
&mut latest_non_finalized_state,
|
||||
prepared,
|
||||
)?;
|
||||
|
||||
// The work is done in the future.
|
||||
timer.finish(
|
||||
module_path!(),
|
||||
line!(),
|
||||
"ReadRequest::CheckBlockProposalValidity",
|
||||
);
|
||||
|
||||
Ok(ReadResponse::ValidBlockProposal)
|
||||
})
|
||||
})
|
||||
.map(|join_result| {
|
||||
join_result.expect("panic in ReadRequest::CheckBlockProposalValidity")
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use zebra_chain::{
|
|||
use crate::{
|
||||
service::{
|
||||
block_iter::any_ancestor_blocks, check::difficulty::POW_ADJUSTMENT_BLOCK_SPAN,
|
||||
finalized_state::FinalizedState, non_finalized_state::NonFinalizedState,
|
||||
finalized_state::ZebraDb, non_finalized_state::NonFinalizedState,
|
||||
},
|
||||
BoxError, PreparedBlock, ValidateContextError,
|
||||
};
|
||||
|
|
@ -365,25 +365,25 @@ where
|
|||
///
|
||||
/// Additional contextual validity checks are performed by the non-finalized [`Chain`].
|
||||
pub(crate) fn initial_contextual_validity(
|
||||
finalized_state: &FinalizedState,
|
||||
finalized_state: &ZebraDb,
|
||||
non_finalized_state: &NonFinalizedState,
|
||||
prepared: &PreparedBlock,
|
||||
) -> Result<(), ValidateContextError> {
|
||||
let relevant_chain = any_ancestor_blocks(
|
||||
non_finalized_state,
|
||||
&finalized_state.db,
|
||||
finalized_state,
|
||||
prepared.block.header.previous_block_hash,
|
||||
);
|
||||
|
||||
// Security: check proof of work before any other checks
|
||||
check::block_is_valid_for_recent_chain(
|
||||
prepared,
|
||||
finalized_state.network(),
|
||||
finalized_state.db.finalized_tip_height(),
|
||||
non_finalized_state.network,
|
||||
finalized_state.finalized_tip_height(),
|
||||
relevant_chain,
|
||||
)?;
|
||||
|
||||
check::nullifier::no_duplicates_in_finalized_chain(prepared, &finalized_state.db)?;
|
||||
check::nullifier::no_duplicates_in_finalized_chain(prepared, finalized_state)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,10 +81,12 @@ fn check_sprout_anchors() {
|
|||
|
||||
// Validate and commit [`block_1`]. This will add an anchor referencing the
|
||||
// empty note commitment tree to the state.
|
||||
assert!(
|
||||
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block_1)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(validate_and_commit_non_finalized(
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block_1
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let check_unmined_tx_anchors_result = unmined_txs.iter().try_for_each(|unmined_tx| {
|
||||
tx_anchors_refer_to_final_treestates(
|
||||
|
|
@ -98,7 +100,7 @@ fn check_sprout_anchors() {
|
|||
|
||||
// Validate and commit [`block_2`]. This will also check the anchors.
|
||||
assert_eq!(
|
||||
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block_2),
|
||||
validate_and_commit_non_finalized(&finalized_state.db, &mut non_finalized_state, block_2),
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
|
|
@ -288,10 +290,12 @@ fn check_sapling_anchors() {
|
|||
Err(ValidateContextError::UnknownSaplingAnchor { .. })
|
||||
));
|
||||
|
||||
assert!(
|
||||
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block1)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(validate_and_commit_non_finalized(
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let check_unmined_tx_anchors_result = unmined_txs.iter().try_for_each(|unmined_tx| {
|
||||
tx_anchors_refer_to_final_treestates(
|
||||
|
|
@ -304,7 +308,7 @@ fn check_sapling_anchors() {
|
|||
assert!(check_unmined_tx_anchors_result.is_ok());
|
||||
|
||||
assert_eq!(
|
||||
validate_and_commit_non_finalized(&finalized_state, &mut non_finalized_state, block2),
|
||||
validate_and_commit_non_finalized(&finalized_state.db, &mut non_finalized_state, block2),
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ proptest! {
|
|||
} else {
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1.clone()
|
||||
);
|
||||
|
|
@ -156,7 +156,7 @@ proptest! {
|
|||
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1
|
||||
);
|
||||
|
|
@ -217,7 +217,7 @@ proptest! {
|
|||
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1
|
||||
);
|
||||
|
|
@ -278,8 +278,10 @@ proptest! {
|
|||
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block1);
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1
|
||||
);
|
||||
|
||||
prop_assert_eq!(
|
||||
commit_result,
|
||||
|
|
@ -364,7 +366,7 @@ proptest! {
|
|||
} else {
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1.clone()
|
||||
);
|
||||
|
|
@ -383,7 +385,7 @@ proptest! {
|
|||
|
||||
let block2 = Arc::new(block2).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block2
|
||||
);
|
||||
|
|
@ -459,8 +461,10 @@ proptest! {
|
|||
} else {
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block1.clone());
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1.clone()
|
||||
);
|
||||
|
||||
prop_assert_eq!(commit_result, Ok(()));
|
||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
|
|
@ -506,8 +510,10 @@ proptest! {
|
|||
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block1);
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1
|
||||
);
|
||||
|
||||
prop_assert_eq!(
|
||||
commit_result,
|
||||
|
|
@ -560,7 +566,7 @@ proptest! {
|
|||
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1
|
||||
);
|
||||
|
|
@ -639,8 +645,10 @@ proptest! {
|
|||
} else {
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block1.clone());
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1.clone()
|
||||
);
|
||||
|
||||
prop_assert_eq!(commit_result, Ok(()));
|
||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
|
|
@ -655,8 +663,10 @@ proptest! {
|
|||
|
||||
let block2 = Arc::new(block2).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block2);
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block2
|
||||
);
|
||||
|
||||
prop_assert_eq!(
|
||||
commit_result,
|
||||
|
|
@ -731,8 +741,10 @@ proptest! {
|
|||
} else {
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block1.clone());
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1.clone()
|
||||
);
|
||||
|
||||
prop_assert_eq!(commit_result, Ok(()));
|
||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
|
|
@ -779,8 +791,10 @@ proptest! {
|
|||
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block1);
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1
|
||||
);
|
||||
|
||||
prop_assert_eq!(
|
||||
commit_result,
|
||||
|
|
@ -837,8 +851,10 @@ proptest! {
|
|||
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block1);
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1
|
||||
);
|
||||
|
||||
prop_assert_eq!(
|
||||
commit_result,
|
||||
|
|
@ -918,8 +934,10 @@ proptest! {
|
|||
} else {
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block1.clone());
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1.clone()
|
||||
);
|
||||
|
||||
prop_assert_eq!(commit_result, Ok(()));
|
||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
|
|
@ -933,8 +951,10 @@ proptest! {
|
|||
|
||||
let block2 = Arc::new(block2).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block2);
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block2
|
||||
);
|
||||
|
||||
prop_assert_eq!(
|
||||
commit_result,
|
||||
|
|
|
|||
|
|
@ -194,8 +194,10 @@ proptest! {
|
|||
} else {
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block1.clone());
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1.clone()
|
||||
);
|
||||
|
||||
// the block was committed
|
||||
prop_assert_eq!(commit_result, Ok(()));
|
||||
|
|
@ -279,8 +281,10 @@ proptest! {
|
|||
} else {
|
||||
let block2 = Arc::new(block2).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block2.clone());
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block2.clone()
|
||||
);
|
||||
|
||||
// the block was committed
|
||||
prop_assert_eq!(commit_result, Ok(()));
|
||||
|
|
@ -357,8 +361,10 @@ proptest! {
|
|||
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block1);
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1
|
||||
);
|
||||
|
||||
// the block was rejected
|
||||
prop_assert_eq!(
|
||||
|
|
@ -419,8 +425,10 @@ proptest! {
|
|||
|
||||
let block2 = Arc::new(block2).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block2);
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block2
|
||||
);
|
||||
|
||||
// the block was rejected
|
||||
prop_assert_eq!(
|
||||
|
|
@ -503,8 +511,10 @@ proptest! {
|
|||
|
||||
let block2 = Arc::new(block2).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block2);
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block2
|
||||
);
|
||||
|
||||
// the block was rejected
|
||||
prop_assert_eq!(
|
||||
|
|
@ -615,8 +625,10 @@ proptest! {
|
|||
} else {
|
||||
let block2 = block2.clone().prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block2.clone());
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block2.clone()
|
||||
);
|
||||
|
||||
// the block was committed
|
||||
prop_assert_eq!(commit_result, Ok(()));
|
||||
|
|
@ -651,8 +663,10 @@ proptest! {
|
|||
|
||||
let block3 = Arc::new(block3).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block3);
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block3
|
||||
);
|
||||
|
||||
// the block was rejected
|
||||
if use_finalized_state_spend {
|
||||
|
|
@ -725,8 +739,10 @@ proptest! {
|
|||
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block1);
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1
|
||||
);
|
||||
|
||||
// the block was rejected
|
||||
prop_assert_eq!(
|
||||
|
|
@ -790,8 +806,10 @@ proptest! {
|
|||
|
||||
let block1 = Arc::new(block1).prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&mut non_finalized_state, block1);
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1
|
||||
);
|
||||
|
||||
// the block was rejected
|
||||
prop_assert_eq!(
|
||||
|
|
@ -885,7 +903,7 @@ fn new_state_with_mainnet_transparent_data(
|
|||
} else {
|
||||
let block1 = block1.clone().prepare();
|
||||
let commit_result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
block1.clone(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -44,9 +44,14 @@ pub struct NonFinalizedState {
|
|||
pub chain_set: BTreeSet<Arc<Chain>>,
|
||||
|
||||
/// The configured Zcash network.
|
||||
//
|
||||
// Note: this field is currently unused, but it's useful for debugging.
|
||||
pub network: Network,
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
/// Configures the non-finalized state to count metrics.
|
||||
///
|
||||
/// Used for skipping metrics counting when testing block proposals
|
||||
/// with a commit to a cloned non-finalized state.
|
||||
pub should_count_metrics: bool,
|
||||
}
|
||||
|
||||
impl NonFinalizedState {
|
||||
|
|
@ -55,6 +60,8 @@ impl NonFinalizedState {
|
|||
NonFinalizedState {
|
||||
chain_set: Default::default(),
|
||||
network,
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
should_count_metrics: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -494,6 +501,11 @@ impl NonFinalizedState {
|
|||
|
||||
/// Update the metrics after `block` is committed
|
||||
fn update_metrics_for_committed_block(&self, height: block::Height, hash: block::Hash) {
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
if !self.should_count_metrics {
|
||||
return;
|
||||
}
|
||||
|
||||
metrics::counter!("state.memory.committed.block.count", 1);
|
||||
metrics::gauge!("state.memory.committed.block.height", height.0 as f64);
|
||||
|
||||
|
|
@ -517,6 +529,11 @@ impl NonFinalizedState {
|
|||
|
||||
/// Update the metrics after `self.chain_set` is modified
|
||||
fn update_metrics_for_chains(&self) {
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
if !self.should_count_metrics {
|
||||
return;
|
||||
}
|
||||
|
||||
metrics::gauge!("state.memory.chain.count", self.chain_set.len() as f64);
|
||||
metrics::gauge!(
|
||||
"state.memory.best.chain.length",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use crate::{
|
|||
constants::MAX_BLOCK_REORG_HEIGHT,
|
||||
service::{
|
||||
check,
|
||||
finalized_state::FinalizedState,
|
||||
finalized_state::{FinalizedState, ZebraDb},
|
||||
non_finalized_state::NonFinalizedState,
|
||||
queued_blocks::{QueuedFinalized, QueuedNonFinalized},
|
||||
BoxError, ChainTipBlock, ChainTipSender, CloneError,
|
||||
|
|
@ -36,17 +36,17 @@ const PARENT_ERROR_MAP_LIMIT: usize = MAX_BLOCK_REORG_HEIGHT as usize * 2;
|
|||
/// non-finalized state if it is contextually valid.
|
||||
#[tracing::instrument(level = "debug", skip(prepared), fields(height = ?prepared.height, hash = %prepared.hash))]
|
||||
pub(crate) fn validate_and_commit_non_finalized(
|
||||
finalized_state: &FinalizedState,
|
||||
finalized_state: &ZebraDb,
|
||||
non_finalized_state: &mut NonFinalizedState,
|
||||
prepared: PreparedBlock,
|
||||
) -> Result<(), CommitBlockError> {
|
||||
check::initial_contextual_validity(finalized_state, non_finalized_state, &prepared)?;
|
||||
let parent_hash = prepared.block.header.previous_block_hash;
|
||||
|
||||
if finalized_state.db.finalized_tip_hash() == parent_hash {
|
||||
non_finalized_state.commit_new_chain(prepared, &finalized_state.db)?;
|
||||
if finalized_state.finalized_tip_hash() == parent_hash {
|
||||
non_finalized_state.commit_new_chain(prepared, finalized_state)?;
|
||||
} else {
|
||||
non_finalized_state.commit_block(prepared, &finalized_state.db)?;
|
||||
non_finalized_state.commit_block(prepared, finalized_state)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -205,7 +205,7 @@ pub fn write_blocks_from_channels(
|
|||
} else {
|
||||
tracing::trace!(?child_hash, "validating queued child");
|
||||
result = validate_and_commit_non_finalized(
|
||||
&finalized_state,
|
||||
&finalized_state.db,
|
||||
&mut non_finalized_state,
|
||||
queued_child,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,10 +24,7 @@ use tower::{buffer::Buffer, timeout::Timeout, util::BoxService, Service, Service
|
|||
use zebra_network as zn;
|
||||
use zebra_state as zs;
|
||||
|
||||
use zebra_chain::{
|
||||
block::{self, Block},
|
||||
transaction::UnminedTxId,
|
||||
};
|
||||
use zebra_chain::{block, transaction::UnminedTxId};
|
||||
use zebra_consensus::chain::VerifyChainError;
|
||||
use zebra_network::{
|
||||
constants::{ADDR_RESPONSE_LIMIT_DENOMINATOR, MAX_ADDRS_IN_MESSAGE},
|
||||
|
|
@ -53,7 +50,10 @@ type BlockDownloadPeerSet =
|
|||
Buffer<BoxService<zn::Request, zn::Response, zn::BoxError>, zn::Request>;
|
||||
type State = Buffer<BoxService<zs::Request, zs::Response, zs::BoxError>, zs::Request>;
|
||||
type Mempool = Buffer<BoxService<mempool::Request, mempool::Response, BoxError>, mempool::Request>;
|
||||
type BlockVerifier = Buffer<BoxService<Arc<Block>, block::Hash, VerifyChainError>, Arc<Block>>;
|
||||
type BlockVerifier = Buffer<
|
||||
BoxService<zebra_consensus::Request, block::Hash, VerifyChainError>,
|
||||
zebra_consensus::Request,
|
||||
>;
|
||||
type GossipedBlockDownloads =
|
||||
BlockDownloads<Timeout<BlockDownloadPeerSet>, Timeout<BlockVerifier>, State>;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use std::{
|
|||
collections::HashMap,
|
||||
convert::TryFrom,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
|
|
@ -18,10 +17,7 @@ use tokio::{sync::oneshot, task::JoinHandle};
|
|||
use tower::{Service, ServiceExt};
|
||||
use tracing_futures::Instrument;
|
||||
|
||||
use zebra_chain::{
|
||||
block::{self, Block},
|
||||
chain_tip::ChainTip,
|
||||
};
|
||||
use zebra_chain::{block, chain_tip::ChainTip};
|
||||
use zebra_network as zn;
|
||||
use zebra_state as zs;
|
||||
|
||||
|
|
@ -77,7 +73,10 @@ pub struct Downloads<ZN, ZV, ZS>
|
|||
where
|
||||
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Clone + 'static,
|
||||
ZN::Future: Send,
|
||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError> + Send + Clone + 'static,
|
||||
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||
+ Send
|
||||
+ Clone
|
||||
+ 'static,
|
||||
ZV::Future: Send,
|
||||
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
||||
ZS::Future: Send,
|
||||
|
|
@ -117,7 +116,10 @@ impl<ZN, ZV, ZS> Stream for Downloads<ZN, ZV, ZS>
|
|||
where
|
||||
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Clone + 'static,
|
||||
ZN::Future: Send,
|
||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError> + Send + Clone + 'static,
|
||||
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||
+ Send
|
||||
+ Clone
|
||||
+ 'static,
|
||||
ZV::Future: Send,
|
||||
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
||||
ZS::Future: Send,
|
||||
|
|
@ -160,7 +162,10 @@ impl<ZN, ZV, ZS> Downloads<ZN, ZV, ZS>
|
|||
where
|
||||
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Clone + 'static,
|
||||
ZN::Future: Send,
|
||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError> + Send + Clone + 'static,
|
||||
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||
+ Send
|
||||
+ Clone
|
||||
+ 'static,
|
||||
ZV::Future: Send,
|
||||
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
||||
ZS::Future: Send,
|
||||
|
|
@ -338,7 +343,7 @@ where
|
|||
}
|
||||
|
||||
verifier
|
||||
.oneshot(block)
|
||||
.oneshot(zebra_consensus::Request::Commit(block))
|
||||
.await
|
||||
.map(|hash| (hash, block_height))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Inbound service tests with a real peer set.
|
||||
|
||||
use std::{iter, net::SocketAddr, sync::Arc};
|
||||
use std::{iter, net::SocketAddr};
|
||||
|
||||
use futures::FutureExt;
|
||||
use indexmap::IndexSet;
|
||||
|
|
@ -13,7 +13,7 @@ use tower::{
|
|||
};
|
||||
|
||||
use zebra_chain::{
|
||||
block::{self, Block, Height},
|
||||
block::{self, Height},
|
||||
parameters::Network,
|
||||
serialization::ZcashDeserializeInto,
|
||||
transaction::{AuthDigest, Hash as TxHash, Transaction, UnminedTx, UnminedTxId, WtxId},
|
||||
|
|
@ -603,7 +603,7 @@ async fn setup(
|
|||
Buffer<BoxService<mempool::Request, mempool::Response, BoxError>, mempool::Request>,
|
||||
Buffer<BoxService<zebra_state::Request, zebra_state::Response, BoxError>, zebra_state::Request>,
|
||||
// mocked services
|
||||
MockService<Arc<Block>, block::Hash, PanicAssertion, VerifyChainError>,
|
||||
MockService<zebra_consensus::Request, block::Hash, PanicAssertion, VerifyChainError>,
|
||||
MockService<transaction::Request, transaction::Response, PanicAssertion, TransactionError>,
|
||||
// real tasks
|
||||
JoinHandle<Result<(), BlockGossipError>>,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! It is used when Zebra is a long way behind the current chain tip.
|
||||
|
||||
use std::{cmp::max, collections::HashSet, pin::Pin, sync::Arc, task::Poll, time::Duration};
|
||||
use std::{cmp::max, collections::HashSet, pin::Pin, task::Poll, time::Duration};
|
||||
|
||||
use color_eyre::eyre::{eyre, Report};
|
||||
use futures::stream::{FuturesUnordered, StreamExt};
|
||||
|
|
@ -15,7 +15,7 @@ use tower::{
|
|||
};
|
||||
|
||||
use zebra_chain::{
|
||||
block::{self, Block, Height},
|
||||
block::{self, Height},
|
||||
chain_tip::ChainTip,
|
||||
parameters::genesis_hash,
|
||||
};
|
||||
|
|
@ -300,7 +300,7 @@ where
|
|||
+ Clone
|
||||
+ 'static,
|
||||
ZS::Future: Send,
|
||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
|
||||
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
|
|
@ -381,7 +381,7 @@ where
|
|||
+ Clone
|
||||
+ 'static,
|
||||
ZS::Future: Send,
|
||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
|
||||
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use tower::{hedge, Service, ServiceExt};
|
|||
use tracing_futures::Instrument;
|
||||
|
||||
use zebra_chain::{
|
||||
block::{self, Block, Height},
|
||||
block::{self, Height},
|
||||
chain_tip::ChainTip,
|
||||
};
|
||||
use zebra_network as zn;
|
||||
|
|
@ -163,7 +163,7 @@ pub struct Downloads<ZN, ZV, ZSTip>
|
|||
where
|
||||
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Sync + 'static,
|
||||
ZN::Future: Send,
|
||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
|
||||
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
|
|
@ -217,7 +217,7 @@ impl<ZN, ZV, ZSTip> Stream for Downloads<ZN, ZV, ZSTip>
|
|||
where
|
||||
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Sync + 'static,
|
||||
ZN::Future: Send,
|
||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
|
||||
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
|
|
@ -264,7 +264,7 @@ impl<ZN, ZV, ZSTip> Downloads<ZN, ZV, ZSTip>
|
|||
where
|
||||
ZN: Service<zn::Request, Response = zn::Response, Error = BoxError> + Send + Sync + 'static,
|
||||
ZN::Future: Send,
|
||||
ZV: Service<Arc<Block>, Response = block::Hash, Error = BoxError>
|
||||
ZV: Service<zebra_consensus::Request, Response = block::Hash, Error = BoxError>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
|
|
@ -516,7 +516,7 @@ where
|
|||
// Verify the block.
|
||||
let mut rsp = verifier
|
||||
.map_err(|error| BlockDownloadVerifyError::VerifierServiceError { error })?
|
||||
.call(block).boxed();
|
||||
.call(zebra_consensus::Request::Commit(block)).boxed();
|
||||
|
||||
// Add a shorter timeout to workaround a known bug (#5125)
|
||||
let short_timeout_max = (max_checkpoint_height + FINAL_CHECKPOINT_BLOCK_VERIFY_TIMEOUT_LIMIT).expect("checkpoint block height is in valid range");
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ async fn sync_blocks_ok() -> Result<(), crate::BoxError> {
|
|||
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
|
||||
|
||||
chain_verifier
|
||||
.expect_request(block0)
|
||||
.expect_request(zebra_consensus::Request::Commit(block0))
|
||||
.await
|
||||
.respond(block0_hash);
|
||||
|
||||
|
|
@ -175,9 +175,9 @@ async fn sync_blocks_ok() -> Result<(), crate::BoxError> {
|
|||
|
||||
for _ in 1..=2 {
|
||||
chain_verifier
|
||||
.expect_request_that(|req| remaining_blocks.remove(&req.hash()).is_some())
|
||||
.expect_request_that(|req| remaining_blocks.remove(&req.block().hash()).is_some())
|
||||
.await
|
||||
.respond_with(|req| req.hash());
|
||||
.respond_with(|req| req.block().hash());
|
||||
}
|
||||
assert_eq!(
|
||||
remaining_blocks,
|
||||
|
|
@ -239,9 +239,9 @@ async fn sync_blocks_ok() -> Result<(), crate::BoxError> {
|
|||
|
||||
for _ in 3..=4 {
|
||||
chain_verifier
|
||||
.expect_request_that(|req| remaining_blocks.remove(&req.hash()).is_some())
|
||||
.expect_request_that(|req| remaining_blocks.remove(&req.block().hash()).is_some())
|
||||
.await
|
||||
.respond_with(|req| req.hash());
|
||||
.respond_with(|req| req.block().hash());
|
||||
}
|
||||
assert_eq!(
|
||||
remaining_blocks,
|
||||
|
|
@ -316,7 +316,7 @@ async fn sync_blocks_duplicate_hashes_ok() -> Result<(), crate::BoxError> {
|
|||
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
|
||||
|
||||
chain_verifier
|
||||
.expect_request(block0)
|
||||
.expect_request(zebra_consensus::Request::Commit(block0))
|
||||
.await
|
||||
.respond(block0_hash);
|
||||
|
||||
|
|
@ -404,9 +404,9 @@ async fn sync_blocks_duplicate_hashes_ok() -> Result<(), crate::BoxError> {
|
|||
|
||||
for _ in 1..=2 {
|
||||
chain_verifier
|
||||
.expect_request_that(|req| remaining_blocks.remove(&req.hash()).is_some())
|
||||
.expect_request_that(|req| remaining_blocks.remove(&req.block().hash()).is_some())
|
||||
.await
|
||||
.respond_with(|req| req.hash());
|
||||
.respond_with(|req| req.block().hash());
|
||||
}
|
||||
assert_eq!(
|
||||
remaining_blocks,
|
||||
|
|
@ -470,9 +470,9 @@ async fn sync_blocks_duplicate_hashes_ok() -> Result<(), crate::BoxError> {
|
|||
|
||||
for _ in 3..=4 {
|
||||
chain_verifier
|
||||
.expect_request_that(|req| remaining_blocks.remove(&req.hash()).is_some())
|
||||
.expect_request_that(|req| remaining_blocks.remove(&req.block().hash()).is_some())
|
||||
.await
|
||||
.respond_with(|req| req.hash());
|
||||
.respond_with(|req| req.block().hash());
|
||||
}
|
||||
assert_eq!(
|
||||
remaining_blocks,
|
||||
|
|
@ -598,7 +598,7 @@ async fn sync_block_too_high_obtain_tips() -> Result<(), crate::BoxError> {
|
|||
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
|
||||
|
||||
chain_verifier
|
||||
.expect_request(block0)
|
||||
.expect_request(zebra_consensus::Request::Commit(block0))
|
||||
.await
|
||||
.respond(block0_hash);
|
||||
|
||||
|
|
@ -759,7 +759,7 @@ async fn sync_block_too_high_extend_tips() -> Result<(), crate::BoxError> {
|
|||
.respond(zn::Response::Blocks(vec![Available(block0.clone())]));
|
||||
|
||||
chain_verifier
|
||||
.expect_request(block0)
|
||||
.expect_request(zebra_consensus::Request::Commit(block0))
|
||||
.await
|
||||
.respond(block0_hash);
|
||||
|
||||
|
|
@ -845,9 +845,9 @@ async fn sync_block_too_high_extend_tips() -> Result<(), crate::BoxError> {
|
|||
|
||||
for _ in 1..=2 {
|
||||
chain_verifier
|
||||
.expect_request_that(|req| remaining_blocks.remove(&req.hash()).is_some())
|
||||
.expect_request_that(|req| remaining_blocks.remove(&req.block().hash()).is_some())
|
||||
.await
|
||||
.respond_with(|req| req.hash());
|
||||
.respond_with(|req| req.block().hash());
|
||||
}
|
||||
assert_eq!(
|
||||
remaining_blocks,
|
||||
|
|
@ -927,7 +927,7 @@ fn setup() -> (
|
|||
impl Future<Output = Result<(), Report>> + Send,
|
||||
SyncStatus,
|
||||
// ChainVerifier
|
||||
MockService<Arc<Block>, block::Hash, PanicAssertion>,
|
||||
MockService<zebra_consensus::Request, block::Hash, PanicAssertion>,
|
||||
// PeerSet
|
||||
MockService<zebra_network::Request, zebra_network::Response, PanicAssertion>,
|
||||
// StateService
|
||||
|
|
|
|||
Loading…
Reference in New Issue