feat: add tx effects tree root to the block header - #25132
Draft
spalladino wants to merge 15 commits into
Draft
Conversation
Forward port of #23932
The tx-effects-tree-root change modifies the protocol types crate, which invalidates both the protocol and mock circuit pinned builds.
Adds the structured tx-effect hash (per-field sub-hashes over the blob encoding slices, 9-field outer hash, tx-hash-bound leaf), the tree node accumulator, four domain separators, and the tx_effects_tree_root field in BlockHeader. BLOCK_HEADER_LENGTH 22->23.
Tx base emits the tx-effect leaf in TxRollupPublicInputs, tx merge accumulates pairwise without zero-skipping, and block root writes the accumulated root into the new BlockHeader field (0 for empty blocks, unhashed leaf for single-tx blocks).
Adds the zero field to the C++ genesis header preimage and updates the hand-maintained GENESIS_BLOCK_HEADER_HASH and GENESIS_ARCHIVE_ROOT.
Runtime-offset writes into the 1088-field private-logs slice lowered to a bb RAM block with ~72k accesses, over the 65535 small-range-constraint cap, aborting write_vk for both tx base circuits. Build the slice in an unconstrained hint and verify with dynamic reads (ROM), mirroring create_tx_blob_data. Encoding unchanged.
The extra block header field grows every reset variant by 2-8k gates.
Adds txEffectsTreeRoot to BlockHeader and TxRollupPublicInputs, the structured TxEffect hash/leaf helpers over the shared blob-slice encodings, Body.computeTxEffectsTreeRoot, Noir conversions, and cross-language test vectors (empty header hash and tx effect leaf pinned between TS and Noir). Real assembly sites stay at zero until the next commit wires them.
Prover header builders, the archiver blob reconstruction path, and TXE block creation now derive the root from the block's tx effects; the checkpoint orchestrator verifies the built header through a single root-computing path.
Regenerates l1-contracts checkpoint fixtures and PXE store snapshots, adds the missing txEffectsTreeRoot entry to the PXE block header oracle wire mapping, and bumps ORACLE_VERSION_MAJOR since the block header oracle return type grew.
The new block header field changes every VK, the protocol contracts hash, and the genesis archive roots, and grows the persisted header encoding, so pre-existing PXE databases are selected away via the schema version bump.
…ount Adds the new header/tx-rollup field to the committed Prover.toml sample inputs (regenerated from a mock orchestrator run where the anchor header hash is validated in-circuit) and rejects TxEffect construction with more contract class logs than the protocol maximum.
Adds getTxEffectMembershipWitness(txHash) to the node, backed by an archiver resolver that rebuilds the block's tx effects tree, verifies the root against the stored header, and returns the leaf index and variable-depth sibling path. UnbalancedMerkleTreeCalculator gains an async-hasher factory so the poseidon tree shares one shaping routine.
Computes each block's tx-effect leaves once when the block is stored (atomically with the block row) instead of recomputing every leaf on each membership witness request. The resolver rebuilds only the internal nodes from the stored leaves and verifies the requested tx's own leaf against the effects it serves. ARCHIVER_DB_VERSION 7->8.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Proving that a note (or any effect) belonged to a given tx from a block header currently requires replaying the checkpoint's sponge blob over every tx's effect data — up to 24,576 fields of preimage. This adds a per-block commitment so "tx X was included in block N and produced exactly effects E" is provable with a handful of hashes (≤16 worst case), revealing only the field of interest.
Design
Tx effect hash (structured, not flat). Each variable-length field of the tx effect is hashed on its own over its slice of the existing blob-field encoding (
create_tx_blob_data/TxEffect.toBlobFields), at its actual runtime length — no new serialization format:The
tx_start_markeris the existing first blob field, which packs the revert code, every array count, and the total field count — so all lengths are bound and the0empty-field sentinel is unambiguous. Structuring the hash this way keeps selective proofs cheap: proving one note hash needs only the note-hashes slice as preimage, with the other six sub-hashes as opaque witness values.Tree shape. The leaves are accumulated bottom-up through the existing rollup proving tree: tx base emits the leaf, tx merge combines pairwise with
poseidon2(DOM_SEP__TX_EFFECTS_TREE, [left, right]), with no zero-skipping (there are no padding txs, so every leaf is a real tx). The result is the same greedily-filled wonky tree as the proving tree itself, whose shape is pinned in-circuit byassert_rollups_filled_greedily— a pure function of the tx count, so the prover has no freedom over it (e.g. 3 txs →H(H(l0,l1),l2), 5 txs →H(H(H(l0,l1),H(l2,l3)),l4)). An empty block has root0; a single-tx block's root is the leaf, unhashed. Leaves and internal nodes use different domain separators, so no leaf/node second-preimage confusion is possible.Header position.
tx_effects_tree_rootsits immediately aftersponge_blob_hash(both are effect commitments), growingBLOCK_HEADER_LENGTHfrom 22 to 23:Approach
BlockRollupPublicInputs, checkpoint PIs, or L1 — it is committed via the header hash only, and TS recomputes it locally wherever headers are assembled (prover builders, archiver blob reconstruction, TXE).TxEffect.computeTxEffectHash/Leaf,Body.computeTxEffectsTreeRoot. Cross-language test vectors pin TS↔Noir (empty header hash, tx effect leaf) and C++↔Noir (genesis header hash).write_vk.API changes
New node API for consuming the root:
getTxEffectMembershipWitness(txHash)returns{ blockNumber, root, leafIndex, siblingPath }— a variable-depth witness againstBlockHeader.txEffectsTreeRoot. The archiver computes each block's leaves once at sync time and persists them (atomically with the block row;ARCHIVER_DB_VERSION7 → 8), so answering a witness request only rebuilds the internal nodes from stored leaves, verifies the root against the stored header, and checks the requested tx's own leaf against the effects it serves.UnbalancedMerkleTreeCalculatorgained an async-hasher factory (createAsync) for the poseidon tree.Breaking changes
BLOCK_HEADER_LENGTH22 → 23, so every tx hash, VK, contract artifact, and the genesis constants change. Oracle interface version 30 → 31 (the block header oracle return type grew); PXE data schema version 13 → 14 (pre-existing PXE DBs are re-initialized on next open). Migration note included.Prover.toml) updated; all fixtures in CI's execute list pass locally.private-kernel-init-simulated's fixture (not executed by CI) remains stale pending the e2e regeneration path.Fixes A-1635