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

fix(consensus): Check that Zebra's state contains the social consensus chain on startup #6163

Merged
merged 16 commits into from
Feb 21, 2023
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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ All notable changes to Zebra are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).

## [Zebra 1.0.0-rc.5](https://github.com/ZcashFoundation/zebra/releases/tag/v1.0.0-rc.5) - 2023-02-TODO INSERT DATE HERE

In this release we ... (TODO)
We also check that Zebra is following the consensus chain each time it starts up.

### Security
- Check that Zebra's state contains the consensus chain each time it starts up. This implements
the "settled network upgrade" consensus rule using all of Zebra's checkpoints ([#6163](https://github.com/ZcashFoundation/zebra/pull/6163)).
*User action required:*
- If your config is based on an old version of Zebra, or you have manually edited it,
make sure `consensus.checkpoint_sync = true`.
This option has been true by default since March 2022.

### Deprecated
- The `consensus.checkpoint_sync` config in `zebrad.toml` is deprecated. It might be ignored or
removed in a future release. ([#6163](https://github.com/ZcashFoundation/zebra/pull/6163))

TODO: add other changes here


## [Zebra 1.0.0-rc.4](https://github.com/ZcashFoundation/zebra/releases/tag/v1.0.0-rc.4) - 2023-01-30

In this release we fixed bugs and inconsistencies between zcashd and zebrad in the output of the `getblocktemplate` RPC method. In addition, we added block proposal mode to the `getblocktemplate` RPC, while we continue the effort of adding and testing mining pool RPC methods.
Expand Down
111 changes: 98 additions & 13 deletions zebra-consensus/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ use std::{
use displaydoc::Display;
use futures::{FutureExt, TryFutureExt};
use thiserror::Error;
use tokio::task::{spawn_blocking, JoinHandle};
use tokio::task::JoinHandle;
use tower::{buffer::Buffer, util::BoxService, Service, ServiceExt};
use tracing::{instrument, Span};
use tracing::{instrument, Instrument, Span};

use zebra_chain::{
block::{self, Height},
Expand Down Expand Up @@ -237,22 +237,22 @@ pub async fn init<S>(
BoxService<transaction::Request, transaction::Response, TransactionError>,
transaction::Request,
>,
JoinHandle<()>,
BackgroundTaskHandles,
Height,
)
where
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
S::Future: Send + 'static,
{
// Pre-download Groth16 parameters in a separate thread.

// Give other tasks priority, before spawning the download task.
// Give other tasks priority before spawning the download and checkpoint tasks.
tokio::task::yield_now().await;

// Pre-download Groth16 parameters in a separate thread.

// The parameter download thread must be launched before initializing any verifiers.
// Otherwise, the download might happen on the startup thread.
let span = Span::current();
let groth16_download_handle = spawn_blocking(move || {
let groth16_download_handle = tokio::task::spawn_blocking(move || {
span.in_scope(|| {
if !debug_skip_parameter_preload {
// The lazy static initializer does the download, if needed,
Expand All @@ -262,6 +262,79 @@ where
})
});

// Make sure the state contains the known best chain checkpoints, in a separate thread.

let checkpoint_state_service = state_service.clone();
let checkpoint_sync = config.checkpoint_sync;
let state_checkpoint_verify_handle = tokio::task::spawn(
// TODO: move this into an async function?
async move {
tracing::info!("starting state checkpoint validation");

// # Consensus
//
// We want to verify all available checkpoints, even if the node is not configured
// to use them for syncing. Zebra's checkpoints are updated with every release,
// which makes sure they include the latest settled network upgrade.
//
// > A network upgrade is settled on a given network when there is a social
// > consensus that it has activated with a given activation block hash.
// > A full validator that potentially risks Mainnet funds or displays Mainnet
// > transaction information to a user MUST do so only for a block chain that
// > includes the activation block of the most recent settled network upgrade,
// > with the corresponding activation block hash. Currently, there is social
// > consensus that NU5 has activated on the Zcash Mainnet and Testnet with the
// > activation block hashes given in § 3.12 ‘Mainnet and Testnet’ on p. 20.
//
// <https://zips.z.cash/protocol/protocol.pdf#blockchain>
let full_checkpoints = CheckpointList::new(network);

for (height, checkpoint_hash) in full_checkpoints.iter() {
let checkpoint_state_service = checkpoint_state_service.clone();
let request = zebra_state::Request::BestChainBlockHash(*height);

match checkpoint_state_service.oneshot(request).await {
Ok(zebra_state::Response::BlockHash(Some(state_hash))) => assert_eq!(
*checkpoint_hash, state_hash,
"invalid block in state: a previous Zebra instance followed an \
incorrect chain. Delete and re-sync your state to use the best chain"
),

Ok(zebra_state::Response::BlockHash(None)) => {
if checkpoint_sync {
tracing::info!(
"state is not fully synced yet, remaining checkpoints will be \
verified during syncing"
);
} else {
tracing::warn!(
"state is not fully synced yet, remaining checkpoints will be \
verified next time Zebra starts up. Zebra will be less secure \
until it is restarted. Use consensus.checkpoint_sync = true \
in zebrad.toml to make sure you are following a valid chain"
);
}

break;
}

Ok(response) => {
unreachable!("unexpected response type: {response:?} from state request")
}
Err(e) => {
tracing::warn!(
"unexpected error: {e:?} in state request while verifying previous \
state checkpoints"
)
}
}
}

tracing::info!("finished state checkpoint validation");
}
.instrument(Span::current()),
);

// transaction verification

let transaction = transaction::Verifier::new(network, state_service.clone());
Expand Down Expand Up @@ -293,18 +366,18 @@ where

let chain = Buffer::new(BoxService::new(chain), VERIFIER_BUFFER_BOUND);

(
chain,
transaction,
let task_handles = BackgroundTaskHandles {
groth16_download_handle,
max_checkpoint_height,
)
state_checkpoint_verify_handle,
};

(chain, transaction, task_handles, max_checkpoint_height)
}

/// Parses the checkpoint list for `network` and `config`.
/// Returns the checkpoint list and maximum checkpoint height.
pub fn init_checkpoint_list(config: Config, network: Network) -> (CheckpointList, Height) {
// TODO: Zebra parses the checkpoint list twice at startup.
// TODO: Zebra parses the checkpoint list three times at startup.
// Instead, cache the checkpoint list for each `network`.
let list = CheckpointList::new(network);

Expand All @@ -317,3 +390,15 @@ pub fn init_checkpoint_list(config: Config, network: Network) -> (CheckpointList

(list, max_checkpoint_height)
}

/// The background task handles for `zebra-consensus` verifier initialization.
#[derive(Debug)]
pub struct BackgroundTaskHandles {
/// A handle to the Groth16 parameter download task.
/// Finishes when the parameters are downloaded and their checksums verified.
pub groth16_download_handle: JoinHandle<()>,

/// A handle to the state checkpoint verify task.
/// Finishes when all the checkpoints are verified, or when the state tip is reached.
pub state_checkpoint_verify_handle: JoinHandle<()>,
}
5 changes: 5 additions & 0 deletions zebra-consensus/src/checkpoint/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,9 @@ impl CheckpointList {
{
self.0.range(range).map(|(height, _)| *height).next_back()
}

/// Returns an iterator over all the checkpoints, in increasing height order.
pub fn iter(&self) -> impl Iterator<Item = (&block::Height, &block::Hash)> {
self.0.iter()
}
}
30 changes: 22 additions & 8 deletions zebra-consensus/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
//! Configuration for semantic verification which is run in parallel.

use serde::{Deserialize, Serialize};

/// Consensus configuration.
/// Configuration for parallel semantic verification:
/// <https://zebra.zfnd.org/dev/rfcs/0002-parallel-verification.html#definitions>
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
/// Should Zebra use its optional checkpoints to sync?
/// Should Zebra make sure that it follows the consensus chain while syncing?
/// This is a developer-only option.
///
/// # Security
///
/// Disabling this option leaves your node vulnerable to some kinds of chain-based attacks.
/// Zebra regularly updates its checkpoints to ensure nodes are following the best chain.
///
/// # Details
///
/// This option is `true` by default, because it prevents some kinds of chain attacks.
///
/// This option is `true` by default, and allows for faster chain synchronization.
/// Disabling this option makes Zebra start full validation earlier.
/// It is slower and less secure.
///
/// Zebra requires some checkpoints to validate legacy network upgrades.
/// But it also ships with optional checkpoints, which can be used instead of full block validation.
/// Zebra requires some checkpoints to simplify validation of legacy network upgrades.
/// Required checkpoints are always active, even when this option is `false`.
///
/// Disabling this option makes Zebra start full validation as soon as possible.
/// This helps developers debug Zebra, by running full validation on more blocks.
/// # Deprecation
///
/// Future versions of Zebra may change the required and optional checkpoints.
/// For security reasons, this option might be deprecated or ignored in a future Zebra
/// release.
pub checkpoint_sync: bool,

/// Skip the pre-download of Groth16 parameters if this option is true.
Expand Down
3 changes: 3 additions & 0 deletions zebra-consensus/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use proptest_derive::Arbitrary;
const MAX_EXPIRY_HEIGHT: block::Height = block::Height::MAX_EXPIRY_HEIGHT;

#[derive(Error, Copy, Clone, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum SubsidyError {
#[error("no coinbase transaction in block")]
NoCoinbase,
Expand All @@ -36,6 +37,7 @@ pub enum SubsidyError {

#[derive(Error, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
#[allow(missing_docs)]
pub enum TransactionError {
#[error("first transaction must be coinbase")]
CoinbasePosition,
Expand Down Expand Up @@ -226,6 +228,7 @@ impl From<BoxError> for TransactionError {
}

#[derive(Error, Clone, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum BlockError {
#[error("block contains invalid transactions")]
Transaction(#[from] TransactionError),
Expand Down
3 changes: 1 addition & 2 deletions zebra-consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,10 @@ mod config;
mod parameters;
mod primitives;
mod script;
pub mod transaction;

pub mod chain;
#[allow(missing_docs)]
pub mod error;
pub mod transaction;

pub use block::{
subsidy::{
Expand Down
11 changes: 7 additions & 4 deletions zebra-state/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
#[macro_use]
extern crate tracing;

pub mod constants;

#[cfg(any(test, feature = "proptest-impl"))]
pub mod arbitrary;

mod config;
pub mod constants;
mod error;
mod request;
mod response;
Expand All @@ -32,8 +33,6 @@ pub use config::{check_and_delete_old_databases, Config};
pub use constants::MAX_BLOCK_REORG_HEIGHT;
pub use error::{BoxError, CloneError, CommitBlockError, ValidateContextError};
pub use request::{FinalizedBlock, HashOrHeight, PreparedBlock, ReadRequest, Request};
#[cfg(feature = "getblocktemplate-rpcs")]
pub use response::GetBlockTemplateChainInfo;
pub use response::{ReadResponse, Response};
pub use service::{
chain_tip::{ChainTipChange, LatestChainTip, TipAction},
Expand All @@ -42,11 +41,15 @@ pub use service::{
OutputIndex, OutputLocation, TransactionLocation,
};

#[cfg(feature = "getblocktemplate-rpcs")]
pub use response::GetBlockTemplateChainInfo;

#[cfg(any(test, feature = "proptest-impl"))]
pub use service::{
arbitrary::{populated_state, CHAIN_TIP_UPDATE_WAIT_LIMIT},
chain_tip::{ChainTipBlock, ChainTipSender},
init_test, init_test_services,
finalized_state::{DiskWriteBatch, WriteDisk},
init_test, init_test_services, ReadStateService,
};

pub(crate) use request::ContextuallyValidBlock;
18 changes: 14 additions & 4 deletions zebra-state/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,14 @@ pub enum Request {
/// Returns [`Response::BestChainNextMedianTimePast`] when successful.
BestChainNextMedianTimePast,

/// Looks up a block hash by height in the current best chain.
///
/// Returns
///
/// * [`Response::BlockHash(Some(hash))`](Response::BlockHash) if the block is in the best chain;
/// * [`Response::BlockHash(None)`](Response::BlockHash) otherwise.
BestChainBlockHash(block::Height),

#[cfg(feature = "getblocktemplate-rpcs")]
/// Performs contextual validation of the given block, but does not commit it to the state.
///
Expand All @@ -625,6 +633,7 @@ impl Request {
"best_chain_tip_nullifiers_anchors"
}
Request::BestChainNextMedianTimePast => "best_chain_next_median_time_past",
Request::BestChainBlockHash(_) => "best_chain_block_hash",
#[cfg(feature = "getblocktemplate-rpcs")]
Request::CheckBlockProposalValidity(_) => "check_block_proposal_validity",
}
Expand Down Expand Up @@ -910,6 +919,7 @@ impl TryFrom<Request> for ReadRequest {
Request::Tip => Ok(ReadRequest::Tip),
Request::Depth(hash) => Ok(ReadRequest::Depth(hash)),
Request::BestChainNextMedianTimePast => Ok(ReadRequest::BestChainNextMedianTimePast),
Request::BestChainBlockHash(hash) => Ok(ReadRequest::BestChainBlockHash(hash)),

Request::Block(hash_or_height) => Ok(ReadRequest::Block(hash_or_height)),
Request::Transaction(tx_hash) => Ok(ReadRequest::Transaction(tx_hash)),
Expand All @@ -933,14 +943,14 @@ impl TryFrom<Request> for ReadRequest {
Err("ReadService does not write blocks")
}

Request::AwaitUtxo(_) => Err("ReadService does not track pending UTXOs. \
Manually convert the request to ReadRequest::AnyChainUtxo, \
and handle pending UTXOs"),

#[cfg(feature = "getblocktemplate-rpcs")]
Request::CheckBlockProposalValidity(prepared) => {
Ok(ReadRequest::CheckBlockProposalValidity(prepared))
}

Request::AwaitUtxo(_) => Err("ReadService does not track pending UTXOs. \
Manually convert the request to ReadRequest::AnyChainUtxo, \
and handle pending UTXOs"),
}
}
}
Loading