feature: Implement checkpoint_sync for checkpoint verification

* add CheckpointList::new_up_to(limit: NetworkUpgrade)
* if checkpoint_sync is false, limit checkpoints to Sapling
* update tests for CheckpointList and chain::init
This commit is contained in:
teor 2020-08-20 12:28:21 +10:00
parent 06f4a59664
commit 78201b456d
5 changed files with 162 additions and 13 deletions

View File

@ -15,6 +15,7 @@
mod tests;
use crate::checkpoint::{CheckpointList, CheckpointVerifier};
use crate::Config;
use futures_util::FutureExt;
use std::{
@ -199,7 +200,8 @@ fn is_higher_than_max_checkpoint(
}
}
/// Return a chain verification service, using `network` and `state_service`.
/// Return a chain verification service, using `config`, `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).
@ -212,6 +214,7 @@ fn is_higher_than_max_checkpoint(
// 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<S>(
config: Config,
network: Network,
state_service: S,
) -> impl Service<
@ -234,7 +237,10 @@ where
.expect("State service poll_ready is Ok");
let block_verifier = crate::block::init(state_service.clone());
let checkpoint_list = CheckpointList::new(network);
let checkpoint_list = match config.checkpoint_sync {
true => CheckpointList::new(network),
false => CheckpointList::new_up_to(network, Sapling),
};
init_from_verifiers(
network,
@ -263,7 +269,7 @@ where
///
/// Panics:
///
/// Panics if the `checkpoint_verifier` is None, and the `initial_tip_height` is
/// Panics if the `checkpoint_list` 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.)

View File

@ -18,6 +18,7 @@ use zebra_chain::{
use zebra_test::transcript::{TransError, Transcript};
use crate::checkpoint::CheckpointList;
use crate::Config;
use super::*;
@ -225,19 +226,29 @@ async fn verify_block() -> Result<(), Report> {
#[tokio::test]
async fn verify_checkpoint_test() -> Result<(), Report> {
verify_checkpoint().await
verify_checkpoint(true).await?;
verify_checkpoint(false).await?;
Ok(())
}
/// Test that checkpoint verifies work.
///
/// Also tests the `chain::init` function.
#[spandoc::spandoc]
async fn verify_checkpoint() -> Result<(), Report> {
async fn verify_checkpoint(checkpoint_sync: bool) -> Result<(), Report> {
zebra_test::init();
let config = Config { checkpoint_sync };
// Test that the chain::init function works. Most of the other tests use
// init_from_verifiers.
let chain_verifier = super::init(Network::Mainnet, zebra_state::in_memory::init()).await;
let chain_verifier = super::init(
config.clone(),
Network::Mainnet,
zebra_state::in_memory::init(),
)
.await;
// Add a timeout layer
let chain_verifier =

View File

@ -18,7 +18,7 @@ use std::{
};
use zebra_chain::block;
use zebra_chain::parameters::Network;
use zebra_chain::parameters::{Network, NetworkUpgrade, NetworkUpgrade::*};
const MAINNET_CHECKPOINTS: &str = include_str!("main-checkpoints.txt");
const TESTNET_CHECKPOINTS: &str = include_str!("test-checkpoints.txt");
@ -86,6 +86,43 @@ impl CheckpointList {
}
}
/// Returns the hard-coded checkpoint list for `network`, up to and
/// including the first checkpoint after the activation of the `limit`
/// network upgrade.
pub fn new_up_to(network: Network, limit: NetworkUpgrade) -> Self {
let full_list = Self::new(network);
match limit {
Genesis | BeforeOverwinter | Overwinter => unreachable!("Caller passed a pre-Sapling network upgrade: Zebra must checkpoint up to Sapling activation"),
_ => {},
};
let activation = match limit.activation_height(network) {
Some(height) => height,
// If it's a future upgrade, it can't possibly limit our past checkpoints
None => return full_list,
};
let last_checkpoint = match full_list.min_height_in_range(activation..) {
Some(height) => height,
// If the full list has no checkpoints after limit, then all checkpoints
// are already under the limit
None => return full_list,
};
let limited_list = full_list
.0
.range(..=last_checkpoint)
.map(|(hash, height)| (*hash, *height));
match Self::from_list(limited_list) {
Ok(list) => list,
Err(_) => unreachable!(
"Unexpected invalid list: a non-empty prefix of a valid list should also be valid"
),
}
}
/// Create a new checkpoint list for `network` from `checkpoint_list`.
///
/// Assumes that the provided genesis checkpoint is correct.
@ -165,6 +202,14 @@ impl CheckpointList {
.expect("checkpoint lists must have at least one checkpoint")
}
/// Return the block height of the lowest checkpoint in a sub-range.
pub fn min_height_in_range<R>(&self, range: R) -> Option<block::Height>
where
R: RangeBounds<block::Height>,
{
self.0.range(range).map(|(height, _)| *height).next()
}
/// Return the block height of the highest checkpoint in a sub-range.
pub fn max_height_in_range<R>(&self, range: R) -> Option<block::Height>
where

View File

@ -2,9 +2,10 @@
use super::*;
use std::ops::Bound::*;
use std::sync::Arc;
use zebra_chain::parameters::{Network, NetworkUpgrade::Sapling};
use zebra_chain::parameters::{Network, Network::*, NetworkUpgrade, NetworkUpgrade::*};
use zebra_chain::{
block::{self, Block},
serialization::ZcashDeserialize,
@ -234,20 +235,20 @@ fn checkpoint_list_load_hard_coded() -> Result<(), Error> {
.parse()
.expect("hard-coded Testnet checkpoint list should parse");
let _ = CheckpointList::new(Network::Mainnet);
let _ = CheckpointList::new(Network::Testnet);
let _ = CheckpointList::new(Mainnet);
let _ = CheckpointList::new(Testnet);
Ok(())
}
#[test]
fn checkpoint_list_hard_coded_sapling_mainnet() -> Result<(), Error> {
checkpoint_list_hard_coded_sapling(Network::Mainnet)
checkpoint_list_hard_coded_sapling(Mainnet)
}
#[test]
fn checkpoint_list_hard_coded_sapling_testnet() -> Result<(), Error> {
checkpoint_list_hard_coded_sapling(Network::Testnet)
checkpoint_list_hard_coded_sapling(Testnet)
}
/// Check that the hard-coded lists cover the Sapling network upgrade
@ -267,3 +268,84 @@ fn checkpoint_list_hard_coded_sapling(network: Network) -> Result<(), Error> {
Ok(())
}
#[test]
fn checkpoint_list_up_to_mainnet() -> Result<(), Error> {
checkpoint_list_up_to(Mainnet, Sapling)?;
checkpoint_list_up_to(Mainnet, Blossom)?;
checkpoint_list_up_to(Mainnet, Heartwood)?;
checkpoint_list_up_to(Mainnet, Canopy)?;
Ok(())
}
#[test]
fn checkpoint_list_up_to_testnet() -> Result<(), Error> {
checkpoint_list_up_to(Testnet, Sapling)?;
checkpoint_list_up_to(Testnet, Blossom)?;
checkpoint_list_up_to(Testnet, Heartwood)?;
checkpoint_list_up_to(Testnet, Canopy)?;
Ok(())
}
/// Check that CheckpointList::new_up_to works
fn checkpoint_list_up_to(network: Network, limit: NetworkUpgrade) -> Result<(), Error> {
zebra_test::init();
let sapling_activation = Sapling
.activation_height(network)
.expect("Unexpected network upgrade info: Sapling must have an activation height");
let limited_list = CheckpointList::new_up_to(network, limit);
let full_list = CheckpointList::new(network);
assert!(
limited_list.max_height() >= sapling_activation,
"Pre-Sapling blocks must be verified by checkpoints"
);
if let Some(limit_activation) = limit.activation_height(network) {
if limit_activation <= full_list.max_height() {
assert!(
limited_list.max_height() >= limit_activation,
"The 'limit' network upgrade must be verified by checkpoints"
);
let next_checkpoint_after_limit = limited_list
.min_height_in_range((Included(limit_activation), Unbounded))
.expect("There must be a checkpoint at or after the limit");
assert_eq!(
limited_list
.min_height_in_range((Excluded(next_checkpoint_after_limit), Unbounded)),
None,
"There must not be multiple checkpoints after the limit"
);
let next_activation = NetworkUpgrade::next(network, limit_activation)
.map(|nu| nu.activation_height(network))
.flatten();
if let Some(next_activation) = next_activation {
// We expect that checkpoints happen much more often than network upgrades
assert!(
limited_list.max_height() < next_activation,
"The next network upgrade after 'limit' must not be verified by checkpoints"
);
}
// We have an effective limit, so skip the "no limit" test
return Ok(());
}
}
// Either the activation height is unspecified, or it is above the maximum
// checkpoint height (in the full checkpoint list)
assert_eq!(
limited_list.max_height(),
full_list.max_height(),
"Future network upgrades must not limit checkpoints"
);
Ok(())
}

View File

@ -43,7 +43,12 @@ impl StartCmd {
let config = app_config();
let state = zebra_state::on_disk::init(config.state.clone(), config.network.network);
let verifier = zebra_consensus::chain::init(config.network.network, state.clone()).await;
let verifier = zebra_consensus::chain::init(
config.consensus.clone(),
config.network.network,
state.clone(),
)
.await;
// The service that our node uses to respond to requests by peers
let node = Buffer::new(