From 9acee0991f39e6c4988edde5ee2e7f9e5f304cd5 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Sat, 18 Jul 2026 15:19:04 -0300 Subject: [PATCH 1/8] chore(fast-inbox): drop inHash from parity and checkpoint header circuits (A-1387) Remove the dead unconstrained in_hash pass-through from InboxParity and ParityPublicInputs, and remove in_hash from the CheckpointHeader consensus format (struct, to_be_bytes, serialization length, Empty, hash fixtures). The Inbox rolling hash is now the checkpoint's only inbox commitment. Retire NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP in favor of MAX_L1_TO_L2_MSGS_PER_CHECKPOINT, and drop the now-unused L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH and the NUM_MSGS_PER_BASE_PARITY / NUM_BASE_PARITY_PER_ROOT_PARITY frontier fan-in constants. Regenerated constants.gen.ts and ConstantsGen.sol via remake-constants. --- .../scripts/constants-codegen/solidity.json | 4 +--- .../src/abis/parity_public_inputs.nr | 7 ------- ...checkpoint_rollup_public_inputs_composer.nr | 13 +++++-------- .../src/checkpoint_root/tests/mod.nr | 13 ++++++------- .../rollup-lib/src/parity/inbox_parity.nr | 6 ------ .../src/parity/tests/inbox_parity_tests.nr | 7 ------- .../src/tests/rollup_fixture_builder.nr | 3 +-- .../crates/types/src/abis/checkpoint_header.nr | 18 ++++++------------ .../crates/types/src/constants.nr | 18 +++--------------- 9 files changed, 22 insertions(+), 67 deletions(-) diff --git a/l1-contracts/scripts/constants-codegen/solidity.json b/l1-contracts/scripts/constants-codegen/solidity.json index 2046ce24d44a..2fcd7cc212c4 100644 --- a/l1-contracts/scripts/constants-codegen/solidity.json +++ b/l1-contracts/scripts/constants-codegen/solidity.json @@ -8,7 +8,5 @@ "EMPTY_EPOCH_OUT_HASH", "FEE_JUICE_ADDRESS", "BLS12_POINT_COMPRESSED_BYTES", - "ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH", - "NUM_MSGS_PER_BASE_PARITY", - "NUM_BASE_PARITY_PER_ROOT_PARITY" + "ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH" ] diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/parity_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/parity_public_inputs.nr index a2260af3e24e..1914ae700516 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/parity_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/parity_public_inputs.nr @@ -3,12 +3,6 @@ use types::traits::{Deserialize, Empty, Serialize}; #[derive(Deserialize, Eq, Serialize)] pub struct ParityPublicInputs { - // The L1 `in_hash`: the sha256 frontier root of the checkpoint's L1-to-L2 messages, checked on L1 against - // `inbox.consume()`. It is an UNCONSTRAINED pass-through: `InboxParity` echoes the value the prover supplies and - // does not recompute it from the messages (the frontier tree is gone from the parity body). Until the Fast Inbox - // flip moves the L1 anchor to the rolling hash, `in_hash` remains the authoritative L1 check, so the prover must - // supply the true frontier root; the in-circuit tie between it and the processed messages is deferred to the flip. - pub in_hash: Field, // Rolling hash of the Inbox message chain before absorbing this checkpoint's messages. pub start_rolling_hash: Field, // Rolling hash of the Inbox message chain after absorbing the `num_msgs` real messages. Each link is @@ -29,7 +23,6 @@ pub struct ParityPublicInputs { impl Empty for ParityPublicInputs { fn empty() -> Self { ParityPublicInputs { - in_hash: 0, start_rolling_hash: 0, end_rolling_hash: 0, start_sponge: L1ToL2MessageSponge::empty(), diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/checkpoint_root/components/checkpoint_rollup_public_inputs_composer.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/checkpoint_root/components/checkpoint_rollup_public_inputs_composer.nr index 56aa0e132933..b1f4bf8df971 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/checkpoint_root/components/checkpoint_rollup_public_inputs_composer.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/checkpoint_root/components/checkpoint_rollup_public_inputs_composer.nr @@ -21,8 +21,8 @@ use types::{ pub struct CheckpointRollupPublicInputsComposer { merged_rollup: BlockRollupPublicInputs, - // Public inputs of the inbox parity proof (verified by the caller). The checkpoint's `in_hash` and - // `inbox_rolling_hash` are sourced from it, and its message sponge is checked against the blocks' accumulated one. + // Public inputs of the inbox parity proof (verified by the caller). The checkpoint's `inbox_rolling_hash` is + // sourced from it, and its message sponge is checked against the blocks' accumulated one. parity: ParityPublicInputs, // The below are all hints: previous_out_hash: AppendOnlyTreeSnapshot, @@ -117,8 +117,8 @@ impl CheckpointRollupPublicInputsComposer { new_archive: merged_rollup.new_archive, previous_out_hash: self.previous_out_hash, new_out_hash, - // Sourced from the inbox parity proof, which commits to the same message list behind the L1-checked - // `in_hash`. Checkpoint merges assert continuity across checkpoints (`right.start == left.end`). + // Sourced from the inbox parity proof, which commits to the checkpoint's message list. Checkpoint merges + // assert continuity across checkpoints (`right.start == left.end`). start_inbox_rolling_hash: self.parity.start_rolling_hash, end_inbox_rolling_hash: self.parity.end_rolling_hash, checkpoint_header_hashes, @@ -145,10 +145,7 @@ impl CheckpointRollupPublicInputsComposer { last_archive_root: merged_rollup.previous_archive.root, block_headers_hash: merged_rollup.block_headers_hash, blobs_hash: self.blobs_hash, - // `in_hash` (the sha256 frontier root, checked on L1) and `inbox_rolling_hash` are sourced from the inbox - // parity proof. `in_hash` is an unconstrained pass-through there (see `ParityPublicInputs::in_hash`); it - // remains the authoritative L1 check until the Fast Inbox flip. - in_hash: self.parity.in_hash, + // Sourced from the inbox parity proof: the checkpoint's only inbox commitment (AZIP-22 Fast Inbox). inbox_rolling_hash: self.parity.end_rolling_hash, epoch_out_hash, slot_number: constants.slot_number, diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/checkpoint_root/tests/mod.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/checkpoint_root/tests/mod.nr index 38b92d5163e4..9dec29e65cba 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/checkpoint_root/tests/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/checkpoint_root/tests/mod.nr @@ -154,8 +154,8 @@ impl TestBuilder { let start_block_number = fixture_builder.start_block_number; if !is_first_block(left_rollup_vk_index) & (left_rollup_vk_index != BLOCK_MERGE_ROLLUP_VK_INDEX) { - // Change the start_block_number to be smaller than the left rollup's start_block_number so that the in_hash - // won't be set on the left rollup. + // Change the start_block_number to be smaller than the left rollup's start_block_number so that the + // first-block designation is not on the left rollup. fixture_builder.start_block_number = start_block_number - 1; } let mut left_rollup = fixture_builder.get_merged_block_rollup_public_inputs( @@ -163,8 +163,8 @@ impl TestBuilder { start_block_number + num_left_blocks - 1, ); if is_first_block(right_rollup_vk_index) { - // Change the start_block_number to be the right rollup's start_block_number so that the in_hash will be set - // on the right rollup. + // Change the start_block_number to be the right rollup's start_block_number so that the first-block + // designation is on the right rollup. fixture_builder.start_block_number = start_block_number + num_left_blocks; } let mut right_rollup = fixture_builder.get_merged_block_rollup_public_inputs( @@ -192,8 +192,8 @@ impl TestBuilder { left_rollup.start_state = hints.previous_block_header.state; // The InboxParity proof commits to the same checkpoint messages the block roots absorbed. Its message sponge - // (empty to the checkpoint's accumulated value) matches the blocks' merged sponge; its rolling hash and in_hash - // feed the checkpoint header. Built at the base slot the block fixtures use for their sponge. + // (empty to the checkpoint's accumulated value) matches the blocks' merged sponge; its rolling hash feeds the + // checkpoint header. Built at the base slot the block fixtures use for their sponge. let inbox_parity = fixture_builder.get_parity_public_inputs(fixture_builder.start_slot_number); Self { @@ -445,7 +445,6 @@ impl TestBuilder { last_archive_root: left.previous_archive.root, block_headers_hash, blobs_hash: self.hints.blobs_hash, - in_hash: self.inbox_parity.in_hash, inbox_rolling_hash: self.inbox_parity.end_rolling_hash, epoch_out_hash, slot_number: left.constants.slot_number, diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/parity/inbox_parity.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/parity/inbox_parity.nr index 83f99510b696..c8784c827a6f 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/parity/inbox_parity.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/parity/inbox_parity.nr @@ -18,10 +18,6 @@ pub struct InboxParityPrivateInputs { pub(crate) start_rolling_hash: Field, // Message-bundle sponge before this checkpoint's messages (empty at checkpoint start). pub(crate) start_sponge: L1ToL2MessageSponge, - // The L1 `in_hash` (sha256 frontier root of this checkpoint's messages), passed through UNCONSTRAINED to the - // public inputs. See `ParityPublicInputs::in_hash`: the frontier tree is gone from this body, so the prover - // supplies the value and the circuit does not recompute or bind it to `msgs`. - pub(crate) in_hash: Field, pub(crate) vk_tree_root: Field, pub(crate) prover_id: Field, } @@ -35,7 +31,6 @@ pub struct InboxParityPrivateInputs { /// - Absorbs the same real messages into the message sponge (real-count absorb; the checkpoint root asserts this /// equals the sponge the block roots accumulate) /// - Asserts the padding lanes past `num_msgs` are zero so they cannot silently enter either accumulator -/// - Passes `in_hash` through unconstrained (see `InboxParityPrivateInputs::in_hash`) /// /// The output feeds the Checkpoint Root circuits, which verify this proof against the `{64, 256, 1024}` VK ladder. /// @@ -51,7 +46,6 @@ pub fn execute(inputs: InboxParityPrivateInputs) -> ParityPublicI end_sponge.absorb(inputs.msgs, inputs.num_msgs); ParityPublicInputs { - in_hash: inputs.in_hash, start_rolling_hash: inputs.start_rolling_hash, end_rolling_hash, start_sponge: inputs.start_sponge, diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/parity/tests/inbox_parity_tests.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/parity/tests/inbox_parity_tests.nr index 3ec816a2eb0d..89c20b998519 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/parity/tests/inbox_parity_tests.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/parity/tests/inbox_parity_tests.nr @@ -18,14 +18,11 @@ fn public_inputs_match_expected() { num_msgs: S, start_rolling_hash: 0, start_sponge: L1ToL2MessageSponge::empty(), - in_hash: 0x1234, vk_tree_root: 42, prover_id: 7, }; let public_inputs = inbox_parity::execute(private_inputs); - // `in_hash` is echoed through unconstrained. - assert_eq(public_inputs.in_hash, 0x1234); assert_eq(public_inputs.vk_tree_root, 42); assert_eq(public_inputs.prover_id, 7); @@ -57,7 +54,6 @@ fn rolling_hash_and_sponge_ignore_padding_and_thread_start() { num_msgs: 3, start_rolling_hash: start, start_sponge, - in_hash: 0, vk_tree_root: 42, prover_id: 7, }; @@ -88,7 +84,6 @@ fn sponge_matches_block_root_real_count_absorb() { num_msgs: 2, start_rolling_hash: 0, start_sponge: L1ToL2MessageSponge::empty(), - in_hash: 0, vk_tree_root: 42, prover_id: 7, }; @@ -113,7 +108,6 @@ fn non_zero_padding_lane_fails() { num_msgs: 2, start_rolling_hash: 0, start_sponge: L1ToL2MessageSponge::empty(), - in_hash: 0, vk_tree_root: 42, prover_id: 7, }; @@ -127,7 +121,6 @@ fn num_msgs_greater_than_size_fails() { num_msgs: S + 1, start_rolling_hash: 0, start_sponge: L1ToL2MessageSponge::empty(), - in_hash: 0, vk_tree_root: 42, prover_id: 7, }; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/tests/rollup_fixture_builder.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/tests/rollup_fixture_builder.nr index 129c55ae4061..c96feaf9d36e 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/tests/rollup_fixture_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/tests/rollup_fixture_builder.nr @@ -48,7 +48,7 @@ pub struct RollupFixtureBuilder { pub protocol_contracts_hash: Field, pub prover_id: Field, - // Used to identify the first block, which will have an empty start_sponge_blob, and non-empty in_hash. + // Used to identify the first block, which will have an empty start_sponge_blob. pub start_block_number: u32, // Used to identify the first checkpoint, which will have an empty start_blob_accumulator. @@ -458,7 +458,6 @@ impl RollupFixtureBuilder { pub fn get_parity_public_inputs(self, slot_number: Field) -> ParityPublicInputs { ParityPublicInputs { - in_hash: slot_number * 85831493, start_rolling_hash: self.get_inbox_rolling_hash(slot_number), end_rolling_hash: self.get_inbox_rolling_hash(slot_number + 1), // The message sponge resets per checkpoint, so it starts empty and ends at the checkpoint's accumulated diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/checkpoint_header.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/checkpoint_header.nr index 59cb06c87a07..ca150f0d819f 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/checkpoint_header.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/checkpoint_header.nr @@ -14,10 +14,9 @@ pub struct CheckpointHeader { pub last_archive_root: Field, pub block_headers_hash: Field, pub blobs_hash: Field, - pub in_hash: Field, - // Inbox rolling-hash chain value after consuming all L1-to-L2 messages bundled into this checkpoint. It is the - // dual of `in_hash` (AZIP-22 Fast Inbox): the truncated-to-field sha256 chain the L1 Inbox accumulates. Currently a - // pass-through commitment; `in_hash` remains the authoritative L1 check until the Fast Inbox flip. + // Inbox rolling-hash chain value after consuming all L1-to-L2 messages bundled into this checkpoint (AZIP-22 Fast + // Inbox): the truncated-to-field sha256 chain the L1 Inbox accumulates. This is the checkpoint's only inbox + // commitment. pub inbox_rolling_hash: Field, // The root of the epoch out hash balanced tree. The out hash of the first checkpoint in the epoch is inserted at // index 0, the second at index 1, and so on. @@ -43,7 +42,6 @@ impl Empty for CheckpointHeader { last_archive_root: 0, block_headers_hash: 0, blobs_hash: 0, - in_hash: 0, inbox_rolling_hash: 0, epoch_out_hash: 0, slot_number: 0, @@ -62,7 +60,6 @@ impl CheckpointHeader { let last_archive_root_bytes: [u8; 32] = self.last_archive_root.to_be_bytes(); let block_headers_hash_bytes: [u8; 32] = self.block_headers_hash.to_be_bytes(); let blobs_hash_bytes: [u8; 32] = self.blobs_hash.to_be_bytes(); - let in_hash_bytes: [u8; 32] = self.in_hash.to_be_bytes(); let inbox_rolling_hash_bytes: [u8; 32] = self.inbox_rolling_hash.to_be_bytes(); let epoch_out_hash_bytes: [u8; 32] = self.epoch_out_hash.to_be_bytes(); let slot_number_bytes: [u8; 32] = self.slot_number.to_be_bytes(); @@ -83,7 +80,6 @@ impl CheckpointHeader { last_archive_root_bytes .concat(block_headers_hash_bytes) .concat(blobs_hash_bytes) - .concat(in_hash_bytes) .concat(inbox_rolling_hash_bytes) .concat(epoch_out_hash_bytes) .concat(slot_number_bytes) @@ -108,7 +104,7 @@ fn empty_checkpoint_header_hash_matches_ts() { let header = CheckpointHeader::empty(); // Generated from checkpoint_header.test.ts - let empty_checkpoint_header_hash_from_ts = 0x0008c3e5bbea4fba57201a69d4bf70a0d255df921fdef4924c22da087f9338c2; + let empty_checkpoint_header_hash_from_ts = 0x002e384af86a480f952aa16443fd29646a9063865e62d7c403fc7ed697bb7712; assert_eq(header.hash(), empty_checkpoint_header_hash_from_ts); } @@ -119,7 +115,6 @@ fn checkpoint_header_hash_matches_ts() { last_archive_root: 123, block_headers_hash: 456, blobs_hash: 77, - in_hash: 88, inbox_rolling_hash: 89, epoch_out_hash: 99, slot_number: 1234, @@ -137,7 +132,7 @@ fn checkpoint_header_hash_matches_ts() { assert_eq(deserialized, header); // Generated from checkpoint_header.test.ts - let checkpoint_header_hash_from_ts = 0x00519b87177a8a5e4edd03f4d820aec6d402497ef1ab70e2ecd4d4c39b339611; + let checkpoint_header_hash_from_ts = 0x00751391e842cd7b2014478255dd3309df86327197a0feb03f0af1b758f62ba5; assert_eq(header.hash(), checkpoint_header_hash_from_ts); } @@ -150,7 +145,6 @@ fn checkpoint_header_hash_large_values_matches_ts() { last_archive_root: MAX_FIELD_VALUE - 123, block_headers_hash: MAX_FIELD_VALUE - 456, blobs_hash: MAX_FIELD_VALUE - 77, - in_hash: MAX_FIELD_VALUE - 88, inbox_rolling_hash: MAX_FIELD_VALUE - 89, epoch_out_hash: MAX_FIELD_VALUE - 99, slot_number: MAX_FIELD_VALUE - 1234, @@ -170,7 +164,7 @@ fn checkpoint_header_hash_large_values_matches_ts() { assert_eq(deserialized, header); // Generated from checkpoint_header.test.ts - let checkpoint_header_hash_large_values_from_ts = 0x00d64307fa93c32ae46a4c3b6a1911d31994daa8d11d50201f0086a6cbaa9bac; + let checkpoint_header_hash_large_values_from_ts = 0x005bd09725c6e77a4a28a7ccdaf7875ba5882431ca3c82e62db96e8a12769ce5; assert_eq(header.hash(), checkpoint_header_hash_large_values_from_ts); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index 262adeec56b3..58dbd4831614 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -61,12 +61,10 @@ pub global NOTE_HASH_SUBTREE_ROOT_SIBLING_PATH_LENGTH: u32 = NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT; pub global NULLIFIER_SUBTREE_ROOT_SIBLING_PATH_LENGTH: u32 = NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT; -pub global L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH: u32 = - L1_TO_L2_MSG_TREE_HEIGHT - L1_TO_L2_MSG_SUBTREE_HEIGHT; // Cap on L1-to-L2 messages bundled into a single L2 block. One quarter of the per-checkpoint // cap: a checkpoint drains its Inbox consumption across up to four message-bearing blocks. pub global MAX_L1_TO_L2_MSGS_PER_BLOCK: u32 = 256; -// Cap on L1-to-L2 messages consumed by a single checkpoint. Semantically today's NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP. +// Cap on L1-to-L2 messages consumed by a single checkpoint. pub global MAX_L1_TO_L2_MSGS_PER_CHECKPOINT: u32 = 1024; // Minimum bucket age, in seconds, at the start of a checkpoint's build frame for its consumption to be mandatory under // the streaming Inbox censorship cutoff. One L1 slot: validators cannot be required to act on @@ -109,9 +107,6 @@ pub global MAX_NULLIFIER_READ_REQUESTS_PER_CALL: u32 = 16; pub global MAX_KEY_VALIDATION_REQUESTS_PER_CALL: u32 = MAX_PRIVATE_LOGS_PER_CALL; // docs:end:constants -// ROLLUP CONTRACT CONSTANTS - constants used only in l1-contracts -pub global NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP: u32 = 1 << L1_TO_L2_MSG_SUBTREE_HEIGHT; - // VK TREE CONSTANTS pub global PRIVATE_KERNEL_INIT_VK_INDEX: u32 = 0; pub global PRIVATE_KERNEL_INNER_VK_INDEX: u32 = 1; @@ -453,7 +448,6 @@ pub global BLOCK_HEADER_LENGTH: u32 = APPEND_ONLY_TREE_SNAPSHOT_LENGTH pub global CHECKPOINT_HEADER_LENGTH: u32 = 1 /* last_archive_root */ + 1 /* block_headers_hash */ + 1 /* blobs_hash */ - + 1 /* in_hash */ + 1 /* inbox_rolling_hash */ + 1 /* out_hash */ + 1 /* slot_number */ @@ -675,12 +669,6 @@ pub global INBOX_PARITY_SIZE_SMALL: u32 = 64; pub global INBOX_PARITY_SIZE_MEDIUM: u32 = 256; pub global INBOX_PARITY_SIZE_LARGE: u32 = 1024; -// Fan-in of the sha256 `in_hash` frontier tree (NUM_BASE_PARITY_PER_ROOT_PARITY * NUM_MSGS_PER_BASE_PARITY == -// NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP). The circuits no longer build this tree, but the constants are still consumed -// by the generated Solidity `Constants` and the L1 `Parity.t.sol` test that checks the frontier-root construction. -pub global NUM_MSGS_PER_BASE_PARITY: u32 = 256; -pub global NUM_BASE_PARITY_PER_ROOT_PARITY: u32 = 4; - // Lengths of the different types of proofs in fields pub global RECURSIVE_PROOF_LENGTH: u32 = 410; pub global NESTED_RECURSIVE_PROOF_LENGTH: u32 = RECURSIVE_PROOF_LENGTH; @@ -1443,7 +1431,7 @@ mod test { use crate::traits::ToField; use super::{ GLOBAL_INDEX_CONTRACT_MIN_REVERTIBLE_SIDE_EFFECT_COUNTER_OFFSET, INBOX_PARITY_SIZE_LARGE, - MAX_ETH_ADDRESS_VALUE, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, + MAX_ETH_ADDRESS_VALUE, MAX_L1_TO_L2_MSGS_PER_CHECKPOINT, TOTAL_COUNTED_SIDE_EFFECTS_PER_CALL, }; @@ -1490,7 +1478,7 @@ mod test { #[test] fn parity_covers_all_l1_to_l2_msgs() { // The largest InboxParity rung must be able to hold a full checkpoint's worth of messages. - assert_eq(INBOX_PARITY_SIZE_LARGE, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP); + assert_eq(INBOX_PARITY_SIZE_LARGE, MAX_L1_TO_L2_MSGS_PER_CHECKPOINT); } #[test] From 7c77dae717811ca38f6a274871379066c3b948c4 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Sat, 18 Jul 2026 15:19:19 -0300 Subject: [PATCH 2/8] chore(fast-inbox): drop inHash from the L1 checkpoint header and delete FrontierLib (A-1387) Remove inHash from the ProposedHeader struct and its hash preimage in ProposedHeaderLib, keeping the packing byte-for-byte in lockstep with the noir and stdlib checkpoint headers. Update the decoder base struct and the propose test helpers, and regenerate the checkpoint fixtures (drop header.inHash, recompute headerHash). Delete FrontierLib and its only remaining users: the parity cross-check test (Parity.t.sol, which verified the deleted parity circuits) and the frontier merkle test/harness. The frontier tree is gone from both circuits and L1. --- .../src/core/libraries/crypto/FrontierLib.sol | 91 ------------------- .../libraries/rollup/ProposedHeaderLib.sol | 2 - l1-contracts/test/Parity.t.sol | 80 ---------------- l1-contracts/test/RollupFieldRange.t.sol | 2 - l1-contracts/test/base/DecoderBase.sol | 2 - l1-contracts/test/base/RollupBase.sol | 2 - .../EscapeHatchIntegrationBase.sol | 5 +- .../test/fixtures/empty_checkpoint_1.json | 3 +- .../test/fixtures/empty_checkpoint_2.json | 3 +- .../test/fixtures/mixed_checkpoint_1.json | 3 +- .../test/fixtures/mixed_checkpoint_2.json | 3 +- .../test/fixtures/single_tx_checkpoint_1.json | 3 +- .../test/fixtures/single_tx_checkpoint_2.json | 3 +- l1-contracts/test/harnesses/Frontier.sol | 42 --------- l1-contracts/test/merkle/Frontier.t.sol | 30 ------ .../ValidatorSelection.t.sol | 2 - 16 files changed, 8 insertions(+), 268 deletions(-) delete mode 100644 l1-contracts/src/core/libraries/crypto/FrontierLib.sol delete mode 100644 l1-contracts/test/Parity.t.sol delete mode 100644 l1-contracts/test/harnesses/Frontier.sol delete mode 100644 l1-contracts/test/merkle/Frontier.t.sol diff --git a/l1-contracts/src/core/libraries/crypto/FrontierLib.sol b/l1-contracts/src/core/libraries/crypto/FrontierLib.sol deleted file mode 100644 index 6fd66130a15a..000000000000 --- a/l1-contracts/src/core/libraries/crypto/FrontierLib.sol +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; - -/** - * @title FrontierLib - * @author Aztec Labs - * @notice Library for managing frontier trees. - */ -library FrontierLib { - struct Forest { - mapping(uint256 index => bytes32 zero) zeros; - } - - struct Tree { - uint256 nextIndex; - mapping(uint256 => bytes32) frontier; - } - - function initialize(Forest storage _self, uint256 _height) internal { - _self.zeros[0] = bytes32(0); - for (uint256 i = 1; i <= _height; i++) { - _self.zeros[i] = Hash.sha256ToField(bytes.concat(_self.zeros[i - 1], _self.zeros[i - 1])); - } - } - - function insertLeaf(Tree storage _self, bytes32 _leaf) internal returns (uint256) { - uint256 index = _self.nextIndex; - uint256 level = computeLevel(index); - bytes32 right = _leaf; - for (uint256 i = 0; i < level; i++) { - right = Hash.sha256ToField(bytes.concat(_self.frontier[i], right)); - } - _self.frontier[level] = right; - - _self.nextIndex++; - - return index; - } - - function root(Tree storage _self, Forest storage _forest, uint256 _height, uint256 _size) - internal - view - returns (bytes32) - { - uint256 next = _self.nextIndex; - if (next == 0) { - return _forest.zeros[_height]; - } - if (next == _size) { - return _self.frontier[_height]; - } - - uint256 index = next - 1; - uint256 level = computeLevel(index); - - // We should start at the highest frontier level with a left leaf - bytes32 temp = _self.frontier[level]; - - uint256 bits = index >> level; - for (uint256 i = level; i < _height; i++) { - bool isRight = bits & 1 == 1; - if (isRight) { - temp = Hash.sha256ToField(bytes.concat(_self.frontier[i], temp)); - } else { - temp = Hash.sha256ToField(bytes.concat(temp, _forest.zeros[i])); - } - bits >>= 1; - } - - return temp; - } - - function isFull(Tree storage _self, uint256 _size) internal view returns (bool) { - return _self.nextIndex == _size; - } - - function computeLevel(uint256 _leafIndex) internal pure returns (uint256) { - // The number of trailing ones is how many times in a row we are the right child. - // e.g., each time this happens we go another layer up to update the parent. - uint256 count = 0; - uint256 index = _leafIndex; - while (index & 1 == 1) { - count++; - index >>= 1; - } - return count; - } -} diff --git a/l1-contracts/src/core/libraries/rollup/ProposedHeaderLib.sol b/l1-contracts/src/core/libraries/rollup/ProposedHeaderLib.sol index 5a456dda77bb..374752f646f9 100644 --- a/l1-contracts/src/core/libraries/rollup/ProposedHeaderLib.sol +++ b/l1-contracts/src/core/libraries/rollup/ProposedHeaderLib.sol @@ -21,7 +21,6 @@ struct ProposedHeader { bytes32 lastArchiveRoot; bytes32 blockHeadersHash; bytes32 blobsHash; - bytes32 inHash; bytes32 inboxRollingHash; bytes32 outHash; Slot slotNumber; @@ -56,7 +55,6 @@ library ProposedHeaderLib { _header.lastArchiveRoot, _header.blockHeadersHash, _header.blobsHash, - _header.inHash, _header.inboxRollingHash, _header.outHash, _header.slotNumber, diff --git a/l1-contracts/test/Parity.t.sol b/l1-contracts/test/Parity.t.sol deleted file mode 100644 index 8ed33bf99e75..000000000000 --- a/l1-contracts/test/Parity.t.sol +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {Test} from "forge-std/Test.sol"; - -import {FrontierMerkle} from "./harnesses/Frontier.sol"; -import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; - -contract ParityTest is Test { - function setUp() public {} - - // Checks whether sha root matches output of base parity circuit - function testRootMatchesBaseParity() public { - uint248[256] memory msgs; - for (uint248 i = 0; i < msgs.length; i++) { - msgs[i] = i; - } - - // We can't use Constants.NUM_MSGS_PER_BASE_PARITY directly when defining the array so we do the check here to - // ensure it does not get outdated. - assertEq(msgs.length, Constants.NUM_MSGS_PER_BASE_PARITY, "NUM_MSGS_PER_BASE_PARITY changed, update msgs."); - - uint256 treeHeight = 8; // log_2(NUM_MSGS_PER_BASE_PARITY) - // We don't have log_2 directly accessible in solidity so I just do the following check here to ensure - // the hardcoded value is not outdated. - assertEq( - 2 ** treeHeight, - Constants.NUM_MSGS_PER_BASE_PARITY, - "Base parity circuit subtree height changed, update treeHeight." - ); - - FrontierMerkle frontier = new FrontierMerkle(treeHeight); - - for (uint256 i = 0; i < msgs.length; i++) { - frontier.insertLeaf(bytes32(bytes.concat(new bytes(1), bytes31(msgs[i])))); - } - // matches noir-protocol-circuits/crates/parity-lib/src/tests/parity_base_tests.nr - bytes32 expectedRoot = 0x00279d4d4dd5bcb9b1a4e742640588b917102f9f8bc97a6c95706ca4e7a8a76b; - assertEq(frontier.root(), expectedRoot, "Root does not match base parity circuit root"); - } - - // Checks whether sha root matches output of root parity circuit - function testRootMatchesRootParity() public { - // sha256 roots coming out of base parity circuits - // matches noir-protocol-circuits/crates/parity-lib/src/root/root_parity_inputs.nr - uint248[4] memory baseRoots = [ - 0xb3a3fc1968999f2c2d798b900bdf0de41311be2a4d20496a7e792a521fc8ab, - 0x43f78e0ebc9633ce336a8c086064d898c32fb5d7d6011f5427459c0b8d14e9, - 0x024259b6404280addcc9319bc5a32c9a5d56af5c93b2f941fa326064fbe963, - 0x53042d820859d80c474d4694e03778f8dc0ac88fc1c3a97b4369c1096e904a - ]; - - // We can't use Constants.NUM_BASE_PARITY_PER_ROOT_PARITY directly when defining the array so we do the check here - // to ensure it does not get outdated. - assertEq( - baseRoots.length, - Constants.NUM_BASE_PARITY_PER_ROOT_PARITY, - "NUM_BASE_PARITY_PER_ROOT_PARITY changed, update baseRoots." - ); - - uint256 treeHeight = 2; // log_2(NUM_BASE_PARITY_PER_ROOT_PARITY) - // We don't have log_2 directly accessible in solidity so I just do the following check here to ensure - // the hardcoded value is not outdated. - assertEq( - 2 ** treeHeight, - Constants.NUM_BASE_PARITY_PER_ROOT_PARITY, - "Root parity circuit subtree height changed, update treeHeight." - ); - - FrontierMerkle frontier = new FrontierMerkle(treeHeight); - - for (uint256 i = 0; i < baseRoots.length; i++) { - frontier.insertLeaf(bytes32(bytes.concat(new bytes(1), bytes31(baseRoots[i])))); - } - - bytes32 expectedRoot = 0x00a0c56543aa73140e5ca27231eee3107bd4e11d62164feb411d77c9d9b2da47; - assertEq(frontier.root(), expectedRoot, "Root does not match root parity circuit root"); - } -} diff --git a/l1-contracts/test/RollupFieldRange.t.sol b/l1-contracts/test/RollupFieldRange.t.sol index 9763efa0ae4e..a3d05d087ddd 100644 --- a/l1-contracts/test/RollupFieldRange.t.sol +++ b/l1-contracts/test/RollupFieldRange.t.sol @@ -164,8 +164,6 @@ contract RollupFieldRangeTest is RollupBase { vm.warp(max(block.timestamp, Timestamp.unwrap(ts))); _populateInbox(full.populate.sender, full.populate.recipient, full.populate.l1ToL2Content); - // The header's inHash field is an unconstrained pass-through hint post-flip; propose does not check it. - header.inHash = bytes32(0); header.gasFees.feePerL2Gas = SafeCast.toUint128(rollup.getManaMinFeeAt(ts, true)); // Every field the range check guards, set to the maximal in-range value. diff --git a/l1-contracts/test/base/DecoderBase.sol b/l1-contracts/test/base/DecoderBase.sol index 7abcb375c1aa..21f1431d7978 100644 --- a/l1-contracts/test/base/DecoderBase.sol +++ b/l1-contracts/test/base/DecoderBase.sol @@ -46,7 +46,6 @@ contract DecoderBase is TestBase { address coinbase; bytes32 feeRecipient; GasFees gasFees; - bytes32 inHash; bytes32 inboxRollingHash; bytes32 lastArchiveRoot; bytes32 outHash; @@ -104,7 +103,6 @@ contract DecoderBase is TestBase { lastArchiveRoot: full.checkpoint.header.lastArchiveRoot, blockHeadersHash: full.checkpoint.header.blockHeadersHash, blobsHash: full.checkpoint.header.blobsHash, - inHash: full.checkpoint.header.inHash, inboxRollingHash: full.checkpoint.header.inboxRollingHash, outHash: full.checkpoint.header.outHash, slotNumber: Slot.wrap(full.checkpoint.header.slotNumber), diff --git a/l1-contracts/test/base/RollupBase.sol b/l1-contracts/test/base/RollupBase.sol index f7080e4cc617..cdf6ee4014a2 100644 --- a/l1-contracts/test/base/RollupBase.sol +++ b/l1-contracts/test/base/RollupBase.sol @@ -171,8 +171,6 @@ contract RollupBase is DecoderBase { // We jump to the time of the block, always past the L1 block the messages above landed in. vm.warp(max(block.timestamp + 1, Timestamp.unwrap(full.checkpoint.header.timestamp))); - // The header's inHash field is an unconstrained pass-through hint post-flip; propose does not check it. - full.checkpoint.header.inHash = bytes32(0); // Streaming Inbox: reference the newest bucket so the checkpoint consumes all messages // seeded above and the mandatory-consumption assert is trivially satisfied (a wrong ref could only revert). uint256 bucketHint = rollup.getInbox().getCurrentBucketSeq(); diff --git a/l1-contracts/test/escape-hatch/integration/EscapeHatchIntegrationBase.sol b/l1-contracts/test/escape-hatch/integration/EscapeHatchIntegrationBase.sol index 5e6cd5fea89e..ea22ac757e75 100644 --- a/l1-contracts/test/escape-hatch/integration/EscapeHatchIntegrationBase.sol +++ b/l1-contracts/test/escape-hatch/integration/EscapeHatchIntegrationBase.sol @@ -130,18 +130,17 @@ abstract contract EscapeHatchIntegrationBase is ValidatorSelectionTestBase { * @dev Uses: * - archive: GENESIS_ARCHIVE_ROOT * - oracleInput: zero - * - header fields from fixture for blockHeadersHash/blobsHash/inHash/outHash, rest overridden + * - header fields from fixture for blockHeadersHash/blobsHash/outHash, rest overridden */ function _buildProposeArgs(address _proposer) internal view returns (ProposeArgs memory args, bytes memory blobs) { bytes32 archive = bytes32(Constants.GENESIS_ARCHIVE_ROOT); Slot slotNumber = rollup.getCurrentSlot(); - // Build header fresh, only copying blockHeadersHash/blobsHash/inHash/outHash from fixture + // Build header fresh, only copying blockHeadersHash/blobsHash/outHash from fixture ProposedHeader memory header = ProposedHeader({ lastArchiveRoot: archive, blockHeadersHash: full.checkpoint.header.blockHeadersHash, blobsHash: full.checkpoint.header.blobsHash, - inHash: full.checkpoint.header.inHash, inboxRollingHash: full.checkpoint.header.inboxRollingHash, outHash: full.checkpoint.header.outHash, slotNumber: slotNumber, diff --git a/l1-contracts/test/fixtures/empty_checkpoint_1.json b/l1-contracts/test/fixtures/empty_checkpoint_1.json index 1f829f1b819b..eb94728b3ccd 100644 --- a/l1-contracts/test/fixtures/empty_checkpoint_1.json +++ b/l1-contracts/test/fixtures/empty_checkpoint_1.json @@ -34,7 +34,6 @@ "lastArchiveRoot": "0x177a4955b31ecaafad999753938a44e526b54c5ba5d536688227f85f15cfbdf5", "blockHeadersHash": "0x2e3e0911389bc48fa8126a93273d016cc7dc08019f8ffc5f1f5ae7d90745eaa2", "blobsHash": "0x00e5b752fe6bc2154155ff3a979c4c5fa91d3ac0d716169ac521e1560fd83b2b", - "inHash": "0x00de7b349d2306334734e4f58b1302a6ed5a6c796a706f6597a5641b6d468223", "inboxRollingHash": "0x00cad6497cf81748167158f149ba70c31f34c68b0ae1b156117de63e073d14b5", "outHash": "0x00c95e0ceb41951039e1592745ec2faea9866f6eaf01bf189a4463b4143af093", "slotNumber": 99, @@ -48,7 +47,7 @@ "totalManaUsed": 0, "accumulatedFees": 0 }, - "headerHash": "0x0002fc455005d993521be4e6f8ed92d1e0b50e58b21445881c87d02ed51a1e4c", + "headerHash": "0x00230a553b85ec7931eded45ec1ea096e47789226758b16dca0403f5ab53f3d6", "numTxs": 0 } } diff --git a/l1-contracts/test/fixtures/empty_checkpoint_2.json b/l1-contracts/test/fixtures/empty_checkpoint_2.json index d2dcd961b92f..5ee1a899f2f8 100644 --- a/l1-contracts/test/fixtures/empty_checkpoint_2.json +++ b/l1-contracts/test/fixtures/empty_checkpoint_2.json @@ -34,7 +34,6 @@ "lastArchiveRoot": "0x09d45c1e01b8596153838a068ddb470ead95e6e1e151f40b4d48664c1e311af6", "blockHeadersHash": "0x0b3bda1754ca30707b8c0bbe72760c68e574cf23309e7e4fd7cabea36b4078da", "blobsHash": "0x000e9acabf609c9c113078ecb383ba6310573ce246958b605452132617d2c960", - "inHash": "0x006504de282a40084bb8098456a915c645d53482d351db52fa9433b6cd638763", "inboxRollingHash": "0x0080dbf2bd9576e81fd99fc0f9d35bfd7fc15a6221d0b6e1e92a1884f14d0c84", "outHash": "0x00c95e0ceb41951039e1592745ec2faea9866f6eaf01bf189a4463b4143af093", "slotNumber": 102, @@ -48,7 +47,7 @@ "totalManaUsed": 0, "accumulatedFees": 0 }, - "headerHash": "0x00120ecaf7a72f61adf3c8ee180acd930401221668a4792a8f91584755840d16", + "headerHash": "0x00a184a160ada9af4ba75961ba596043d76f56c75b2e7e9652a0c162a7bf59b9", "numTxs": 0 } } diff --git a/l1-contracts/test/fixtures/mixed_checkpoint_1.json b/l1-contracts/test/fixtures/mixed_checkpoint_1.json index b1604aceb038..fda840d182d1 100644 --- a/l1-contracts/test/fixtures/mixed_checkpoint_1.json +++ b/l1-contracts/test/fixtures/mixed_checkpoint_1.json @@ -67,7 +67,6 @@ "lastArchiveRoot": "0x177a4955b31ecaafad999753938a44e526b54c5ba5d536688227f85f15cfbdf5", "blockHeadersHash": "0x087b6f59388fa4207876ee1b50521ff838d6eed422d0ad07ff996393f32a77e6", "blobsHash": "0x001bef3ff3f657c565ff86d5072186f7f2bfddb8a31ca0714562c164fe954d84", - "inHash": "0x00de7b349d2306334734e4f58b1302a6ed5a6c796a706f6597a5641b6d468223", "inboxRollingHash": "0x00cad6497cf81748167158f149ba70c31f34c68b0ae1b156117de63e073d14b5", "outHash": "0x00cffdbb0e7f5e164d314d781d38ae31230910a3bae4c34e7df6222df71b1539", "slotNumber": 99, @@ -81,7 +80,7 @@ "totalManaUsed": 0, "accumulatedFees": 0 }, - "headerHash": "0x00c1431d117825d25f8169e35eb70ed4b16c0fc4b3cac85506852c240d03740e", + "headerHash": "0x00f1c11fddfca8ff6073d1715c8b886d501facad96b7b032fb440462849697b8", "numTxs": 4 } } diff --git a/l1-contracts/test/fixtures/mixed_checkpoint_2.json b/l1-contracts/test/fixtures/mixed_checkpoint_2.json index d2125d5786a4..2fdaa2de220d 100644 --- a/l1-contracts/test/fixtures/mixed_checkpoint_2.json +++ b/l1-contracts/test/fixtures/mixed_checkpoint_2.json @@ -67,7 +67,6 @@ "lastArchiveRoot": "0x0c87f76b6c5cc918111d2acc975bf7f2133f5c3cc23c8cf6cc1c3e78f5c84a3e", "blockHeadersHash": "0x144dbe32a03df9ccc672207c93cd22099eb91e44a71e1676148cd3c6c6c98b9e", "blobsHash": "0x008bd0b669b942b57ccf85d3401214db65cde3608afa0b2ae0e57f35ec60d72e", - "inHash": "0x006504de282a40084bb8098456a915c645d53482d351db52fa9433b6cd638763", "inboxRollingHash": "0x0080dbf2bd9576e81fd99fc0f9d35bfd7fc15a6221d0b6e1e92a1884f14d0c84", "outHash": "0x008a85da85a596471f2e5fe402fde332723da8d24b6e7affd60d16c7cb7e9020", "slotNumber": 102, @@ -81,7 +80,7 @@ "totalManaUsed": 0, "accumulatedFees": 0 }, - "headerHash": "0x00ab5faa05cb38faa2cd1fc62c40007f64594c9fdc3bb8b7b478a351a7c7dc5a", + "headerHash": "0x00e0c076d07c17d50e3aefe9ca264ba7a4894fab4956272bfbea600e9e34bb0f", "numTxs": 4 } } diff --git a/l1-contracts/test/fixtures/single_tx_checkpoint_1.json b/l1-contracts/test/fixtures/single_tx_checkpoint_1.json index 6d409f05d521..1969d1dfa7f7 100644 --- a/l1-contracts/test/fixtures/single_tx_checkpoint_1.json +++ b/l1-contracts/test/fixtures/single_tx_checkpoint_1.json @@ -43,7 +43,6 @@ "lastArchiveRoot": "0x177a4955b31ecaafad999753938a44e526b54c5ba5d536688227f85f15cfbdf5", "blockHeadersHash": "0x07db5c24565ad9a2c9d39ef7d9a4446e9742d6090567ff28aef9a45f4738a5cb", "blobsHash": "0x00ec2400a7cfc9d975cb0802980d49387588738160a0cf0301f07e1abad6456c", - "inHash": "0x00de7b349d2306334734e4f58b1302a6ed5a6c796a706f6597a5641b6d468223", "inboxRollingHash": "0x00cad6497cf81748167158f149ba70c31f34c68b0ae1b156117de63e073d14b5", "outHash": "0x007c92c6cf05665e1c02a305370a4d38bcb8b555261c6b39c862f8c067d6bcb7", "slotNumber": 99, @@ -57,7 +56,7 @@ "totalManaUsed": 0, "accumulatedFees": 0 }, - "headerHash": "0x0072a35395a79027db315321cbdaba5bc0b070979c3395b7369ec902ca48d9b2", + "headerHash": "0x0018784f81057f236b2da6a1e9bc20cb7c931343cbecb2035b605dc8ca5dd3cb", "numTxs": 1 } } diff --git a/l1-contracts/test/fixtures/single_tx_checkpoint_2.json b/l1-contracts/test/fixtures/single_tx_checkpoint_2.json index dc1244dcd73c..5487087aed88 100644 --- a/l1-contracts/test/fixtures/single_tx_checkpoint_2.json +++ b/l1-contracts/test/fixtures/single_tx_checkpoint_2.json @@ -43,7 +43,6 @@ "lastArchiveRoot": "0x0f198bb13b63a3a8791a8b9994167bbe1554811a106fda0acb5e6c6539b1713e", "blockHeadersHash": "0x12813725f2d16ce92088d2401ffa4a53ce6061bf75b77320ca7b8ef6c5145adf", "blobsHash": "0x002b8ae4c9f405529e2b689b829852ad52f77acdac57d1dbac3dabea1760affc", - "inHash": "0x006504de282a40084bb8098456a915c645d53482d351db52fa9433b6cd638763", "inboxRollingHash": "0x0080dbf2bd9576e81fd99fc0f9d35bfd7fc15a6221d0b6e1e92a1884f14d0c84", "outHash": "0x0007eac1d76cddf92b28b8f11cd292f199f35dfc588376092986575cef487f59", "slotNumber": 102, @@ -57,7 +56,7 @@ "totalManaUsed": 0, "accumulatedFees": 0 }, - "headerHash": "0x000b3c0bf27d3123a81b66570e4b63734cb06bbfd260a70fabbbfd41dec13255", + "headerHash": "0x00782cfd7fa9f4e407c734be2832e8afb818d2ef8a0a11d288a3df34bcae0047", "numTxs": 1 } } diff --git a/l1-contracts/test/harnesses/Frontier.sol b/l1-contracts/test/harnesses/Frontier.sol deleted file mode 100644 index 719e5692c8b5..000000000000 --- a/l1-contracts/test/harnesses/Frontier.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {FrontierLib} from "@aztec/core/libraries/crypto/FrontierLib.sol"; - -import {Ownable} from "@oz/access/Ownable.sol"; - -// This truncates each hash and hash preimage to 31 bytes to follow Noir. -// It follows the logic in /noir-protocol-circuits/crates/parity-lib/src/utils/sha256_merkle_tree.nr -// TODO(Miranda): Possibly nuke this contract, and use a generic version which can either use -// regular sha256 or sha256ToField when emulating circuits -contract FrontierMerkle is Ownable { - using FrontierLib for FrontierLib.Tree; - using FrontierLib for FrontierLib.Forest; - - uint256 public immutable HEIGHT; - uint256 public immutable SIZE; - - // Practically immutable value as we only set it in the constructor. - FrontierLib.Forest internal forest; - - FrontierLib.Tree internal tree; - - constructor(uint256 _height) Ownable(msg.sender) { - HEIGHT = _height; - SIZE = 2 ** _height; - forest.initialize(_height); - } - - function insertLeaf(bytes32 _leaf) external onlyOwner returns (uint256) { - return tree.insertLeaf(_leaf); - } - - function root() external view returns (bytes32) { - return tree.root(forest, HEIGHT, SIZE); - } - - function isFull() external view returns (bool) { - return tree.isFull(SIZE); - } -} diff --git a/l1-contracts/test/merkle/Frontier.t.sol b/l1-contracts/test/merkle/Frontier.t.sol deleted file mode 100644 index b6b8e3844a90..000000000000 --- a/l1-contracts/test/merkle/Frontier.t.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {Test} from "forge-std/Test.sol"; - -import {NaiveMerkle} from "./Naive.sol"; -import {FrontierMerkle} from "../harnesses/Frontier.sol"; - -contract FrontierTest is Test { - function setUp() public { - // Pause gas metering as calculating the root on each insert is expensive - vm.pauseGasMetering(); - } - - function testFrontier() public { - uint256 depth = 10; - - NaiveMerkle merkle = new NaiveMerkle(depth); - FrontierMerkle frontier = new FrontierMerkle(depth); - - uint256 upper = frontier.SIZE(); - for (uint256 i = 0; i < upper; i++) { - bytes32 leaf = sha256(abi.encode(i + 1)); - merkle.insertLeaf(leaf); - frontier.insertLeaf(leaf); - assertEq(merkle.computeRoot(), frontier.root(), "Frontier Roots should be equal"); - } - } -} diff --git a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol index 80820932ec1b..a968be6d803f 100644 --- a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol +++ b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol @@ -546,8 +546,6 @@ contract ValidatorSelectionTest is ValidatorSelectionTestBase { { uint128 manaMinFee = SafeCast.toUint128(rollup.getManaMinFeeAt(Timestamp.wrap(block.timestamp), true)); - // The header's inHash field is an unconstrained pass-through hint post-flip; propose does not check it. - header.inHash = bytes32(0); header.gasFees.feePerL2Gas = manaMinFee; } From 6baff8760c57a4fafbdf22f836e82ab2f2d2272c Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Sat, 18 Jul 2026 15:19:49 -0300 Subject: [PATCH 3/8] chore(fast-inbox): follow the checkpoint header inHash removal through stdlib and node (A-1387) Mirror the consensus-format change in the stdlib CheckpointHeader (struct, serialization, viem, hash fixtures) and the circuit-ABI stdlib types (ParityPublicInputs, InboxParityPrivateInputs) and their noir conversion mappings. Sweep the header-inHash populate/read sites: the lightweight checkpoint builder, the checkpoint-proving orchestrator, the sequencer fixture writer, and the checkpoint proposal (its block-level inHash now sources zero rather than the removed header field). Regenerate the checkpoint p2p wire fixture for the shorter header. Replace NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP with MAX_L1_TO_L2_MSGS_PER_CHECKPOINT across the TS consumers. The block-level inHash struct fields (BlockProposal, L2Block) and stdlib/src/messaging/in_hash.ts remain for the node cleanup issue. --- .../src/l1/calldata_retriever.test.ts | 4 +- .../archiver/src/test/mock_structs.ts | 10 +- yarn-project/ethereum/src/contracts/rollup.ts | 1 - .../src/base_parity_inputs.test.ts | 5 +- .../ivc-integration/src/bb_js_debug.test.ts | 5 +- .../src/conversion/server.ts | 3 - .../light/lightweight_checkpoint_builder.ts | 6 +- .../orchestrator/checkpoint-proving-state.ts | 4 +- .../src/test/bb_prover_parity.test.ts | 6 +- .../schema_tests.ts | 1 - .../l1_publisher.integration.test.ts | 120 +++++------------- .../src/publisher/write_json.ts | 1 - .../src/sequencer/sequencer.test.ts | 4 +- .../sequencer-client/src/test/utils.ts | 3 +- yarn-project/stdlib/src/messaging/in_hash.ts | 4 +- .../stdlib/src/messaging/inbox_leaf.ts | 8 +- .../stdlib/src/p2p/checkpoint_proposal.ts | 13 +- .../stdlib/src/p2p/wire_compat_fixtures.ts | 11 +- .../src/parity/inbox_parity_private_inputs.ts | 6 - .../stdlib/src/parity/parity_public_inputs.ts | 14 +- .../src/rollup/checkpoint_header.test.ts | 8 +- .../stdlib/src/rollup/checkpoint_header.ts | 20 +-- .../checkpoint_root_rollup_private_inputs.ts | 8 +- yarn-project/stdlib/src/tests/factories.ts | 9 +- yarn-project/stdlib/src/tests/mocks.ts | 5 +- yarn-project/txe/src/state_machine/index.ts | 1 - .../src/duties/validation_service.test.ts | 6 +- .../validator-client/src/validator.test.ts | 7 +- .../src/native/native_world_state.test.ts | 8 +- yarn-project/world-state/src/test/utils.ts | 6 +- .../src/world-state-db/merkle_tree_db.ts | 2 +- 31 files changed, 88 insertions(+), 221 deletions(-) diff --git a/yarn-project/archiver/src/l1/calldata_retriever.test.ts b/yarn-project/archiver/src/l1/calldata_retriever.test.ts index cc589173dbf9..ce2fa5c3f3c3 100644 --- a/yarn-project/archiver/src/l1/calldata_retriever.test.ts +++ b/yarn-project/archiver/src/l1/calldata_retriever.test.ts @@ -1248,7 +1248,7 @@ describe('CalldataRetriever', () => { expect(result.blockHash).toBe(tx.blockHash); // Verify all components are properly decoded - expect(result.header.inHash).toBeInstanceOf(Fr); + expect(result.header.inboxRollingHash).toBeInstanceOf(Fr); expect(result.header.gasFees).toBeInstanceOf(GasFees); // Verify instrumentation was called @@ -1322,7 +1322,7 @@ describe('CalldataRetriever', () => { expect(result.blockHash).toBe(blockHash); // Verify all components are properly decoded - expect(result.header.inHash).toBeInstanceOf(Fr); + expect(result.header.inboxRollingHash).toBeInstanceOf(Fr); expect(result.header.gasFees).toBeInstanceOf(GasFees); // Verify proxy implementation was checked diff --git a/yarn-project/archiver/src/test/mock_structs.ts b/yarn-project/archiver/src/test/mock_structs.ts index 5cc54cfe9bad..7cd23455029e 100644 --- a/yarn-project/archiver/src/test/mock_structs.ts +++ b/yarn-project/archiver/src/test/mock_structs.ts @@ -1,8 +1,4 @@ -import { - MAX_NOTE_HASHES_PER_TX, - NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, - PRIVATE_LOG_SIZE_IN_FIELDS, -} from '@aztec/constants'; +import { MAX_L1_TO_L2_MSGS_PER_CHECKPOINT, MAX_NOTE_HASHES_PER_TX, PRIVATE_LOG_SIZE_IN_FIELDS } from '@aztec/constants'; import { makeTuple } from '@aztec/foundation/array'; import { BlockNumber, CheckpointNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types'; import { Buffer16, Buffer32 } from '@aztec/foundation/buffer'; @@ -104,12 +100,12 @@ export function makeInboxMessagesWithFullBlocks( opts: { initialCheckpointNumber?: CheckpointNumber } = {}, ): InboxMessage[] { const { initialCheckpointNumber = CheckpointNumber(13) } = opts; - return makeInboxMessages(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * blockCount, { + return makeInboxMessages(MAX_L1_TO_L2_MSGS_PER_CHECKPOINT * blockCount, { // Keep the compact global index from makeInboxMessages; only spread the (now-vestigial) checkpoint assignment // across blocks so multi-block coverage still exercises differing checkpoint numbers. overrideFn: (msg, i) => { const checkpointNumber = CheckpointNumber( - initialCheckpointNumber + Math.floor(i / NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP), + initialCheckpointNumber + Math.floor(i / MAX_L1_TO_L2_MSGS_PER_CHECKPOINT), ); return { ...msg, checkpointNumber }; }, diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index f3f39b1020ef..ba41242c3e5f 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -79,7 +79,6 @@ export type ViemHeader = { lastArchiveRoot: `0x${string}`; blockHeadersHash: `0x${string}`; blobsHash: `0x${string}`; - inHash: `0x${string}`; inboxRollingHash: `0x${string}`; outHash: `0x${string}`; slotNumber: bigint; diff --git a/yarn-project/ivc-integration/src/base_parity_inputs.test.ts b/yarn-project/ivc-integration/src/base_parity_inputs.test.ts index 2d5ea50b7092..278ed58ca3a7 100644 --- a/yarn-project/ivc-integration/src/base_parity_inputs.test.ts +++ b/yarn-project/ivc-integration/src/base_parity_inputs.test.ts @@ -15,7 +15,7 @@ import { createLogger } from '@aztec/foundation/log'; import { Noir } from '@aztec/noir-noir_js'; import { ServerCircuitArtifacts } from '@aztec/noir-protocol-circuits-types/server'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree'; -import { L1ToL2MessageSponge, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging'; +import { L1ToL2MessageSponge } from '@aztec/stdlib/messaging'; import { InboxParityPrivateInputs } from '@aztec/stdlib/parity'; import { jest } from '@jest/globals'; @@ -47,7 +47,6 @@ describe('Inbox Parity Benchmark Inputs', () => { l1ToL2Messages, Fr.ZERO, L1ToL2MessageSponge.empty(), - computeInHashFromL1ToL2Messages(l1ToL2Messages), vkTreeRoot, Fr.random(), ); @@ -75,8 +74,6 @@ describe('Inbox Parity Benchmark Inputs', () => { num_absorbed: startSponge.numAbsorbed, }, // eslint-disable-next-line camelcase - in_hash: inputs.inHash.toString(), - // eslint-disable-next-line camelcase vk_tree_root: inputs.vkTreeRoot.toString(), // eslint-disable-next-line camelcase prover_id: inputs.proverId.toString(), diff --git a/yarn-project/ivc-integration/src/bb_js_debug.test.ts b/yarn-project/ivc-integration/src/bb_js_debug.test.ts index f478e61dd182..9ce6333f5fcb 100644 --- a/yarn-project/ivc-integration/src/bb_js_debug.test.ts +++ b/yarn-project/ivc-integration/src/bb_js_debug.test.ts @@ -12,7 +12,7 @@ import { createLogger } from '@aztec/foundation/log'; import { Noir } from '@aztec/noir-noir_js'; import { ServerCircuitArtifacts } from '@aztec/noir-protocol-circuits-types/server'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree'; -import { L1ToL2MessageSponge, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging'; +import { L1ToL2MessageSponge } from '@aztec/stdlib/messaging'; import { InboxParityPrivateInputs } from '@aztec/stdlib/parity'; import { jest } from '@jest/globals'; @@ -62,7 +62,6 @@ describe('BB.js Debug Wrapper', () => { l1ToL2Messages, Fr.ZERO, L1ToL2MessageSponge.empty(), - computeInHashFromL1ToL2Messages(l1ToL2Messages), vkTreeRoot, Fr.random(), ); @@ -88,8 +87,6 @@ describe('BB.js Debug Wrapper', () => { num_absorbed: startSponge.numAbsorbed, }, // eslint-disable-next-line camelcase - in_hash: inboxParityInputs.inHash.toString(), - // eslint-disable-next-line camelcase vk_tree_root: inboxParityInputs.vkTreeRoot.toString(), // eslint-disable-next-line camelcase prover_id: inboxParityInputs.proverId.toString(), diff --git a/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts b/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts index f37702498807..0e32f920b309 100644 --- a/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts +++ b/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts @@ -481,7 +481,6 @@ export function mapAvmProofDataToNoir( function mapParityPublicInputsToNoir(parityPublicInputs: ParityPublicInputs): ParityPublicInputsNoir { return { - in_hash: mapFieldToNoir(parityPublicInputs.inHash), start_rolling_hash: mapFieldToNoir(parityPublicInputs.startRollingHash), end_rolling_hash: mapFieldToNoir(parityPublicInputs.endRollingHash), start_sponge: mapL1ToL2MessageSpongeToNoir(parityPublicInputs.startSponge), @@ -520,7 +519,6 @@ export function mapRootRollupPublicInputsFromNoir( */ export function mapParityPublicInputsFromNoir(parityPublicInputs: ParityPublicInputsNoir): ParityPublicInputs { return new ParityPublicInputs( - mapFieldFromNoir(parityPublicInputs.in_hash), mapFieldFromNoir(parityPublicInputs.start_rolling_hash), mapFieldFromNoir(parityPublicInputs.end_rolling_hash), mapL1ToL2MessageSpongeFromNoir(parityPublicInputs.start_sponge), @@ -739,7 +737,6 @@ export function mapInboxParityPrivateInputsToNoir(inputs: InboxParityPrivateInpu num_msgs: mapNumberToNoir(inputs.numMessages), start_rolling_hash: mapFieldToNoir(inputs.startRollingHash), start_sponge: mapL1ToL2MessageSpongeToNoir(inputs.startSponge), - in_hash: mapFieldToNoir(inputs.inHash), vk_tree_root: mapFieldToNoir(inputs.vkTreeRoot), prover_id: mapFieldToNoir(inputs.proverId), }; diff --git a/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.ts b/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.ts index b50ef0313ed0..e2481445632b 100644 --- a/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.ts +++ b/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.ts @@ -248,7 +248,7 @@ export class LightweightCheckpointBuilder { this.blocks.push(block); // Accumulate the streaming bundle now that the block is fully built, so a mid-build throw above leaves the - // checkpoint's message list (and thus its inHash/rolling hash) consistent with the blocks actually built. + // checkpoint's message list (and thus its rolling hash) consistent with the blocks actually built. this.l1ToL2Messages.push(...l1ToL2Messages); const [msSpongeAbsorb] = await elapsed(() => this.spongeBlob.absorb(blockBlobFields)); @@ -283,9 +283,6 @@ export class LightweightCheckpointBuilder { const blobs = await getBlobsPerL1Block(this.blobFields); const blobsHash = computeBlobsHashFromBlobs(blobs); - // Legacy inHash is dead post-flip; the checkpoint header carries zero (AZIP-22 Fast Inbox). The consensus - // rolling hash over the consumed messages is the authoritative Inbox commitment. - const inHash = Fr.ZERO; const inboxRollingHash = accumulateInboxRollingHash(this.previousInboxRollingHash, this.l1ToL2Messages); const { slotNumber, coinbase, feeRecipient, gasFees } = this.constants; @@ -303,7 +300,6 @@ export class LightweightCheckpointBuilder { const header = CheckpointHeader.from({ lastArchiveRoot: this.lastArchives[0].root, blobsHash, - inHash, inboxRollingHash, epochOutHash, blockHeadersHash, diff --git a/yarn-project/prover-client/src/orchestrator/checkpoint-proving-state.ts b/yarn-project/prover-client/src/orchestrator/checkpoint-proving-state.ts index 04697e6e6249..98d3d9946ac3 100644 --- a/yarn-project/prover-client/src/orchestrator/checkpoint-proving-state.ts +++ b/yarn-project/prover-client/src/orchestrator/checkpoint-proving-state.ts @@ -161,15 +161,13 @@ export class CheckpointProvingState { /** * Builds the checkpoint's single InboxParity input. The circuit is sized to the smallest ladder rung that fits the * message count; the rolling hash starts from the previous checkpoint's end and the message sponge starts empty (it - * resets per checkpoint). `in_hash` (the L1 frontier root) is supplied as an unconstrained pass-through hint. + * resets per checkpoint). */ public getInboxParityInputs(): InboxParityPrivateInputs { return InboxParityPrivateInputs.fromMessages( this.l1ToL2Messages, this.startInboxRollingHash, L1ToL2MessageSponge.empty(), - // Legacy in_hash is dead post-flip; the InboxParity pass-through hint carries zero (AZIP-22 Fast Inbox). - Fr.ZERO, this.constants.vkTreeRoot, this.constants.proverId, ); diff --git a/yarn-project/prover-client/src/test/bb_prover_parity.test.ts b/yarn-project/prover-client/src/test/bb_prover_parity.test.ts index fbb920407ead..4848af4efd85 100644 --- a/yarn-project/prover-client/src/test/bb_prover_parity.test.ts +++ b/yarn-project/prover-client/src/test/bb_prover_parity.test.ts @@ -5,7 +5,7 @@ import { createLogger } from '@aztec/foundation/log'; import type { ServerProtocolArtifact } from '@aztec/noir-protocol-circuits-types/server'; import { ServerCircuitVks } from '@aztec/noir-protocol-circuits-types/server/vks'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree'; -import { L1ToL2MessageSponge, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging'; +import { L1ToL2MessageSponge } from '@aztec/stdlib/messaging'; import { INBOX_PARITY_SIZES, InboxParityPrivateInputs, type InboxParitySize } from '@aztec/stdlib/parity'; import { TestContext } from '../mocks/test_context.js'; @@ -48,15 +48,11 @@ describe('prover/bb_prover/parity', () => { // Fill the rung with real messages so `numMessages === size` (the largest circuit for that rung). const messages = Array.from({ length: size }, () => Fr.random()); const proverId = Fr.random(); - // The in_hash is a sha256 frontier root (top byte zeroed to fit the field), which `ParityPublicInputs` enforces; - // compute it from the messages rather than using a raw random field. - const inHash = computeInHashFromL1ToL2Messages(messages); const inputs = InboxParityPrivateInputs.fromMessages( messages, Fr.ZERO, L1ToL2MessageSponge.empty(), - inHash, getVKTreeRoot(), proverId, ); diff --git a/yarn-project/pxe/src/storage/backwards_compatibility_tests/schema_tests.ts b/yarn-project/pxe/src/storage/backwards_compatibility_tests/schema_tests.ts index a9a75a77ffbc..c1802c49f19b 100644 --- a/yarn-project/pxe/src/storage/backwards_compatibility_tests/schema_tests.ts +++ b/yarn-project/pxe/src/storage/backwards_compatibility_tests/schema_tests.ts @@ -276,7 +276,6 @@ export const SCHEMA_TESTS: readonly SchemaTest[] = [ new Fr(5n), new Fr(7n), new Fr(11n), - new Fr(13n), new Fr(15n), new Fr(17n), SlotNumber(19), diff --git a/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts b/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts index 1d15418c5d42..80b91613043f 100644 --- a/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts +++ b/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts @@ -1,5 +1,4 @@ import type { ArchiverDataSource } from '@aztec/archiver'; -import { MockL1ToL2MessageSource } from '@aztec/archiver/test'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { Fr } from '@aztec/aztec.js/fields'; import { createLogger } from '@aztec/aztec.js/log'; @@ -48,7 +47,7 @@ import { retryUntil } from '@aztec/foundation/retry'; import { sleep } from '@aztec/foundation/sleep'; import { hexToBuffer } from '@aztec/foundation/string'; import { TestDateProvider } from '@aztec/foundation/timer'; -import { InboxAbi, RollupAbi } from '@aztec/l1-artifacts'; +import { RollupAbi } from '@aztec/l1-artifacts'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree'; import { ProtocolContractsList, protocolContractsHash } from '@aztec/protocol-contracts'; import { LightweightCheckpointBuilder } from '@aztec/prover-client/light'; @@ -87,7 +86,7 @@ import { NativeWorldStateService, ServerWorldStateSynchronizer, type WorldStateC import { beforeEach, describe, expect, it, jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; -import { type Address, encodeFunctionData, getAbiItem, getAddress, getContract, multicall3Abi } from 'viem'; +import { type Address, encodeFunctionData, getAbiItem, getAddress, multicall3Abi } from 'viem'; import { type PrivateKeyAccount, privateKeyToAccount } from 'viem/accounts'; import { foundry } from 'viem/chains'; @@ -112,8 +111,7 @@ const logger = createLogger('integration_l1_publisher'); // depending on @aztec/aztec-node, which would create a sequencer-client <-> aztec-node cycle. const config: SequencerClientConfig & L1ContractsConfig = { ...getL1ContractsConfigEnvVars(), ...getConfigEnvVars() }; -// Several consecutive checkpoints, each consuming the L1->L2 messages sent while it was being built, so real -// messages are genuinely consumed and validated on L1 (AZIP-22 Fast Inbox). +// Must exceed the inbox lag (network default 2) so at least one checkpoint consumes a real L1->L2 message. const numberOfConsecutiveBlocks = 3; jest.setTimeout(1000000); @@ -140,11 +138,6 @@ describe('L1Publisher integration', () => { let builderDb: NativeWorldStateService; - // Backs the blockSource mock's streaming L1->L2 message queries. The world-state synchronizer reconstructs each - // block's consumed message bundle from Inbox buckets (AZIP-22 Fast Inbox) when it syncs a block back, so the test - // registers one bucket per published block here (see buildAndPublishBlock). - let messageSource: MockL1ToL2MessageSource; - // The header of the last block let prevHeader: BlockHeader; @@ -281,20 +274,6 @@ describe('L1Publisher integration', () => { checkpointNumber: CheckpointNumber.ZERO, indexWithinCheckpoint: IndexWithinCheckpoint(0), }; - // Seed the genesis sentinel bucket (seq 0, no messages) so the world-state synchronizer can resolve a - // totalMsgCount of 0 to a bucket when reconstructing the first block's message bundle. - messageSource = new MockL1ToL2MessageSource(0); - messageSource.setInboxBucket( - { - seq: 0n, - inboxRollingHash: Fr.ZERO, - totalMsgCount: 0n, - timestamp: 0n, - msgCount: 0, - lastMessageIndex: 0n, - }, - [], - ); blockSource = mock({ getBlocks(query: BlocksQuery) { if (!('from' in query)) { @@ -370,14 +349,6 @@ describe('L1Publisher integration', () => { getBlockNumber(): Promise { return Promise.resolve(BlockNumber(blocks.at(-1)?.number ?? BlockNumber.ZERO)); }, - // Streaming L1->L2 message reconstruction (AZIP-22 Fast Inbox): the world-state synchronizer resolves each - // block's consumed message bundle from the Inbox buckets registered per published block in buildAndPublishBlock. - getInboxBucketByTotalMsgCount(totalMsgCount: bigint) { - return messageSource.getInboxBucketByTotalMsgCount(totalMsgCount); - }, - getL1ToL2MessagesBetweenBuckets(fromExclusive: bigint, toInclusive: bigint) { - return messageSource.getL1ToL2MessagesBetweenBuckets(fromExclusive, toInclusive); - }, }); const worldStateConfig: WorldStateConfig = { @@ -473,16 +444,13 @@ describe('L1Publisher integration', () => { /** * Build a checkpoint with a single block using the LightweightCheckpointBuilder. - * This properly computes all checkpoint header fields (blobsHash, blockHeadersHash, inHash, inboxRollingHash, - * epochOutHash, etc.). `previousInboxRollingHash` is the previous checkpoint's rolling hash (zero at genesis), so - * the header's `inboxRollingHash` continues the on-chain Inbox chain over `l1ToL2Messages`. + * This properly computes all checkpoint header fields (blobsHash, blockHeadersHash, inHash, epochOutHash, etc.) */ const buildCheckpoint = async ( globalVariables: GlobalVariables, txs: ProcessedTx[], l1ToL2Messages: Fr[], previousCheckpointOutHashes: Fr[] = [], - previousInboxRollingHash: Fr = Fr.ZERO, ): Promise => { await worldStateSynchronizer.syncImmediate(); const tempFork = await worldStateSynchronizer.fork(BlockNumber(globalVariables.blockNumber - 1)); @@ -503,7 +471,7 @@ describe('L1Publisher integration', () => { checkpointNumber, checkpointConstants, previousCheckpointOutHashes, - previousInboxRollingHash, + Fr.ZERO, tempFork, ); @@ -517,8 +485,7 @@ describe('L1Publisher integration', () => { const buildSingleCheckpoint = async ( opts: { l1ToL2Messages?: Fr[]; blockNumber?: BlockNumber; slot?: SlotNumber } = {}, ) => { - // By default a single checkpoint consumes no Inbox messages (bucketHint 0 against the genesis bucket). - const l1ToL2Messages = opts.l1ToL2Messages ?? []; + const l1ToL2Messages = opts.l1ToL2Messages ?? new Array(MAX_L1_TO_L2_MSGS_PER_CHECKPOINT).fill(Fr.ZERO); const txs = await Promise.all([makeProcessedTx(0x1000), makeProcessedTx(0x2000)]); const ts = (await l1Client.getBlock()).timestamp; @@ -535,6 +502,7 @@ describe('L1Publisher integration', () => { new GasFees(0, await rollup.getManaMinFeeAt(timestamp, true)), ); const checkpoint = await buildCheckpoint(globalVariables, txs, l1ToL2Messages); + blockSource.getL1ToL2Messages.mockResolvedValueOnce(l1ToL2Messages); return { checkpoint, l1ToL2Messages }; }; @@ -548,8 +516,10 @@ describe('L1Publisher integration', () => { describe('block building', () => { beforeEach(async () => { - // This suite proposes consecutive checkpoints, each consuming the streaming-Inbox messages sent while it was - // being built (AZIP-22 Fast Inbox), so real messages are genuinely consumed and validated on L1. + // This suite proposes consecutive checkpoints and models the inbox lag by hand (a checkpoint + // consumes the L1->L2 messages sent inboxLag checkpoints earlier -- see the shift register in + // buildAndPublishBlock). It inherits the network default lag and proposes enough checkpoints + // that a real message is actually consumed and validated on L1. await setup(); }); @@ -564,18 +534,11 @@ describe('L1Publisher integration', () => { '0x1647b194c649f5dd01d7c832f89b0f496043c9150797923ea89e93d5ac619a93', ); - // Streaming Inbox consumption (AZIP-22 Fast Inbox): each checkpoint consumes every message sent so far, so its - // header rolling hash continues the previous checkpoint's and matches the Inbox's current bucket. Consuming - // through the newest bucket trivially satisfies the mandatory-consumption assert. A checkpoint's messages are - // sent one per L1 block, so they span several Inbox buckets; the checkpoint's consumed bucket is the newest, - // whose sequence we read straight off the deployed Inbox and mirror into messageSource so the propose bucket - // hint can be looked up by the checkpoint's cumulative message count. - const inbox = getContract({ - address: getAddress(l1ContractAddresses.inboxAddress.toString()), - abi: InboxAbi, - client: l1Client, - }); - let previousInboxRollingHash = Fr.ZERO; + // The deployed rollup consumes L1->L2 messages with a lag of `inboxLag` checkpoints: a message + // inserted while building checkpoint N is only consumable at checkpoint N + inboxLag. Model that + // with a shift register so a real message is genuinely consumed and validated on L1. + const inboxLag = getL1ContractsConfigEnvVars().inboxLag; + const messagesInFlight: Fr[][] = Array.from({ length: inboxLag }, () => []); const blobFieldsPerCheckpoint: Fr[][] = []; // The below batched blob is used for testing different epochs with 1..numberOfConsecutiveBlocks blocks on L1. // For real usage, always collect ALL epoch blobs first then call .batch(). @@ -586,11 +549,15 @@ describe('L1Publisher integration', () => { // and causes a chain prune const l1ToL2Content = range(Math.min(16, MAX_L1_TO_L2_MSGS_PER_CHECKPOINT), 128 * i + 1 + 0x400).map(fr); - const currentL1ToL2Messages: Fr[] = []; + const sentThisCheckpoint: Fr[] = []; for (let j = 0; j < l1ToL2Content.length; j++) { - currentL1ToL2Messages.push(await sendToL2(l1ToL2Content[j], recipientAddress)); + sentThisCheckpoint.push(await sendToL2(l1ToL2Content[j], recipientAddress)); } + // Consume the messages sent inboxLag checkpoints ago, then enqueue this checkpoint's messages. + const currentL1ToL2Messages = messagesInFlight.shift()!; + messagesInFlight.push(sentThisCheckpoint); + // Ensure that each transaction has unique (non-intersecting nullifier values) const totalNullifiersPerBlock = 4 * MAX_NULLIFIERS_PER_TX; const txs = await timesParallel(numTxs, txIndex => @@ -612,38 +579,14 @@ describe('L1Publisher integration', () => { new GasFees(0, await rollup.getManaMinFeeAt(timestamp, true)), ); - const checkpoint = await buildCheckpoint( - globalVariables, - txs, - currentL1ToL2Messages, - [], - previousInboxRollingHash, - ); - previousInboxRollingHash = checkpoint.header.inboxRollingHash; + const checkpoint = await buildCheckpoint(globalVariables, txs, currentL1ToL2Messages); const block = checkpoint.blocks[0]; - // Mirror the Inbox's newest bucket into messageSource so the world-state synchronizer can rebuild this - // block's consumed bundle when it syncs the block back on the next iteration, and so the propose bucket hint - // resolves to the sequence whose rolling hash the checkpoint header committed to. One bucket per block keyed - // by its cumulative L1->L2 tree leaf count -- exactly the value world-state looks the bucket up by. - const currentBucketSeq = await inbox.read.getCurrentBucketSeq(); - const cumulativeMsgCount = BigInt(block.header.state.l1ToL2MessageTree.nextAvailableLeafIndex); - messageSource.setInboxBucket( - { - seq: currentBucketSeq, - inboxRollingHash: checkpoint.header.inboxRollingHash, - totalMsgCount: cumulativeMsgCount, - timestamp, - msgCount: currentL1ToL2Messages.length, - lastMessageIndex: cumulativeMsgCount - 1n, - }, - currentL1ToL2Messages, - ); - const totalManaUsed = txs.reduce((acc, tx) => acc.add(new Fr(tx.gasUsed.billedGas.l2Gas)), Fr.ZERO); expect(totalManaUsed.toBigInt()).toEqual(block.header.totalManaUsed.toBigInt()); prevHeader = block.header; + blockSource.getL1ToL2Messages.mockResolvedValueOnce(currentL1ToL2Messages); const checkpointBlobFields = checkpoint.toBlobFields(); const blockBlobs = await getBlobsPerL1Block(checkpointBlobFields); @@ -667,13 +610,11 @@ describe('L1Publisher integration', () => { deployerAccount.address, ); - // The checkpoint consumed everything sent so far, so its consumed bucket is the Inbox's newest. - const bucketHint = (await messageSource.getInboxBucketByTotalMsgCount(cumulativeMsgCount))!.seq; await publisher.enqueueProposeCheckpoint( checkpoint, CommitteeAttestationsAndSigners.empty(getSignatureContext()), Signature.empty(), - bucketHint, + 0n, ); // Align chain time so the bundle simulate and the L1 send both run at the header's slot. await progressToSlot(BigInt(checkpoint.header.slotNumber)); @@ -719,7 +660,7 @@ describe('L1Publisher integration', () => { oracleInput: { feeAssetPriceModifier: 0n, }, - bucketHint, + bucketHint: 0n, }, CommitteeAttestationsAndSigners.packAttestations([]), [], @@ -1027,8 +968,9 @@ describe('L1Publisher integration', () => { }); it(`shows propose custom errors if tx simulation fails`, async () => { - // Set up different l1-to-l2 messages than the ones on the inbox, so the checkpoint's inboxRollingHash does not - // match the referenced Inbox bucket and the submission reverts at the streaming-consumption check. + // Set up different l1-to-l2 messages than the ones on the inbox, so this submission reverts because the + // INBOX.consume does not match the header.inHash and we get a Rollup__BlobHash that is not caught by + // validateHeader before. const l1ToL2Messages = new Array(MAX_L1_TO_L2_MSGS_PER_CHECKPOINT).fill(new Fr(1n)); const { checkpoint } = await buildSingleCheckpoint({ l1ToL2Messages }); @@ -1044,12 +986,12 @@ describe('L1Publisher integration', () => { await progressToSlot(BigInt(checkpoint.header.slotNumber)); const result = await publisher.sendRequests(); expect(result).toBeUndefined(); - // 0xed1f7bb5 == Rollup__InvalidInboxRollingHash selector + // 0xcd6f4233 == Rollup__InvalidInHash selector expect(loggerWarnSpy).toHaveBeenCalledWith( 'Bundle entry dropped: action reverted in sim', expect.objectContaining({ action: 'propose', - returnData: expect.stringMatching(/^0xed1f7bb5/), + returnData: expect.stringMatching(/^0xcd6f4233/), }), ); }); diff --git a/yarn-project/sequencer-client/src/publisher/write_json.ts b/yarn-project/sequencer-client/src/publisher/write_json.ts index 401a94908a6f..e3a2b74e1603 100644 --- a/yarn-project/sequencer-client/src/publisher/write_json.ts +++ b/yarn-project/sequencer-client/src/publisher/write_json.ts @@ -55,7 +55,6 @@ export async function writeJson( lastArchiveRoot: asHex(checkpointHeader.lastArchiveRoot), blockHeadersHash: asHex(checkpointHeader.blockHeadersHash), blobsHash: asHex(checkpointHeader.blobsHash), - inHash: asHex(checkpointHeader.inHash), inboxRollingHash: asHex(checkpointHeader.inboxRollingHash), outHash: asHex(checkpointHeader.epochOutHash), slotNumber: Number(checkpointHeader.slotNumber), diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index 95a866e36a3e..2f77bc1c6cc2 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -1,4 +1,4 @@ -import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants'; +import { MAX_L1_TO_L2_MSGS_PER_CHECKPOINT } from '@aztec/constants'; import { type EpochCache, type EpochCommitteeInfo, PROPOSER_PIPELINING_SLOT_OFFSET } from '@aztec/epoch-cache'; import { NoCommitteeError, type RollupContract } from '@aztec/ethereum/contracts'; import { @@ -349,7 +349,7 @@ describe('sequencer', () => { }); l1ToL2MessageSource = mock({ - getL1ToL2Messages: () => Promise.resolve(Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(Fr.ZERO)), + getL1ToL2Messages: () => Promise.resolve(Array(MAX_L1_TO_L2_MSGS_PER_CHECKPOINT).fill(Fr.ZERO)), getL2Tips: mockFn().mockResolvedValue({ proposed: { number: lastBlockNumber, hash }, checkpointed: { diff --git a/yarn-project/sequencer-client/src/test/utils.ts b/yarn-project/sequencer-client/src/test/utils.ts index 92845b668f07..5bb506d4f729 100644 --- a/yarn-project/sequencer-client/src/test/utils.ts +++ b/yarn-project/sequencer-client/src/test/utils.ts @@ -114,7 +114,7 @@ export function createMockSignatures(signer: Secp256k1Signer): CommitteeAttestat /** * Creates a CheckpointHeader from an L2Block for testing purposes. - * Uses mock values for blockHeadersHash, blobsHash and inHash since L2Block doesn't have these fields. + * Uses mock values for blockHeadersHash and blobsHash since L2Block doesn't have these fields. */ function createCheckpointHeaderFromBlock(block: L2Block): CheckpointHeader { const gv = block.header.globalVariables; @@ -122,7 +122,6 @@ function createCheckpointHeaderFromBlock(block: L2Block): CheckpointHeader { block.header.lastArchive.root, Fr.random(), // blockHeadersHash - mock value for testing Fr.random(), // blobsHash - mock value for testing - Fr.random(), // inHash - mock value for testing Fr.random(), // inboxRollingHash - mock value for testing Fr.random(), // outHash - mock value for testing gv.slotNumber, diff --git a/yarn-project/stdlib/src/messaging/in_hash.ts b/yarn-project/stdlib/src/messaging/in_hash.ts index 52833bf937fe..fd5191abb5c1 100644 --- a/yarn-project/stdlib/src/messaging/in_hash.ts +++ b/yarn-project/stdlib/src/messaging/in_hash.ts @@ -1,4 +1,4 @@ -import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants'; +import { MAX_L1_TO_L2_MSGS_PER_CHECKPOINT } from '@aztec/constants'; import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; import { computeBalancedShaRoot } from '@aztec/foundation/trees'; @@ -7,6 +7,6 @@ import { computeBalancedShaRoot } from '@aztec/foundation/trees'; * Computes the inHash for a checkpoint (or the first block in a checkpoint) given its l1 to l2 messages. */ export function computeInHashFromL1ToL2Messages(unpaddedL1ToL2Messages: Fr[]): Fr { - const l1ToL2Messages = padArrayEnd(unpaddedL1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP); + const l1ToL2Messages = padArrayEnd(unpaddedL1ToL2Messages, Fr.ZERO, MAX_L1_TO_L2_MSGS_PER_CHECKPOINT); return new Fr(computeBalancedShaRoot(l1ToL2Messages.map(msg => msg.toBuffer()))); } diff --git a/yarn-project/stdlib/src/messaging/inbox_leaf.ts b/yarn-project/stdlib/src/messaging/inbox_leaf.ts index 21d703d06150..397c29a07efa 100644 --- a/yarn-project/stdlib/src/messaging/inbox_leaf.ts +++ b/yarn-project/stdlib/src/messaging/inbox_leaf.ts @@ -1,4 +1,4 @@ -import { INITIAL_CHECKPOINT_NUMBER, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants'; +import { INITIAL_CHECKPOINT_NUMBER, MAX_L1_TO_L2_MSGS_PER_CHECKPOINT } from '@aztec/constants'; import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; import { CheckpointNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; @@ -24,7 +24,7 @@ export class InboxLeaf { } static smallestIndexForCheckpoint(checkpointNumber: CheckpointNumber): bigint { - return BigInt(checkpointNumber - INITIAL_CHECKPOINT_NUMBER) * BigInt(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP); + return BigInt(checkpointNumber - INITIAL_CHECKPOINT_NUMBER) * BigInt(MAX_L1_TO_L2_MSGS_PER_CHECKPOINT); } /** @@ -33,12 +33,12 @@ export class InboxLeaf { */ static indexRangeForCheckpoint(checkpointNumber: CheckpointNumber): [bigint, bigint] { const start = this.smallestIndexForCheckpoint(checkpointNumber); - const end = start + BigInt(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP); + const end = start + BigInt(MAX_L1_TO_L2_MSGS_PER_CHECKPOINT); return [start, end]; } /** Returns the checkpoint number for a given leaf index */ static checkpointNumberFromIndex(index: bigint): CheckpointNumber { - return CheckpointNumber(Number(index / BigInt(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP)) + INITIAL_CHECKPOINT_NUMBER); + return CheckpointNumber(Number(index / BigInt(MAX_L1_TO_L2_MSGS_PER_CHECKPOINT)) + INITIAL_CHECKPOINT_NUMBER); } } diff --git a/yarn-project/stdlib/src/p2p/checkpoint_proposal.ts b/yarn-project/stdlib/src/p2p/checkpoint_proposal.ts index 126e24b56dc4..4c5f0f402927 100644 --- a/yarn-project/stdlib/src/p2p/checkpoint_proposal.ts +++ b/yarn-project/stdlib/src/p2p/checkpoint_proposal.ts @@ -110,14 +110,8 @@ export class CheckpointProposal extends Gossipable implements Signable { ) { super(); - // Check that last block properties match those of the checkpoint. - if (lastBlock && 'inHash' in lastBlock && !lastBlock.inHash.equals(checkpointHeader.inHash)) { - throw new Error( - `CheckpointProposal lastBlock inHash ${lastBlock.inHash} does not match checkpoint inHash ${checkpointHeader.inHash}`, - ); - } - // The last block's bucket reference (AZIP-22 Fast Inbox) commits to the same rolling hash as the checkpoint header, - // mirroring the inHash cross-check above. Optional pre-flip: only enforced when the reference is set. + // Check that last block properties match those of the checkpoint. The last block's bucket reference (AZIP-22 Fast + // Inbox) commits to the same rolling hash as the checkpoint header. Only enforced when the reference is set. if (lastBlock?.bucketRef && !lastBlock.bucketRef.inboxRollingHash.equals(checkpointHeader.inboxRollingHash)) { throw new Error( `CheckpointProposal lastBlock bucketRef rolling hash ${lastBlock.bucketRef.inboxRollingHash} does not match checkpoint inboxRollingHash ${checkpointHeader.inboxRollingHash}`, @@ -147,7 +141,6 @@ export class CheckpointProposal extends Gossipable implements Signable { /** * Extract a BlockProposal from the last block info. - * Uses inHash from checkpointHeader.contentCommitment.inHash */ getBlockProposal(): BlockProposal | undefined { if (!this.lastBlock) { @@ -157,7 +150,7 @@ export class CheckpointProposal extends Gossipable implements Signable { return new BlockProposal( this.lastBlock.blockHeader, this.lastBlock.indexWithinCheckpoint, - this.checkpointHeader.inHash, + Fr.ZERO, this.archive, this.lastBlock.txHashes, this.lastBlock.signature, diff --git a/yarn-project/stdlib/src/p2p/wire_compat_fixtures.ts b/yarn-project/stdlib/src/p2p/wire_compat_fixtures.ts index 61f5720243ce..ca38bc7724f0 100644 --- a/yarn-project/stdlib/src/p2p/wire_compat_fixtures.ts +++ b/yarn-project/stdlib/src/p2p/wire_compat_fixtures.ts @@ -1,13 +1,14 @@ -// Golden wire fixtures captured from the pre-Fast-Inbox serialization format (AZIP-22, A-1381). They pin the exact -// bytes a legacy peer produces and consumes, so the optional bucket-reference tail added to proposals stays wire -// compatible: an unset proposal must serialize to these bytes, and decoding these bytes must yield no bucket reference. +// Golden wire fixtures that pin the exact bytes a peer produces and consumes, so the optional bucket-reference tail +// added to proposals stays wire compatible: an unset proposal must serialize to these bytes, and decoding these bytes +// must yield no bucket reference. The block-proposal fixtures predate the bucket-reference change (AZIP-22, A-1381); +// the checkpoint-proposal fixture was refreshed for the checkpoint-header format change that dropped `inHash`. // // Both fixtures come from a deterministic proposal built with: // BlockHeader.empty(), IndexWithinCheckpoint(3), inHash=Fr(42), archiveRoot=Fr(99), // txHashes=[TxHash.fromField(Fr(7)), TxHash.fromField(Fr(8))], Signature.empty(), EMPTY_COORDINATION_SIGNATURE_CONTEXT // (checkpoint: CheckpointHeader.empty(), archive=Fr(123), feeAssetPriceModifier=0, empty signature/context, and a // lastBlock with BlockHeader.empty(), IndexWithinCheckpoint(4), txHashes=[TxHash.fromField(Fr(7))], empty signature). -// DO NOT regenerate from current code — the whole point is that these bytes predate the change. +// Regenerate the checkpoint fixture only when the checkpoint serialization format changes on purpose. /** Legacy `BlockProposal.toBuffer()` bytes (no signedTxs, no bucket reference). */ export const LEGACY_BLOCK_PROPOSAL_HEX = @@ -15,7 +16,7 @@ export const LEGACY_BLOCK_PROPOSAL_HEX = /** Legacy `CheckpointProposal.toBuffer()` bytes (lastBlock without signedTxs or bucket reference). */ export const LEGACY_CHECKPOINT_PROPOSAL_HEX = - '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000700000000'; + '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000700000000'; /** Legacy `BlockProposal.getPayloadToSign()` bytes (no bucket reference). */ export const LEGACY_BLOCK_PROPOSAL_PAYLOAD_HEX = diff --git a/yarn-project/stdlib/src/parity/inbox_parity_private_inputs.ts b/yarn-project/stdlib/src/parity/inbox_parity_private_inputs.ts index 70639ab9fa16..fb799887d425 100644 --- a/yarn-project/stdlib/src/parity/inbox_parity_private_inputs.ts +++ b/yarn-project/stdlib/src/parity/inbox_parity_private_inputs.ts @@ -43,8 +43,6 @@ export class InboxParityPrivateInputs { public readonly startRollingHash: Fr, /** Message-bundle sponge before this checkpoint's messages (empty at checkpoint start). */ public readonly startSponge: L1ToL2MessageSponge, - /** The L1 `in_hash` (sha256 frontier root), passed through unconstrained by the circuit. */ - public readonly inHash: Fr, /** Root of the VK tree. */ public readonly vkTreeRoot: Fr, /** Prover identity committed to by the circuit, for sybil protection. */ @@ -63,7 +61,6 @@ export class InboxParityPrivateInputs { messages: Fr[], startRollingHash: Fr, startSponge: L1ToL2MessageSponge, - inHash: Fr, vkTreeRoot: Fr, proverId: Fr, ): InboxParityPrivateInputs { @@ -75,7 +72,6 @@ export class InboxParityPrivateInputs { messages.length, startRollingHash, startSponge, - inHash, vkTreeRoot, proverId, ); @@ -89,7 +85,6 @@ export class InboxParityPrivateInputs { new Fr(this.numMessages), this.startRollingHash, this.startSponge, - this.inHash, this.vkTreeRoot, this.proverId, ); @@ -117,7 +112,6 @@ export class InboxParityPrivateInputs { reader.readObject(L1ToL2MessageSponge), Fr.fromBuffer(reader), Fr.fromBuffer(reader), - Fr.fromBuffer(reader), ); } diff --git a/yarn-project/stdlib/src/parity/parity_public_inputs.ts b/yarn-project/stdlib/src/parity/parity_public_inputs.ts index 40a9d12b48fd..2c07ab721ad1 100644 --- a/yarn-project/stdlib/src/parity/parity_public_inputs.ts +++ b/yarn-project/stdlib/src/parity/parity_public_inputs.ts @@ -8,11 +8,6 @@ import { L1ToL2MessageSponge } from '../messaging/l1_to_l2_message_sponge.js'; export class ParityPublicInputs { constructor( - /** - * The L1 `in_hash` (sha256 frontier root of the checkpoint's messages). Unconstrained pass-through: InboxParity - * echoes the value the prover supplies; it stays the authoritative L1 check until the Fast Inbox flip. - */ - public inHash: Fr, /** Inbox rolling hash before absorbing this checkpoint's messages. */ public startRollingHash: Fr, /** Inbox rolling hash after absorbing the `numMsgs` real messages. */ @@ -27,11 +22,7 @@ export class ParityPublicInputs { public vkTreeRoot: Fr, /** Prover identity committed to by the circuit, for sybil protection. */ public proverId: Fr, - ) { - if (inHash.toBuffer()[0] != 0) { - throw new Error(`inHash buffer must be 31 bytes. Got 32 bytes`); - } - } + ) {} /** * Serializes the inputs to a buffer. @@ -39,7 +30,6 @@ export class ParityPublicInputs { */ toBuffer() { return serializeToBuffer( - this.inHash, this.startRollingHash, this.endRollingHash, this.startSponge, @@ -79,7 +69,6 @@ export class ParityPublicInputs { */ static getFields(fields: FieldsOf) { return [ - fields.inHash, fields.startRollingHash, fields.endRollingHash, fields.startSponge, @@ -98,7 +87,6 @@ export class ParityPublicInputs { static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); return new ParityPublicInputs( - reader.readObject(Fr), reader.readObject(Fr), reader.readObject(Fr), reader.readObject(L1ToL2MessageSponge), diff --git a/yarn-project/stdlib/src/rollup/checkpoint_header.test.ts b/yarn-project/stdlib/src/rollup/checkpoint_header.test.ts index 5c29c4ce8017..764a57fdab9c 100644 --- a/yarn-project/stdlib/src/rollup/checkpoint_header.test.ts +++ b/yarn-project/stdlib/src/rollup/checkpoint_header.test.ts @@ -22,7 +22,7 @@ describe('CheckpointHeader', () => { const header = CheckpointHeader.empty(); const hash = header.hash().toString(); - expect(hash).toMatchInlineSnapshot(`"0x0008c3e5bbea4fba57201a69d4bf70a0d255df921fdef4924c22da087f9338c2"`); + expect(hash).toMatchInlineSnapshot(`"0x002e384af86a480f952aa16443fd29646a9063865e62d7c403fc7ed697bb7712"`); // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data updateInlineTestData( @@ -37,7 +37,6 @@ describe('CheckpointHeader', () => { lastArchiveRoot: new Fr(123), blockHeadersHash: new Fr(456), blobsHash: new Fr(77), - inHash: new Fr(88), inboxRollingHash: new Fr(89), epochOutHash: new Fr(99), slotNumber: SlotNumber(1234), @@ -50,7 +49,7 @@ describe('CheckpointHeader', () => { }); const hash = header.hash().toString(); - expect(hash).toMatchInlineSnapshot(`"0x00519b87177a8a5e4edd03f4d820aec6d402497ef1ab70e2ecd4d4c39b339611"`); + expect(hash).toMatchInlineSnapshot(`"0x00751391e842cd7b2014478255dd3309df86327197a0feb03f0af1b758f62ba5"`); // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data updateInlineTestData( @@ -65,7 +64,6 @@ describe('CheckpointHeader', () => { lastArchiveRoot: new Fr(MAX_FIELD_VALUE - 123n), blockHeadersHash: new Fr(MAX_FIELD_VALUE - 456n), blobsHash: new Fr(MAX_FIELD_VALUE - 77n), - inHash: new Fr(MAX_FIELD_VALUE - 88n), inboxRollingHash: new Fr(MAX_FIELD_VALUE - 89n), epochOutHash: new Fr(MAX_FIELD_VALUE - 99n), slotNumber: SlotNumber(1234), @@ -81,7 +79,7 @@ describe('CheckpointHeader', () => { const hash = header.hash().toString(); - expect(hash).toMatchInlineSnapshot(`"0x00d64307fa93c32ae46a4c3b6a1911d31994daa8d11d50201f0086a6cbaa9bac"`); + expect(hash).toMatchInlineSnapshot(`"0x005bd09725c6e77a4a28a7ccdaf7875ba5882431ca3c82e62db96e8a12769ce5"`); // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data updateInlineTestData( diff --git a/yarn-project/stdlib/src/rollup/checkpoint_header.ts b/yarn-project/stdlib/src/rollup/checkpoint_header.ts index 32cbc58874e0..bd59f7746a97 100644 --- a/yarn-project/stdlib/src/rollup/checkpoint_header.ts +++ b/yarn-project/stdlib/src/rollup/checkpoint_header.ts @@ -30,12 +30,10 @@ export class CheckpointHeader { public blockHeadersHash: Fr, /** Hash of the blobs in the checkpoint. */ public blobsHash: Fr, - /** Root of the l1 to l2 messages subtree. */ - public inHash: Fr, /** - * Inbox rolling-hash chain value after consuming all L1-to-L2 messages bundled into this checkpoint. The dual of - * `inHash` (AZIP-22 Fast Inbox): the truncated-to-field sha256 chain the L1 Inbox accumulates. Currently a - * pass-through commitment; `inHash` remains the authoritative L1 check until the Fast Inbox flip. + * Inbox rolling-hash chain value after consuming all L1-to-L2 messages bundled into this checkpoint (AZIP-22 Fast + * Inbox): the truncated-to-field sha256 chain the L1 Inbox accumulates. This is the checkpoint's only inbox + * commitment. */ public inboxRollingHash: Fr, /** @@ -68,7 +66,6 @@ export class CheckpointHeader { lastArchiveRoot: schemas.Fr, blockHeadersHash: schemas.Fr, blobsHash: schemas.Fr, - inHash: schemas.Fr, inboxRollingHash: schemas.Fr, epochOutHash: schemas.Fr, slotNumber: schemas.SlotNumber, @@ -87,7 +84,6 @@ export class CheckpointHeader { fields.lastArchiveRoot, fields.blockHeadersHash, fields.blobsHash, - fields.inHash, fields.inboxRollingHash, fields.epochOutHash, fields.slotNumber, @@ -113,7 +109,6 @@ export class CheckpointHeader { reader.readObject(Fr), reader.readObject(Fr), reader.readObject(Fr), - reader.readObject(Fr), SlotNumber(Fr.fromBuffer(reader).toNumber()), reader.readUInt64(), reader.readObject(EthAddress), @@ -129,7 +124,6 @@ export class CheckpointHeader { this.lastArchiveRoot.equals(other.lastArchiveRoot) && this.blockHeadersHash.equals(other.blockHeadersHash) && this.blobsHash.equals(other.blobsHash) && - this.inHash.equals(other.inHash) && this.inboxRollingHash.equals(other.inboxRollingHash) && this.epochOutHash.equals(other.epochOutHash) && this.slotNumber === other.slotNumber && @@ -159,7 +153,6 @@ export class CheckpointHeader { this.lastArchiveRoot, this.blockHeadersHash, this.blobsHash, - this.inHash, this.inboxRollingHash, this.epochOutHash, new Fr(this.slotNumber), @@ -181,7 +174,6 @@ export class CheckpointHeader { lastArchiveRoot: Fr.ZERO, blockHeadersHash: Fr.ZERO, blobsHash: Fr.ZERO, - inHash: Fr.ZERO, inboxRollingHash: Fr.ZERO, epochOutHash: Fr.ZERO, slotNumber: SlotNumber.ZERO, @@ -200,7 +192,6 @@ export class CheckpointHeader { lastArchiveRoot: Fr.random(), blockHeadersHash: Fr.random(), blobsHash: Fr.random(), - inHash: Fr.random(), inboxRollingHash: Fr.random(), epochOutHash: Fr.random(), slotNumber: SlotNumber(Math.floor(Math.random() * 1000) + 1), @@ -219,7 +210,6 @@ export class CheckpointHeader { this.lastArchiveRoot.isZero() && this.blockHeadersHash.isZero() && this.blobsHash.isZero() && - this.inHash.isZero() && this.inboxRollingHash.isZero() && this.epochOutHash.isZero() && this.slotNumber === 0 && @@ -249,7 +239,6 @@ export class CheckpointHeader { Fr.fromString(header.lastArchiveRoot), Fr.fromString(header.blockHeadersHash), Fr.fromString(header.blobsHash), - Fr.fromString(header.inHash), Fr.fromString(header.inboxRollingHash), Fr.fromString(header.outHash), SlotNumber.fromBigInt(header.slotNumber), @@ -275,7 +264,6 @@ export class CheckpointHeader { lastArchiveRoot: this.lastArchiveRoot.toString(), blockHeadersHash: this.blockHeadersHash.toString(), blobsHash: this.blobsHash.toString(), - inHash: this.inHash.toString(), inboxRollingHash: this.inboxRollingHash.toString(), outHash: this.epochOutHash.toString(), slotNumber: BigInt(this.slotNumber), @@ -296,7 +284,6 @@ export class CheckpointHeader { lastArchive: this.lastArchiveRoot.toString(), blockHeadersHash: this.blockHeadersHash.toString(), blobsHash: this.blobsHash.toString(), - inHash: this.inHash.toString(), inboxRollingHash: this.inboxRollingHash.toString(), epochOutHash: this.epochOutHash.toString(), slotNumber: this.slotNumber, @@ -314,7 +301,6 @@ export class CheckpointHeader { lastArchiveRoot: ${this.lastArchiveRoot.toString()}, blockHeadersHash: ${this.blockHeadersHash.toString()}, blobsHash: ${inspect(this.blobsHash)}, - inHash: ${inspect(this.inHash)}, inboxRollingHash: ${inspect(this.inboxRollingHash)}, epochOutHash: ${inspect(this.epochOutHash)}, slotNumber: ${this.slotNumber}, diff --git a/yarn-project/stdlib/src/rollup/checkpoint_root_rollup_private_inputs.ts b/yarn-project/stdlib/src/rollup/checkpoint_root_rollup_private_inputs.ts index 7838c30ee375..01f908d77221 100644 --- a/yarn-project/stdlib/src/rollup/checkpoint_root_rollup_private_inputs.ts +++ b/yarn-project/stdlib/src/rollup/checkpoint_root_rollup_private_inputs.ts @@ -119,8 +119,8 @@ export class CheckpointRootRollupPrivateInputs { RollupHonkProofData, ], /** - * Inbox parity proof over the checkpoint's L1-to-L2 messages (AZIP-22 Fast Inbox): it commits to the same message - * list behind the L1-checked `inHash`, and its message sponge is checked against the blocks' accumulated one. + * Inbox parity proof over the checkpoint's L1-to-L2 messages (AZIP-22 Fast Inbox): it commits to the checkpoint's + * message list, and its message sponge is checked against the blocks' accumulated one. */ public inboxParity: UltraHonkProofData, public hints: CheckpointRootRollupHints, @@ -168,8 +168,8 @@ export class CheckpointRootSingleBlockRollupPrivateInputs { constructor( public previousRollup: RollupHonkProofData, /** - * Inbox parity proof over the checkpoint's L1-to-L2 messages (AZIP-22 Fast Inbox): it commits to the same message - * list behind the L1-checked `inHash`, and its message sponge is checked against the block's accumulated one. + * Inbox parity proof over the checkpoint's L1-to-L2 messages (AZIP-22 Fast Inbox): it commits to the checkpoint's + * message list, and its message sponge is checked against the block's accumulated one. */ public inboxParity: UltraHonkProofData, public hints: CheckpointRootRollupHints, diff --git a/yarn-project/stdlib/src/tests/factories.ts b/yarn-project/stdlib/src/tests/factories.ts index 06f5fcdd5e00..20b8ca8a0af0 100644 --- a/yarn-project/stdlib/src/tests/factories.ts +++ b/yarn-project/stdlib/src/tests/factories.ts @@ -17,6 +17,7 @@ import { MAX_ENQUEUED_CALLS_PER_TX, MAX_KEY_VALIDATION_REQUESTS_PER_CALL, MAX_L1_TO_L2_MSGS_PER_BLOCK, + MAX_L1_TO_L2_MSGS_PER_CHECKPOINT, MAX_L2_TO_L1_MSGS_PER_CALL, MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASHES_PER_CALL, @@ -36,7 +37,6 @@ import { NOTE_HASH_SUBTREE_ROOT_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_ROOT_SIBLING_PATH_LENGTH, NULLIFIER_TREE_HEIGHT, - NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, PRIVATE_LOG_SIZE_IN_FIELDS, PUBLIC_DATA_TREE_HEIGHT, RECURSIVE_ROLLUP_HONK_PROOF_LENGTH, @@ -821,7 +821,6 @@ export function makeCheckpointRollupPublicInputs(seed = 0) { export function makeParityPublicInputs(seed = 0): ParityPublicInputs { return new ParityPublicInputs( - new Fr(BigInt(seed + 0x200)), new Fr(BigInt(seed + 0x400)), new Fr(BigInt(seed + 0x500)), makeL1ToL2MessageSponge(seed + 0x550), @@ -842,7 +841,6 @@ export function makeInboxParityPrivateInputs(seed = 0): InboxParityPrivateInputs numMsgs, new Fr(seed + 0x3500), makeL1ToL2MessageSponge(seed + 0x3800), - new Fr(seed + 0x3900), new Fr(seed + 0x4000), new Fr(seed + 0x5000), ); @@ -888,7 +886,6 @@ export function makeCheckpointHeader(seed = 0, overrides: Partial { const archive = Fr.random(); const checkpointHeader = makeCheckpointHeader(1); - // Create the block proposal first (as the sequencer would), using the checkpoint's inHash - // so that getSender() can verify the block proposal sender matches + // Create the block proposal first (as the sequencer would) so that getSender() can verify the block proposal + // sender matches. The block-level inHash is dead post-flip (AZIP-22 Fast Inbox), so it carries zero. const blockProposal = await service.createBlockProposal( blockHeader, CheckpointNumber(1), indexWithinCheckpoint, - checkpointHeader.inHash, + Fr.ZERO, archive, txs, addresses[0], diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index 1af680ff3218..0a0ae849ea4a 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -593,7 +593,7 @@ describe('ValidatorClient', () => { const emptyInHash = computeInHashFromL1ToL2Messages([]); const checkpointProposal = await makeCheckpointProposal({ signer, - checkpointHeader: makeCheckpointHeader(1, { slotNumber: proposal.slotNumber, inHash: emptyInHash }), + checkpointHeader: makeCheckpointHeader(1, { slotNumber: proposal.slotNumber }), archiveRoot: Fr.random(), lastBlock: { blockHeader: makeBlockHeader(1, { blockNumber, slotNumber: proposal.slotNumber }), @@ -669,9 +669,8 @@ describe('ValidatorClient', () => { validatorClient.updateConfig({ skipPushProposedBlocksToArchiver: false }); validatorClient.getProposalHandler().register(p2pClient, true); - const emptyInHash = computeInHashFromL1ToL2Messages([]); const checkpointProposal = await makeCheckpointProposal({ - checkpointHeader: makeCheckpointHeader(1, { slotNumber: proposal.slotNumber, inHash: emptyInHash }), + checkpointHeader: makeCheckpointHeader(1, { slotNumber: proposal.slotNumber }), archiveRoot: Fr.random(), lastBlock: { blockHeader: makeBlockHeader(1, { blockNumber, slotNumber: proposal.slotNumber }), @@ -680,7 +679,7 @@ describe('ValidatorClient', () => { }, }); const equivocatedCheckpointProposal = await makeCheckpointProposal({ - checkpointHeader: makeCheckpointHeader(1, { slotNumber: proposal.slotNumber, inHash: emptyInHash }), + checkpointHeader: makeCheckpointHeader(1, { slotNumber: proposal.slotNumber }), archiveRoot: Fr.random(), lastBlock: { blockHeader: makeBlockHeader(1, { blockNumber, slotNumber: proposal.slotNumber }), diff --git a/yarn-project/world-state/src/native/native_world_state.test.ts b/yarn-project/world-state/src/native/native_world_state.test.ts index aeceac469d4e..e205170c8676 100644 --- a/yarn-project/world-state/src/native/native_world_state.test.ts +++ b/yarn-project/world-state/src/native/native_world_state.test.ts @@ -1,12 +1,12 @@ import { ARCHIVE_HEIGHT, L1_TO_L2_MSG_TREE_HEIGHT, + MAX_L1_TO_L2_MSGS_PER_CHECKPOINT, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_TREE_HEIGHT, NULLIFIER_TREE_HEIGHT, - NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, PUBLIC_DATA_TREE_HEIGHT, } from '@aztec/constants'; import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types'; @@ -170,7 +170,7 @@ describe('NativeWorldState', () => { const status = await ws.handleL2BlockAndMessages(block, messages); - // Non-first blocks append their bundle exactly as given (no padding to NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP). + // Non-first blocks append their bundle exactly as given (no padding to MAX_L1_TO_L2_MSGS_PER_CHECKPOINT). expect(status.meta.messageTreeMeta.size).toBe(BigInt(numMessages)); }); }); @@ -1508,8 +1508,8 @@ describe('NativeWorldState', () => { expect(status.meta.messageTreeMeta).toMatchObject({ depth: L1_TO_L2_MSG_TREE_HEIGHT, - size: BigInt(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * (i + 1)), - committedSize: BigInt(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * (i + 1)), + size: BigInt(MAX_L1_TO_L2_MSGS_PER_CHECKPOINT * (i + 1)), + committedSize: BigInt(MAX_L1_TO_L2_MSGS_PER_CHECKPOINT * (i + 1)), initialSize: BigInt(0), oldestHistoricBlock: 1, unfinalizedBlockHeight: i + 1, diff --git a/yarn-project/world-state/src/test/utils.ts b/yarn-project/world-state/src/test/utils.ts index 8870091b3951..4ce5c907bc44 100644 --- a/yarn-project/world-state/src/test/utils.ts +++ b/yarn-project/world-state/src/test/utils.ts @@ -1,8 +1,8 @@ import { + MAX_L1_TO_L2_MSGS_PER_CHECKPOINT, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, NULLIFIER_SUBTREE_HEIGHT, - NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, } from '@aztec/constants'; import { asyncMap } from '@aztec/foundation/async-map'; import { BlockNumber, type CheckpointNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types'; @@ -78,7 +78,7 @@ export function mockBlock( size: number, fork: MerkleTreeWriteOperations, maxEffects: number | undefined = 1000, // Defaults to the maximum tx effects. - numL1ToL2Messages: number = NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, + numL1ToL2Messages: number = MAX_L1_TO_L2_MSGS_PER_CHECKPOINT, isFirstBlockInCheckpoint: boolean = true, ) { return mockBlockWithIndex(blockNum, isFirstBlockInCheckpoint ? 0 : 1, size, fork, numL1ToL2Messages, maxEffects); @@ -94,7 +94,7 @@ export async function mockBlockWithIndex( indexWithinCheckpoint: number, size: number, fork: MerkleTreeWriteOperations, - numL1ToL2Messages: number = NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, + numL1ToL2Messages: number = MAX_L1_TO_L2_MSGS_PER_CHECKPOINT, maxEffects: number | undefined = 1000, // Defaults to the maximum tx effects. ) { const block = await L2Block.random(blockNum, { diff --git a/yarn-project/world-state/src/world-state-db/merkle_tree_db.ts b/yarn-project/world-state/src/world-state-db/merkle_tree_db.ts index 1dabb0aacd30..f1cb5cb06f70 100644 --- a/yarn-project/world-state/src/world-state-db/merkle_tree_db.ts +++ b/yarn-project/world-state/src/world-state-db/merkle_tree_db.ts @@ -34,7 +34,7 @@ export interface MerkleTreeAdminDatabase extends ForkMerkleTreeOperations, Reado * Handles a single L2 block: inserts its note hashes, nullifiers, public data writes, and the block's L1-to-L2 * message bundle into the merkle trees. Any block may carry a message bundle and transition the L1-to-L2 message * tree, not just the first block of a checkpoint. A first-in-checkpoint bundle is padded to - * NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP to match how the circuits build the tree; a non-first bundle is appended + * MAX_L1_TO_L2_MSGS_PER_CHECKPOINT to match how the circuits build the tree; a non-first bundle is appended * exactly as given. Padding is a transitional concern of this method that moves entirely to the caller at the flip. * @param block - The L2 block to handle. * @param l1ToL2Messages - The L1 to L2 messages for the block. From a5e2d29ff67a1a4d01fa2eab6c1a1b3bed8c56b2 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Sat, 18 Jul 2026 18:34:52 -0300 Subject: [PATCH 4/8] chore(fast-inbox): fix l1 publisher revert assertion after inHash removal (A-1387) The propose-failure test asserted the deleted Rollup__InvalidInHash selector (0xcd6f4233). With the legacy inHash L1 check gone, a checkpoint carrying messages that do not match the Inbox now reverts at validateInboxConsumption with Rollup__InvalidInboxRollingHash (0xed1f7bb5). --- .../src/publisher/l1_publisher.integration.test.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts b/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts index 80b91613043f..30811efa799a 100644 --- a/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts +++ b/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts @@ -968,9 +968,8 @@ describe('L1Publisher integration', () => { }); it(`shows propose custom errors if tx simulation fails`, async () => { - // Set up different l1-to-l2 messages than the ones on the inbox, so this submission reverts because the - // INBOX.consume does not match the header.inHash and we get a Rollup__BlobHash that is not caught by - // validateHeader before. + // Set up different l1-to-l2 messages than the ones on the inbox, so the checkpoint's inboxRollingHash does not + // match the referenced Inbox bucket and the submission reverts at the streaming-consumption check. const l1ToL2Messages = new Array(MAX_L1_TO_L2_MSGS_PER_CHECKPOINT).fill(new Fr(1n)); const { checkpoint } = await buildSingleCheckpoint({ l1ToL2Messages }); @@ -986,12 +985,12 @@ describe('L1Publisher integration', () => { await progressToSlot(BigInt(checkpoint.header.slotNumber)); const result = await publisher.sendRequests(); expect(result).toBeUndefined(); - // 0xcd6f4233 == Rollup__InvalidInHash selector + // 0xed1f7bb5 == Rollup__InvalidInboxRollingHash selector expect(loggerWarnSpy).toHaveBeenCalledWith( 'Bundle entry dropped: action reverted in sim', expect.objectContaining({ action: 'propose', - returnData: expect.stringMatching(/^0xcd6f4233/), + returnData: expect.stringMatching(/^0xed1f7bb5/), }), ); }); From 97d7873fd154a21132368eb2b4880b070eeba936 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Sun, 19 Jul 2026 17:28:55 -0300 Subject: [PATCH 5/8] test(fast-inbox): regenerate pxe L2 tips snapshot for the inHash-less checkpoint header (A-1387) Dropping inHash from the checkpoint header changes the checkpoint hash preimage, so the checkpoint ids the tips store persists change value. The stored format is unchanged (read-defaultable), so only the snapshot is regenerated; no PXE_DATA_SCHEMA_VERSION bump. --- .../__snapshots__/L2TipsKVStore.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/L2TipsKVStore.json b/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/L2TipsKVStore.json index e83ff396e83e..c919b85f457b 100644 --- a/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/L2TipsKVStore.json +++ b/yarn-project/pxe/src/storage/backwards_compatibility_tests/__snapshots__/L2TipsKVStore.json @@ -16,7 +16,7 @@ "pxe_l2_tip_checkpoints": [ { "key": "utf8:checkpointed", - "value": "{\"number\":47,\"hash\":\"0x009bac3aae6ac2c7945a4686ec07381ac88bbd323cbfaccd6dffea9b254ae820\"}" + "value": "{\"number\":47,\"hash\":\"0x00407c60c560276f14936ea39b073d155bc34a485e8a83c0439c9b8ef83f737e\"}" }, { "key": "utf8:proven", From 85a0724859741ae7d960df05d95400daa78d6406 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 21 Jul 2026 11:38:06 -0300 Subject: [PATCH 6/8] test(fast-inbox): drive l1 publisher inbox consumption through the streaming bucket selector (A-1387) The A-1387 rewrite that removed inHash left this test's inbox handling half-adapted: it hardcoded previousInboxRollingHash to Fr.ZERO and proposed with bucketHint 0n while consuming real L1->L2 messages, so message-consuming checkpoints reverted on L1 with Rollup__InvalidInboxRollingHash / UnconsumedInboxMessages. INBOX_LAG_SECONDS is an L1-time censorship cutoff (not whole checkpoints), so no checkpoint-depth shift register can reproduce the aged bucket set. Mirror the real Inbox buckets into the test's MockL1ToL2MessageSource, then reuse the production selectInboxBucketForBlock (which mirrors ProposeLib.validateInboxConsumption) to pick exactly the buckets each checkpoint must consume, deriving the consumed bundle, the propose bucket hint, and the header rolling hash from that one selection so header, world state, and L1 agree. Matches the A-1388 rewrite of this file so the segment rebase dissolves to zero. --- .../l1_publisher.integration.test.ts | 142 +++++++++++++++--- 1 file changed, 118 insertions(+), 24 deletions(-) diff --git a/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts b/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts index 30811efa799a..4b3982555784 100644 --- a/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts +++ b/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts @@ -1,4 +1,5 @@ import type { ArchiverDataSource } from '@aztec/archiver'; +import { MockL1ToL2MessageSource } from '@aztec/archiver/test'; import { AztecAddress } from '@aztec/aztec.js/addresses'; import { Fr } from '@aztec/aztec.js/fields'; import { createLogger } from '@aztec/aztec.js/log'; @@ -13,6 +14,8 @@ import { } from '@aztec/blob-lib'; import { GENESIS_ARCHIVE_ROOT, + INBOX_LAG_SECONDS, + MAX_L1_TO_L2_MSGS_PER_BLOCK, MAX_L1_TO_L2_MSGS_PER_CHECKPOINT, MAX_NULLIFIERS_PER_TX, MAX_PROCESSABLE_L2_GAS, @@ -47,7 +50,7 @@ import { retryUntil } from '@aztec/foundation/retry'; import { sleep } from '@aztec/foundation/sleep'; import { hexToBuffer } from '@aztec/foundation/string'; import { TestDateProvider } from '@aztec/foundation/timer'; -import { RollupAbi } from '@aztec/l1-artifacts'; +import { InboxAbi, RollupAbi } from '@aztec/l1-artifacts'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree'; import { ProtocolContractsList, protocolContractsHash } from '@aztec/protocol-contracts'; import { LightweightCheckpointBuilder } from '@aztec/prover-client/light'; @@ -86,11 +89,12 @@ import { NativeWorldStateService, ServerWorldStateSynchronizer, type WorldStateC import { beforeEach, describe, expect, it, jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; -import { type Address, encodeFunctionData, getAbiItem, getAddress, multicall3Abi } from 'viem'; +import { type Address, encodeFunctionData, getAbiItem, getAddress, getContract, multicall3Abi } from 'viem'; import { type PrivateKeyAccount, privateKeyToAccount } from 'viem/accounts'; import { foundry } from 'viem/chains'; import { type SequencerClientConfig, getConfigEnvVars } from '../config.js'; +import { selectInboxBucketForBlock } from '../sequencer/inbox_bucket_selector.js'; import { sendL1ToL2Message } from './l1_to_l2_messaging.js'; import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js'; import { SequencerPublisher } from './sequencer-publisher.js'; @@ -111,7 +115,8 @@ const logger = createLogger('integration_l1_publisher'); // depending on @aztec/aztec-node, which would create a sequencer-client <-> aztec-node cycle. const config: SequencerClientConfig & L1ContractsConfig = { ...getL1ContractsConfigEnvVars(), ...getConfigEnvVars() }; -// Must exceed the inbox lag (network default 2) so at least one checkpoint consumes a real L1->L2 message. +// Several consecutive checkpoints, each consuming the L1->L2 messages sent while it was being built, so real +// messages are genuinely consumed and validated on L1 (AZIP-22 Fast Inbox). const numberOfConsecutiveBlocks = 3; jest.setTimeout(1000000); @@ -138,6 +143,11 @@ describe('L1Publisher integration', () => { let builderDb: NativeWorldStateService; + // Backs the blockSource mock's streaming L1->L2 message queries. The world-state synchronizer reconstructs each + // block's consumed message bundle from Inbox buckets (AZIP-22 Fast Inbox) when it syncs a block back, so the test + // registers one bucket per published block here (see buildAndPublishBlock). + let messageSource: MockL1ToL2MessageSource; + // The header of the last block let prevHeader: BlockHeader; @@ -274,6 +284,21 @@ describe('L1Publisher integration', () => { checkpointNumber: CheckpointNumber.ZERO, indexWithinCheckpoint: IndexWithinCheckpoint(0), }; + // Seed the genesis sentinel bucket (seq 0, no messages) so the world-state synchronizer can resolve a + // totalMsgCount of 0 to a bucket when reconstructing the first block's message bundle. + messageSource = new MockL1ToL2MessageSource(0); + messageSource.setInboxBucket( + { + seq: 0n, + inboxRollingHash: Fr.ZERO, + totalMsgCount: 0n, + timestamp: 0n, + msgCount: 0, + lastMessageIndex: 0n, + isOpen: false, + }, + [], + ); blockSource = mock({ getBlocks(query: BlocksQuery) { if (!('from' in query)) { @@ -349,6 +374,14 @@ describe('L1Publisher integration', () => { getBlockNumber(): Promise { return Promise.resolve(BlockNumber(blocks.at(-1)?.number ?? BlockNumber.ZERO)); }, + // Streaming L1->L2 message reconstruction (AZIP-22 Fast Inbox): the world-state synchronizer resolves each + // block's consumed message bundle from the Inbox buckets registered per published block in buildAndPublishBlock. + getInboxBucketByTotalMsgCount(totalMsgCount: bigint) { + return messageSource.getInboxBucketByTotalMsgCount(totalMsgCount); + }, + getL1ToL2MessagesBetweenBuckets(fromExclusive: bigint, toInclusive: bigint) { + return messageSource.getL1ToL2MessagesBetweenBuckets(fromExclusive, toInclusive); + }, }); const worldStateConfig: WorldStateConfig = { @@ -444,13 +477,16 @@ describe('L1Publisher integration', () => { /** * Build a checkpoint with a single block using the LightweightCheckpointBuilder. - * This properly computes all checkpoint header fields (blobsHash, blockHeadersHash, inHash, epochOutHash, etc.) + * This properly computes all checkpoint header fields (blobsHash, blockHeadersHash, inboxRollingHash, + * epochOutHash, etc.). `previousInboxRollingHash` is the previous checkpoint's rolling hash (zero at genesis), so + * the header's `inboxRollingHash` continues the on-chain Inbox chain over `l1ToL2Messages`. */ const buildCheckpoint = async ( globalVariables: GlobalVariables, txs: ProcessedTx[], l1ToL2Messages: Fr[], previousCheckpointOutHashes: Fr[] = [], + previousInboxRollingHash: Fr = Fr.ZERO, ): Promise => { await worldStateSynchronizer.syncImmediate(); const tempFork = await worldStateSynchronizer.fork(BlockNumber(globalVariables.blockNumber - 1)); @@ -471,7 +507,7 @@ describe('L1Publisher integration', () => { checkpointNumber, checkpointConstants, previousCheckpointOutHashes, - Fr.ZERO, + previousInboxRollingHash, tempFork, ); @@ -485,7 +521,8 @@ describe('L1Publisher integration', () => { const buildSingleCheckpoint = async ( opts: { l1ToL2Messages?: Fr[]; blockNumber?: BlockNumber; slot?: SlotNumber } = {}, ) => { - const l1ToL2Messages = opts.l1ToL2Messages ?? new Array(MAX_L1_TO_L2_MSGS_PER_CHECKPOINT).fill(Fr.ZERO); + // By default a single checkpoint consumes no Inbox messages (bucketHint 0 against the genesis bucket). + const l1ToL2Messages = opts.l1ToL2Messages ?? []; const txs = await Promise.all([makeProcessedTx(0x1000), makeProcessedTx(0x2000)]); const ts = (await l1Client.getBlock()).timestamp; @@ -502,7 +539,6 @@ describe('L1Publisher integration', () => { new GasFees(0, await rollup.getManaMinFeeAt(timestamp, true)), ); const checkpoint = await buildCheckpoint(globalVariables, txs, l1ToL2Messages); - blockSource.getL1ToL2Messages.mockResolvedValueOnce(l1ToL2Messages); return { checkpoint, l1ToL2Messages }; }; @@ -516,10 +552,8 @@ describe('L1Publisher integration', () => { describe('block building', () => { beforeEach(async () => { - // This suite proposes consecutive checkpoints and models the inbox lag by hand (a checkpoint - // consumes the L1->L2 messages sent inboxLag checkpoints earlier -- see the shift register in - // buildAndPublishBlock). It inherits the network default lag and proposes enough checkpoints - // that a real message is actually consumed and validated on L1. + // This suite proposes consecutive checkpoints, each consuming the streaming-Inbox messages sent while it was + // being built (AZIP-22 Fast Inbox), so real messages are genuinely consumed and validated on L1. await setup(); }); @@ -534,11 +568,24 @@ describe('L1Publisher integration', () => { '0x1647b194c649f5dd01d7c832f89b0f496043c9150797923ea89e93d5ac619a93', ); - // The deployed rollup consumes L1->L2 messages with a lag of `inboxLag` checkpoints: a message - // inserted while building checkpoint N is only consumable at checkpoint N + inboxLag. Model that - // with a shift register so a real message is genuinely consumed and validated on L1. - const inboxLag = getL1ContractsConfigEnvVars().inboxLag; - const messagesInFlight: Fr[][] = Array.from({ length: inboxLag }, () => []); + // Streaming Inbox consumption (AZIP-22 Fast Inbox): the L1 Rollup only lets a checkpoint consume Inbox buckets + // that have aged past the censorship cutoff (`toTimestamp(slot - 1) - INBOX_LAG_SECONDS`), measured in L1 time, + // not whole checkpoints. Each checkpoint mirrors the real Inbox buckets into messageSource, then reuses the + // production `selectInboxBucketForBlock` (which mirrors `ProposeLib.validateInboxConsumption`) to pick exactly + // the buckets it must consume, deriving the consumed bundle, the propose bucket hint, and the header rolling + // hash from that one selection so header, world state, and L1 agree by construction. + const inbox = getContract({ + address: getAddress(l1ContractAddresses.inboxAddress.toString()), + abi: InboxAbi, + client: l1Client, + }); + // Every message sent to the Inbox, in insertion order, so each bucket's leaves can be mirrored into messageSource. + const allSentMessages: Fr[] = []; + let mirroredThroughSeq = 0n; + let mirroredThroughTotal = 0n; + // The last Inbox bucket this checkpoint chain has consumed through; genesis sentinel to start. + let parent = { seq: 0n, totalMsgCount: 0n }; + let previousInboxRollingHash = Fr.ZERO; const blobFieldsPerCheckpoint: Fr[][] = []; // The below batched blob is used for testing different epochs with 1..numberOfConsecutiveBlocks blocks on L1. // For real usage, always collect ALL epoch blobs first then call .batch(). @@ -553,10 +600,29 @@ describe('L1Publisher integration', () => { for (let j = 0; j < l1ToL2Content.length; j++) { sentThisCheckpoint.push(await sendToL2(l1ToL2Content[j], recipientAddress)); } - - // Consume the messages sent inboxLag checkpoints ago, then enqueue this checkpoint's messages. - const currentL1ToL2Messages = messagesInFlight.shift()!; - messagesInFlight.push(sentThisCheckpoint); + allSentMessages.push(...sentThisCheckpoint); + + // Mirror the Inbox's new buckets (seq, timestamp, rolling hash, totals) and their leaves into messageSource, + // so the selector, the world-state synchronizer, and L1 all read the same bucket state. + const currentBucketSeq = await inbox.read.getCurrentBucketSeq(); + for (let seq = mirroredThroughSeq + 1n; seq <= currentBucketSeq; seq++) { + const bucket = await inbox.read.getBucket([seq]); + const bucketMessages = allSentMessages.slice(Number(mirroredThroughTotal), Number(bucket.totalMsgCount)); + messageSource.setInboxBucket( + { + seq, + inboxRollingHash: Fr.fromString(bucket.rollingHash), + totalMsgCount: bucket.totalMsgCount, + timestamp: bucket.timestamp, + msgCount: Number(bucket.msgCount), + lastMessageIndex: bucket.totalMsgCount - 1n, + isOpen: seq === currentBucketSeq, + }, + bucketMessages, + ); + mirroredThroughTotal = bucket.totalMsgCount; + } + mirroredThroughSeq = currentBucketSeq; // Ensure that each transaction has unique (non-intersecting nullifier values) const totalNullifiersPerBlock = 4 * MAX_NULLIFIERS_PER_TX; @@ -579,14 +645,42 @@ describe('L1Publisher integration', () => { new GasFees(0, await rollup.getManaMinFeeAt(timestamp, true)), ); - const checkpoint = await buildCheckpoint(globalVariables, txs, currentL1ToL2Messages); + // Reuse the production streaming selector to pick the buckets this single-block (hence last-block) checkpoint + // must consume, then derive the consumed bundle, the propose bucket hint, and the rolling-hash cursor from + // that one selection so the header, world state, and L1 all agree. + const buildFrameStart = await rollup.getTimestampForSlot(SlotNumber(slot - 1)); + const cutoffTimestamp = buildFrameStart - BigInt(INBOX_LAG_SECONDS); + const selection = await selectInboxBucketForBlock({ + messageSource, + now: buildFrameStart, + lagSeconds: BigInt(INBOX_LAG_SECONDS), + parent, + checkpointStartTotalMsgCount: parent.totalMsgCount, + perBlockCap: MAX_L1_TO_L2_MSGS_PER_BLOCK, + perCheckpointCap: MAX_L1_TO_L2_MSGS_PER_CHECKPOINT, + isLastBlock: true, + cutoffTimestamp, + }); + const currentL1ToL2Messages = selection.consume ? selection.bundle : []; + const bucketHint = selection.consume ? selection.bucket.seq : parent.seq; + + const checkpoint = await buildCheckpoint( + globalVariables, + txs, + currentL1ToL2Messages, + [], + previousInboxRollingHash, + ); + previousInboxRollingHash = checkpoint.header.inboxRollingHash; const block = checkpoint.blocks[0]; + if (selection.consume) { + parent = { seq: selection.bucket.seq, totalMsgCount: selection.bucket.totalMsgCount }; + } const totalManaUsed = txs.reduce((acc, tx) => acc.add(new Fr(tx.gasUsed.billedGas.l2Gas)), Fr.ZERO); expect(totalManaUsed.toBigInt()).toEqual(block.header.totalManaUsed.toBigInt()); prevHeader = block.header; - blockSource.getL1ToL2Messages.mockResolvedValueOnce(currentL1ToL2Messages); const checkpointBlobFields = checkpoint.toBlobFields(); const blockBlobs = await getBlobsPerL1Block(checkpointBlobFields); @@ -614,7 +708,7 @@ describe('L1Publisher integration', () => { checkpoint, CommitteeAttestationsAndSigners.empty(getSignatureContext()), Signature.empty(), - 0n, + bucketHint, ); // Align chain time so the bundle simulate and the L1 send both run at the header's slot. await progressToSlot(BigInt(checkpoint.header.slotNumber)); @@ -660,7 +754,7 @@ describe('L1Publisher integration', () => { oracleInput: { feeAssetPriceModifier: 0n, }, - bucketHint: 0n, + bucketHint, }, CommitteeAttestationsAndSigners.packAttestations([]), [], From 61af362551a99a5a3f37d9e68690e7a2501aba9f Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 27 Jul 2026 15:27:27 -0300 Subject: [PATCH 7/8] test(fast-inbox): drop isOpen from the l1 publisher inbox bucket mirror (A-1387) --- .../src/publisher/l1_publisher.integration.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts b/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts index 4b3982555784..05f09d1745e5 100644 --- a/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts +++ b/yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts @@ -295,7 +295,6 @@ describe('L1Publisher integration', () => { timestamp: 0n, msgCount: 0, lastMessageIndex: 0n, - isOpen: false, }, [], ); @@ -616,7 +615,6 @@ describe('L1Publisher integration', () => { timestamp: bucket.timestamp, msgCount: Number(bucket.msgCount), lastMessageIndex: bucket.totalMsgCount - 1n, - isOpen: seq === currentBucketSeq, }, bucketMessages, ); From 666df4c59e5395d8e7582e0db840c338fac04e5c Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 27 Jul 2026 16:57:08 -0300 Subject: [PATCH 8/8] docs(fast-inbox): correct the is_first_block and sole-block-variant rationale (A-1387) --- .../src/abis/block_rollup_public_inputs.nr | 12 +++++++----- .../checkpoint_root_single_block_rollup.nr | 6 ++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/block_rollup_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/block_rollup_public_inputs.nr index d78a718b3269..e014d61946b2 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/block_rollup_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/block_rollup_public_inputs.nr @@ -40,11 +40,13 @@ pub struct BlockRollupPublicInputs { // stored in the checkpoint header, enabling validation of the blocks included in a checkpoint given their headers. pub block_headers_hash: Field, - // Whether this block range starts at the first block of its checkpoint. Only the first block root sets this true; - // merges propagate it from the left rollup and `validate_consecutive_block_rollups` asserts the right rollup's is - // false, so it can only reach the checkpoint root through the leftmost leaf. It replaces `in_hash`'s former - // structural role: the checkpoint root asserts the merged value is true, and it drives the block-end blob-absorb - // flag (the l1-to-l2 tree root is absorbed only for the first block). + // Whether this block range starts at the first block of its checkpoint. Only the first block root variants set it + // true; merges propagate it from the left rollup (`merge_block_rollups`), and + // `validate_consecutive_block_rollups` asserts the right rollup of every merge is not a first block. The flag is + // therefore a proof that the range's leftmost leaf is a first-variant block root, which is what lets the + // checkpoint root assert its leftmost block is one (`checkpoint_root_inputs_validator`) even though it cannot see + // the leaf VK indices. Since only first variants may be fully empty (no txs and no messages), that also confines + // a fully-empty filler block to the first position of a checkpoint. pub is_first_block: bool, // Poseidon2 message-bundle sponge threaded across the blocks of the checkpoint. The leftmost block starts from the diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/checkpoint_root/checkpoint_root_single_block_rollup.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/checkpoint_root/checkpoint_root_single_block_rollup.nr index 79a4ef5f2f1a..3265df9d7c37 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/checkpoint_root/checkpoint_root_single_block_rollup.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/checkpoint_root/checkpoint_root_single_block_rollup.nr @@ -21,8 +21,10 @@ use types::{ // // That `is_first_block` assertion is the only thing keeping a message-only block from standing as a checkpoint's sole // block. If it is ever relaxed (e.g. replaced by a "leftmost block's start_msg_sponge is empty" check), this entry -// must be dropped in the same change, or a transaction-less single-block checkpoint — one whose blob omits the -// first-block l1-to-l2 root — would silently become provable. +// must be dropped in the same change, because barring the message-only variant from standing alone is what keeps +// each checkpoint shape to a single valid encoding: a sole transaction-less block carrying a message bundle is +// already provable as an empty-tx-first block, which accepts a non-empty bundle. It is also what keeps the rule +// confining fully-empty blocks to the first position enforceable. global ALLOWED_PREVIOUS_VK_INDICES: [u32; 4] = [ BLOCK_ROOT_FIRST_ROLLUP_VK_INDEX, BLOCK_ROOT_SINGLE_TX_FIRST_ROLLUP_VK_INDEX,