Zebra/zebra-consensus/src/block.rs

249 lines
8.7 KiB
Rust

//! Block verification for Zebra.
//!
//! Verification occurs in multiple stages:
//! - getting blocks (disk- or network-bound)
//! - context-free verification of signatures, proofs, and scripts (CPU-bound)
//! - context-dependent verification of the chain state (depends on previous blocks)
//!
//! Verification is provided via a `tower::Service`, to support backpressure and batch
//! verification.
mod check;
#[cfg(test)]
mod tests;
use chrono::Utc;
use color_eyre::eyre::{eyre, Report};
use futures_util::FutureExt;
use std::{
error,
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
time::Duration,
};
use tokio::time;
use tower::{buffer::Buffer, Service, ServiceExt};
use zebra_chain::block::{self, Block};
/// A service that verifies blocks.
#[derive(Debug)]
struct BlockVerifier<S>
where
S: Service<zebra_state::Request, Response = zebra_state::Response, Error = Error>
+ Send
+ Clone
+ 'static,
S::Future: Send + 'static,
{
/// The underlying `ZebraState`, possibly wrapped in other services.
// TODO: contextual verification
#[allow(dead_code)]
state_service: S,
}
/// The error type for the BlockVerifier Service.
// TODO(jlusby): Error = Report ?
type Error = Box<dyn error::Error + Send + Sync + 'static>;
/// The BlockVerifier service implementation.
///
/// The state service is only used for contextual verification.
/// (The `ChainVerifier` updates the state.)
impl<S> Service<Arc<Block>> for BlockVerifier<S>
where
S: Service<zebra_state::Request, Response = zebra_state::Response, Error = Error>
+ Send
+ Clone
+ 'static,
S::Future: Send + 'static,
{
type Response = block::Hash;
type Error = Error;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
// We use the state for contextual verification, and we expect those
// queries to be fast. So we don't need to call
// `state_service.poll_ready()` here.
Poll::Ready(Ok(()))
}
fn call(&mut self, block: Arc<Block>) -> Self::Future {
let mut state = self.state_service.clone();
// TODO(jlusby): Error = Report, handle errors from state_service.
async move {
let hash = block.hash();
// These checks only apply to generated blocks. We check the block
// height for parsed blocks when we deserialize them.
let height = block
.coinbase_height()
.ok_or("Invalid block: missing block height.")?;
if height > block::Height::MAX {
Err("Invalid block height: greater than the maximum height.")?;
}
// Check that this block is actually a new block
if BlockVerifier::get_block(&mut state, hash).await?.is_some() {
Err(format!("Block has already been verified. {:?} {:?}", height, hash))?;
}
// Do the difficulty checks first, to raise the threshold for
// attacks that use any other fields.
let difficulty_threshold = block.header.difficulty_threshold.to_expanded().ok_or("Invalid difficulty threshold in block header.")?;
if hash > difficulty_threshold {
Err("Block failed the difficulty filter: hash must be less than or equal to the difficulty threshold.")?;
}
block.header.is_equihash_solution_valid()?;
// Since errors cause an early exit, try to do the
// quick checks first.
// Field validity and structure checks
let now = Utc::now();
block.header.is_time_valid_at(now)?;
check::is_coinbase_first(&block)?;
// TODO:
// - context-free header verification: merkle root
// - contextual verification
// As a temporary solution for chain gaps, wait for the previous block,
// and check its height.
// TODO:
// - Add a previous block height and hash constraint to the AddBlock request,
// so that we can verify in parallel, then check constraints before committing
//
// Skip contextual checks for the genesis block
let previous_block_hash = block.header.previous_block_hash;
if previous_block_hash != crate::parameters::GENESIS_PREVIOUS_BLOCK_HASH {
if height == block::Height(0) {
Err("Invalid block: height is 0, but previous block hash is not null.")?;
}
let expected_height = block::Height(height.0 - 1);
tracing::trace!(?expected_height, ?previous_block_hash, "Waiting for previous block");
metrics::gauge!("block.waiting.block.height", expected_height.0 as i64);
metrics::counter!("block.waiting.count", 1);
let previous_block = BlockVerifier::await_block(
&mut state,
previous_block_hash,
expected_height,
)
.await?;
let previous_height = previous_block.coinbase_height().unwrap();
if previous_height != expected_height {
Err("Invalid block height: must be 1 more than the previous block height.")?;
}
}
tracing::trace!(?height, ?hash, "Verified block");
metrics::gauge!(
"block.verified.block.height",
height.0 as _
);
metrics::counter!("block.verified.block.count", 1);
Ok(hash)
}
.boxed()
}
}
/// The BlockVerifier implementation.
///
/// The state service is only used for contextual verification.
/// (The `ChainVerifier` updates the state.)
impl<S> BlockVerifier<S>
where
S: Service<zebra_state::Request, Response = zebra_state::Response, Error = Error>
+ Send
+ Clone
+ 'static,
S::Future: Send + 'static,
{
/// Get the block for `hash`, using `state`.
///
/// If there is no block for that hash, returns `Ok(None)`.
/// Returns an error if `state.poll_ready` errors.
async fn get_block(state: &mut S, hash: block::Hash) -> Result<Option<Arc<Block>>, Report> {
let block = state
.ready_and()
.await
.map_err(|e| eyre!(e))?
.call(zebra_state::Request::GetBlock { hash })
.await
.map(|response| match response {
zebra_state::Response::Block { block } => block,
_ => unreachable!("GetBlock request can only result in Response::Block"),
})
.ok();
Ok(block)
}
/// Wait until a block with `hash` is in `state`.
///
/// Returns an error if `state.poll_ready` errors.
async fn await_block(
state: &mut S,
hash: block::Hash,
height: block::Height,
) -> Result<Arc<Block>, Report> {
loop {
match BlockVerifier::get_block(state, hash).await? {
Some(block) => return Ok(block),
// Busy-waiting is only a temporary solution to waiting for blocks.
// TODO:
// - Get an AwaitBlock future from the state
// - Replace with AddBlock constraints
None => {
tracing::debug!(?height, ?hash, "Waiting for state to have block");
time::delay_for(Duration::from_millis(50)).await
}
};
}
}
}
/// Return a block verification service, using the provided state service.
///
/// The block verifier holds a state service of type `S`, used as context for
/// block validation. This state is pluggable to allow for testing or
/// instrumentation.
///
/// The returned type is opaque to allow instrumentation or other wrappers, but
/// can be boxed for storage. It is also `Clone` to allow sharing of a
/// verification service.
///
/// This function should be called only once for a particular state service (and
/// the result be shared, cloning if needed). Constructing multiple services
/// from the same underlying state might cause synchronisation bugs.
pub fn init<S>(
state_service: S,
) -> impl Service<
Arc<Block>,
Response = block::Hash,
Error = Error,
Future = impl Future<Output = Result<block::Hash, Error>>,
> + Send
+ Clone
+ 'static
where
S: Service<zebra_state::Request, Response = zebra_state::Response, Error = Error>
+ Send
+ Clone
+ 'static,
S::Future: Send + 'static,
{
Buffer::new(BlockVerifier { state_service }, 1)
}