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:
parent
06f4a59664
commit
78201b456d
|
@ -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.)
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue