diff --git a/zebra-consensus/src/checkpoint.rs b/zebra-consensus/src/checkpoint.rs index 9d5816a5..7de82be8 100644 --- a/zebra-consensus/src/checkpoint.rs +++ b/zebra-consensus/src/checkpoint.rs @@ -23,6 +23,8 @@ use list::CheckpointList; use types::{Progress, Progress::*}; use types::{Target, Target::*}; +use crate::parameters; + use futures_util::FutureExt; use std::{ collections::BTreeMap, @@ -38,6 +40,7 @@ use tower::Service; use zebra_chain::block::{Block, BlockHeaderHash}; use zebra_chain::types::BlockHeight; +use zebra_chain::Network; /// The inner error type for CheckpointVerifier. // TODO(jlusby): Error = Report ? @@ -82,6 +85,9 @@ pub const MAX_QUEUED_BLOCKS_PER_HEIGHT: usize = 4; struct CheckpointVerifier { // Inputs // + /// The network for this verifier. + network: Network, + /// The checkpoint list for this verifier. checkpoint_list: CheckpointList, @@ -107,7 +113,8 @@ struct CheckpointVerifier { /// /// Contains non-service utility functions for CheckpointVerifiers. impl CheckpointVerifier { - /// Return a checkpoint verification service, using the provided `checkpoint_list`. + /// Return a checkpoint verification service for `network`, using + /// `checkpoint_list`. /// /// This function should be called only once for a particular checkpoint list (and /// network), rather than constructing multiple verification services based on the @@ -119,10 +126,12 @@ impl CheckpointVerifier { // functions and enum variants it uses, are only used in the tests. #[allow(dead_code)] fn new( + network: Network, checkpoint_list: impl IntoIterator, ) -> Result { Ok(CheckpointVerifier { - checkpoint_list: CheckpointList::new(checkpoint_list)?, + network, + checkpoint_list: CheckpointList::new(network, checkpoint_list)?, queued: BTreeMap::new(), // We start by verifying the genesis block, by itself verifier_progress: Progress::BeforeGenesis, @@ -413,12 +422,7 @@ impl CheckpointVerifier { // Since genesis blocks are hard-coded in zcashd, and not verified // like other blocks, the genesis parent hash is set by the // consensus parameters. - // - // TODO(teor): get the genesis block parent hash from the consensus - // parameters - // In the meantime, try `[0; 32])`, because the genesis block has no - // parent block. (And in Bitcoin, `null` is `[0; 32]`.) - BeforeGenesis => BlockHeaderHash([0; 32]), + BeforeGenesis => parameters::genesis_previous_block_hash(self.network), PreviousCheckpoint(hash) => hash, FinalCheckpoint => return, }; diff --git a/zebra-consensus/src/checkpoint/list.rs b/zebra-consensus/src/checkpoint/list.rs index 72866ebe..cbaf5fc0 100644 --- a/zebra-consensus/src/checkpoint/list.rs +++ b/zebra-consensus/src/checkpoint/list.rs @@ -5,6 +5,8 @@ //! Checkpoints can be used to verify their ancestors, by chaining backwards //! to another checkpoint, via each block's parent block hash. +use crate::parameters; + use std::{ collections::{BTreeMap, HashSet}, error, @@ -13,6 +15,7 @@ use std::{ use zebra_chain::block::BlockHeaderHash; use zebra_chain::types::BlockHeight; +use zebra_chain::Network; /// The inner error type for CheckpointVerifier. // TODO(jlusby): Error = Report ? @@ -28,13 +31,14 @@ type Error = Box; pub struct CheckpointList(BTreeMap); impl CheckpointList { - /// Create a new checkpoint list from `checkpoint_list`. + /// Create a new checkpoint list for `network` from `checkpoint_list`. /// /// Checkpoint heights and checkpoint hashes must be unique. /// /// There must be a checkpoint for the genesis block at BlockHeight 0. /// (All other checkpoints are optional.) pub fn new( + network: Network, checkpoint_list: impl IntoIterator, ) -> Result { // BTreeMap silently ignores duplicates, so we count the checkpoints @@ -53,6 +57,13 @@ impl CheckpointList { _ => Err("checkpoints must start at the genesis block height 0")?, }; + // Check the hash of the genesis block against the supplied network + match checkpoints.values().next() { + Some(h) if h == ¶meters::genesis_hash(network) => {} + Some(_) => Err("the genesis checkpoint does not match the network genesis hash")?, + _ => unreachable!("already checked for an empty list"), + }; + // This check rejects duplicate heights, whether they have the same or // different hashes if checkpoints.len() != original_len { @@ -117,6 +128,7 @@ mod tests { use std::sync::Arc; + use zebra_chain::Network::*; use zebra_chain::{block::Block, serialization::ZcashDeserialize}; /// Make a checkpoint list containing only the genesis block @@ -135,7 +147,7 @@ mod tests { // Make a checkpoint list containing the genesis block let checkpoint_list: BTreeMap = checkpoint_data.iter().cloned().collect(); - let _ = CheckpointList::new(checkpoint_list)?; + let _ = CheckpointList::new(Mainnet, checkpoint_list)?; Ok(()) } @@ -162,7 +174,7 @@ mod tests { // Make a checkpoint list containing all the blocks let checkpoint_list: BTreeMap = checkpoint_data.iter().cloned().collect(); - let _ = CheckpointList::new(checkpoint_list)?; + let _ = CheckpointList::new(Mainnet, checkpoint_list)?; Ok(()) } @@ -170,7 +182,8 @@ mod tests { /// Make sure that an empty checkpoint list fails #[test] fn checkpoint_list_empty_fail() -> Result<(), Error> { - let _ = CheckpointList::new(Vec::new()).expect_err("empty checkpoint lists should fail"); + let _ = CheckpointList::new(Mainnet, Vec::new()) + .expect_err("empty checkpoint lists should fail"); Ok(()) } @@ -191,12 +204,35 @@ mod tests { // Make a checkpoint list containing the non-genesis block let checkpoint_list: BTreeMap = checkpoint_data.iter().cloned().collect(); - let _ = CheckpointList::new(checkpoint_list) + let _ = CheckpointList::new(Mainnet, checkpoint_list) .expect_err("a checkpoint list with no genesis block should fail"); Ok(()) } + /// Make sure that a checkpoint list for the wrong network fails + #[test] + fn checkpoint_list_wrong_net_fail() -> Result<(), Error> { + // Parse the mainnet genesis block + let mut checkpoint_data = Vec::new(); + let block = + Arc::::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])?; + let hash: BlockHeaderHash = block.as_ref().into(); + checkpoint_data.push(( + block.coinbase_height().expect("test block has height"), + hash, + )); + + // Make a checkpoint list containing the mainnet genesis block + let checkpoint_list: BTreeMap = + checkpoint_data.iter().cloned().collect(); + // But use the test network + let _ = CheckpointList::new(Testnet, checkpoint_list) + .expect_err("a checkpoint list for the wrong network should fail"); + + Ok(()) + } + /// Make sure a checkpoint list that contains a null hash fails #[test] fn checkpoint_list_null_hash_fail() -> Result<(), Error> { @@ -205,7 +241,7 @@ mod tests { // Make a checkpoint list containing the non-genesis block let checkpoint_list: BTreeMap = checkpoint_data.iter().cloned().collect(); - let _ = CheckpointList::new(checkpoint_list) + let _ = CheckpointList::new(Mainnet, checkpoint_list) .expect_err("a checkpoint list with a null block hash should fail"); Ok(()) @@ -222,7 +258,7 @@ mod tests { // Make a checkpoint list containing the non-genesis block let checkpoint_list: BTreeMap = checkpoint_data.iter().cloned().collect(); - let _ = CheckpointList::new(checkpoint_list).expect_err( + let _ = CheckpointList::new(Mainnet, checkpoint_list).expect_err( "a checkpoint list with an invalid block height (BlockHeight::MAX + 1) should fail", ); @@ -231,7 +267,7 @@ mod tests { // Make a checkpoint list containing the non-genesis block let checkpoint_list: BTreeMap = checkpoint_data.iter().cloned().collect(); - let _ = CheckpointList::new(checkpoint_list) + let _ = CheckpointList::new(Mainnet, checkpoint_list) .expect_err("a checkpoint list with an invalid block height (u32::MAX) should fail"); Ok(()) @@ -256,7 +292,7 @@ mod tests { } // Make a checkpoint list containing some duplicate blocks - let _ = CheckpointList::new(checkpoint_data) + let _ = CheckpointList::new(Mainnet, checkpoint_data) .expect_err("checkpoint lists with duplicate blocks should fail"); Ok(()) @@ -282,7 +318,7 @@ mod tests { checkpoint_data.push((BlockHeight(1), BlockHeaderHash([0xbb; 32]))); // Make a checkpoint list containing some duplicate blocks - let _ = CheckpointList::new(checkpoint_data) + let _ = CheckpointList::new(Mainnet, checkpoint_data) .expect_err("checkpoint lists with duplicate heights should fail"); Ok(()) @@ -308,7 +344,7 @@ mod tests { checkpoint_data.push((BlockHeight(2), BlockHeaderHash([0xcc; 32]))); // Make a checkpoint list containing some duplicate blocks - let _ = CheckpointList::new(checkpoint_data) + let _ = CheckpointList::new(Mainnet, checkpoint_data) .expect_err("checkpoint lists with duplicate hashes should fail"); Ok(()) diff --git a/zebra-consensus/src/checkpoint/tests.rs b/zebra-consensus/src/checkpoint/tests.rs index 74500eb4..557823cd 100644 --- a/zebra-consensus/src/checkpoint/tests.rs +++ b/zebra-consensus/src/checkpoint/tests.rs @@ -13,6 +13,7 @@ use tower::{Service, ServiceExt}; use tracing_futures::Instrument; use zebra_chain::serialization::ZcashDeserialize; +use zebra_chain::Network::*; /// The timeout we apply to each verify future during testing. /// @@ -45,7 +46,7 @@ async fn single_item_checkpoint_list() -> Result<(), Report> { .collect(); let mut checkpoint_verifier = - CheckpointVerifier::new(genesis_checkpoint_list).map_err(|e| eyre!(e))?; + CheckpointVerifier::new(Mainnet, genesis_checkpoint_list).map_err(|e| eyre!(e))?; assert_eq!( checkpoint_verifier.previous_checkpoint_height(), @@ -124,7 +125,8 @@ async fn multi_item_checkpoint_list() -> Result<(), Report> { .map(|(_block, height, hash)| (*height, *hash)) .collect(); - let mut checkpoint_verifier = CheckpointVerifier::new(checkpoint_list).map_err(|e| eyre!(e))?; + let mut checkpoint_verifier = + CheckpointVerifier::new(Mainnet, checkpoint_list).map_err(|e| eyre!(e))?; assert_eq!( checkpoint_verifier.previous_checkpoint_height(), @@ -250,7 +252,8 @@ async fn continuous_blockchain() -> Result<(), Report> { .map(|(_block, height, hash)| (*height, *hash)) .collect(); - let mut checkpoint_verifier = CheckpointVerifier::new(checkpoint_list).map_err(|e| eyre!(e))?; + let mut checkpoint_verifier = + CheckpointVerifier::new(Mainnet, checkpoint_list).map_err(|e| eyre!(e))?; // Setup checks assert_eq!( @@ -347,7 +350,7 @@ async fn block_higher_than_max_checkpoint_fail() -> Result<(), Report> { .collect(); let mut checkpoint_verifier = - CheckpointVerifier::new(genesis_checkpoint_list).map_err(|e| eyre!(e))?; + CheckpointVerifier::new(Mainnet, genesis_checkpoint_list).map_err(|e| eyre!(e))?; assert_eq!( checkpoint_verifier.previous_checkpoint_height(), @@ -422,7 +425,7 @@ async fn wrong_checkpoint_hash_fail() -> Result<(), Report> { .collect(); let mut checkpoint_verifier = - CheckpointVerifier::new(genesis_checkpoint_list).map_err(|e| eyre!(e))?; + CheckpointVerifier::new(Mainnet, genesis_checkpoint_list).map_err(|e| eyre!(e))?; assert_eq!( checkpoint_verifier.previous_checkpoint_height(), @@ -601,7 +604,8 @@ async fn checkpoint_drop_cancel() -> Result<(), Report> { .map(|(_block, height, hash)| (*height, *hash)) .collect(); - let mut checkpoint_verifier = CheckpointVerifier::new(checkpoint_list).map_err(|e| eyre!(e))?; + let mut checkpoint_verifier = + CheckpointVerifier::new(Mainnet, checkpoint_list).map_err(|e| eyre!(e))?; assert_eq!( checkpoint_verifier.previous_checkpoint_height(),