From 0ef5a70f5e460fa6e0efd539ef24d0fd19a28d41 Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 30 Jun 2026 20:40:42 -0600 Subject: [PATCH 1/4] feat(state): carry and store the per-block ZIP-244 auth-data root Propagate each block's ZIP-244 `auth_data_root` alongside its note-commitment roots, without changing how the committer authenticates blocks. This is the data-model / wire / storage half of the verified-commitment-trees successor-header authentication; a follow-up consumes it in the committer. - Add `auth_data_root` to `BlockCommitmentRoots` (the `tree_aux` header-sync payload) and to the `commitment_roots_by_height` serving-index value (64 -> 96 bytes). `FromDisk` stays backward compatible: pre-release 64-byte rows decode with a zero auth-data root. Consolidated under state DB format version 27.3.0 (no version bump). - Compute and store the auth-data root at commit, thread it through the provisional Zakura header-roots store/read, the `BlockRoots` serve paths, and the `CommitHeaderRange` plumbing. - Bump the Zakura header-sync stream format to version 5. This is a breaking wire change: a v4 and a v5 node cannot exchange header ranges. The auth-data root is carried and stored but not yet consumed by the committer, so consensus behavior is unchanged. --- zebra-chain/src/parallel/commitment_aux.rs | 21 +++++++++++++- zebra-network/src/zakura/handler.rs | 8 ++--- zebra-network/src/zakura/header_sync/pipe.rs | 1 + zebra-network/src/zakura/header_sync/tests.rs | 1 + zebra-network/src/zakura/header_sync/wire.rs | 10 +++++-- zebra-network/src/zakura/testkit/cluster.rs | 1 + zebra-replay-bench/src/index.rs | 5 ++++ zebra-state/CHANGELOG.md | 7 +++++ zebra-state/src/constants.rs | 13 +++++++-- zebra-state/src/service.rs | 9 ++++++ .../service/finalized_state/commitment_aux.rs | 16 +++++++++- .../finalized_state/disk_format/shielded.rs | 29 +++++++++++++++---- .../src/service/finalized_state/tests/prop.rs | 2 ++ .../service/finalized_state/zebra_db/block.rs | 9 +++++- .../zebra_db/block/tests/vectors.rs | 1 + .../finalized_state/zebra_db/rollback.rs | 12 +++++++- .../finalized_state/zebra_db/shielded.rs | 21 ++++++++++++-- zebra-state/src/service/tests.rs | 1 + zebrad/src/commands/start.rs | 1 + 19 files changed, 147 insertions(+), 21 deletions(-) diff --git a/zebra-chain/src/parallel/commitment_aux.rs b/zebra-chain/src/parallel/commitment_aux.rs index a32a82d4aaa..cc44b6b69b3 100644 --- a/zebra-chain/src/parallel/commitment_aux.rs +++ b/zebra-chain/src/parallel/commitment_aux.rs @@ -13,7 +13,8 @@ use std::io; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use crate::{ - block, orchard, sapling, + block::{self, merkle::AuthDataRoot}, + orchard, sapling, serialization::{SerializationError, ZcashDeserialize, ZcashSerialize}, }; @@ -33,6 +34,18 @@ pub struct BlockCommitmentRoots { pub sapling_root: sapling::tree::Root, /// The Orchard note-commitment tree root as of the end of this block (empty below NU5). pub orchard_root: orchard::tree::Root, + /// The authorizing-data root (ZIP-244 `hashAuthDataRoot`) of *this* block's own + /// transactions. + /// + /// Carried so a recipient can authenticate the *predecessor's* note-commitment + /// roots against this block's NU5+ header commitment + /// (`hashBlockCommitments = BLAKE2b(chainHistoryRoot ‖ authDataRoot ‖ 0)`) without + /// downloading this block's body. Like the other roots it carries no trust: it is + /// only the co-input to a hash check against a checkpoint-committed header, so a + /// wrong value fails verification rather than being accepted. Default/zero below + /// NU5, where the header commits the chain-history root directly and this field is + /// unused. + pub auth_data_root: AuthDataRoot, } impl ZcashSerialize for BlockCommitmentRoots { @@ -40,6 +53,7 @@ impl ZcashSerialize for BlockCommitmentRoots { writer.write_u32::(self.height.0)?; self.sapling_root.zcash_serialize(&mut writer)?; self.orchard_root.zcash_serialize(&mut writer)?; + writer.write_all(&<[u8; 32]>::from(self.auth_data_root))?; Ok(()) } } @@ -52,10 +66,14 @@ impl ZcashDeserialize for BlockCommitmentRoots { let height = block::Height(reader.read_u32::()?); let sapling_root = sapling::tree::Root::zcash_deserialize(&mut reader)?; let orchard_root = orchard::tree::Root::zcash_deserialize(&mut reader)?; + let mut auth_data_root = [0u8; 32]; + reader.read_exact(&mut auth_data_root)?; + let auth_data_root = AuthDataRoot::from(auth_data_root); Ok(BlockCommitmentRoots { height, sapling_root, orchard_root, + auth_data_root, }) } } @@ -71,6 +89,7 @@ mod tests { height: block::Height(1_687_200), sapling_root: sapling::tree::NoteCommitmentTree::default().root(), orchard_root: orchard::tree::NoteCommitmentTree::default().root(), + auth_data_root: AuthDataRoot::from([7u8; 32]), }; let bytes = roots diff --git a/zebra-network/src/zakura/handler.rs b/zebra-network/src/zakura/handler.rs index bb699f7db3b..b17a53f7b22 100644 --- a/zebra-network/src/zakura/handler.rs +++ b/zebra-network/src/zakura/handler.rs @@ -187,7 +187,7 @@ const _: () = assert!(LEGACY_REQUEST_STREAM_KIND == super::legacy_gossip::ZAKURA_STREAM_LEGACY_REQUESTS); const _: () = assert!(DISCOVERY_STREAM_KIND == super::discovery::ZAKURA_STREAM_DISCOVERY); const _: () = assert!(HEADER_SYNC_STREAM_KIND == super::header_sync::ZAKURA_STREAM_HEADER_SYNC); -const _: () = assert!(ZAKURA_STREAM_VERSION_4 == ZAKURA_HEADER_SYNC_STREAM_VERSION); +const _: () = assert!(ZAKURA_STREAM_VERSION_5 == ZAKURA_HEADER_SYNC_STREAM_VERSION); const _: () = assert!(LEGACY_REQUEST_BLOCKS_BY_HASH == super::legacy_gossip::MSG_REQUEST_BLOCKS_BY_HASH); const _: () = assert!( @@ -4122,7 +4122,7 @@ fn should_run_freshness_reaper( /// The only stream-kind version this v1 handler serves. Every known kind is /// at version 1; a peer naming any other version of a known kind is rejected. const ZAKURA_STREAM_VERSION_1: u16 = 1; -const ZAKURA_STREAM_VERSION_4: u16 = 4; +const ZAKURA_STREAM_VERSION_5: u16 = 5; /// Returns whether the handler can serve a stream with this kind and version. /// @@ -6481,7 +6481,7 @@ mod tests { }, Stream { kind: HEADER_SYNC_STREAM_KIND, - version: ZAKURA_STREAM_VERSION_4, + version: ZAKURA_STREAM_VERSION_5, frame_cap: 1024, capability: ZAKURA_CAP_HEADER_SYNC, mode: StreamMode::Ordered, @@ -6501,7 +6501,7 @@ mod tests { (LEGACY_GOSSIP_STREAM_KIND, ZAKURA_STREAM_VERSION_1), (LEGACY_REQUEST_STREAM_KIND, ZAKURA_STREAM_VERSION_1), (DISCOVERY_STREAM_KIND, ZAKURA_STREAM_VERSION_1), - (HEADER_SYNC_STREAM_KIND, ZAKURA_STREAM_VERSION_4), + (HEADER_SYNC_STREAM_KIND, ZAKURA_STREAM_VERSION_5), (ZAKURA_STREAM_BLOCK_SYNC, ZAKURA_STREAM_VERSION_1), ] { assert!( diff --git a/zebra-network/src/zakura/header_sync/pipe.rs b/zebra-network/src/zakura/header_sync/pipe.rs index e4777d2a18d..5938eef47f6 100644 --- a/zebra-network/src/zakura/header_sync/pipe.rs +++ b/zebra-network/src/zakura/header_sync/pipe.rs @@ -608,6 +608,7 @@ mod tests { height: block::Height(1), sapling_root: sapling::tree::NoteCommitmentTree::default().root(), orchard_root: orchard::tree::NoteCommitmentTree::default().root(), + auth_data_root: block::merkle::AuthDataRoot::from([0u8; 32]), }], } .encode_frame() diff --git a/zebra-network/src/zakura/header_sync/tests.rs b/zebra-network/src/zakura/header_sync/tests.rs index 51356500825..ec0f1807cac 100644 --- a/zebra-network/src/zakura/header_sync/tests.rs +++ b/zebra-network/src/zakura/header_sync/tests.rs @@ -210,6 +210,7 @@ fn root_at(height: block::Height) -> BlockCommitmentRoots { height, sapling_root: sapling::tree::NoteCommitmentTree::default().root(), orchard_root: orchard::tree::NoteCommitmentTree::default().root(), + auth_data_root: block::merkle::AuthDataRoot::from([0u8; 32]), } } diff --git a/zebra-network/src/zakura/header_sync/wire.rs b/zebra-network/src/zakura/header_sync/wire.rs index eff54299071..d1c4097c9b1 100644 --- a/zebra-network/src/zakura/header_sync/wire.rs +++ b/zebra-network/src/zakura/header_sync/wire.rs @@ -5,7 +5,11 @@ pub const ZAKURA_STREAM_HEADER_SYNC: u16 = 5; /// Version of the native header-sync stream. /// /// Version 4 carries one tree-aux root for each non-empty range header. -pub const ZAKURA_HEADER_SYNC_STREAM_VERSION: u16 = 4; +/// Version 5 extends each tree-aux root with the block's ZIP-244 `auth_data_root` +/// (the co-input needed to authenticate the predecessor's note-commitment roots +/// against this block's NU5+ header commitment). This is a breaking wire change: +/// a v4 and a v5 node cannot exchange header-sync ranges. +pub const ZAKURA_HEADER_SYNC_STREAM_VERSION: u16 = 5; /// Peer status advertisement. pub const MSG_HS_STATUS: u8 = 1; @@ -29,8 +33,8 @@ pub(super) const HEADER_SYNC_MESSAGE_TYPE_BYTES: usize = 1; pub(super) const HEADER_SYNC_COUNT_BYTES: usize = 4; pub(super) const HEADER_SYNC_HAS_ROOTS_BYTES: usize = 1; pub(super) const HEADER_SYNC_BODY_SIZE_BYTES: usize = 4; -/// Encoded [`BlockCommitmentRoots`]: height + Sapling root + Orchard root. -pub(super) const HEADER_SYNC_BLOCK_COMMITMENT_ROOTS_BYTES: usize = 4 + 32 + 32; +/// Encoded [`BlockCommitmentRoots`]: height + Sapling root + Orchard root + auth-data root. +pub(super) const HEADER_SYNC_BLOCK_COMMITMENT_ROOTS_BYTES: usize = 4 + 32 + 32 + 32; pub(super) const COMMON_HEADER_BYTES: usize = 1_487; pub(super) const REGTEST_HEADER_BYTES: usize = 177; pub(super) const HEADER_SYNC_FANOUT: usize = 3; diff --git a/zebra-network/src/zakura/testkit/cluster.rs b/zebra-network/src/zakura/testkit/cluster.rs index b2a30f81c55..3d1ba7e8923 100644 --- a/zebra-network/src/zakura/testkit/cluster.rs +++ b/zebra-network/src/zakura/testkit/cluster.rs @@ -207,6 +207,7 @@ mod tests { height, sapling_root: sapling::tree::NoteCommitmentTree::default().root(), orchard_root: orchard::tree::NoteCommitmentTree::default().root(), + auth_data_root: block::merkle::AuthDataRoot::from([0u8; 32]), } } diff --git a/zebra-replay-bench/src/index.rs b/zebra-replay-bench/src/index.rs index b75388344d8..97fed5dbc18 100644 --- a/zebra-replay-bench/src/index.rs +++ b/zebra-replay-bench/src/index.rs @@ -130,6 +130,11 @@ pub fn run_roots( height, sapling_root: sapling.root(), orchard_root: orchard.root(), + auth_data_root: state + .db + .block(height.into()) + .map(|block| block.auth_data_root()) + .unwrap_or_else(|| zebra_chain::block::merkle::AuthDataRoot::from([0u8; 32])), }); let done = h - start + 1; if done.is_multiple_of(5000) || done == total { diff --git a/zebra-state/CHANGELOG.md b/zebra-state/CHANGELOG.md index 3ecf99dde55..04a86d934a7 100644 --- a/zebra-state/CHANGELOG.md +++ b/zebra-state/CHANGELOG.md @@ -12,6 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extended value-pool disk serialization with an Ironwood slot after the deferred pool, and consolidated the current verified-commitment-trees state database format changes under version `27.3.0`. +- Extended the `commitment_roots_by_height` serving index (and the `tree_aux` header-sync + payload it is served from) with each block's per-height ZIP-244 `auth_data_root`, so a node + can serve the co-input a peer needs to authenticate a block's note-commitment roots against + its successor's NU5+ header commitment. Consolidated under database format version `27.3.0` + (no version bump); serving-index rows from an earlier pre-release build (64 bytes, no + auth-data root) remain readable. Carrying the auth-data root bumps the Zakura header-sync + stream format to version 5, a breaking wire change (all peers must run the new format). - 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 diff --git a/zebra-state/src/constants.rs b/zebra-state/src/constants.rs index e9af1a35b9f..2667f7b0b5e 100644 --- a/zebra-state/src/constants.rs +++ b/zebra-state/src/constants.rs @@ -95,13 +95,20 @@ const DATABASE_FORMAT_VERSION: u64 = 27; /// Version 3 adds the verified-commitment-trees state format: /// - the `fast_sync_metadata` column family, which records fast-sync handoff state, /// - the `commitment_roots_by_height` serving index (design §4), a compact per-height -/// `(sapling_root, orchard_root)` map every node writes so a fast-synced node can serve -/// `tree_aux` roots without per-height trees, and +/// `(sapling_root, orchard_root, auth_data_root)` map every node writes so a fast-synced node +/// can serve `tree_aux` roots without per-height trees. The per-height ZIP-244 `auth_data_root` +/// is the co-input needed to authenticate a block's note-commitment roots against its +/// successor's NU5+ header commitment without re-reading the successor body, and /// - the on-open repair for incompatible stored history-tree bytes before background format /// checks read the tip tree. /// /// New databases populate the serving index going forward; existing ones open with it empty and -/// serve from per-height trees as before. +/// serve from per-height trees as before. Serving-index rows written by an earlier pre-release +/// build of this version (without the `auth_data_root`, 64 instead of 96 bytes) decode with a +/// zero auth-data root (compatibility code in +/// [`disk_format::shielded::CommitmentRootsByHeight`]'s `FromDisk`), and heights with such a row +/// fall back to the body-wait commit path until they are re-served with a real root — so this +/// remains a single consolidated pre-release version-3 format. const DATABASE_FORMAT_MINOR_VERSION: u64 = 3; /// The database format patch version, incremented each time the on-disk database format has a diff --git a/zebra-state/src/service.rs b/zebra-state/src/service.rs index d86c6d2376d..4b9aa43b3df 100644 --- a/zebra-state/src/service.rs +++ b/zebra-state/src/service.rs @@ -1531,6 +1531,15 @@ where height, sapling_root: sapling.root(), orchard_root: orchard.root(), + // The non-finalized chain holds the full block, so derive its + // ZIP-244 auth-data root to serve as the predecessor-authentication + // co-input (zero only if the block is unexpectedly absent). + auth_data_root: chain + .block(height.into()) + .map(|block| block.block.auth_data_root()) + .unwrap_or_else(|| { + zebra_chain::block::merkle::AuthDataRoot::from([0u8; 32]) + }), }), _ => None, } diff --git a/zebra-state/src/service/finalized_state/commitment_aux.rs b/zebra-state/src/service/finalized_state/commitment_aux.rs index 007367a8a5b..83b408dea21 100644 --- a/zebra-state/src/service/finalized_state/commitment_aux.rs +++ b/zebra-state/src/service/finalized_state/commitment_aux.rs @@ -20,7 +20,10 @@ use std::{ }; use thiserror::Error; -use zebra_chain::{block, orchard, sapling, sprout}; +use zebra_chain::{ + block::{self, merkle::AuthDataRoot}, + orchard, sapling, sprout, +}; use super::{FromDisk, IntoDisk, ZebraDb}; @@ -527,6 +530,14 @@ pub(crate) fn produce_block_roots( height, sapling_root: sapling.root(), orchard_root: orchard.root(), + // Below the upgrade height the serving index does not exist, so derive the + // auth-data root from the locally stored block (this archival node holds the + // body for these heights). Zero only if the body is somehow absent, in which + // case the recipient simply re-fetches from a node that has it. + auth_data_root: db + .block(height.into()) + .map(|block| block.auth_data_root()) + .unwrap_or_else(|| AuthDataRoot::from([0u8; 32])), }); } roots @@ -652,11 +663,13 @@ mod tests { height: block::Height(10), sapling_root: sapling::tree::NoteCommitmentTree::default().root(), orchard_root: orchard::tree::NoteCommitmentTree::default().root(), + auth_data_root: AuthDataRoot::from([0u8; 32]), }, BlockCommitmentRoots { height: block::Height(11), sapling_root: sapling::tree::NoteCommitmentTree::default().root(), orchard_root: orchard::tree::NoteCommitmentTree::default().root(), + auth_data_root: AuthDataRoot::from([0u8; 32]), }, ]; let roots = roots @@ -698,6 +711,7 @@ mod tests { height: block::Height(42), sapling_root: sapling::tree::NoteCommitmentTree::default().root(), orchard_root: orchard::tree::NoteCommitmentTree::default().root(), + auth_data_root: AuthDataRoot::from([0u8; 32]), }]); assert!( diff --git a/zebra-state/src/service/finalized_state/disk_format/shielded.rs b/zebra-state/src/service/finalized_state/disk_format/shielded.rs index 2cdad46dc74..2f0bf4bdc60 100644 --- a/zebra-state/src/service/finalized_state/disk_format/shielded.rs +++ b/zebra-state/src/service/finalized_state/disk_format/shielded.rs @@ -8,7 +8,7 @@ use bincode::Options; use zebra_chain::{ - block::Height, + block::{merkle::AuthDataRoot, Height}, orchard, sapling, sprout, subtree::{NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex}, }; @@ -100,15 +100,22 @@ pub struct CommitmentRootsByHeight { pub sapling: sapling::tree::Root, /// The Orchard note-commitment tree root at this height. pub orchard: orchard::tree::Root, + /// The ZIP-244 authorizing-data root (`hashAuthDataRoot`) of this block's + /// transactions. Stored alongside the note-commitment roots so this node can + /// serve it as the co-input needed to authenticate the *predecessor's* roots + /// against this block's NU5+ header commitment, without re-reading the body. + /// Default/zero below NU5. + pub auth_data_root: AuthDataRoot, } impl IntoDisk for CommitmentRootsByHeight { - type Bytes = [u8; 64]; + type Bytes = [u8; 96]; fn as_bytes(&self) -> Self::Bytes { - let mut out = [0u8; 64]; + let mut out = [0u8; 96]; out[..32].copy_from_slice(&IntoDisk::as_bytes(&self.sapling)); - out[32..].copy_from_slice(&IntoDisk::as_bytes(&self.orchard)); + out[32..64].copy_from_slice(&IntoDisk::as_bytes(&self.orchard)); + out[64..].copy_from_slice(&<[u8; 32]>::from(self.auth_data_root)); out } } @@ -116,9 +123,21 @@ impl IntoDisk for CommitmentRootsByHeight { impl FromDisk for CommitmentRootsByHeight { fn from_bytes(bytes: impl AsRef<[u8]>) -> Self { let bytes = bytes.as_ref(); + // Backwards compatible with the pre-auth-data 64-byte rows written by an earlier + // pre-release build of this database version: those decode with a zero auth-data + // root, so the writer falls back to the body-wait path for those heights until + // they are re-served with a real root. New rows are 96 bytes. + let auth_data_root = if bytes.len() >= 96 { + let mut auth_data_root = [0u8; 32]; + auth_data_root.copy_from_slice(&bytes[64..96]); + AuthDataRoot::from(auth_data_root) + } else { + AuthDataRoot::from([0u8; 32]) + }; CommitmentRootsByHeight { sapling: sapling::tree::Root::from_bytes(&bytes[..32]), - orchard: orchard::tree::Root::from_bytes(&bytes[32..]), + orchard: orchard::tree::Root::from_bytes(&bytes[32..64]), + auth_data_root, } } } diff --git a/zebra-state/src/service/finalized_state/tests/prop.rs b/zebra-state/src/service/finalized_state/tests/prop.rs index 2e6a533c78a..34b2faeac54 100644 --- a/zebra-state/src/service/finalized_state/tests/prop.rs +++ b/zebra-state/src/service/finalized_state/tests/prop.rs @@ -695,6 +695,7 @@ fn vct_peer_source_defers_unverifiable_tip_root_until_successor() -> Result<()> height: Height(i as u32), sapling_root: trees.sapling.root(), orchard_root: trees.orchard.root(), + auth_data_root: blocks[i].block.auth_data_root(), }); } } @@ -827,6 +828,7 @@ fn vct_peer_source_bad_root_refill_commits_same_height() -> Result<()> { height: Height(i as u32), sapling_root: trees.sapling.root(), orchard_root: trees.orchard.root(), + auth_data_root: blocks[i].block.auth_data_root(), }; if i == target { correct_target_root = Some(root.clone()); diff --git a/zebra-state/src/service/finalized_state/zebra_db/block.rs b/zebra-state/src/service/finalized_state/zebra_db/block.rs index 75598a299c1..11ba0135b26 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block.rs @@ -509,7 +509,7 @@ impl ZebraDb { } #[allow(clippy::unwrap_in_result)] - fn zakura_header(&self, height: block::Height) -> Option> { + pub(crate) fn zakura_header(&self, height: block::Height) -> Option> { let header_by_height = self.db.cf_handle(ZAKURA_HEADER_BY_HEIGHT).unwrap(); self.db.zs_get(&header_by_height, &height) } @@ -535,6 +535,7 @@ impl ZebraDb { height, sapling_root: value.sapling, orchard_root: value.orchard, + auth_data_root: value.auth_data_root, }); } roots @@ -557,6 +558,7 @@ impl ZebraDb { CommitmentRootsByHeight { sapling: roots.sapling_root, orchard: roots.orchard_root, + auth_data_root: roots.auth_data_root, }, ); } @@ -1443,6 +1445,10 @@ fn inferred_header_range_roots( height, sapling_root: sapling::tree::NoteCommitmentTree::default().root(), orchard_root: orchard::tree::NoteCommitmentTree::default().root(), + // Placeholder default roots: this fallback range carries no real roots + // (the recipient re-verifies and rejects them), so the auth-data root is + // an unused zero here too. + auth_data_root: zebra_chain::block::merkle::AuthDataRoot::from([0u8; 32]), }) }) .collect() @@ -2106,6 +2112,7 @@ impl DiskWriteBatch { CommitmentRootsByHeight { sapling: roots.sapling_root, orchard: roots.orchard_root, + auth_data_root: roots.auth_data_root, }, ); } diff --git a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs index 2c412f9488e..3438b54b717 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/block/tests/vectors.rs @@ -1290,6 +1290,7 @@ fn root_at(height: Height) -> BlockCommitmentRoots { height, sapling_root: sapling::tree::NoteCommitmentTree::default().root(), orchard_root: orchard::tree::NoteCommitmentTree::default().root(), + auth_data_root: zebra_chain::block::merkle::AuthDataRoot::from([0u8; 32]), } } diff --git a/zebra-state/src/service/finalized_state/zebra_db/rollback.rs b/zebra-state/src/service/finalized_state/zebra_db/rollback.rs index 964a51a23fe..1782a0fa383 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/rollback.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/rollback.rs @@ -1256,19 +1256,28 @@ mod tests { Height(1), &retained_sapling, &retained_orchard, + &zebra_chain::block::merkle::AuthDataRoot::from([0u8; 32]), ); batch.insert_commitment_roots_by_height( &db, Height(2), &retained_sapling, &retained_orchard, + &zebra_chain::block::merkle::AuthDataRoot::from([0u8; 32]), + ); + batch.insert_commitment_roots_by_height( + &db, + Height(3), + &removed_sapling, + &removed_orchard, + &zebra_chain::block::merkle::AuthDataRoot::from([0u8; 32]), ); - batch.insert_commitment_roots_by_height(&db, Height(3), &removed_sapling, &removed_orchard); batch.insert_commitment_roots_by_height( &db, Height(4), &retained_sapling, &retained_orchard, + &zebra_chain::block::merkle::AuthDataRoot::from([0u8; 32]), ); db.write_batch(batch) .expect("seeding fast-path roots succeeds"); @@ -1376,6 +1385,7 @@ mod tests { Height(height), &sapling_root(height.into()), &orchard_root(height.into()), + &zebra_chain::block::merkle::AuthDataRoot::from([0u8; 32]), ); } db.write_batch(batch) diff --git a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs index bdcfc6dbf58..655ba5c3b76 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs @@ -20,7 +20,7 @@ use std::{ use std::ops::RangeInclusive; use zebra_chain::{ - block::Height, + block::{merkle::AuthDataRoot, Height}, orchard, parallel::{commitment_aux::BlockCommitmentRoots, tree::NoteCommitmentTrees}, sapling, sprout, @@ -146,6 +146,7 @@ impl ZebraDb { height, sapling_root: value.sapling, orchard_root: value.orchard, + auth_data_root: value.auth_data_root, }); } roots @@ -664,6 +665,13 @@ impl DiskWriteBatch { .. } = finalized; + // The ZIP-244 auth-data root of this block, stored in the serving index so this + // node can hand it to a peer as the co-input needed to authenticate the + // *predecessor's* note-commitment roots against this block's NU5+ header + // commitment (without the peer re-reading this block's body). Same value the + // commitment check above already verified against the header. + let auth_data_root = finalized.block.auth_data_root(); + // Record the upgrade height `U` once, on the first block this binary commits: the lowest // height in the serving index, and the boundary below which roots are served from the // pre-upgrade per-height trees instead. Written on both commit paths so it is set even for @@ -695,7 +703,13 @@ impl DiskWriteBatch { // Persist the per-height roots into the serving index even though no per-height // tree is written, so this fast-synced node can still serve `tree_aux` roots // (design §4); otherwise the root-serving fleet collapses as nodes fast-sync. - self.insert_commitment_roots_by_height(zebra_db, *height, &sapling_root, &orchard_root); + self.insert_commitment_roots_by_height( + zebra_db, + *height, + &sapling_root, + &orchard_root, + &auth_data_root, + ); self.update_history_tree(zebra_db, history_tree); return; } @@ -745,6 +759,7 @@ impl DiskWriteBatch { *height, ¬e_commitment_trees.sapling.root(), ¬e_commitment_trees.orchard.root(), + &auth_data_root, ); self.update_history_tree(zebra_db, history_tree); @@ -834,6 +849,7 @@ impl DiskWriteBatch { height: Height, sapling_root: &sapling::tree::Root, orchard_root: &orchard::tree::Root, + auth_data_root: &AuthDataRoot, ) { let cf = zebra_db.db.cf_handle(COMMITMENT_ROOTS_BY_HEIGHT).unwrap(); self.zs_insert( @@ -842,6 +858,7 @@ impl DiskWriteBatch { CommitmentRootsByHeight { sapling: *sapling_root, orchard: *orchard_root, + auth_data_root: *auth_data_root, }, ); } diff --git a/zebra-state/src/service/tests.rs b/zebra-state/src/service/tests.rs index 81ad0d0c48c..438e3166226 100644 --- a/zebra-state/src/service/tests.rs +++ b/zebra-state/src/service/tests.rs @@ -47,6 +47,7 @@ fn root_at(height: Height) -> BlockCommitmentRoots { height, sapling_root: sapling::tree::NoteCommitmentTree::default().root(), orchard_root: orchard::tree::NoteCommitmentTree::default().root(), + auth_data_root: zebra_chain::block::merkle::AuthDataRoot::from([0u8; 32]), } } diff --git a/zebrad/src/commands/start.rs b/zebrad/src/commands/start.rs index 98dec3a2af5..96264d7d9b8 100644 --- a/zebrad/src/commands/start.rs +++ b/zebrad/src/commands/start.rs @@ -2100,6 +2100,7 @@ mod zakura_header_sync_driver_tests { height, sapling_root: sapling::tree::NoteCommitmentTree::default().root(), orchard_root: orchard::tree::NoteCommitmentTree::default().root(), + auth_data_root: zebra_chain::block::merkle::AuthDataRoot::from([0u8; 32]), } } From 6989eed8c8f9a77a94bb7ec3f9c5933e71622fa3 Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 30 Jun 2026 21:14:53 -0600 Subject: [PATCH 2/4] test(state): update commitment_roots_by_height snapshots for the auth-data root The serving-index value grew 64->96 bytes with the per-height auth-data root, so the raw-column-family snapshots gain the 32-byte auth-data root (the all-0xFF ZIP-244 placeholder for these pre-NU5 blocks). --- .../commitment_roots_by_height_raw_data@mainnet_0.snap | 2 +- .../commitment_roots_by_height_raw_data@mainnet_1.snap | 4 ++-- .../commitment_roots_by_height_raw_data@mainnet_2.snap | 6 +++--- .../commitment_roots_by_height_raw_data@testnet_0.snap | 2 +- .../commitment_roots_by_height_raw_data@testnet_1.snap | 4 ++-- .../commitment_roots_by_height_raw_data@testnet_2.snap | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@mainnet_0.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@mainnet_0.snap index c96257d2aff..dacf204005b 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@mainnet_0.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@mainnet_0.snap @@ -5,6 +5,6 @@ expression: cf_data [ KV( k: "000000", - v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f", + v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ), ] diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@mainnet_1.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@mainnet_1.snap index 50911e30cd8..ac60e994439 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@mainnet_1.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@mainnet_1.snap @@ -5,10 +5,10 @@ expression: cf_data [ KV( k: "000000", - v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f", + v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ), KV( k: "000001", - v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f", + v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ), ] diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@mainnet_2.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@mainnet_2.snap index 5f670090392..cc494f2667f 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@mainnet_2.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@mainnet_2.snap @@ -5,14 +5,14 @@ expression: cf_data [ KV( k: "000000", - v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f", + v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ), KV( k: "000001", - v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f", + v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ), KV( k: "000002", - v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f", + v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ), ] diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@testnet_0.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@testnet_0.snap index c96257d2aff..dacf204005b 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@testnet_0.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@testnet_0.snap @@ -5,6 +5,6 @@ expression: cf_data [ KV( k: "000000", - v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f", + v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ), ] diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@testnet_1.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@testnet_1.snap index 50911e30cd8..ac60e994439 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@testnet_1.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@testnet_1.snap @@ -5,10 +5,10 @@ expression: cf_data [ KV( k: "000000", - v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f", + v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ), KV( k: "000001", - v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f", + v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ), ] diff --git a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@testnet_2.snap b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@testnet_2.snap index 5f670090392..cc494f2667f 100644 --- a/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@testnet_2.snap +++ b/zebra-state/src/service/finalized_state/disk_format/tests/snapshots/commitment_roots_by_height_raw_data@testnet_2.snap @@ -5,14 +5,14 @@ expression: cf_data [ KV( k: "000000", - v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f", + v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ), KV( k: "000001", - v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f", + v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ), KV( k: "000002", - v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f", + v: "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493eae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ), ] From 3664fcc161ca95f766acae4756ed1db2fa1d36aa Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 30 Jun 2026 21:14:53 -0600 Subject: [PATCH 3/4] chore(clippy): fix stable clippy 1.96 drift in upstream zakura files CI's stable clippy advanced to 1.96 since #336/#337 merged, newly flagging two pre-existing issues that block this PR: - admission.rs: an `admission_decision` doc comment was orphaned above `request_deadline` (empty_line_after_doc_comments); moved it to its function. - blocksync_fuzz: allow too_many_arguments on the test-harness `spawn_action_driver`. --- .../src/zakura/block_sync/admission.rs | 17 ++++++++--------- .../src/zakura/testkit/blocksync_fuzz/mod.rs | 1 + 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/zebra-network/src/zakura/block_sync/admission.rs b/zebra-network/src/zakura/block_sync/admission.rs index aa96485c722..c10abcf8a21 100644 --- a/zebra-network/src/zakura/block_sync/admission.rs +++ b/zebra-network/src/zakura/block_sync/admission.rs @@ -56,15 +56,6 @@ pub(super) fn request_priority( } } -/// Returns the admission decision for a candidate block response starting at `start_height`. -/// -/// Floor-rescue requests may use any available response budget up to `response_byte_cap`. -/// Speculative requests above the floor are admitted only while the configured reorder -/// lookahead byte and block limits still have capacity. -/// -/// Returns `None` when no bytes can be admitted, or when an above-floor request would -/// exceed the lookahead limits. - /// The per-request network deadline (the one sanctioned timer), set by priority: /// /// - **Floor**: a short fixed leash. On expiry the lowest missing height is rescued @@ -97,6 +88,14 @@ pub(super) fn request_deadline( } } +/// Returns the admission decision for a candidate block response starting at `start_height`. +/// +/// Floor-rescue requests may use any available response budget up to `response_byte_cap`. +/// Speculative requests above the floor are admitted only while the configured reorder +/// lookahead byte and block limits still have capacity. +/// +/// Returns `None` when no bytes can be admitted, or when an above-floor request would +/// exceed the lookahead limits. pub(super) fn admission_decision( config: &ZakuraBlockSyncConfig, snapshot: AdmissionSnapshot, diff --git a/zebra-network/src/zakura/testkit/blocksync_fuzz/mod.rs b/zebra-network/src/zakura/testkit/blocksync_fuzz/mod.rs index 99641cc0b51..95f6c3c921e 100644 --- a/zebra-network/src/zakura/testkit/blocksync_fuzz/mod.rs +++ b/zebra-network/src/zakura/testkit/blocksync_fuzz/mod.rs @@ -204,6 +204,7 @@ async fn stop_task(task: &mut JoinHandle<()>) { } /// Answers the reactor's actions from the corpus and mock apply frontier. +#[allow(clippy::too_many_arguments)] fn spawn_action_driver( handle: BlockSyncHandle, mut actions: mpsc::Receiver, From 6d277b8871b0a307f5c1277b0f44ae571bed4efa Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 30 Jun 2026 21:38:06 -0600 Subject: [PATCH 4/4] test(config): store #337 BBR block-sync config fields in the config fixture #337 (BBR-lite congestion control) added block-sync config fields (bbr_*, floor_bypass_slots, floor_rescue_timeout, no_progress_peer_cooldown) without updating the stored config fixture, so `last_config_is_stored` fails: the generated config matches no stored config. Regenerate the newest fixture to include them. Unrelated to this PR's auth-data-root change (which adds no config fields). --- zebrad/tests/common/configs/v5.0.0-rc.5.toml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/zebrad/tests/common/configs/v5.0.0-rc.5.toml b/zebrad/tests/common/configs/v5.0.0-rc.5.toml index cdbb8caa899..2220e9aa8c0 100644 --- a/zebrad/tests/common/configs/v5.0.0-rc.5.toml +++ b/zebrad/tests/common/configs/v5.0.0-rc.5.toml @@ -99,8 +99,21 @@ message_rate_per_second = 2048 stream_open_rate_per_second = 32 [network.zakura.block_sync] +bbr_cwnd_gain_percent = 200 +bbr_cwnd_unit = "bytes" +bbr_delay_gradient_percent = 150 +bbr_delivery_rate_window = "10s" +bbr_min_cwnd = 4 +bbr_min_cwnd_bytes = 4194304 +bbr_probe_bw_gain_percent = 125 +bbr_probe_rtt_duration = "200ms" +bbr_probe_rtt_interval = "10s" +bbr_rtprop_window = "10s" +bbr_startup_growth_percent = 200 fanout = 1 +floor_bypass_slots = 2 floor_peer_avoid_cooldown = "8s" +floor_rescue_timeout = "2s" initial_inflight_requests = 64 max_blocks_per_response = 1 max_inflight_block_bytes = 6442450944 @@ -109,6 +122,7 @@ max_reorder_lookahead_blocks = 4096 max_reorder_lookahead_bytes = 6408896512 max_response_bytes = 33554432 max_submitted_block_applies = 401 +no_progress_peer_cooldown = "3m" request_timeout = "8s" size_deviation_tolerance = 200 status_refresh_interval = "30s"