feature: Check the genesis hash in checkpoint lists

And use the consensus parameters to get the genesis previous block hash.
This commit is contained in:
teor 2020-07-15 19:51:54 +10:00 committed by Henry de Valence
parent 648d8daf12
commit 39e67c8748
3 changed files with 69 additions and 25 deletions

View File

@ -23,6 +23,8 @@ use list::CheckpointList;
use types::{Progress, Progress::*}; use types::{Progress, Progress::*};
use types::{Target, Target::*}; use types::{Target, Target::*};
use crate::parameters;
use futures_util::FutureExt; use futures_util::FutureExt;
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
@ -38,6 +40,7 @@ use tower::Service;
use zebra_chain::block::{Block, BlockHeaderHash}; use zebra_chain::block::{Block, BlockHeaderHash};
use zebra_chain::types::BlockHeight; use zebra_chain::types::BlockHeight;
use zebra_chain::Network;
/// The inner error type for CheckpointVerifier. /// The inner error type for CheckpointVerifier.
// TODO(jlusby): Error = Report ? // TODO(jlusby): Error = Report ?
@ -82,6 +85,9 @@ pub const MAX_QUEUED_BLOCKS_PER_HEIGHT: usize = 4;
struct CheckpointVerifier { struct CheckpointVerifier {
// Inputs // Inputs
// //
/// The network for this verifier.
network: Network,
/// The checkpoint list for this verifier. /// The checkpoint list for this verifier.
checkpoint_list: CheckpointList, checkpoint_list: CheckpointList,
@ -107,7 +113,8 @@ struct CheckpointVerifier {
/// ///
/// Contains non-service utility functions for CheckpointVerifiers. /// Contains non-service utility functions for CheckpointVerifiers.
impl CheckpointVerifier { 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 /// This function should be called only once for a particular checkpoint list (and
/// network), rather than constructing multiple verification services based on the /// 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. // functions and enum variants it uses, are only used in the tests.
#[allow(dead_code)] #[allow(dead_code)]
fn new( fn new(
network: Network,
checkpoint_list: impl IntoIterator<Item = (BlockHeight, BlockHeaderHash)>, checkpoint_list: impl IntoIterator<Item = (BlockHeight, BlockHeaderHash)>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Ok(CheckpointVerifier { Ok(CheckpointVerifier {
checkpoint_list: CheckpointList::new(checkpoint_list)?, network,
checkpoint_list: CheckpointList::new(network, checkpoint_list)?,
queued: BTreeMap::new(), queued: BTreeMap::new(),
// We start by verifying the genesis block, by itself // We start by verifying the genesis block, by itself
verifier_progress: Progress::BeforeGenesis, verifier_progress: Progress::BeforeGenesis,
@ -413,12 +422,7 @@ impl CheckpointVerifier {
// Since genesis blocks are hard-coded in zcashd, and not verified // Since genesis blocks are hard-coded in zcashd, and not verified
// like other blocks, the genesis parent hash is set by the // like other blocks, the genesis parent hash is set by the
// consensus parameters. // consensus parameters.
// BeforeGenesis => parameters::genesis_previous_block_hash(self.network),
// 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]),
PreviousCheckpoint(hash) => hash, PreviousCheckpoint(hash) => hash,
FinalCheckpoint => return, FinalCheckpoint => return,
}; };

View File

@ -5,6 +5,8 @@
//! Checkpoints can be used to verify their ancestors, by chaining backwards //! Checkpoints can be used to verify their ancestors, by chaining backwards
//! to another checkpoint, via each block's parent block hash. //! to another checkpoint, via each block's parent block hash.
use crate::parameters;
use std::{ use std::{
collections::{BTreeMap, HashSet}, collections::{BTreeMap, HashSet},
error, error,
@ -13,6 +15,7 @@ use std::{
use zebra_chain::block::BlockHeaderHash; use zebra_chain::block::BlockHeaderHash;
use zebra_chain::types::BlockHeight; use zebra_chain::types::BlockHeight;
use zebra_chain::Network;
/// The inner error type for CheckpointVerifier. /// The inner error type for CheckpointVerifier.
// TODO(jlusby): Error = Report ? // TODO(jlusby): Error = Report ?
@ -28,13 +31,14 @@ type Error = Box<dyn error::Error + Send + Sync + 'static>;
pub struct CheckpointList(BTreeMap<BlockHeight, BlockHeaderHash>); pub struct CheckpointList(BTreeMap<BlockHeight, BlockHeaderHash>);
impl CheckpointList { 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. /// Checkpoint heights and checkpoint hashes must be unique.
/// ///
/// There must be a checkpoint for the genesis block at BlockHeight 0. /// There must be a checkpoint for the genesis block at BlockHeight 0.
/// (All other checkpoints are optional.) /// (All other checkpoints are optional.)
pub fn new( pub fn new(
network: Network,
checkpoint_list: impl IntoIterator<Item = (BlockHeight, BlockHeaderHash)>, checkpoint_list: impl IntoIterator<Item = (BlockHeight, BlockHeaderHash)>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
// BTreeMap silently ignores duplicates, so we count the checkpoints // 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")?, _ => 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 == &parameters::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 // This check rejects duplicate heights, whether they have the same or
// different hashes // different hashes
if checkpoints.len() != original_len { if checkpoints.len() != original_len {
@ -117,6 +128,7 @@ mod tests {
use std::sync::Arc; use std::sync::Arc;
use zebra_chain::Network::*;
use zebra_chain::{block::Block, serialization::ZcashDeserialize}; use zebra_chain::{block::Block, serialization::ZcashDeserialize};
/// Make a checkpoint list containing only the genesis block /// Make a checkpoint list containing only the genesis block
@ -135,7 +147,7 @@ mod tests {
// Make a checkpoint list containing the genesis block // Make a checkpoint list containing the genesis block
let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> = let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> =
checkpoint_data.iter().cloned().collect(); checkpoint_data.iter().cloned().collect();
let _ = CheckpointList::new(checkpoint_list)?; let _ = CheckpointList::new(Mainnet, checkpoint_list)?;
Ok(()) Ok(())
} }
@ -162,7 +174,7 @@ mod tests {
// Make a checkpoint list containing all the blocks // Make a checkpoint list containing all the blocks
let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> = let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> =
checkpoint_data.iter().cloned().collect(); checkpoint_data.iter().cloned().collect();
let _ = CheckpointList::new(checkpoint_list)?; let _ = CheckpointList::new(Mainnet, checkpoint_list)?;
Ok(()) Ok(())
} }
@ -170,7 +182,8 @@ mod tests {
/// Make sure that an empty checkpoint list fails /// Make sure that an empty checkpoint list fails
#[test] #[test]
fn checkpoint_list_empty_fail() -> Result<(), Error> { 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(()) Ok(())
} }
@ -191,12 +204,35 @@ mod tests {
// Make a checkpoint list containing the non-genesis block // Make a checkpoint list containing the non-genesis block
let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> = let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> =
checkpoint_data.iter().cloned().collect(); 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"); .expect_err("a checkpoint list with no genesis block should fail");
Ok(()) 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::<Block>::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<BlockHeight, BlockHeaderHash> =
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 /// Make sure a checkpoint list that contains a null hash fails
#[test] #[test]
fn checkpoint_list_null_hash_fail() -> Result<(), Error> { fn checkpoint_list_null_hash_fail() -> Result<(), Error> {
@ -205,7 +241,7 @@ mod tests {
// Make a checkpoint list containing the non-genesis block // Make a checkpoint list containing the non-genesis block
let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> = let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> =
checkpoint_data.iter().cloned().collect(); 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"); .expect_err("a checkpoint list with a null block hash should fail");
Ok(()) Ok(())
@ -222,7 +258,7 @@ mod tests {
// Make a checkpoint list containing the non-genesis block // Make a checkpoint list containing the non-genesis block
let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> = let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> =
checkpoint_data.iter().cloned().collect(); 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", "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 // Make a checkpoint list containing the non-genesis block
let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> = let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> =
checkpoint_data.iter().cloned().collect(); 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"); .expect_err("a checkpoint list with an invalid block height (u32::MAX) should fail");
Ok(()) Ok(())
@ -256,7 +292,7 @@ mod tests {
} }
// Make a checkpoint list containing some duplicate blocks // 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"); .expect_err("checkpoint lists with duplicate blocks should fail");
Ok(()) Ok(())
@ -282,7 +318,7 @@ mod tests {
checkpoint_data.push((BlockHeight(1), BlockHeaderHash([0xbb; 32]))); checkpoint_data.push((BlockHeight(1), BlockHeaderHash([0xbb; 32])));
// Make a checkpoint list containing some duplicate blocks // 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"); .expect_err("checkpoint lists with duplicate heights should fail");
Ok(()) Ok(())
@ -308,7 +344,7 @@ mod tests {
checkpoint_data.push((BlockHeight(2), BlockHeaderHash([0xcc; 32]))); checkpoint_data.push((BlockHeight(2), BlockHeaderHash([0xcc; 32])));
// Make a checkpoint list containing some duplicate blocks // 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"); .expect_err("checkpoint lists with duplicate hashes should fail");
Ok(()) Ok(())

View File

@ -13,6 +13,7 @@ use tower::{Service, ServiceExt};
use tracing_futures::Instrument; use tracing_futures::Instrument;
use zebra_chain::serialization::ZcashDeserialize; use zebra_chain::serialization::ZcashDeserialize;
use zebra_chain::Network::*;
/// The timeout we apply to each verify future during testing. /// The timeout we apply to each verify future during testing.
/// ///
@ -45,7 +46,7 @@ async fn single_item_checkpoint_list() -> Result<(), Report> {
.collect(); .collect();
let mut checkpoint_verifier = 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!( assert_eq!(
checkpoint_verifier.previous_checkpoint_height(), checkpoint_verifier.previous_checkpoint_height(),
@ -124,7 +125,8 @@ async fn multi_item_checkpoint_list() -> Result<(), Report> {
.map(|(_block, height, hash)| (*height, *hash)) .map(|(_block, height, hash)| (*height, *hash))
.collect(); .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!( assert_eq!(
checkpoint_verifier.previous_checkpoint_height(), checkpoint_verifier.previous_checkpoint_height(),
@ -250,7 +252,8 @@ async fn continuous_blockchain() -> Result<(), Report> {
.map(|(_block, height, hash)| (*height, *hash)) .map(|(_block, height, hash)| (*height, *hash))
.collect(); .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 // Setup checks
assert_eq!( assert_eq!(
@ -347,7 +350,7 @@ async fn block_higher_than_max_checkpoint_fail() -> Result<(), Report> {
.collect(); .collect();
let mut checkpoint_verifier = 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!( assert_eq!(
checkpoint_verifier.previous_checkpoint_height(), checkpoint_verifier.previous_checkpoint_height(),
@ -422,7 +425,7 @@ async fn wrong_checkpoint_hash_fail() -> Result<(), Report> {
.collect(); .collect();
let mut checkpoint_verifier = 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!( assert_eq!(
checkpoint_verifier.previous_checkpoint_height(), checkpoint_verifier.previous_checkpoint_height(),
@ -601,7 +604,8 @@ async fn checkpoint_drop_cancel() -> Result<(), Report> {
.map(|(_block, height, hash)| (*height, *hash)) .map(|(_block, height, hash)| (*height, *hash))
.collect(); .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!( assert_eq!(
checkpoint_verifier.previous_checkpoint_height(), checkpoint_verifier.previous_checkpoint_height(),