2021-06-03 15:48:40 -07:00
|
|
|
//! Top-level semantic block verification for Zebra.
|
|
|
|
//!
|
|
|
|
//! Verifies blocks using the [`CheckpointVerifier`] or full [`BlockVerifier`],
|
|
|
|
//! depending on the config and block height.
|
2021-06-18 10:43:05 -07:00
|
|
|
//!
|
|
|
|
//! # Correctness
|
|
|
|
//!
|
|
|
|
//! Block and transaction verification requests should be wrapped in a timeout, because:
|
|
|
|
//! - checkpoint verification waits for previous blocks, and
|
|
|
|
//! - full block and transaction verification wait for UTXOs from previous blocks.
|
|
|
|
//!
|
|
|
|
//! Otherwise, verification of out-of-order and invalid blocks and transactions can hang
|
|
|
|
//! indefinitely.
|
2021-06-03 15:48:40 -07:00
|
|
|
|
2020-07-20 18:34:23 -07:00
|
|
|
use std::{
|
|
|
|
future::Future,
|
|
|
|
pin::Pin,
|
|
|
|
sync::Arc,
|
|
|
|
task::{Context, Poll},
|
|
|
|
};
|
2021-08-25 08:07:26 -07:00
|
|
|
|
|
|
|
use displaydoc::Display;
|
|
|
|
use futures::{FutureExt, TryFutureExt};
|
2020-09-21 11:54:06 -07:00
|
|
|
use thiserror::Error;
|
2021-11-19 15:02:56 -08:00
|
|
|
use tokio::task::{spawn_blocking, JoinHandle};
|
2020-09-09 20:16:11 -07:00
|
|
|
use tower::{buffer::Buffer, util::BoxService, Service, ServiceExt};
|
2020-09-11 12:53:19 -07:00
|
|
|
use tracing::instrument;
|
2020-07-20 18:34:23 -07:00
|
|
|
|
2020-09-09 18:53:40 -07:00
|
|
|
use zebra_chain::{
|
|
|
|
block::{self, Block},
|
2021-06-17 17:05:28 -07:00
|
|
|
parameters::Network,
|
2020-09-09 18:53:40 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
use zebra_state as zs;
|
2020-07-20 18:34:23 -07:00
|
|
|
|
2020-09-09 20:16:11 -07:00
|
|
|
use crate::{
|
|
|
|
block::BlockVerifier,
|
2021-02-19 16:43:38 -08:00
|
|
|
block::VerifyBlockError,
|
|
|
|
checkpoint::{CheckpointList, CheckpointVerifier, VerifyCheckpointError},
|
2021-08-25 08:07:26 -07:00
|
|
|
error::TransactionError,
|
|
|
|
script, transaction, BoxError, Config,
|
2020-09-09 20:16:11 -07:00
|
|
|
};
|
|
|
|
|
2021-08-25 08:07:26 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
|
|
|
|
|
|
|
/// The bound for the chain verifier and transaction verifier buffers.
|
2021-02-08 14:13:55 -08:00
|
|
|
///
|
|
|
|
/// We choose the verifier buffer bound based on the maximum number of
|
|
|
|
/// concurrent verifier users, to avoid contention:
|
2021-08-25 08:07:26 -07:00
|
|
|
/// - the `ChainSync` block download and verify stream
|
|
|
|
/// - the `Inbound` block download and verify stream
|
|
|
|
/// - the `Mempool` transaction download and verify stream
|
|
|
|
/// - a block miner component, which we might add in future, and
|
2021-02-08 14:13:55 -08:00
|
|
|
/// - 1 extra slot to avoid contention.
|
|
|
|
///
|
|
|
|
/// We deliberately add extra slots, because they only cost a small amount of
|
|
|
|
/// memory, but missing slots can significantly slow down Zebra.
|
2021-08-25 08:07:26 -07:00
|
|
|
const VERIFIER_BUFFER_BOUND: usize = 5;
|
2021-02-08 14:13:55 -08:00
|
|
|
|
2020-09-09 20:16:11 -07:00
|
|
|
/// The chain verifier routes requests to either the checkpoint verifier or the
|
|
|
|
/// block verifier, depending on the maximum checkpoint height.
|
2021-06-18 10:43:05 -07:00
|
|
|
///
|
|
|
|
/// # Correctness
|
|
|
|
///
|
|
|
|
/// Block verification requests should be wrapped in a timeout, so that
|
|
|
|
/// out-of-order and invalid requests do not hang indefinitely. See the [`chain`](`crate::chain`)
|
|
|
|
/// module documentation for details.
|
2021-08-25 08:07:26 -07:00
|
|
|
struct ChainVerifier<S, V>
|
2020-09-02 21:23:57 -07:00
|
|
|
where
|
2020-09-09 20:16:11 -07:00
|
|
|
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
2020-09-02 21:23:57 -07:00
|
|
|
S::Future: Send + 'static,
|
2021-08-25 08:07:26 -07:00
|
|
|
V: Service<transaction::Request, Response = transaction::Response, Error = BoxError>
|
|
|
|
+ Send
|
|
|
|
+ Clone
|
|
|
|
+ 'static,
|
|
|
|
V::Future: Send + 'static,
|
2020-09-02 21:23:57 -07:00
|
|
|
{
|
2021-08-25 08:07:26 -07:00
|
|
|
block: BlockVerifier<S, V>,
|
2021-02-19 16:43:38 -08:00
|
|
|
checkpoint: CheckpointVerifier<S>,
|
2020-09-09 20:16:11 -07:00
|
|
|
max_checkpoint_height: block::Height,
|
2020-07-20 18:34:23 -07:00
|
|
|
}
|
|
|
|
|
2021-06-03 15:48:40 -07:00
|
|
|
/// An error while semantically verifying a block.
|
2020-09-21 11:54:06 -07:00
|
|
|
#[derive(Debug, Display, Error)]
|
|
|
|
pub enum VerifyChainError {
|
|
|
|
/// block could not be checkpointed
|
2021-02-19 16:43:38 -08:00
|
|
|
Checkpoint(#[source] VerifyCheckpointError),
|
2020-09-21 11:54:06 -07:00
|
|
|
/// block could not be verified
|
2021-02-19 16:43:38 -08:00
|
|
|
Block(#[source] VerifyBlockError),
|
2020-09-21 11:54:06 -07:00
|
|
|
}
|
|
|
|
|
2021-08-25 08:07:26 -07:00
|
|
|
impl<S, V> Service<Arc<Block>> for ChainVerifier<S, V>
|
2020-07-20 18:34:23 -07:00
|
|
|
where
|
2020-09-09 20:16:11 -07:00
|
|
|
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
2020-07-20 18:34:23 -07:00
|
|
|
S::Future: Send + 'static,
|
2021-08-25 08:07:26 -07:00
|
|
|
V: Service<transaction::Request, Response = transaction::Response, Error = BoxError>
|
|
|
|
+ Send
|
|
|
|
+ Clone
|
|
|
|
+ 'static,
|
|
|
|
V::Future: Send + 'static,
|
2020-07-20 18:34:23 -07:00
|
|
|
{
|
2020-08-15 23:20:01 -07:00
|
|
|
type Response = block::Hash;
|
2020-09-21 11:54:06 -07:00
|
|
|
type Error = VerifyChainError;
|
2020-07-20 18:34:23 -07:00
|
|
|
type Future =
|
|
|
|
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
|
|
|
|
2021-02-19 16:43:38 -08:00
|
|
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
2021-03-26 20:50:46 -07:00
|
|
|
// CORRECTNESS
|
|
|
|
//
|
|
|
|
// The current task must be scheduled for wakeup every time we return
|
|
|
|
// `Poll::Pending`.
|
|
|
|
//
|
|
|
|
// If either verifier is unready, this task is scheduled for wakeup when it becomes
|
|
|
|
// ready.
|
2021-02-08 14:13:55 -08:00
|
|
|
//
|
2021-02-19 16:43:38 -08:00
|
|
|
// We acquire checkpoint readiness before block readiness, to avoid an unlikely
|
|
|
|
// hang during the checkpoint to block verifier transition. If the checkpoint and
|
|
|
|
// block verifiers are contending for the same buffer/batch, we want the checkpoint
|
|
|
|
// verifier to win, so that checkpoint verification completes, and block verification
|
|
|
|
// can start. (Buffers and batches have multiple slots, so this contention is unlikely.)
|
|
|
|
use futures::ready;
|
|
|
|
// The chain verifier holds one slot in each verifier, for each concurrent task.
|
|
|
|
// Therefore, any shared buffers or batches polled by these verifiers should double
|
|
|
|
// their bounds. (For example, the state service buffer.)
|
|
|
|
ready!(self
|
|
|
|
.checkpoint
|
|
|
|
.poll_ready(cx)
|
|
|
|
.map_err(VerifyChainError::Checkpoint))?;
|
|
|
|
ready!(self.block.poll_ready(cx).map_err(VerifyChainError::Block))?;
|
2021-02-08 14:13:55 -08:00
|
|
|
Poll::Ready(Ok(()))
|
2020-07-20 18:34:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn call(&mut self, block: Arc<Block>) -> Self::Future {
|
2020-10-21 21:06:21 -07:00
|
|
|
match block.coinbase_height() {
|
|
|
|
Some(height) if height <= self.max_checkpoint_height => self
|
2021-02-19 16:43:38 -08:00
|
|
|
.checkpoint
|
|
|
|
.call(block)
|
2020-10-21 21:06:21 -07:00
|
|
|
.map_err(VerifyChainError::Checkpoint)
|
|
|
|
.boxed(),
|
|
|
|
// This also covers blocks with no height, which the block verifier
|
|
|
|
// will reject immediately.
|
|
|
|
_ => self
|
2021-02-19 16:43:38 -08:00
|
|
|
.block
|
|
|
|
.call(block)
|
2020-10-21 21:06:21 -07:00
|
|
|
.map_err(VerifyChainError::Block)
|
|
|
|
.boxed(),
|
2020-07-20 18:34:23 -07:00
|
|
|
}
|
2020-07-29 03:51:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-19 15:02:56 -08:00
|
|
|
/// Initialize block and transaction verification services,
|
|
|
|
/// and pre-download Groth16 parameters if needed.
|
|
|
|
///
|
|
|
|
/// Returns a block verifier, transaction verifier,
|
|
|
|
/// and the Groth16 parameter download task [`JoinHandle`].
|
2020-11-12 11:43:17 -08:00
|
|
|
///
|
|
|
|
/// The consensus configuration is specified by `config`, and the Zcash network
|
|
|
|
/// to verify blocks for is specified by `network`.
|
|
|
|
///
|
|
|
|
/// The block verification service asynchronously performs semantic verification
|
|
|
|
/// checks. Blocks that pass semantic verification are submitted to the supplied
|
|
|
|
/// `state_service` for contextual verification before being committed to the chain.
|
2020-07-23 18:47:48 -07:00
|
|
|
///
|
2021-08-25 08:07:26 -07:00
|
|
|
/// The transaction verification service asynchronously performs semantic verification
|
|
|
|
/// checks. Transactions that pass semantic verification return an `Ok` result to the caller.
|
|
|
|
///
|
2021-11-19 15:02:56 -08:00
|
|
|
/// Pre-downloads the Sapling and Sprout Groth16 parameters if needed,
|
|
|
|
/// checks they were downloaded correctly, and loads them into Zebra.
|
|
|
|
/// (The transaction verifier automatically downloads the parameters on first use.
|
|
|
|
/// But the parameter downloads can take around 10 minutes.
|
|
|
|
/// So we pre-download the parameters, to avoid verification timeouts.)
|
|
|
|
///
|
2020-09-09 20:16:11 -07:00
|
|
|
/// This function should only be called once for a particular state service.
|
2020-11-12 11:43:17 -08:00
|
|
|
///
|
|
|
|
/// Dropped requests are cancelled on a best-effort basis, but may continue to be processed.
|
2021-06-18 10:43:05 -07:00
|
|
|
///
|
|
|
|
/// # Correctness
|
|
|
|
///
|
2021-08-25 08:07:26 -07:00
|
|
|
/// Block and transaction verification requests should be wrapped in a timeout,
|
|
|
|
/// so that out-of-order and invalid requests do not hang indefinitely.
|
|
|
|
/// See the [`chain`](`crate::chain`) module documentation for details.
|
2020-09-11 12:53:19 -07:00
|
|
|
#[instrument(skip(state_service))]
|
2020-07-23 18:47:48 -07:00
|
|
|
pub async fn init<S>(
|
2020-08-19 19:28:21 -07:00
|
|
|
config: Config,
|
2020-07-20 19:24:58 -07:00
|
|
|
network: Network,
|
2021-02-19 16:43:38 -08:00
|
|
|
mut state_service: S,
|
2021-08-25 08:07:26 -07:00
|
|
|
) -> (
|
|
|
|
Buffer<BoxService<Arc<Block>, block::Hash, VerifyChainError>, Arc<Block>>,
|
|
|
|
Buffer<
|
|
|
|
BoxService<transaction::Request, transaction::Response, TransactionError>,
|
|
|
|
transaction::Request,
|
|
|
|
>,
|
2021-11-19 15:02:56 -08:00
|
|
|
JoinHandle<()>,
|
2021-08-25 08:07:26 -07:00
|
|
|
)
|
2020-07-20 19:24:58 -07:00
|
|
|
where
|
2020-09-09 20:16:11 -07:00
|
|
|
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
2020-07-20 19:24:58 -07:00
|
|
|
S::Future: Send + 'static,
|
|
|
|
{
|
2021-11-23 09:42:44 -08:00
|
|
|
// Pre-download Groth16 parameters in a separate thread.
|
2021-11-19 15:02:56 -08:00
|
|
|
|
2021-11-25 08:26:32 -08:00
|
|
|
// Give other tasks priority, before spawning the download task.
|
|
|
|
tokio::task::yield_now().await;
|
2021-11-19 15:02:56 -08:00
|
|
|
|
2021-11-25 08:26:32 -08:00
|
|
|
// The parameter download thread must be launched before initializing any verifiers.
|
|
|
|
// Otherwise, the download might happen on the startup thread.
|
|
|
|
let groth16_download_handle = spawn_blocking(|| {
|
2021-11-19 15:02:56 -08:00
|
|
|
// The lazy static initializer does the download, if needed,
|
|
|
|
// and the file hash checks.
|
|
|
|
lazy_static::initialize(&crate::groth16::GROTH16_PARAMETERS);
|
|
|
|
});
|
|
|
|
|
2021-08-25 08:07:26 -07:00
|
|
|
// transaction verification
|
|
|
|
|
|
|
|
let script = script::Verifier::new(state_service.clone());
|
|
|
|
let transaction = transaction::Verifier::new(network, script);
|
|
|
|
let transaction = Buffer::new(BoxService::new(transaction), VERIFIER_BUFFER_BOUND);
|
|
|
|
|
|
|
|
// block verification
|
|
|
|
|
2020-09-09 20:16:11 -07:00
|
|
|
let list = CheckpointList::new(network);
|
|
|
|
|
|
|
|
let max_checkpoint_height = if config.checkpoint_sync {
|
|
|
|
list.max_height()
|
|
|
|
} else {
|
2021-06-17 17:05:28 -07:00
|
|
|
list.min_height_in_range(network.mandatory_checkpoint_height()..)
|
2021-03-12 10:09:39 -08:00
|
|
|
.expect("hardcoded checkpoint list extends past canopy activation")
|
2020-09-09 20:16:11 -07:00
|
|
|
};
|
|
|
|
|
2020-09-10 09:49:13 -07:00
|
|
|
let tip = match state_service
|
2021-11-02 11:46:57 -07:00
|
|
|
.ready()
|
2021-02-19 16:43:38 -08:00
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.call(zs::Request::Tip)
|
2020-09-09 18:53:40 -07:00
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
{
|
2020-09-09 20:16:11 -07:00
|
|
|
zs::Response::Tip(tip) => tip,
|
|
|
|
_ => unreachable!("wrong response to Request::Tip"),
|
2020-09-09 18:53:40 -07:00
|
|
|
};
|
2020-09-11 12:53:19 -07:00
|
|
|
tracing::info!(?tip, ?max_checkpoint_height, "initializing chain verifier");
|
2020-07-20 19:24:58 -07:00
|
|
|
|
2021-08-25 08:07:26 -07:00
|
|
|
let block = BlockVerifier::new(network, state_service.clone(), transaction.clone());
|
2021-03-10 18:07:37 -08:00
|
|
|
let checkpoint = CheckpointVerifier::from_checkpoint_list(list, network, tip, state_service);
|
2021-08-25 08:07:26 -07:00
|
|
|
let chain = ChainVerifier {
|
|
|
|
block,
|
|
|
|
checkpoint,
|
|
|
|
max_checkpoint_height,
|
|
|
|
};
|
|
|
|
|
|
|
|
let chain = Buffer::new(BoxService::new(chain), VERIFIER_BUFFER_BOUND);
|
2020-07-20 18:34:23 -07:00
|
|
|
|
2021-11-19 15:02:56 -08:00
|
|
|
(chain, transaction, groth16_download_handle)
|
2020-07-20 18:34:23 -07:00
|
|
|
}
|