Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
833c0e8
perf(state): parallelize checkpoint-zone note-commitment tree commit
p0mvn Jun 17, 2026
0432d8c
address v12 bug
p0mvn Jun 17, 2026
b5bb138
fix(sync): handle empty block response without panicking
p0mvn Jun 17, 2026
7c851a8
perf(chain): parallelize auth data root computation across transactions
p0mvn Jun 17, 2026
8f627c7
perf(state): record per-block transaction count under commit-metrics
p0mvn Jun 17, 2026
07e48ce
perf(state): isolate checkpoint-commit crypto in a dedicated rayon pool
p0mvn Jun 17, 2026
69ce0cb
perf(consensus): precompute auth data root concurrently in the checkp…
p0mvn Jun 18, 2026
3ddaf9b
perf(consensus): instrument serial checkpoint-verify feed stages (com…
p0mvn Jun 18, 2026
229c620
perf: de-duplicate the librustzcash conversion for txid and auth digest
p0mvn Jun 18, 2026
4469923
perf(sync): precompute checkpoint-verified blocks concurrently in dow…
p0mvn Jun 18, 2026
a879b45
instrument verify->commit handoff: writer wait/busy/channel-depth (co…
p0mvn Jun 18, 2026
3336b86
instrument write_block: batch_reads/prepare_batch + per-sub-batch (co…
p0mvn Jun 18, 2026
168e4ae
instrument value_pools + precommit_metrics sub-batches (commit-metrics)
p0mvn Jun 18, 2026
db66c70
perf(state): serialize raw transactions in parallel when writing blocks
p0mvn Jun 18, 2026
30976c7
perf(state): compute block size in parallel + run block-write batch p…
p0mvn Jun 18, 2026
65cec65
prototype(state): any-order finalized commit pipeline (no throughput …
p0mvn Jun 18, 2026
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
310 changes: 310 additions & 0 deletions ANY_ORDER_COMMIT_DESIGN.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions zebra-chain/proptest-regressions/parallel/batch_frontier.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc e6b99b1bc89a4692d82729a22abc108af1e15537ee5e124b91e19c952bce4ee7 # shrinks to prefix_len = 0, batch = [TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(0), TestNode(102), TestNode(9419101705697858680), TestNode(510312245048786735), TestNode(2409037999938848265), TestNode(17952681726410662532), TestNode(9871315311868101043), TestNode(8695758199261085473), TestNode(9905333123009332806), TestNode(950518047781477098), TestNode(13155598055379452971)]
21 changes: 20 additions & 1 deletion zebra-chain/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,26 @@ impl Block {
///
/// [ZIP-244]: https://zips.z.cash/zip-0244
pub fn auth_data_root(&self) -> AuthDataRoot {
self.transactions.iter().collect::<AuthDataRoot>()
use rayon::prelude::*;

// Computing each transaction's authorizing-data digest dominates this
// function for blocks with many (or large) shielded transactions: each
// `auth_digest` re-serializes the transaction and BLAKE2b-hashes its
// authorizing data, scaling with the transaction's shielded I/O. The
// digests are independent, so compute them across the rayon pool.
//
// `collect` into a `Vec` preserves transaction order, so the Merkle root
// built from these digests is byte-identical to the sequential version
// (asserted by a differential proptest in `block::tests::prop`).
self.transactions
.par_iter()
.map(|tx| {
tx.auth_digest()
.unwrap_or(crate::block::merkle::AUTH_DIGEST_PLACEHOLDER)
})
.collect::<Vec<_>>()
.into_iter()
.collect::<AuthDataRoot>()
}
}

Expand Down
14 changes: 14 additions & 0 deletions zebra-chain/src/block/tests/prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,20 @@ proptest! {
}
}
}

/// `Block::auth_data_root` computes per-transaction auth digests across the
/// rayon pool; this asserts the parallel result is byte-identical to the
/// sequential computation (same digests, same transaction order, same root).
#[test]
fn auth_data_root_parallel_matches_sequential(block in any::<Block>()) {
let _init_guard = zebra_test::init();

let sequential: crate::block::merkle::AuthDataRoot =
block.transactions.iter().collect();
let parallel = block.auth_data_root();

prop_assert_eq!(sequential, parallel);
}
}

/// Test [`Block::coinbase_height`].
Expand Down
107 changes: 107 additions & 0 deletions zebra-chain/src/orchard/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,76 @@ impl NoteCommitmentTree {
}
}

/// Appends a batch of note commitment x-coordinates to the tree in parallel,
/// returning the index and root of any [`TRACKED_SUBTREE_HEIGHT`] subtree
/// completed by the batch.
///
/// The result is identical to calling [`Self::append`] for each commitment in
/// order — and capturing the last completed subtree — but the Merkle hashing
/// is parallelized across the rayon pool. This equivalence is enforced by the
/// differential property tests in [`crate::parallel::batch_frontier`].
///
/// Returns an error if the tree would overflow its capacity.
#[allow(clippy::unwrap_in_result)]
pub fn append_batch(
&mut self,
cms: &[NoteCommitmentUpdate],
) -> Result<Option<(NoteCommitmentSubtreeIndex, Node)>, NoteCommitmentTreeError> {
use crate::parallel::batch_frontier::parallel_append;

if cms.is_empty() {
return Ok(None);
}

let nodes: Vec<Node> = cms.iter().map(|cm_x| (*cm_x).into()).collect();

let old_size = self.inner.tree_size();
let new_size = old_size + nodes.len() as u64;

// A block has fewer than 2^16 actions (consensus rule), so the batch
// crosses at most one tracked-subtree (2^TRACKED_SUBTREE_HEIGHT) boundary.
let subtree_size = 1u64 << TRACKED_SUBTREE_HEIGHT;
let boundary = (old_size / subtree_size + 1) * subtree_size;

let frontier = self.inner.clone();

let (frontier, completed) = if boundary <= new_size {
let head_len = (boundary - old_size) as usize;
let (head, tail) = nodes.split_at(head_len);

let f1 = parallel_append(frontier, head.to_vec())
.map_err(|_| NoteCommitmentTreeError::FullTree)?;

let index = NoteCommitmentSubtreeIndex(
((boundary >> TRACKED_SUBTREE_HEIGHT) - 1)
.try_into()
.expect("subtree index fits in u16"),
);
let root = f1
.value()
.expect("just appended at least one leaf")
.root(Some(incrementalmerkletree::Level::from(
TRACKED_SUBTREE_HEIGHT,
)));

let f2 = parallel_append(f1, tail.to_vec())
.map_err(|_| NoteCommitmentTreeError::FullTree)?;
(f2, Some((index, root)))
} else {
let f =
parallel_append(frontier, nodes).map_err(|_| NoteCommitmentTreeError::FullTree)?;
(f, None)
};

self.inner = frontier;
*self
.cached_root
.get_mut()
.expect("a thread that previously held exclusive lock access panicked") = None;

Ok(completed)
}

/// Returns frontier of non-empty tree, or `None` if the tree is empty.
fn frontier(&self) -> Option<&NonEmptyFrontier<Node>> {
self.inner.value()
Expand Down Expand Up @@ -709,3 +779,40 @@ impl From<Vec<pallas::Base>> for NoteCommitmentTree {
tree
}
}

#[cfg(test)]
mod tests {
use incrementalmerkletree::{frontier::Frontier, Position};

use super::*;

fn note_commitment(value: u64) -> NoteCommitmentUpdate {
let mut bytes = [0; 32];
bytes[..8].copy_from_slice(&value.to_le_bytes());

Option::<pallas::Base>::from(pallas::Base::from_repr(bytes))
.expect("small little-endian integers are canonical field elements")
}

#[test]
fn append_batch_overflow_preserves_tree_and_cached_root() {
let max_position = (1u64 << MERKLE_DEPTH) - 1;
let leaf = Node(note_commitment(1));
let ommers = vec![Node(note_commitment(2)); usize::from(MERKLE_DEPTH)];
let inner = Frontier::from_parts(Position::from(max_position), leaf, ommers)
.expect("max-depth frontier is valid");
let mut tree = NoteCommitmentTree {
inner,
cached_root: Default::default(),
};

let _ = tree.root();
let original = tree.clone();

let result = tree.append_batch(&[note_commitment(3)]);

assert_eq!(result, Err(NoteCommitmentTreeError::FullTree));
tree.assert_frontier_eq(&original);
assert_eq!(tree.root(), original.root());
}
}
1 change: 1 addition & 0 deletions zebra-chain/src/parallel.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Parallel chain update methods.

pub mod batch_frontier;
pub mod tree;
Loading
Loading