Skip to content
Draft
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
8 changes: 7 additions & 1 deletion zebra-replay-bench/src/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,13 @@ pub fn run(
(successor, auth)
}
};
Some((successor, Some(auth)))
Some((
successor.header.clone(),
successor
.coinbase_height()
.expect("replay blocks have a coinbase height"),
auth,
))
} else {
None
};
Expand Down
9 changes: 7 additions & 2 deletions zebra-state/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
supplied roots against its own header commitments during header sync, without downloading the
block body. Rows grow to 152 bytes with a backward-compatible `FromDisk`; still consolidated
under `27.3.0`. Carried in the (already breaking, still version 5) Zakura header-sync stream
format, with `auth_data_root` serialized last. This is the data-carrying half only — nothing
consumes the new fields yet.
format, with `auth_data_root` serialized last.
- The finalized committer now authenticates a verified-commitment-trees fast block against its
already-committed successor *header* (the header chain runs far ahead of the body frontier)
plus that stored auth-data root, instead of waiting for the successor *body*, removing a
commit-pipeline stall at checkpoint/VCT boundaries. Heights whose serving-index row predates
the auth-data root (an earlier pre-release build, zero root) fall back to the body-wait commit
path until re-served.
- Added the `vct_upgrade_metadata` column family, recording the upgrade height `U` (the lowest
height this binary committed). `tree_aux` root serving now stitches the per-height trees below
`U` with the serving index at and above `U`, so a node that upgraded mid-chain serves a range
Expand Down
69 changes: 51 additions & 18 deletions zebra-state/src/service/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,16 +165,24 @@ where
Ok(())
}

/// Check that `block` is contextually valid for `network`, using
/// the `history_tree` up to and including the previous block.
#[tracing::instrument(skip(block, history_tree))]
pub(crate) fn block_commitment_is_valid_for_chain_history(
block: Arc<Block>,
/// Check that a block's header commitment is valid for `network`, using the
/// `history_tree` up to and including the previous block, the block's `height`, and a
/// resolved ZIP-244 `auth_data_root`.
///
/// This is the body-free core of [`block_commitment_is_valid_for_chain_history`]: it
/// reads only header fields, so the verified-commitment-trees fast path can confirm a
/// block's supplied note-commitment roots against its already-committed *successor's*
/// header before the successor's body has been downloaded. The `auth_data_root` is the
/// successor's own ZIP-244 auth-data root, carried over header sync (it cannot be
/// recomputed without the successor body).
pub(crate) fn header_commitment_is_valid_for_chain_history(
header: &block::Header,
height: block::Height,
network: &Network,
history_tree: &HistoryTree,
precomputed_auth_data_root: Option<AuthDataRoot>,
auth_data_root: AuthDataRoot,
) -> Result<(), ValidateContextError> {
match block.commitment(network)? {
match header.commitment(network, height)? {
block::Commitment::PreSaplingReserved(_)
| block::Commitment::FinalSaplingRoot(_)
| block::Commitment::ChainHistoryActivationReserved => {
Expand All @@ -200,7 +208,7 @@ pub(crate) fn block_commitment_is_valid_for_chain_history(
//
// https://zips.z.cash/protocol/protocol.pdf#blockheader
//
// The network is checked by [`Block::commitment`] above; it will only
// The network is checked by [`Header::commitment`] above; it will only
// return the chain history root if it's Heartwood or Canopy.
let history_tree_root = history_tree
.hash()
Expand All @@ -222,25 +230,18 @@ pub(crate) fn block_commitment_is_valid_for_chain_history(
// > [NU5 onward] hashBlockCommitments MUST be set to the value of
// > hashBlockCommitments for this block, as specified in [ZIP-244].
//
// The network is checked by [`Block::commitment`] above; it will only
// The network is checked by [`Header::commitment`] above; it will only
// return the block commitments if it's NU5 onward.
let history_tree_root = history_tree
.hash()
.or_else(|| {
(NetworkUpgrade::Heartwood.activation_height(network)
== block.coinbase_height())
.then_some(block::CHAIN_HISTORY_ACTIVATION_RESERVED.into())
(NetworkUpgrade::Heartwood.activation_height(network) == Some(height))
.then_some(block::CHAIN_HISTORY_ACTIVATION_RESERVED.into())
})
.expect(
"the history tree of the previous block must exist \
since the current block has a ChainHistoryBlockTxAuthCommitment",
);
// Use the auth data root precomputed by the verifier when available
// (it is byte-identical to recomputing it here), so the committer
// does not repeat the per-transaction auth-digest work on its
// single-threaded critical path.
let auth_data_root =
precomputed_auth_data_root.unwrap_or_else(|| block.auth_data_root());

let hash_block_commitments = ChainHistoryBlockTxAuthCommitmentHash::from_commitments(
&history_tree_root,
Expand All @@ -261,6 +262,38 @@ pub(crate) fn block_commitment_is_valid_for_chain_history(
}
}

/// Check that `block` is contextually valid for `network`, using
/// the `history_tree` up to and including the previous block.
#[tracing::instrument(skip(block, history_tree))]
pub(crate) fn block_commitment_is_valid_for_chain_history(
block: Arc<Block>,
network: &Network,
history_tree: &HistoryTree,
precomputed_auth_data_root: Option<AuthDataRoot>,
) -> Result<(), ValidateContextError> {
// The original `Block::commitment` errored with `MissingBlockHeight` for a block with
// no coinbase height; preserve that here.
let height = block
.coinbase_height()
.ok_or(CommitmentError::MissingBlockHeight {
block_hash: block.hash(),
})?;

// Use the auth data root precomputed by the verifier when available (it is
// byte-identical to recomputing it here), so the committer does not repeat the
// per-transaction auth-digest work on its single-threaded critical path. Only the
// NU5+ commitment actually consumes it.
let auth_data_root = precomputed_auth_data_root.unwrap_or_else(|| block.auth_data_root());

header_commitment_is_valid_for_chain_history(
&block.header,
height,
network,
history_tree,
auth_data_root,
)
}

/// Returns `ValidateContextError::OrphanedBlock` if the height of the given
/// block is less than or equal to the finalized tip height.
fn block_is_not_orphaned(
Expand Down
51 changes: 39 additions & 12 deletions zebra-state/src/service/finalized_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use std::{
};

use zebra_chain::{
block::{self, merkle::AuthDataRoot, Block},
block::{self, merkle::AuthDataRoot},
ironwood, orchard,
parallel::tree::{BlockNotePrecompute, NoteCommitmentTrees},
parameters::Network,
Expand Down Expand Up @@ -780,6 +780,30 @@ impl FinalizedState {
self.db.network()
}

/// Source the successor of `height` — its header, height, and ZIP-244 auth-data root —
/// from the already-committed header chain, so the committer can authenticate
/// `height`'s supplied verified-commitment-trees roots without the successor *body*.
///
/// Header sync commits headers (and their per-height roots, including the auth-data
/// root) far ahead of the body frontier, so the successor header is normally already
/// present. Returns `None` when it is not (e.g. at the header tip, or before a v2
/// header-sync peer has served the auth-data root for that height), in which case the
/// caller waits for the successor body instead.
pub(crate) fn successor_header_auth_for(
&self,
height: block::Height,
) -> Option<(Arc<block::Header>, block::Height, AuthDataRoot)> {
let successor = (height + 1)?;
let header = self.db.zakura_header(successor)?;
let auth_data_root = self
.db
.zakura_header_commitment_roots_by_height_range(successor..=successor)
.first()
.filter(|root| root.height == successor)
.map(|root| root.auth_data_root)?;
Some((header, successor, auth_data_root))
}

/// Commit a checkpoint-verified block to the state.
///
/// It's the caller's responsibility to ensure that blocks are committed in
Expand All @@ -789,7 +813,7 @@ impl FinalizedState {
ordered_block: QueuedCheckpointVerified,
prev_note_commitment_trees: Option<NoteCommitmentTrees>,
note_precompute: Option<BlockNotePrecompute>,
next_checkpoint: Option<(Arc<Block>, Option<AuthDataRoot>)>,
next_checkpoint: Option<(Arc<block::Header>, block::Height, AuthDataRoot)>,
) -> Result<
(CheckpointVerifiedBlock, NoteCommitmentTrees),
(QueuedCheckpointVerified, CommitCheckpointVerifiedError),
Expand Down Expand Up @@ -848,12 +872,14 @@ impl FinalizedState {
finalizable_block: FinalizableBlock,
prev_note_commitment_trees: Option<NoteCommitmentTrees>,
note_precompute: Option<BlockNotePrecompute>,
// The next checkpoint block (and its precomputed
// auth data root), used to verify this block's fixture roots before the fast
// path trusts them. `None` is only valid for fast blocks at the checkpoint
// handoff, where the embedded final frontiers independently authenticate
// this height's roots, or outside the checkpoint commit path.
next_checkpoint: Option<(Arc<Block>, Option<AuthDataRoot>)>,
// The successor block's header, height, and ZIP-244 auth-data root, used to
// verify this block's fixture roots before the fast path trusts them. Only the
// successor *header* and auth-data root are needed (not its body), so this can be
// sourced from the committed header chain when the successor body has not been
// downloaded yet. `None` is only valid for fast blocks at the checkpoint handoff,
// where the embedded final frontiers independently authenticate this height's
// roots, or outside the checkpoint commit path.
next_checkpoint: Option<(Arc<block::Header>, block::Height, AuthDataRoot)>,
source: &str,
) -> Result<(block::Hash, NoteCommitmentTrees), CommitCheckpointVerifiedError> {
let (
Expand Down Expand Up @@ -948,10 +974,11 @@ impl FinalizedState {
is_prevalidated,
),
];
if let Some((next_block, next_auth)) = &next_checkpoint {
if let Some((next_header, next_height, next_auth)) = &next_checkpoint {
verification_items.push(
commitment_aux_verify::CommitmentRootVerification::header_only(
next_block.clone(),
next_header.clone(),
*next_height,
*next_auth,
),
);
Expand All @@ -973,10 +1000,10 @@ impl FinalizedState {
self.vct_reject_supplied_root(height, error)
})?;

if let Some((next_block, _next_auth)) = &next_checkpoint {
if let Some((next_header, _next_height, _next_auth)) = &next_checkpoint {
self.vct_prevalidated_next = Some((
(height + 1).expect("checkpoint block heights are valid"),
next_block.hash(),
next_header.hash(),
));
} else if self
.vct
Expand Down
Loading
Loading