Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to limit checkpoints to Sapling activation #931

Merged
merged 3 commits into from
Aug 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions zebra-consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@ edition = "2018"
[dependencies]
chrono = "0.4.13"
color-eyre = "0.5"
once_cell = "1.4"
rand = "0.7"
redjubjub = "0.2"
serde = { version = "1", features = ["serde_derive"] }

metrics = "0.12"
futures = "0.3.5"
futures-util = "0.3.5"
metrics = "0.12"
tokio = { version = "0.2.22", features = ["time", "sync", "stream", "tracing"] }
tower = "0.3"
tower-util = "0.3"
tracing = "0.1.19"
tracing-futures = "0.2.4"
once_cell = "1.4"

tower-fallback = { path = "../tower-fallback/" }
tower-batch = { path = "../tower-batch/" }
Expand Down
12 changes: 9 additions & 3 deletions zebra-consensus/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
mod tests;

use crate::checkpoint::{CheckpointList, CheckpointVerifier};
use crate::Config;

use futures_util::FutureExt;
use std::{
Expand Down Expand Up @@ -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).
Expand All @@ -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<
Expand All @@ -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,
Expand Down Expand Up @@ -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.)
Expand Down
17 changes: 14 additions & 3 deletions zebra-consensus/src/chain/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use zebra_chain::{
use zebra_test::transcript::{TransError, Transcript};

use crate::checkpoint::CheckpointList;
use crate::Config;

use super::*;

Expand Down Expand Up @@ -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?;
teor2345 marked this conversation as resolved.
Show resolved Hide resolved

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 =
Expand Down
47 changes: 46 additions & 1 deletion zebra-consensus/src/checkpoint/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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 {
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
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.
Expand Down Expand Up @@ -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
Expand Down
92 changes: 87 additions & 5 deletions zebra-consensus/src/checkpoint/list/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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(())
}
22 changes: 22 additions & 0 deletions zebra-consensus/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//! Configuration for zebra-consensus

use serde::{Deserialize, Serialize};

/// Consensus configuration.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
/// Should Zebra sync using checkpoints?
///
/// Setting this option to true enables post-Sapling checkpoints.
/// (Zebra always checkpoints on Sapling activation.)
pub checkpoint_sync: bool,
}

impl Default for Config {
fn default() -> Self {
Self {
checkpoint_sync: false,
}
}
}
4 changes: 4 additions & 0 deletions zebra-consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@
pub mod block;
pub mod chain;
pub mod checkpoint;
pub mod config;
pub mod mempool;
pub mod parameters;

#[allow(dead_code)] // Remove this once transaction verification is implemented
mod primitives;
mod script;
mod transaction;

pub use crate::config::Config;
2 changes: 1 addition & 1 deletion zebra-state/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const MAX_BLOCK_REORG_HEIGHT: block::Height = block::Height(MIN_TRASPARENT_COINB

/// Configuration for the state service.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
/// The root directory for storing cached data.
///
Expand Down
7 changes: 6 additions & 1 deletion zebrad/src/commands/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 4 additions & 0 deletions zebrad/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::{net::SocketAddr, path::PathBuf};

use serde::{Deserialize, Serialize};

use zebra_consensus::Config as ConsensusSection;
use zebra_network::Config as NetworkSection;
use zebra_state::Config as StateSection;

Expand All @@ -19,6 +20,9 @@ use zebra_state::Config as StateSection;
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct ZebradConfig {
/// Consensus configuration
pub consensus: ConsensusSection,

/// Metrics configuration
pub metrics: MetricsSection,

Expand Down