zebra/zebra-consensus/src/checkpoint/tests.rs

549 lines
17 KiB
Rust
Raw Normal View History

//! Tests for checkpoint-based block verification
use super::*;
use super::types::Progress::*;
use super::types::Target::*;
use color_eyre::eyre::{eyre, Report};
use futures::future::TryFutureExt;
use std::{cmp::min, mem::drop, time::Duration};
use tokio::time::timeout;
use tower::{Service, ServiceExt};
use zebra_chain::serialization::ZcashDeserialize;
/// The timeout we apply to each verify future during testing.
///
/// The checkpoint verifier uses `tokio::sync::oneshot` channels as futures.
/// If the verifier doesn't send a message on the channel, any tests that
/// await the channel future will hang.
///
/// This value is set to a large value, to avoid spurious failures due to
/// high system load.
const VERIFY_TIMEOUT_SECONDS: u64 = 10;
#[tokio::test]
async fn single_item_checkpoint_list_test() -> Result<(), Report> {
single_item_checkpoint_list().await
}
#[spandoc::spandoc]
async fn single_item_checkpoint_list() -> Result<(), Report> {
zebra_test::init();
let block0 =
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])?;
let hash0: BlockHeaderHash = block0.as_ref().into();
// Make a checkpoint list containing only the genesis block
let genesis_checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> =
[(block0.coinbase_height().unwrap(), hash0)]
.iter()
.cloned()
.collect();
let mut checkpoint_verifier =
CheckpointVerifier::new(genesis_checkpoint_list).map_err(|e| eyre!(e))?;
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
BeforeGenesis
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(0)
);
/// SPANDOC: Make sure the verifier service is ready
let ready_verifier_service = checkpoint_verifier
.ready_and()
.map_err(|e| eyre!(e))
.await?;
/// SPANDOC: Set up the future for block 0
let verify_future = timeout(
Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
ready_verifier_service.call(block0.clone()),
);
/// SPANDOC: Wait for the response for block 0
// TODO(teor || jlusby): check error kind
let verify_response = verify_future
.map_err(|e| eyre!(e))
.await
.expect("timeout should not happen")
.expect("block should verify");
assert_eq!(verify_response, hash0);
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
FinalCheckpoint
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
FinishedVerifying
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(0)
);
Ok(())
}
#[tokio::test]
async fn multi_item_checkpoint_list_test() -> Result<(), Report> {
multi_item_checkpoint_list().await
}
#[spandoc::spandoc]
async fn multi_item_checkpoint_list() -> Result<(), Report> {
zebra_test::init();
// Parse all the blocks
let mut checkpoint_data = Vec::new();
for b in &[
&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..],
&zebra_test::vectors::BLOCK_MAINNET_1_BYTES[..],
// TODO(teor): not continuous, so they hang
//&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..],
//&zebra_test::vectors::BLOCK_MAINNET_434873_BYTES[..],
] {
let block = Arc::<Block>::zcash_deserialize(*b)?;
let hash: BlockHeaderHash = block.as_ref().into();
checkpoint_data.push((block.clone(), block.coinbase_height().unwrap(), hash));
}
// Make a checkpoint list containing all the blocks
let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> = checkpoint_data
.iter()
.map(|(_block, height, hash)| (*height, *hash))
.collect();
let mut checkpoint_verifier = CheckpointVerifier::new(checkpoint_list).map_err(|e| eyre!(e))?;
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
BeforeGenesis
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(1)
);
// Now verify each block
for (block, height, hash) in checkpoint_data {
/// SPANDOC: Make sure the verifier service is ready
let ready_verifier_service = checkpoint_verifier
.ready_and()
.map_err(|e| eyre!(e))
.await?;
/// SPANDOC: Set up the future for block {?height}
let verify_future = timeout(
Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
ready_verifier_service.call(block.clone()),
);
/// SPANDOC: Wait for the response for block {?height}
// TODO(teor || jlusby): check error kind
let verify_response = verify_future
.map_err(|e| eyre!(e))
.await
.expect("timeout should not happen")
.expect("future should succeed");
assert_eq!(verify_response, hash);
if height < checkpoint_verifier.checkpoint_list.max_height() {
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
PreviousCheckpoint(height)
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
} else {
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
FinalCheckpoint
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
FinishedVerifying
);
}
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(1)
);
}
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
FinalCheckpoint
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
FinishedVerifying
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(1)
);
Ok(())
}
#[tokio::test]
async fn block_higher_than_max_checkpoint_fail_test() -> Result<(), Report> {
block_higher_than_max_checkpoint_fail().await
}
#[spandoc::spandoc]
async fn block_higher_than_max_checkpoint_fail() -> Result<(), Report> {
zebra_test::init();
let block0 =
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])?;
let block415000 =
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])?;
// Make a checkpoint list containing only the genesis block
let genesis_checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> =
[(block0.coinbase_height().unwrap(), block0.as_ref().into())]
.iter()
.cloned()
.collect();
let mut checkpoint_verifier =
CheckpointVerifier::new(genesis_checkpoint_list).map_err(|e| eyre!(e))?;
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
BeforeGenesis
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(0)
);
/// SPANDOC: Make sure the verifier service is ready
let ready_verifier_service = checkpoint_verifier
.ready_and()
.map_err(|e| eyre!(e))
.await?;
/// SPANDOC: Set up the future for block 415000
let verify_future = timeout(
Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
ready_verifier_service.call(block415000.clone()),
);
/// SPANDOC: Wait for the response for block 415000, and expect failure
// TODO(teor || jlusby): check error kind
let _ = verify_future
.map_err(|e| eyre!(e))
.await
.expect("timeout should not happen")
.expect_err("bad block hash should fail");
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
BeforeGenesis
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(0)
);
Ok(())
}
#[tokio::test]
async fn wrong_checkpoint_hash_fail_test() -> Result<(), Report> {
wrong_checkpoint_hash_fail().await
}
#[spandoc::spandoc]
async fn wrong_checkpoint_hash_fail() -> Result<(), Report> {
zebra_test::init();
let good_block0 =
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])?;
let good_block0_hash: BlockHeaderHash = good_block0.as_ref().into();
// Change the header hash
let mut bad_block0 = good_block0.clone();
let mut bad_block0 = Arc::make_mut(&mut bad_block0);
bad_block0.header.version = 0;
let bad_block0: Arc<Block> = bad_block0.clone().into();
// Make a checkpoint list containing the genesis block checkpoint
let genesis_checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> =
[(good_block0.coinbase_height().unwrap(), good_block0_hash)]
.iter()
.cloned()
.collect();
let mut checkpoint_verifier =
CheckpointVerifier::new(genesis_checkpoint_list).map_err(|e| eyre!(e))?;
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
BeforeGenesis
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(0)
);
/// SPANDOC: Make sure the verifier service is ready (1/3)
let ready_verifier_service = checkpoint_verifier
.ready_and()
.map_err(|e| eyre!(e))
.await?;
/// SPANDOC: Set up the future for bad block 0 (1/3)
// TODO(teor || jlusby): check error kind
let bad_verify_future_1 = timeout(
Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
ready_verifier_service.call(bad_block0.clone()),
);
// We can't await the future yet, because bad blocks aren't cleared
// until the chain is verified
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
BeforeGenesis
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(0)
);
/// SPANDOC: Make sure the verifier service is ready (2/3)
let ready_verifier_service = checkpoint_verifier
.ready_and()
.map_err(|e| eyre!(e))
.await?;
/// SPANDOC: Set up the future for bad block 0 again (2/3)
// TODO(teor || jlusby): check error kind
let bad_verify_future_2 = timeout(
Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
ready_verifier_service.call(bad_block0.clone()),
);
// We can't await the future yet, because bad blocks aren't cleared
// until the chain is verified
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
BeforeGenesis
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(0)
);
/// SPANDOC: Make sure the verifier service is ready (3/3)
let ready_verifier_service = checkpoint_verifier
.ready_and()
.map_err(|e| eyre!(e))
.await?;
/// SPANDOC: Set up the future for good block 0 (3/3)
let good_verify_future = timeout(
Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
ready_verifier_service.call(good_block0.clone()),
);
/// SPANDOC: Wait for the response for good block 0, and expect success (3/3)
// TODO(teor || jlusby): check error kind
let verify_response = good_verify_future
.map_err(|e| eyre!(e))
.await
.expect("timeout should not happen")
.expect("future should succeed");
assert_eq!(verify_response, good_block0_hash);
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
FinalCheckpoint
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
FinishedVerifying
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(0)
);
// Now, await the bad futures, which should have completed
/// SPANDOC: Wait for the response for block 0, and expect failure (1/3)
// TODO(teor || jlusby): check error kind
let _ = bad_verify_future_1
.map_err(|e| eyre!(e))
.await
.expect("timeout should not happen")
.expect_err("bad block hash should fail");
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
FinalCheckpoint
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
FinishedVerifying
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(0)
);
/// SPANDOC: Wait for the response for block 0, and expect failure again (2/3)
// TODO(teor || jlusby): check error kind
let _ = bad_verify_future_2
.map_err(|e| eyre!(e))
.await
.expect("timeout should not happen")
.expect_err("bad block hash should fail");
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
FinalCheckpoint
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
FinishedVerifying
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(0)
);
Ok(())
}
#[tokio::test]
async fn checkpoint_drop_cancel_test() -> Result<(), Report> {
checkpoint_drop_cancel().await
}
#[spandoc::spandoc]
async fn checkpoint_drop_cancel() -> Result<(), Report> {
zebra_test::init();
// Parse all the blocks
let mut checkpoint_data = Vec::new();
for b in &[
&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..],
&zebra_test::vectors::BLOCK_MAINNET_1_BYTES[..],
&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..],
&zebra_test::vectors::BLOCK_MAINNET_434873_BYTES[..],
] {
let block = Arc::<Block>::zcash_deserialize(*b)?;
let hash: BlockHeaderHash = block.as_ref().into();
checkpoint_data.push((block.clone(), block.coinbase_height().unwrap(), hash));
}
// Make a checkpoint list containing all the blocks
let checkpoint_list: BTreeMap<BlockHeight, BlockHeaderHash> = checkpoint_data
.iter()
.map(|(_block, height, hash)| (*height, *hash))
.collect();
let mut checkpoint_verifier = CheckpointVerifier::new(checkpoint_list).map_err(|e| eyre!(e))?;
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
BeforeGenesis
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(434873)
);
let mut futures = Vec::new();
// Now collect verify futures for each block
for (block, height, hash) in checkpoint_data {
/// SPANDOC: Make sure the verifier service is ready
let ready_verifier_service = checkpoint_verifier
.ready_and()
.map_err(|e| eyre!(e))
.await?;
/// SPANDOC: Set up the future for block {?height}
let verify_future = timeout(
Duration::from_secs(VERIFY_TIMEOUT_SECONDS),
ready_verifier_service.call(block.clone()),
);
futures.push((verify_future, height, hash));
// Only continuous checkpoints verify
assert_eq!(
checkpoint_verifier.previous_checkpoint_height(),
PreviousCheckpoint(BlockHeight(min(height.0, 1)))
);
assert_eq!(
checkpoint_verifier.target_checkpoint_height(),
WaitingForBlocks
);
assert_eq!(
checkpoint_verifier.checkpoint_list.max_height(),
BlockHeight(434873)
);
}
// Now drop the verifier, to cancel the futures
drop(checkpoint_verifier);
for (verify_future, height, hash) in futures {
/// SPANDOC: Check the response for block {?height}
let verify_response = verify_future
.map_err(|e| eyre!(e))
.await
.expect("timeout should not happen");
if height <= BlockHeight(1) {
let verify_hash =
verify_response.expect("Continuous checkpoints should have succeeded before drop");
assert_eq!(verify_hash, hash);
} else {
// TODO(teor || jlusby): check error kind
verify_response.expect_err("Pending futures should fail on drop");
}
}
Ok(())
}