Skip to content

Commit

Permalink
Merge of #5870
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Jan 11, 2023
2 parents b060408 + 9ce7d8a commit 1ed02cd
Show file tree
Hide file tree
Showing 39 changed files with 821 additions and 252 deletions.
5 changes: 4 additions & 1 deletion zebra-consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
54 changes: 41 additions & 13 deletions zebra-consensus/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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),
}
Expand Down Expand Up @@ -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,
Expand All @@ -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());

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
34 changes: 26 additions & 8 deletions zebra-consensus/src/block/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
40 changes: 40 additions & 0 deletions zebra-consensus/src/block/request.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
50 changes: 32 additions & 18 deletions zebra-consensus/src/block/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -55,23 +54,29 @@ 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();

// 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();

Expand Down Expand Up @@ -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),
),
]
});

Expand Down
29 changes: 21 additions & 8 deletions zebra-consensus/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};

Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
}
}
}
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 1ed02cd

Please sign in to comment.