Skip to content

Commit

Permalink
consensus: Add a checkpoint verifier stub (ZcashFoundation#502)
Browse files Browse the repository at this point in the history
consensus: Add a checkpoint verifier stub

This stub only verifies blocks whose hashes are in the checkpoint
list.

It doesn't have tests, chain child verifies to their ancestors, or
support checkpoint maximum height queries.

Part of ZcashFoundation#429.
  • Loading branch information
teor2345 committed Jun 24, 2020
1 parent 5fa4f9b commit 8bdf953
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 1 deletion.
2 changes: 1 addition & 1 deletion zebra-chain/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl fmt::Debug for Sha256dChecksum {
/// # Invariants
///
/// Users should not construct block heights greater than or equal to `500_000_000`.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct BlockHeight(pub u32);

#[cfg(test)]
Expand Down
158 changes: 158 additions & 0 deletions zebra-consensus/src/checkpoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//! Checkpoint-based block verification for Zebra.
//!
//! Checkpoint-based verification uses a list of checkpoint hashes to speed up the
//! initial chain sync for Zebra. This list is distributed with Zebra.
//!
//! The CheckpointVerifier compares each block's `BlockHeaderHash` against the known
//! checkpoint hashes. If it matches, then the block is verified, and added to the
//! `ZebraState`. Otherwise, if the block's height is lower than the maximum checkpoint
//! height, the block awaits the verification of its child block.
//!
//! Verification is provided via a `tower::Service`, to support backpressure and batch
//! verification.
use futures_util::FutureExt;
use std::{
collections::HashMap,
error,
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use tower::{Service, ServiceExt};

use zebra_chain::block::{Block, BlockHeaderHash};
use zebra_chain::types::BlockHeight;

struct CheckpointVerifier<S> {
/// The underlying `ZebraState`.
state_service: S,

/// Each checkpoint consists of a coinbase height and block header hash.
///
/// Checkpoints should be chosen to avoid forks or chain reorganizations,
/// which only happen in the last few hundred blocks in the chain.
/// (zcashd allows chain reorganizations up to 99 blocks, and prunes
/// orphaned side-chains after 288 blocks.)
checkpoint_list: Arc<HashMap<BlockHeight, BlockHeaderHash>>,
}

/// The error type for the CheckpointVerifier Service.
// TODO(jlusby): Error = Report ?
type Error = Box<dyn error::Error + Send + Sync + 'static>;

/// The CheckpointVerifier service implementation.
///
/// After verification, blocks are added to the underlying state service.
impl<S> Service<Arc<Block>> for CheckpointVerifier<S>
where
S: Service<zebra_state::Request, Response = zebra_state::Response, Error = Error>
+ Send
+ Clone
+ 'static,
S::Future: Send + 'static,
{
type Response = BlockHeaderHash;
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 don't expect the state to exert backpressure on verifier users,
// so we don't need to call `state_service.poll_ready()` here.
Poll::Ready(Ok(()))
}

fn call(&mut self, block: Arc<Block>) -> Self::Future {
// TODO(jlusby): Error = Report, handle errors from state_service.
let mut state_service = self.state_service.clone();
let checkpoint_list = self.checkpoint_list.clone();

async move {
if checkpoint_list.is_empty() {
return Err("the checkpoint list is empty".into());
};

let block_height = match block.coinbase_height() {
Some(height) => height,
None => return Err("the block does not have a coinbase height".into()),
};

// TODO(teor):
// - implement chaining from checkpoints to their ancestors
// - if chaining is expensive, move this check to the Future
// - should the state contain a mapping from previous_block_hash to block?
let checkpoint_hash = match checkpoint_list.get(&block_height) {
Some(&hash) => hash,
None => return Err("the block's height is not a checkpoint height".into()),
};

// Hashing is expensive, so we do it as late as possible
if BlockHeaderHash::from(block.as_ref()) != checkpoint_hash {
// The block is on a side-chain
return Err("the block hash does not match the checkpoint hash".into());
}

// `Tower::Buffer` requires a 1:1 relationship between `poll()`s
// and `call()`s, because it reserves a buffer slot in each
// `call()`.
// TODO(teor): what happens if the await fails?
let add_block = state_service
.ready_and()
.await?
.call(zebra_state::Request::AddBlock {
block: block.clone(),
});

match add_block.await? {
zebra_state::Response::Added { hash } => Ok(hash),
_ => Err("adding block to zebra-state failed".into()),
}
}
.boxed()
}
}

// TODO(teor):
// - add a function for the maximum checkpoint height
// (We can pre-calculate the result in init(), if we want.)
// - check that block.coinbase_height() <= max_checkpoint_height

/// Return a checkpoint verification service, using the provided state service.
///
/// The checkpoint verifier holds a state service of type `S`, into 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 be called only once for a particular state service (and
/// the result be shared) rather than constructing multiple verification services
/// backed by the same state layer.
pub fn init<S>(
state_service: S,
checkpoint_list: impl Into<Arc<HashMap<BlockHeight, BlockHeaderHash>>>,
) -> impl Service<
Arc<Block>,
Response = BlockHeaderHash,
Error = Error,
Future = impl Future<Output = Result<BlockHeaderHash, Error>>,
> + Send
+ 'static
where
S: Service<zebra_state::Request, Response = zebra_state::Response, Error = Error>
+ Send
+ Clone
+ 'static,
S::Future: Send + 'static,
{
CheckpointVerifier {
state_service,
checkpoint_list: checkpoint_list.into(),
}
}

// TODO(teor): tests
1 change: 1 addition & 0 deletions zebra-consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
#![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_consensus")]
#![deny(missing_docs)]

pub mod checkpoint;
pub mod mempool;
pub mod verify;

0 comments on commit 8bdf953

Please sign in to comment.