//! Chain state updates for Zebra. //! //! Chain state updates occur in multiple stages: //! - verify blocks (using `BlockVerifier` or `CheckpointVerifier`) //! - update the list of verified blocks on disk //! - create the chain state needed to verify child blocks //! - choose the best tip from all the available chain tips //! - update the mature chain state on disk //! - prune orphaned side-chains //! //! Chain state updates are provided via a `tower::Service`, to support //! backpressure and batch verification. #[cfg(test)] mod tests; use crate::checkpoint::{CheckpointList, CheckpointVerifier}; use crate::parameters::NetworkUpgrade::Sapling; use futures_util::FutureExt; use std::{ error, future::Future, pin::Pin, sync::Arc, task::{Context, Poll}, }; use tower::{buffer::Buffer, Service, ServiceExt}; use tracing_futures::Instrument; use zebra_chain::block::{Block, BlockHeaderHash}; use zebra_chain::types::BlockHeight; use zebra_chain::Network; /// The maximum expected gap between blocks. /// /// Used to identify unexpected high blocks. const MAX_EXPECTED_BLOCK_GAP: u32 = 100_000; /// A wrapper type that holds the `ChainVerifier`'s `CheckpointVerifier`, and /// its associated state. #[derive(Clone)] struct ChainCheckpointVerifier { /// The underlying `CheckpointVerifier`, wrapped in a buffer, so we can /// clone and share it with futures. verifier: Buffer>, /// The maximum checkpoint height for `checkpoint_verifier`. max_height: BlockHeight, } /// A service that verifies the chain, using its associated `CheckpointVerifier` /// and `BlockVerifier`. struct ChainVerifier where BV: Service, Response = BlockHeaderHash, Error = Error> + Send + Clone + 'static, BV::Future: Send + 'static, S: Service + Send + Clone + 'static, S::Future: Send + 'static, { /// The underlying `BlockVerifier`, possibly wrapped in other services. block_verifier: BV, /// The `ChainVerifier`'s underlying `CheckpointVerifier`, and its /// associated state. /// /// None if all the checkpoints have been verified. checkpoint: Option, /// The underlying `ZebraState`, possibly wrapped in other services. state_service: S, /// The most recent block height that was submitted to the verifier. /// /// Used for debugging. /// /// Updated before verification: the block at this height might not be valid. /// Not updated for unexpected high blocks. last_block_height: Option, } /// The error type for the ChainVerifier Service. // TODO(jlusby): Error = Report ? type Error = Box; /// The ChainVerifier service implementation. /// /// After verification, blocks are added to the underlying state service. impl Service> for ChainVerifier where BV: Service, Response = BlockHeaderHash, Error = Error> + Send + Clone + 'static, BV::Future: Send + 'static, S: Service + Send + Clone + 'static, S::Future: Send + 'static, { type Response = BlockHeaderHash; type Error = Error; type Future = Pin> + Send + 'static>>; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { // We don't expect the state or verifiers to exert backpressure on our // users, so we don't need to call `state_service.poll_ready()` here. // (And we don't know which verifier to choose at this point, anyway.) Poll::Ready(Ok(())) } fn call(&mut self, block: Arc) -> Self::Future { // TODO(jlusby): Error = Report, handle errors from state_service. let span = tracing::debug_span!( "block_verify", height = ?block.coinbase_height(), hash = ?block.hash() ); let block_height = block.coinbase_height(); let mut block_verifier = self.block_verifier.clone(); let mut state_service = self.state_service.clone(); let checkpoint_verifier = self.checkpoint.clone().map(|c| c.verifier); let max_checkpoint_height = self.checkpoint.clone().map(|c| c.max_height); // Log an info-level message on unexpected high blocks let is_unexpected_high_block = match (block_height, self.last_block_height) { (Some(BlockHeight(block_height)), Some(BlockHeight(last_block_height))) if (block_height > last_block_height + MAX_EXPECTED_BLOCK_GAP) => { true } (Some(block_height), _) => { // Update the last height if the block height was expected self.last_block_height = Some(block_height); false } // The other cases are covered by the verifiers _ => false, }; async move { // TODO(teor): in the post-sapling checkpoint range, allow callers // to use BlockVerifier, CheckpointVerifier, or both. // Call a verifier based on the block height and checkpoints. if is_higher_than_max_checkpoint(block_height, max_checkpoint_height) { // Log an info-level message on early high blocks. // The sync service rejects most of these blocks, but we // still want to know if a large number get through. if is_unexpected_high_block { tracing::info!(?block_height, "unexpected high block"); } block_verifier .ready_and() .await? .call(block.clone()) .await?; } else { checkpoint_verifier .expect("Missing checkpoint verifier: verifier must be Some if max checkpoint height is Some") .ready_and() .await? .call(block.clone()) .await?; } let add_block = state_service .ready_and() .await? .call(zebra_state::Request::AddBlock { block }); match add_block.await? { zebra_state::Response::Added { hash } => Ok(hash), _ => Err("adding block to zebra-state failed".into()), } } .instrument(span) .boxed() } } /// Returns true if block_height is higher than the maximum checkpoint /// height. Also returns true if there is no maximum checkpoint height. /// /// Returns false if the block does not have a height. fn is_higher_than_max_checkpoint( block_height: Option, max_checkpoint_height: Option, ) -> bool { match (block_height, max_checkpoint_height) { (Some(block_height), Some(max_checkpoint_height)) => block_height > max_checkpoint_height, (_, None) => true, (None, _) => false, } } /// Return a chain verification service, using `network` and `state_service`. /// /// Gets the initial tip from the state service, and uses it to create a block /// verifier and checkpoint verifier (if needed). /// /// This function should only be called once for a particular state service. If /// you need shared block or checkpoint verfiers, create them yourself, and pass /// them to `init_from_verifiers`. // // TODO: revise this interface when we generate our own blocks, or validate // mempool transactions. We might want to share the BlockVerifier, and we // might not want to add generated blocks to the state. pub async fn init( network: Network, state_service: S, ) -> impl Service< Arc, Response = BlockHeaderHash, Error = Error, Future = impl Future>, > + Send + Clone + 'static where S: Service + Send + Clone + 'static, S::Future: Send + 'static, { let initial_tip = zebra_state::current_tip(state_service.clone()) .await .expect("State service poll_ready is Ok"); let block_verifier = crate::block::init(state_service.clone()); let checkpoint_list = CheckpointList::new(network); init_from_verifiers( network, block_verifier, Some(checkpoint_list), state_service, initial_tip, ) } /// Return a chain verification service, using the provided block verifier, /// checkpoint list, and state service. /// /// The chain verifier holds a state service of type `S`, used as context for /// block validation and to which newly verified blocks will be committed. 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 only be called once for a particular state service and /// block verifier (and the result be shared, cloning if needed). Constructing /// multiple services from the same underlying state might cause synchronisation /// bugs. /// /// Panics: /// /// Panics if the `checkpoint_verifier` is None, and the `initial_tip_height` is /// below the Sapling network upgrade for `network`. (The `block_verifier` can't /// verify all the constraints on pre-Sapling blocks, so they require /// checkpoints.) pub(crate) fn init_from_verifiers( network: Network, block_verifier: BV, checkpoint_list: Option, state_service: S, initial_tip: Option>, ) -> impl Service< Arc, Response = BlockHeaderHash, Error = Error, Future = impl Future>, > + Send + Clone + 'static where BV: Service, Response = BlockHeaderHash, Error = Error> + Send + Clone + 'static, BV::Future: Send + 'static, S: Service + Send + Clone + 'static, S::Future: Send + 'static, { let max_checkpoint_height = checkpoint_list.clone().map(|c| c.max_height()); let initial_height = initial_tip.clone().map(|b| b.coinbase_height()).flatten(); let initial_hash = initial_tip.clone().map(|b| b.hash()); tracing::info!( ?network, ?max_checkpoint_height, ?initial_height, ?initial_hash, "initialising ChainVerifier" ); let sapling_activation = Sapling .activation_height(network) .expect("Unexpected network upgrade info: Sapling must have an activation height"); let checkpoint = match (initial_height, checkpoint_list, max_checkpoint_height) { // If we need to verify pre-Sapling blocks, make sure we have checkpoints for them. (None, None, _) => panic!("We have no checkpoints, and we have no cached blocks: Pre-Sapling blocks must be verified by checkpoints"), (Some(initial_height), None, _) if (initial_height < sapling_activation) => panic!("We have no checkpoints, and we don't have a cached Sapling activation block: Pre-Sapling blocks must be verified by checkpoints"), // If we're past the checkpoint range, don't create a checkpoint verifier. (Some(initial_height), _, Some(max_checkpoint_height)) if (initial_height > max_checkpoint_height) => None, // No list, no checkpoint verifier (_, None, _) => None, (_, Some(_), None) => panic!("Missing max checkpoint height: height must be Some if verifier is Some"), // We've done all the checks we need to create a checkpoint verifier (_, Some(list), Some(max_height)) => Some( ChainCheckpointVerifier { verifier: Buffer::new(CheckpointVerifier::from_checkpoint_list(list, initial_tip), 1), max_height, }), }; Buffer::new( ChainVerifier { block_verifier, checkpoint, state_service, last_block_height: initial_height, }, 1, ) }