diff --git a/l1-contracts/src/core/libraries/compressed-data/CheckpointLog.sol b/l1-contracts/src/core/libraries/compressed-data/CheckpointLog.sol index 1458fe4683e1..96af3d3ac257 100644 --- a/l1-contracts/src/core/libraries/compressed-data/CheckpointLog.sol +++ b/l1-contracts/src/core/libraries/compressed-data/CheckpointLog.sol @@ -34,7 +34,7 @@ struct TempCheckpointLog { bytes32 attestationsHash; bytes32 payloadDigest; Slot slotNumber; - // Streaming Inbox consumption counts (AZIP-22 Fast Inbox). `inboxMsgTotal` is the cumulative Inbox message count + // Streaming Inbox consumption counts. `inboxMsgTotal` is the cumulative Inbox message count // consumed as of this checkpoint (the child's parent-total origin); `inboxConsumedBucket` is the bucket sequence // number the header's rolling hash corresponds to. Declared next to the slot number so the three share one storage // slot (4 + 8 + 8 of 32 bytes): propose writes and reads that slot for the slot-progression check anyway. diff --git a/l1-contracts/src/core/libraries/rollup/EpochProofLib.sol b/l1-contracts/src/core/libraries/rollup/EpochProofLib.sol index 5d6cdb40285b..1d738c501410 100644 --- a/l1-contracts/src/core/libraries/rollup/EpochProofLib.sol +++ b/l1-contracts/src/core/libraries/rollup/EpochProofLib.sol @@ -290,7 +290,7 @@ library EpochProofLib { ); } - // Boundary anchoring for the Inbox rolling-hash chain (AZIP-22 Fast Inbox), mirroring previousArchive/endArchive: + // Boundary anchoring for the Inbox rolling-hash chain, mirroring previousArchive/endArchive: // both ends of the claimed chain segment must match the rolling hashes recorded at propose for checkpoints // _start - 1 and _end. The start needs this to be sound - the previous checkpoint's header is not among this // proof's public inputs, so nothing else pins where the segment begins. The end is already pinned transitively @@ -343,7 +343,7 @@ library EpochProofLib { publicInputs[2] = _args.outHash; - // Inbox rolling-hash chain segment consumed across the epoch (AZIP-22 Fast Inbox). The start is validated above + // Inbox rolling-hash chain segment consumed across the epoch. The start is validated above // against the record written at propose for checkpoint _start - 1; the end is pinned transitively through the // stored checkpoint header hashes (see the anchoring block in assertAcceptable). publicInputs[3] = _args.previousInboxRollingHash; diff --git a/l1-contracts/src/core/libraries/rollup/ProposeLib.sol b/l1-contracts/src/core/libraries/rollup/ProposeLib.sol index a5e7c6516999..23dd2aeb7d20 100644 --- a/l1-contracts/src/core/libraries/rollup/ProposeLib.sol +++ b/l1-contracts/src/core/libraries/rollup/ProposeLib.sol @@ -34,7 +34,7 @@ struct ProposeArgs { bytes32 archive; OracleInput oracleInput; ProposedHeader header; - // Sequence number of the Inbox bucket the header's `inboxRollingHash` corresponds to (AZIP-22 Fast Inbox). + // Sequence number of the Inbox bucket the header's `inboxRollingHash` corresponds to. // Unsigned lookup aid kept out of the attested payload digest: a wrong hint can only revert, never change what is // accepted, since integrity comes from the rolling-hash equality check against the committee-signed header. uint256 bucketHint; @@ -269,8 +269,8 @@ library ProposeLib { uint256 checkpointNumber = tips.getPending() + 1; tips = tips.updatePending(checkpointNumber); - // Validate the streaming Inbox consumption against the parent checkpoint's consumed position (AZIP-22 Fast - // Inbox). The parent is checkpointNumber - 1, always available: checkpoint 0 carries the {0,0,0} genesis base + // Validate the streaming Inbox consumption against the parent checkpoint's consumed position. + // The parent is checkpointNumber - 1, always available: checkpoint 0 carries the {0,0,0} genesis base // case written at initialization. rollupStore.tips is not committed until below, so the parent read still sees // the parent as the pending tip. The returned cumulative total is stored in this checkpoint's record so its // child validates against it and, since temp-log records rewind with the pending chain on a prune, the record diff --git a/l1-contracts/src/core/libraries/rollup/STFLib.sol b/l1-contracts/src/core/libraries/rollup/STFLib.sol index f2ef1ed2fa43..eca3e15e7ade 100644 --- a/l1-contracts/src/core/libraries/rollup/STFLib.sol +++ b/l1-contracts/src/core/libraries/rollup/STFLib.sol @@ -134,7 +134,7 @@ library STFLib { excessMana: 0, manaUsed: 0, ethPerFeeAsset: _initialEthPerFeeAsset, congestionCost: 0, proverCost: 0 }), // Genesis Inbox consumption base case, matching the Inbox's genesis bucket-0 sentinel {0, 0, 0}, so - // checkpoint 1 validates its consumption against it (AZIP-22 Fast Inbox). + // checkpoint 1 validates its consumption against it. inboxRollingHash: bytes32(0), inboxMsgTotal: 0, inboxConsumedBucket: 0 @@ -311,7 +311,7 @@ library STFLib { } /** - * @notice Retrieves the cumulative Inbox message count consumed as of a checkpoint (AZIP-22 Fast Inbox) + * @notice Retrieves the cumulative Inbox message count consumed as of a checkpoint * @dev Gas-efficient accessor reading only the streaming-inbox consumed total. Reverts if the checkpoint is stale. * @param _checkpointNumber The checkpoint number to get the consumed total for * @return The cumulative Inbox message count consumed as of the checkpoint @@ -321,7 +321,7 @@ library STFLib { } /** - * @notice Retrieves the Inbox rolling hash a checkpoint committed to (AZIP-22 Fast Inbox) + * @notice Retrieves the Inbox rolling hash a checkpoint committed to * @dev Gas-efficient accessor reading only the streaming-inbox rolling hash. Reverts if the checkpoint is stale. * @param _checkpointNumber The checkpoint number to get the rolling hash for * @return The consensus Inbox rolling hash recorded for the checkpoint diff --git a/l1-contracts/src/core/messagebridge/Inbox.sol b/l1-contracts/src/core/messagebridge/Inbox.sol index f4782e4738ac..4258afb2cc64 100644 --- a/l1-contracts/src/core/messagebridge/Inbox.sol +++ b/l1-contracts/src/core/messagebridge/Inbox.sol @@ -130,7 +130,7 @@ contract Inbox is IInbox { currentTree = trees[inProgress]; } - // Compact cumulative message index (AZIP-22 Fast Inbox): the zero-based position of this message in the Inbox's + // Compact cumulative message index: the zero-based position of this message in the Inbox's // insertion order, equal to the number of messages inserted before it. It is embedded in the leaf preimage and // matches the streaming L1-to-L2 tree's leaf count, so consumers do not need per-checkpoint tree geometry. uint256 index = totalMessagesInserted; diff --git a/l1-contracts/test/Inbox.t.sol b/l1-contracts/test/Inbox.t.sol index b6bd78685573..7f9e36d4c1e4 100644 --- a/l1-contracts/test/Inbox.t.sol +++ b/l1-contracts/test/Inbox.t.sol @@ -93,7 +93,7 @@ contract InboxTest is Test { function testFuzzInsert(DataStructures.L1ToL2Msg memory _message) public checkInvariant { Inbox.InboxState memory stateBefore = inbox.getState(); - // Compact cumulative index (AZIP-22 Fast Inbox): the message's index is the count inserted before it. + // Compact cumulative index: the message's index is the count inserted before it. uint256 globalLeafIndex = stateBefore.totalMessagesInserted; DataStructures.L1ToL2Msg memory message = _boundMessage(_message, globalLeafIndex); diff --git a/l1-contracts/test/InboxBuckets.t.sol b/l1-contracts/test/InboxBuckets.t.sol index 800e99e9782a..5f23173c2416 100644 --- a/l1-contracts/test/InboxBuckets.t.sol +++ b/l1-contracts/test/InboxBuckets.t.sol @@ -132,7 +132,7 @@ contract InboxBucketsTest is Test { recipient: recipient, content: content, secretHash: secretHash, - // Compact cumulative index (AZIP-22 Fast Inbox): the first message against a fresh Inbox has index 0. + // Compact cumulative index: the first message against a fresh Inbox has index 0. index: inbox.getState().totalMessagesInserted }); bytes32 leaf = Hash.sha256ToField(message); diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 5234b2a2053e..830e62d33c9f 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -470,7 +470,7 @@ contract RollupTest is RollupBase { interim.feeAmount = interim.manaUsed * interim.minFee + interim.portalBalance; header.accumulatedFees = interim.feeAmount; - // Streaming Inbox (AZIP-22 Fast Inbox): nothing is seeded here, so reference the genesis bucket (hash 0). + // Streaming Inbox: nothing is seeded here, so reference the genesis bucket (hash 0). header.inboxRollingHash = bytes32(0); // Assert that balance have NOT been increased by proposing the checkpoint @@ -937,7 +937,7 @@ contract RollupTest is RollupBase { } // The epoch-proof anchoring pins the rolling-hash chain start to the record written at propose for checkpoint - // start - 1 (AZIP-22 Fast Inbox), mirroring previousArchive. A wrong previousInboxRollingHash must be rejected. + // start - 1, mirroring previousArchive. A wrong previousInboxRollingHash must be rejected. function testGetEpochProofPublicInputsRejectsWrongPreviousInboxRollingHash() public setUpFor("empty_checkpoint_1") { _proposeCheckpoint("empty_checkpoint_1", 1); @@ -965,7 +965,7 @@ contract RollupTest is RollupBase { } // The end of the rolling-hash chain segment is pinned to the hash recorded at propose for the epoch's last - // checkpoint (AZIP-22 Fast Inbox), mirroring endArchive. A wrong endInboxRollingHash must be rejected here rather + // checkpoint, mirroring endArchive. A wrong endInboxRollingHash must be rejected here rather // than surfacing as a generic proof-verification failure. function testGetEpochProofPublicInputsRejectsWrongEndInboxRollingHash() public setUpFor("empty_checkpoint_1") { _proposeCheckpoint("empty_checkpoint_1", 1); diff --git a/l1-contracts/test/RollupFieldRange.t.sol b/l1-contracts/test/RollupFieldRange.t.sol index 4e80826d22b3..3a910ac3e9d6 100644 --- a/l1-contracts/test/RollupFieldRange.t.sol +++ b/l1-contracts/test/RollupFieldRange.t.sol @@ -175,7 +175,7 @@ contract RollupFieldRangeTest is RollupBase { vm.blobhashes(this.getBlobHashes(full.checkpoint.blobCommitments)); - // Streaming Inbox (AZIP-22 Fast Inbox): nothing is seeded here, so reference the genesis bucket (hash 0). + // Streaming Inbox: nothing is seeded here, so reference the genesis bucket (hash 0). header.inboxRollingHash = bytes32(0); ProposeArgs memory args = diff --git a/l1-contracts/test/base/RollupBase.sol b/l1-contracts/test/base/RollupBase.sol index b327b8f4f579..4f50e6b3c4eb 100644 --- a/l1-contracts/test/base/RollupBase.sol +++ b/l1-contracts/test/base/RollupBase.sol @@ -78,8 +78,8 @@ contract RollupBase is DecoderBase { previousArchive: parentCheckpointLog.archive, endArchive: endFull.checkpoint.archive, outHash: endFull.checkpoint.header.outHash, - // Anchor the rolling-hash chain start to the record written at propose for checkpoint start - 1 (AZIP-22 Fast - // Inbox). The end value is unchecked on L1 but supplied for completeness. + // Anchor the rolling-hash chain start to the record written at propose for checkpoint start - 1. + // The end value is unchecked on L1 but supplied for completeness. previousInboxRollingHash: proposedHeaders[startCheckpointNumber - 1].inboxRollingHash, endInboxRollingHash: proposedHeaders[endCheckpointNumber].inboxRollingHash, proverId: _prover @@ -174,7 +174,7 @@ contract RollupBase is DecoderBase { // Legacy frontier root for the header's inHash field. Unchecked at propose post-flip, but kept because the // fixtures were generated with it as part of the header hash. full.checkpoint.header.inHash = rollup.getInbox().getRoot(full.checkpoint.checkpointNumber); - // Streaming Inbox (AZIP-22 Fast Inbox): reference the newest bucket so the checkpoint consumes all messages + // 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(); full.checkpoint.header.inboxRollingHash = rollup.getInbox().getBucket(bucketHint).rollingHash; diff --git a/l1-contracts/test/benchmark/happy.t.sol b/l1-contracts/test/benchmark/happy.t.sol index d11f02ae6d9a..7cbf18d9f0da 100644 --- a/l1-contracts/test/benchmark/happy.t.sol +++ b/l1-contracts/test/benchmark/happy.t.sol @@ -274,7 +274,7 @@ contract BenchmarkRollupTest is FeeModelTestPoints, DecoderBase { header.totalManaUsed = manaSpent; header.accumulatedFees = uint256(manaMinFee) * manaSpent; - // Streaming Inbox (AZIP-22 Fast Inbox): reference the newest bucket (nothing seeded here, so the genesis bucket). + // Streaming Inbox: reference the newest bucket (nothing seeded here, so the genesis bucket). uint256 bucketHint = rollup.getInbox().getCurrentBucketSeq(); header.inboxRollingHash = rollup.getInbox().getBucket(bucketHint).rollingHash; diff --git a/l1-contracts/test/compression/PreHeating.t.sol b/l1-contracts/test/compression/PreHeating.t.sol index aaf1915a8c4a..7bce6f907c2f 100644 --- a/l1-contracts/test/compression/PreHeating.t.sol +++ b/l1-contracts/test/compression/PreHeating.t.sol @@ -327,7 +327,7 @@ contract PreHeatingTest is FeeModelTestPoints, DecoderBase { header.totalManaUsed = manaSpent; header.accumulatedFees = uint256(manaMinFee) * manaSpent; - // Streaming Inbox (AZIP-22 Fast Inbox): reference the newest bucket (nothing seeded here, so the genesis bucket). + // Streaming Inbox: reference the newest bucket (nothing seeded here, so the genesis bucket). uint256 bucketHint = rollup.getInbox().getCurrentBucketSeq(); header.inboxRollingHash = rollup.getInbox().getBucket(bucketHint).rollingHash; diff --git a/l1-contracts/test/escape-hatch/integration/EscapeHatchIntegrationBase.sol b/l1-contracts/test/escape-hatch/integration/EscapeHatchIntegrationBase.sol index 0436dc40e53c..5e6cd5fea89e 100644 --- a/l1-contracts/test/escape-hatch/integration/EscapeHatchIntegrationBase.sol +++ b/l1-contracts/test/escape-hatch/integration/EscapeHatchIntegrationBase.sol @@ -155,7 +155,7 @@ abstract contract EscapeHatchIntegrationBase is ValidatorSelectionTestBase { accumulatedFees: 0 }); - // Streaming Inbox (AZIP-22 Fast Inbox): reference the newest bucket (genesis here; nothing is seeded). + // Streaming Inbox: reference the newest bucket (genesis here; nothing is seeded). uint256 bucketHint = rollup.getInbox().getCurrentBucketSeq(); header.inboxRollingHash = rollup.getInbox().getBucket(bucketHint).rollingHash; @@ -211,7 +211,7 @@ abstract contract EscapeHatchIntegrationBase is ValidatorSelectionTestBase { header.gasFees.feePerL2Gas = manaMinFee; } - // Streaming Inbox (AZIP-22 Fast Inbox): reference the newest bucket (genesis here; nothing is seeded). + // Streaming Inbox: reference the newest bucket (genesis here; nothing is seeded). uint256 bucketHint = rollup.getInbox().getCurrentBucketSeq(); header.inboxRollingHash = rollup.getInbox().getBucket(bucketHint).rollingHash; diff --git a/l1-contracts/test/escape-hatch/integration/invalidate.t.sol b/l1-contracts/test/escape-hatch/integration/invalidate.t.sol index fee7f17d26ba..de6808f2a11e 100644 --- a/l1-contracts/test/escape-hatch/integration/invalidate.t.sol +++ b/l1-contracts/test/escape-hatch/integration/invalidate.t.sol @@ -139,7 +139,7 @@ contract invalidateTest is EscapeHatchIntegrationBase { header.gasFees.feePerL2Gas = manaMinFee; } - // Streaming Inbox (AZIP-22 Fast Inbox): reference the newest bucket (genesis here; nothing is seeded). + // Streaming Inbox: reference the newest bucket (genesis here; nothing is seeded). uint256 bucketHint = rollup.getInbox().getCurrentBucketSeq(); header.inboxRollingHash = rollup.getInbox().getBucket(bucketHint).rollingHash; diff --git a/l1-contracts/test/fees/FeeHeaderOverflow.t.sol b/l1-contracts/test/fees/FeeHeaderOverflow.t.sol index 84a7c85b6b92..4d5057bc1112 100644 --- a/l1-contracts/test/fees/FeeHeaderOverflow.t.sol +++ b/l1-contracts/test/fees/FeeHeaderOverflow.t.sol @@ -98,7 +98,7 @@ contract FeeHeaderOverflowTest is DecoderBase { header.gasFees.feePerDaGas = 0; header.totalManaUsed = 0; - // Streaming Inbox (AZIP-22 Fast Inbox): reference the newest bucket (genesis here; nothing is seeded). + // Streaming Inbox: reference the newest bucket (genesis here; nothing is seeded). uint256 bucketHint = _rollup.getInbox().getCurrentBucketSeq(); header.inboxRollingHash = _rollup.getInbox().getBucket(bucketHint).rollingHash; diff --git a/l1-contracts/test/fees/FeeRollup.t.sol b/l1-contracts/test/fees/FeeRollup.t.sol index ac8ef93bd8f3..52219fd3e200 100644 --- a/l1-contracts/test/fees/FeeRollup.t.sol +++ b/l1-contracts/test/fees/FeeRollup.t.sol @@ -269,7 +269,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { if (rollup.getCurrentSlot() == nextSlot) { TestPoint memory point = points[Slot.unwrap(nextSlot) - 1]; Checkpoint memory b = getCheckpoint(); - // Streaming Inbox (AZIP-22 Fast Inbox): reference the newest bucket (genesis here; nothing is seeded). + // Streaming Inbox: reference the newest bucket (genesis here; nothing is seeded). uint256 bucketHint = rollup.getInbox().getCurrentBucketSeq(); b.header.inboxRollingHash = rollup.getInbox().getBucket(bucketHint).rollingHash; skipBlobCheck(address(rollup)); @@ -365,7 +365,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { Checkpoint memory b = getCheckpoint(); - // Streaming Inbox (AZIP-22 Fast Inbox): reference the newest bucket (genesis here; nothing is seeded). + // Streaming Inbox: reference the newest bucket (genesis here; nothing is seeded). uint256 bucketHint = rollup.getInbox().getCurrentBucketSeq(); b.header.inboxRollingHash = rollup.getInbox().getBucket(bucketHint).rollingHash; skipBlobCheck(address(rollup)); diff --git a/l1-contracts/test/harnesses/InboxHarness.sol b/l1-contracts/test/harnesses/InboxHarness.sol index 1213b0d53e07..b7489a0d4a9f 100644 --- a/l1-contracts/test/harnesses/InboxHarness.sol +++ b/l1-contracts/test/harnesses/InboxHarness.sol @@ -40,7 +40,7 @@ contract InboxHarness is Inbox { } function getNextMessageIndex() external view returns (uint256) { - // Compact cumulative index (AZIP-22 Fast Inbox): the next message's index is the count inserted so far. + // Compact cumulative index: the next message's index is the count inserted so far. return state.totalMessagesInserted; } } diff --git a/l1-contracts/test/portals/TokenPortal.t.sol b/l1-contracts/test/portals/TokenPortal.t.sol index 54e0dd51c736..ef51998626b8 100644 --- a/l1-contracts/test/portals/TokenPortal.t.sol +++ b/l1-contracts/test/portals/TokenPortal.t.sol @@ -119,7 +119,7 @@ contract TokenPortalTest is Test { testERC20.approve(address(tokenPortal), mintAmount); // Check for the expected message. - // Compact cumulative index (AZIP-22 Fast Inbox): the first message against a fresh Inbox has index 0. + // Compact cumulative index: the first message against a fresh Inbox has index 0. uint256 expectedIndex = 0; DataStructures.L1ToL2Msg memory expectedMessage = _createExpectedMintPrivateL1ToL2Message(expectedIndex); @@ -148,7 +148,7 @@ contract TokenPortalTest is Test { testERC20.approve(address(tokenPortal), mintAmount); // Check for the expected message. - // Compact cumulative index (AZIP-22 Fast Inbox): the first message against a fresh Inbox has index 0. + // Compact cumulative index: the first message against a fresh Inbox has index 0. uint256 expectedIndex = 0; DataStructures.L1ToL2Msg memory expectedMessage = _createExpectedMintPublicL1ToL2Message(expectedIndex); bytes32 expectedLeaf = expectedMessage.sha256ToField(); diff --git a/l1-contracts/test/tmnt419.t.sol b/l1-contracts/test/tmnt419.t.sol index 4dbe23a9b31d..2cd12d72c9d5 100644 --- a/l1-contracts/test/tmnt419.t.sol +++ b/l1-contracts/test/tmnt419.t.sol @@ -173,7 +173,7 @@ contract Tmnt419Test is RollupBase { header.gasFees.feePerL2Gas = SafeCast.toUint128(rollup.getManaMinFeeAt(Timestamp.wrap(block.timestamp), true)); header.totalManaUsed = MANA_TARGET; - // Streaming Inbox (AZIP-22 Fast Inbox): reference the newest bucket so any seeded messages are consumed. + // Streaming Inbox: reference the newest bucket so any seeded messages are consumed. uint256 bucketHint = rollup.getInbox().getCurrentBucketSeq(); header.inboxRollingHash = rollup.getInbox().getBucket(bucketHint).rollingHash; diff --git a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol index 77e46e2f1775..19da2d10ca76 100644 --- a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol +++ b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol @@ -551,7 +551,7 @@ contract ValidatorSelectionTest is ValidatorSelectionTestBase { header.gasFees.feePerL2Gas = manaMinFee; } - // Streaming Inbox (AZIP-22 Fast Inbox): reference the newest bucket, consuming the messages seeded above. + // Streaming Inbox: reference the newest bucket, consuming the messages seeded above. uint256 bucketHint = inbox.getCurrentBucketSeq(); header.inboxRollingHash = inbox.getBucket(bucketHint).rollingHash; diff --git a/l1-contracts/test/validator-selection/tmnt207.t.sol b/l1-contracts/test/validator-selection/tmnt207.t.sol index 77442e3db366..f01f9b6440a2 100644 --- a/l1-contracts/test/validator-selection/tmnt207.t.sol +++ b/l1-contracts/test/validator-selection/tmnt207.t.sol @@ -263,7 +263,7 @@ contract Tmnt207Test is RollupBase { header.gasFees.feePerL2Gas = SafeCast.toUint128(rollup.getManaMinFeeAt(Timestamp.wrap(block.timestamp), true)); header.totalManaUsed = MANA_TARGET; - // Streaming Inbox (AZIP-22 Fast Inbox): reference the newest bucket (genesis here; nothing is seeded). + // Streaming Inbox: reference the newest bucket (genesis here; nothing is seeded). uint256 bucketHint = rollup.getInbox().getCurrentBucketSeq(); header.inboxRollingHash = rollup.getInbox().getBucket(bucketHint).rollingHash; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/l1_to_l2_message_bundle.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/l1_to_l2_message_bundle.nr index dd015f2b1d5c..dda5fc4593c7 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/l1_to_l2_message_bundle.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/l1_to_l2_message_bundle.nr @@ -5,7 +5,7 @@ use types::{ }; /// A block's L1-to-L2 message bundle: the real message leaves it inserts into the L1-to-L2 message tree, and the -/// count a block root needs to append them and absorb them into the message sponge (AZIP-22 Fast Inbox). +/// count a block root needs to append them and absorb them into the message sponge. /// /// The `num_msgs` real messages occupy the leading lanes; the same count drives both the compact (unpadded) tree /// append and the message-sponge absorb, matching the checkpoint's variable-size `InboxParity` proof. Lanes past diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/components/block_rollup_public_inputs_composer.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/components/block_rollup_public_inputs_composer.nr index b5236ec9693d..65a5c38e9bbe 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/components/block_rollup_public_inputs_composer.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/components/block_rollup_public_inputs_composer.nr @@ -145,7 +145,7 @@ impl BlockRollupPublicInputsComposer { /// previous block (empty for the first block). /// /// A single `bundle.num_msgs` count drives both the compact (unpadded) tree append and the message-sponge absorb, - /// so the sponge matches the checkpoint's variable-size `InboxParity` proof (AZIP-22 Fast Inbox). The lanes past + /// so the sponge matches the checkpoint's variable-size `InboxParity` proof. The lanes past /// `num_msgs` must be zero. pub fn with_message_bundle( &mut self, @@ -270,7 +270,7 @@ impl BlockRollupPublicInputsComposer { }; // Absorb data for this block into the end sponge blob. The l1-to-l2 message tree root is absorbed for every - // block (AZIP-22 Fast Inbox): any block can insert its own bundle, so the root is per-block, not + // block: any block can insert its own bundle, so the root is per-block, not // checkpoint-constant. let mut block_end_sponge_blob = self.end_sponge_blob; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/tests/mod.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/tests/mod.nr index 7c5d74930e64..802a8d9ce380 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/tests/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/tests/mod.nr @@ -281,7 +281,7 @@ impl TestBuilder { } // The block's message bundle: exactly `num_msgs` real leaves, appended compactly to the tree and absorbed into the - // message sponge (AZIP-22 Fast Inbox). + // message sponge. fn message_bundle(self) -> L1ToL2MessageBundle { L1ToL2MessageBundle { messages: self.l1_to_l2_messages, num_msgs: self.num_msgs } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/blob_data/sponge_blob.nr b/noir-projects/noir-protocol-circuits/crates/types/src/blob_data/sponge_blob.nr index af057122b328..98dbb9c5b265 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/blob_data/sponge_blob.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/blob_data/sponge_blob.nr @@ -136,7 +136,7 @@ impl SpongeBlob { total_mana_used, ); - // The l1-to-l2 message tree root (the last field) is absorbed for every block (AZIP-22 Fast Inbox): any block + // The l1-to-l2 message tree root (the last field) is absorbed for every block: any block // can now insert its own bundle, so the root differs per block and blob-syncing nodes reconstruct each block's // message-tree root from the blob alone. self.absorb(blob_data, blob_data.len()); 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 bfeb40002568..262adeec56b3 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -63,7 +63,7 @@ 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 (AZIP-22 Fast Inbox). One quarter of the per-checkpoint +// 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. diff --git a/yarn-project/archiver/src/archiver-sync.test.ts b/yarn-project/archiver/src/archiver-sync.test.ts index 068c9a2d4c1f..3cfffde67ee8 100644 --- a/yarn-project/archiver/src/archiver-sync.test.ts +++ b/yarn-project/archiver/src/archiver-sync.test.ts @@ -187,7 +187,7 @@ describe('Archiver Sync', () => { await archiver?.stop(); }); - // Returns every stored L1-to-L2 message leaf (as hex), in insertion order (compact indexing, AZIP-22 Fast Inbox). + // Returns every stored L1-to-L2 message leaf (as hex), in insertion order (compact indexing). const getStoredLeaves = async () => (await toArray(archiverStore.messages.iterateL1ToL2Messages())).map(m => m.leaf.toString()); const asHex = (leaves: Fr[]) => leaves.map(l => l.toString()); diff --git a/yarn-project/archiver/src/l1/data_retrieval.test.ts b/yarn-project/archiver/src/l1/data_retrieval.test.ts index fd4dac61e9ff..93eed1186c01 100644 --- a/yarn-project/archiver/src/l1/data_retrieval.test.ts +++ b/yarn-project/archiver/src/l1/data_retrieval.test.ts @@ -81,7 +81,7 @@ describe('data_retrieval', () => { ); // Each block's L1-to-L2 message tree root must be reconstructed from its own blob data, not the - // checkpoint's first block. Post-flip (AZIP-22 streaming inbox) any block can insert messages, so + // checkpoint's first block. Any block can insert messages, so // intra-checkpoint blocks carry distinct roots; using the first block's root forks follower nodes. expect(reconstructedBlock1.header.state.l1ToL2MessageTree.root.toString()).toEqual( block1BlobData.l1ToL2MessageRoot.toString(), diff --git a/yarn-project/archiver/src/l1/data_retrieval.ts b/yarn-project/archiver/src/l1/data_retrieval.ts index e12f6a5c92eb..c6f7c8b8941b 100644 --- a/yarn-project/archiver/src/l1/data_retrieval.ts +++ b/yarn-project/archiver/src/l1/data_retrieval.ts @@ -87,7 +87,7 @@ export async function retrievedToPublishedCheckpoint({ const l2Blocks: L2Block[] = []; for (let i = 0; i < blocksBlobData.length; i++) { const blockBlobData = blocksBlobData[i]; - // Post-flip (AZIP-22 streaming inbox) the blob carries a per-block L1-to-L2 message tree root: any block + // The blob carries a per-block L1-to-L2 message tree root: any block // within a checkpoint can insert messages, so reconstruction must use each block's own root rather than // the checkpoint's first block. const { diff --git a/yarn-project/archiver/src/modules/l1_synchronizer.ts b/yarn-project/archiver/src/modules/l1_synchronizer.ts index 55f7d5a2639e..ba5876f77500 100644 --- a/yarn-project/archiver/src/modules/l1_synchronizer.ts +++ b/yarn-project/archiver/src/modules/l1_synchronizer.ts @@ -1015,8 +1015,6 @@ export class ArchiverL1Synchronizer implements Traceable { for (const calldataCheckpoint of checkpointsToIngest) { const published = publishedByNumber.get(calldataCheckpoint.checkpointNumber)!; - // The legacy inHash cross-check is dead post-flip (the header carries zero): the consensus Inbox rolling hash - // is verified on L1 at propose, so no equivalent check is needed here (AZIP-22 Fast Inbox). validCheckpoints.push(published); this.log.debug( `Ingesting new checkpoint ${published.checkpoint.number} with ${published.checkpoint.blocks.length} blocks`, diff --git a/yarn-project/archiver/src/store/block_store.ts b/yarn-project/archiver/src/store/block_store.ts index 983e14d1341b..83525e469460 100644 --- a/yarn-project/archiver/src/store/block_store.ts +++ b/yarn-project/archiver/src/store/block_store.ts @@ -1393,7 +1393,7 @@ export class BlockStore { const archive = lastBlock.archive; const checkpointOutHash = Checkpoint.getCheckpointOutHash(blocks); // The last block's L1-to-L2 leaf count is the checkpoint's cumulative consumed Inbox total under compact - // indexing, which is what `propose` records on L1 for it (AZIP-22 Fast Inbox). + // indexing, which is what `propose` records on L1 for it. const inboxMsgTotal = BigInt(lastBlock.header.state.l1ToL2MessageTree.nextAvailableLeafIndex); await this.#proposedCheckpoints.set(proposed.checkpointNumber, { diff --git a/yarn-project/archiver/src/test/mock_archiver.ts b/yarn-project/archiver/src/test/mock_archiver.ts index 5f1f86f758c7..029ea7f8a205 100644 --- a/yarn-project/archiver/src/test/mock_archiver.ts +++ b/yarn-project/archiver/src/test/mock_archiver.ts @@ -76,7 +76,7 @@ export class MockPrefilledArchiver extends MockArchiver { } // Register the Inbox buckets the streaming world-state synchronizer reconstructs each block's consumed - // message bundle from (AZIP-22 Fast Inbox): a genesis sentinel (totalMsgCount 0) so a leaf count of 0 + // message bundle from: a genesis sentinel (totalMsgCount 0) so a leaf count of 0 // resolves to a bucket, plus one bucket per message-carrying checkpoint whose cumulative totalMsgCount // matches the block's post-insertion L1-to-L2 leaf count. Rebuilt from the full prefilled chain (not just // this call's checkpoints) so a reorg re-prefill that replaces a suffix keeps the cumulative aligned. diff --git a/yarn-project/archiver/src/test/mock_structs.ts b/yarn-project/archiver/src/test/mock_structs.ts index 89e2226d3604..5cc54cfe9bad 100644 --- a/yarn-project/archiver/src/test/mock_structs.ts +++ b/yarn-project/archiver/src/test/mock_structs.ts @@ -33,7 +33,7 @@ export function makeInboxMessage( const { l1BlockHash = Buffer32.random() } = overrides; const { leaf = Fr.random() } = overrides; const { rollingHash = updateRollingHash(previousRollingHash, leaf) } = overrides; - // Compact global insertion index (AZIP-22 Fast Inbox): defaults to the first slot. + // Compact global insertion index: defaults to the first slot. const { index = 0n } = overrides; const { inboxRollingHash = updateInboxRollingHash(Fr.ZERO, leaf) } = overrides; // Default each message to its own bucket, keyed monotonically off its global index. diff --git a/yarn-project/blob-lib/src/encoding/block_blob_data.test.ts b/yarn-project/blob-lib/src/encoding/block_blob_data.test.ts index e77baca41a0f..c47c45d8a431 100644 --- a/yarn-project/blob-lib/src/encoding/block_blob_data.test.ts +++ b/yarn-project/blob-lib/src/encoding/block_blob_data.test.ts @@ -5,7 +5,7 @@ describe('block blob data', () => { it('encodes and decodes a block carrying the l1-to-l2 message root', () => { const blockBlobData = makeBlockBlobData({ numTxs: 3 }); expect(blockBlobData.txs.length).toBe(3); - // Every block carries the l1-to-l2 message tree root post-flip (AZIP-22 Fast Inbox). + // Every block carries the l1-to-l2 message tree root. expect(blockBlobData.l1ToL2MessageRoot).toBeDefined(); const encoded = encodeBlockBlobData(blockBlobData); diff --git a/yarn-project/blob-lib/src/encoding/block_blob_data.ts b/yarn-project/blob-lib/src/encoding/block_blob_data.ts index 007503617d3d..fd5d6607a7df 100644 --- a/yarn-project/blob-lib/src/encoding/block_blob_data.ts +++ b/yarn-project/blob-lib/src/encoding/block_blob_data.ts @@ -17,7 +17,7 @@ import { type TxBlobData, decodeTxBlobData, encodeTxBlobData } from './tx_blob_d // Must match the implementation in `noir-protocol-circuits/crates/types/src/blob_data/block_blob_data.nr`. -// Every block carries the L1-to-L2 message tree root (AZIP-22 Fast Inbox): once any block can insert its own +// Every block carries the L1-to-L2 message tree root: once any block can insert its own // message bundle, the root is per-block, so blob-syncing nodes reconstruct each block's message-tree root from the // blob alone. export const NUM_BLOCK_END_BLOB_FIELDS = 7; diff --git a/yarn-project/blob-lib/src/encoding/fixtures.ts b/yarn-project/blob-lib/src/encoding/fixtures.ts index be84348ca9e1..c416c0e32d6a 100644 --- a/yarn-project/blob-lib/src/encoding/fixtures.ts +++ b/yarn-project/blob-lib/src/encoding/fixtures.ts @@ -149,7 +149,7 @@ export function makeBlockEndBlobData({ noteHashRoot: fr(seed + 0x300), nullifierRoot: fr(seed + 0x400), publicDataRoot: fr(seed + 0x500), - // Every block carries the l1-to-l2 message tree root post-flip (AZIP-22 Fast Inbox). + // Every block carries the l1-to-l2 message tree root. l1ToL2MessageRoot: fr(seed + 0x600), ...blockEndBlobDataOverrides, }; diff --git a/yarn-project/end-to-end/src/single-node/cross-chain/l1_to_l2_inbox_drift.test.ts b/yarn-project/end-to-end/src/single-node/cross-chain/l1_to_l2_inbox_drift.test.ts index 07be54357a7e..f936cb180be8 100644 --- a/yarn-project/end-to-end/src/single-node/cross-chain/l1_to_l2_inbox_drift.test.ts +++ b/yarn-project/end-to-end/src/single-node/cross-chain/l1_to_l2_inbox_drift.test.ts @@ -20,8 +20,8 @@ jest.setTimeout(300_000); // L1→L2 messaging via Inbox: an L1→L2 message survives a rollup prune. Uses CrossChainMessagingTest // (prod sequencer, pipelining preset: ethSlot=4s, aztecSlot=12s, inboxLag=2, minTxsPerBlock=1, -// aztecProofSubmissionEpochs=2, aztecEpochDuration=4) with EpochTestSettler for auto-proving. Post-flip -// (AZIP-22) messages carry a compact L1-assigned index and stream in by insertion order rather than +// aztecProofSubmissionEpochs=2, aztecEpochDuration=4) with EpochTestSettler for auto-proving. Messages +// carry a compact L1-assigned index and stream in by insertion order rather than // pinning to a checkpoint, so a message inserted while the proposed chain drifts must still be // re-consumed with a stable index after the chain prunes back. Runs over private and public scope via // it.each, sharing one node stood up once in beforeAll. @@ -127,7 +127,7 @@ describe('single-node/cross-chain/l1_to_l2_inbox_drift', () => { } }; - // Post-flip (AZIP-22), L1→L2 messages carry a compact, L1-assigned global leaf index and are streamed + // L1→L2 messages carry a compact, L1-assigned global leaf index and are streamed // into the L2 tree in insertion order (subject to inbox lag and per-block/checkpoint caps) rather than // being pinned to a fixed checkpoint. This scenario stresses that message state survives an L2 reorg: // we let the proposed chain drift by mining several unproven checkpoints, insert a message during the @@ -183,7 +183,7 @@ describe('single-node/cross-chain/l1_to_l2_inbox_drift', () => { markProvenEnabled = true; await markAsProven(); - // Post-flip invariant: the compact-indexed message survives the L2 prune and is re-consumed on the + // The invariant under test: the compact-indexed message survives the L2 prune and is re-consumed on the // new chain within a reasonable window (its bucket and rolling-hash state must persist across the // reorg), becoming consumable from the requested scope. await waitForMessageReady(msgHash, scope); diff --git a/yarn-project/end-to-end/src/single-node/cross-chain/streaming_inbox.test.ts b/yarn-project/end-to-end/src/single-node/cross-chain/streaming_inbox.test.ts new file mode 100644 index 000000000000..1764ceb01b72 --- /dev/null +++ b/yarn-project/end-to-end/src/single-node/cross-chain/streaming_inbox.test.ts @@ -0,0 +1,328 @@ +import type { AztecAddress } from '@aztec/aztec.js/addresses'; +import { generateClaimSecret } from '@aztec/aztec.js/ethereum'; +import { Fr } from '@aztec/aztec.js/fields'; +import type { Logger } from '@aztec/aztec.js/log'; +import type { AztecNode } from '@aztec/aztec.js/node'; +import { TxExecutionResult } from '@aztec/aztec.js/tx'; +import type { Wallet } from '@aztec/aztec.js/wallet'; +import { INBOX_LAG_SECONDS } from '@aztec/constants'; +import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types'; +import { retryUntil } from '@aztec/foundation/retry'; +import { TestContract } from '@aztec/noir-test-contracts.js/Test'; +import { getSlotAtTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers'; + +import { jest } from '@jest/globals'; + +import { L1_DIRECT_WRITE_ACCOUNT_INDEX, PIPELINING_SETUP_OPTS } from '../../fixtures/fixtures.js'; +import { CrossChainMessagingTest } from './cross_chain_messaging_test.js'; +import { createL1ToL2MessageHelpers } from './message_test_helpers.js'; + +jest.setTimeout(600_000); + +// Streaming Inbox e2e coverage the legacy per-checkpoint suite could not express: when every L1->L2 message +// entered at the first block of the *next* checkpoint, mid-checkpoint inclusion, message-only blocks, and +// per-block streaming latency had no observable surface. Runs the production +// pipelining sequencer via CrossChainMessagingTest with a widened slot (36s / 6s blocks -> up to ~4 blocks +// per checkpoint) so a message can become lag-eligible partway through a checkpoint and land in a non-first +// block. minTxsPerBlock=0 lets a checkpoint carry a zero-tx block whose only content is a streaming bundle. +// +// Grounded on l1_to_l2.test.ts (send/wait helpers, TestContract arbitrary-sender consume) and +// cross_chain_public_message.test.ts (same-block public consume). All cases share one node stood up once. +describe('single-node/cross-chain/streaming_inbox', () => { + let t: CrossChainMessagingTest; + + let log: Logger; + let aztecNode: AztecNode; + let wallet: Wallet; + let user1Address: AztecAddress; + let testContract: TestContract; + + let sendMessageToL2: ReturnType['sendMessageToL2']; + let advanceBlock: ReturnType['advanceBlock']; + let waitForMessageReady: ReturnType['waitForMessageReady']; + + const markAsProven = () => t.cheatCodes.rollup.markAsProven(); + + beforeAll(async () => { + t = new CrossChainMessagingTest( + 'streaming_inbox', + // A 36s slot with 6s blocks yields up to ~4 blocks per checkpoint (the pipelining timing model gives + // maxBlocks = floor((36 - 0.5 - (0.5 + D)) / D) = 4 for D=6), which is what lets a message aged past + // INBOX_LAG_SECONDS land in a non-first block of the same checkpoint. minTxsPerBlock=0 permits a + // zero-tx message-only block (the FI-05 relaxation). + { ...PIPELINING_SETUP_OPTS, aztecSlotDuration: 36, blockDurationMs: 6000, minTxsPerBlock: 0 }, + { aztecProofSubmissionEpochs: 2, aztecEpochDuration: 4 }, + { syncChainTip: 'checkpointed' }, + // Pass arbitrary L1->L2 messages straight to a TestContract; no token bridge needed. + { l1HarnessAccountIndex: L1_DIRECT_WRITE_ACCOUNT_INDEX, deployTokenBridge: false }, + ); + await t.setup(); + + ({ logger: log, wallet, user1Address, aztecNode } = t); + ({ contract: testContract } = await TestContract.deploy(wallet).send({ from: user1Address })); + + ({ sendMessageToL2, advanceBlock, waitForMessageReady } = createL1ToL2MessageHelpers({ + t, + aztecNode, + wallet, + user1Address, + log, + markAsProven, + })); + }, 600_000); + + afterAll(async () => { + await t.teardown(); + }); + + /** The L1 block timestamp at which an L1->L2 message was inserted; equals the message's Inbox bucket key. */ + const getMessageL1Timestamp = async (l1BlockNumber: bigint): Promise => { + const block = await t.harnessL1Client.getBlock({ blockNumber: l1BlockNumber }); + return block.timestamp; + }; + + /** + * Finds the L2 block that inserted `msgHash` into the L1-to-L2 message tree by scanning forward from + * `fromBlock` for the first block whose committed tree resolves a membership witness. Under the streaming + * Inbox a message enters the tree at the block that consumes its Inbox bucket, which need not be the first + * block of a checkpoint. Returns the block-data (checkpoint number + index within checkpoint) of that block. + */ + const findInsertingBlock = (msgHash: Fr, fromBlock: BlockNumber) => { + return retryUntil( + async () => { + const tip = await aztecNode.getBlockNumber(); + for (let n = fromBlock; n <= tip; n = BlockNumber(n + 1)) { + const witness = await aztecNode.getL1ToL2MessageMembershipWitness(n, msgHash); + if (witness !== undefined) { + const data = await aztecNode.getBlockData(n); + return { blockNumber: n, checkpointNumber: data!.checkpointNumber, index: data!.indexWithinCheckpoint }; + } + } + return undefined; + }, + `find block inserting message ${msgHash.toString()}`, + 240, + 0.5, + ); + }; + + /** + * Runs `fn` while a background loop feeds empty txs, so checkpoints build multiple blocks promptly rather + * than stalling on an empty pool. advanceBlock also refreshes the L1 proof window, keeping the chain from + * pruning mid-test. Callers must pass a no-op `onNotReady` to any readiness wait so it does not send its own + * wallet txs concurrently (which would race the feeder on the wallet nonce). + */ + const withBackgroundFeeder = async (fn: () => Promise): Promise => { + let feeding = true; + const feeder = (async () => { + while (feeding) { + try { + await advanceBlock(); + } catch (err) { + log.warn(`Feeder tx failed: ${(err as Error).message}`); + } + } + })(); + try { + return await fn(); + } finally { + feeding = false; + await feeder; + } + }; + + // Test 1 (mid-checkpoint inclusion): a message sent mid-checkpoint becomes available in a *later* block of + // the same checkpoint (indexWithinCheckpoint > 0), which the legacy first-block-of-next-checkpoint flow + // could never produce. Feeds a steady tx stream so checkpoints fill to multiple blocks, times the send so + // the message ages past INBOX_LAG_SECONDS partway through a checkpoint's build, then locates the inserting + // block. Retries with fresh messages so a message that happens to age exactly at a checkpoint boundary (and + // lands at index 0) does not fail the run. + it('includes a message in a non-first block of a checkpoint (mid-checkpoint streaming)', async () => { + const { slotDuration } = t.constants; + + await withBackgroundFeeder(async () => { + let inserting: { blockNumber: BlockNumber; checkpointNumber: number; index: number } | undefined; + let insertedMsgHash: Fr | undefined; + + for (let attempt = 0; attempt < 4 && inserting === undefined; attempt++) { + // Aim the send so the message ages past the lag partway through a checkpoint's build window. The + // eligibility instant is T + INBOX_LAG_SECONDS; targeting it a few seconds into an upcoming build + // window lands it on a non-first block across the ~4-block checkpoint. The eligible window is wide + // (any block after the first whose build time exceeds T + lag), so exact timing is not required. + const nowTs = BigInt(await t.cheatCodes.eth.lastBlockTimestamp()); + const currentSlot = getSlotAtTimestamp(nowTs, t.constants); + const targetSlot = SlotNumber(Number(currentSlot) + 3); + const sendTargetTs = getTimestampForSlot(targetSlot, t.constants) - BigInt(INBOX_LAG_SECONDS) + 4n; + log.warn(`Attempt ${attempt}: waiting for L1 to reach ${sendTargetTs} before sending message`, { + currentSlot, + targetSlot, + }); + await retryUntil( + async () => BigInt(await t.cheatCodes.eth.lastBlockTimestamp()) >= sendTargetTs, + `L1 reaches ${sendTargetTs}`, + Number(slotDuration) * 6, + 0.2, + ); + + const blockAtSend = await aztecNode.getBlockNumber(); + const [, secretHash] = await generateClaimSecret(); + const message = { recipient: testContract.address, content: Fr.random(), secretHash }; + const { msgHash } = await sendMessageToL2(message); + log.warn(`Sent message ${msgHash.toString()} at block ${blockAtSend}`); + + // The background feeder drives block production; findInsertingBlock polls the committed tree without + // sending its own wallet txs (which would race the feeder on the nonce). + const found = await findInsertingBlock(msgHash, BlockNumber(blockAtSend + 1)); + log.warn(`Message ${msgHash.toString()} inserted at block ${found.blockNumber}`, { + checkpointNumber: found.checkpointNumber, + index: found.index, + }); + + if (found.index > 0) { + inserting = found; + insertedMsgHash = msgHash; + } else { + log.warn(`Message landed at index 0 (checkpoint boundary); retrying with a fresh message`); + } + } + + expect(inserting).toBeDefined(); + // A non-first block of its checkpoint carried the message: streaming placed it mid-checkpoint, which the + // legacy path (all messages at the first block of the next checkpoint) could never do. + expect(inserting!.index).toBeGreaterThan(0); + // The immediately preceding block did not yet have the message, confirming this block is the one that + // inserted it (rather than the message having been present since an earlier block of the checkpoint). + const priorWitness = await aztecNode.getL1ToL2MessageMembershipWitness( + BlockNumber(inserting!.blockNumber - 1), + insertedMsgHash!, + ); + expect(priorWitness).toBeUndefined(); + }); + }); + + // Test 2 (latency bound): the delay between a message's L1 inclusion and the L2 block that makes it + // available stays within the streaming bound. Asserted in slot-denominated terms (L1/L2 timestamps, not + // wall-clock): the including block's timestamp minus the message's L1 timestamp must be at most + // INBOX_LAG_SECONDS + 2 * slotDuration (lag + a full slot straddle + one slot of CI slack). No lower bound + // is asserted (eligibility is already enforced by L1 and the validator). The wall-clock latency is logged + // for information only. + it('makes a message available within the streaming latency bound', async () => { + const { slotDuration } = t.constants; + const maxDelaySeconds = BigInt(INBOX_LAG_SECONDS) + 2n * BigInt(slotDuration); + + await withBackgroundFeeder(async () => { + const blockAtSend = await aztecNode.getBlockNumber(); + const wallClockAtSend = Date.now(); + const [, secretHash] = await generateClaimSecret(); + const message = { recipient: testContract.address, content: Fr.random(), secretHash }; + const { msgHash, txReceipt } = await sendMessageToL2(message); + const messageL1Ts = await getMessageL1Timestamp(txReceipt.blockNumber!); + log.warn(`Sent message ${msgHash.toString()} with L1 timestamp ${messageL1Ts}`); + + // The background feeder drives block production; findInsertingBlock polls the committed tree without + // sending its own wallet txs (which would race the feeder on the nonce). + const inserting = await findInsertingBlock(msgHash, BlockNumber(blockAtSend + 1)); + const wallClockLatencyMs = Date.now() - wallClockAtSend; + const insertingBlock = (await aztecNode.getBlock(inserting.blockNumber))!; + const includingBlockTs = insertingBlock.header.globalVariables.timestamp; + const delaySeconds = includingBlockTs - messageL1Ts; + + // Informational only: the wall-clock number flakes under CI load, so it is never + // asserted on; the slot-denominated bound below is the real check. + log.warn(`Streaming latency for message ${msgHash.toString()}`, { + messageL1Ts, + includingBlockTs, + delaySeconds: Number(delaySeconds), + maxDelaySeconds: Number(maxDelaySeconds), + wallClockLatencyMs, + }); + + expect(delaySeconds).toBeGreaterThan(0n); + expect(delaySeconds).toBeLessThanOrEqual(maxDelaySeconds); + }); + }); + + // Test 3 (message-only block): on an empty tx pool, the block that consumes a message carries zero txs and a + // non-empty streaming bundle (the FI-05 shape exercised on a live chain), and the chain keeps proving past + // it. Drains the pool first, then sends a single message and asserts the inserting block has no tx effects. + it('produces a message-only block on an empty tx pool and keeps proving', async () => { + // Let the pool drain so the checkpoint that consumes the message is not padded with unrelated txs. + await retryUntil( + async () => !(await aztecNode.getPendingTxCount()), + 'tx pool drains', + Number(t.constants.slotDuration) * 3, + 0.5, + ); + + const blockAtSend = await aztecNode.getBlockNumber(); + const [, secretHash] = await generateClaimSecret(); + const message = { recipient: testContract.address, content: Fr.random(), secretHash }; + const { msgHash } = await sendMessageToL2(message); + log.warn(`Sent message ${msgHash.toString()} on an empty pool`); + + // Do not feed txs; the sequencer builds empty checkpoints until the message ages past the lag, at which + // point a zero-tx block consumes it. findInsertingBlock polls the committed tree without sending txs, so + // the pool stays empty and the block that consumes the message carries only the bundle. + const inserting = await findInsertingBlock(msgHash, BlockNumber(blockAtSend + 1)); + + const insertingBlock = (await aztecNode.getBlock(inserting.blockNumber, { includeTransactions: true }))!; + log.warn(`Message ${msgHash.toString()} inserted at block ${inserting.blockNumber}`, { + checkpointNumber: inserting.checkpointNumber, + index: inserting.index, + txCount: insertingBlock.body.txEffects.length, + }); + + // The inserting block carried the message with no txs: a message-only block. + expect(insertingBlock.body.txEffects.length).toBe(0); + // The membership witness resolving proves the block's bundle was non-empty (it inserted the message). + expect(await aztecNode.getL1ToL2MessageMembershipWitness('latest', msgHash)).toBeDefined(); + + // The chain keeps proving past the message-only block. + await markAsProven(); + await retryUntil( + async () => (await aztecNode.getBlockNumber('proven')) >= inserting.blockNumber, + `proven tip reaches block ${inserting.blockNumber}`, + Number(t.constants.slotDuration) * t.epochDuration * 3, + 1, + ); + expect(await aztecNode.getBlockNumber('proven')).toBeGreaterThanOrEqual(inserting.blockNumber); + }); + + // Test 4 (send-then-consume on the streaming path): a message inserted by the streaming Inbox is consumed by + // a public L2 tx, passing the compact leaf index, and cannot be consumed twice. Same-block consumption is + // available (a block's BlockConstantData.l1_to_l2_tree_snapshot pins to that block's post-bundle + // root, so the public/AVM read sees the just-inserted message), but which block consumes the message relative + // to its insertion depends on sequencer timing under the production sequencer; the block relationship is + // logged for visibility while the robust invariants asserted are the successful compact-index consume and the + // double-spend revert. Mirrors cross_chain_public_message.test.ts. + it('consumes a streaming-inserted message by compact index and rejects double-spend', async () => { + const l1Account = t.ethAccount; + const blockAtSend = await aztecNode.getBlockNumber(); + const [secret, secretHash] = await generateClaimSecret(); + const message = { recipient: testContract.address, content: Fr.random(), secretHash }; + const { msgHash, globalLeafIndex } = await sendMessageToL2(message); + log.warn(`Sent message ${msgHash.toString()} with compact index ${globalLeafIndex}`); + + await waitForMessageReady(msgHash, 'public'); + const inserting = await findInsertingBlock(msgHash, BlockNumber(blockAtSend + 1)); + + const { receipt: txReceipt } = await testContract.methods + .consume_message_from_arbitrary_sender_public(message.content, secret, l1Account, globalLeafIndex.toBigInt()) + .send({ from: user1Address }); + expect(txReceipt.blockNumber).toBeGreaterThan(0); + // The compact leaf index from the Inbox event resolves to the same message the node inserted. + const [resolvedIndex] = (await aztecNode.getL1ToL2MessageMembershipWitness('latest', msgHash))!; + expect(resolvedIndex).toBe(globalLeafIndex.toBigInt()); + log.warn(`Consumed message ${msgHash.toString()} in block ${txReceipt.blockNumber}`, { + insertingBlock: inserting.blockNumber, + consumeBlock: txReceipt.blockNumber, + sameBlock: Number(txReceipt.blockNumber) === Number(inserting.blockNumber), + }); + + // The message was inserted and consumed; a second consume must revert (the leaf is nullified). + const { receipt: failedReceipt } = await testContract.methods + .consume_message_from_arbitrary_sender_public(message.content, secret, l1Account, globalLeafIndex.toBigInt()) + .send({ from: user1Address, wait: { dontThrowOnRevert: true } }); + expect(failedReceipt.executionResult).toBe(TxExecutionResult.REVERTED); + }); +}); 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 8d31e2dc0d31..b50ef0313ed0 100644 --- a/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.ts +++ b/yarn-project/prover-client/src/light/lightweight_checkpoint_builder.ts @@ -179,7 +179,7 @@ export class LightweightCheckpointBuilder { const isFirstBlock = this.blocks.length === 0; // A non-first block with no txs is only allowed when it inserts a non-empty L1-to-L2 message bundle: the - // message-only block shape (AZIP-22 Fast Inbox), proven by the msgs-only block root. + // message-only block shape, proven by the msgs-only block root. if (!isFirstBlock && txs.length === 0 && l1ToL2Messages.length === 0) { throw new Error('Cannot add an empty non-first block that carries no L1-to-L2 messages.'); } diff --git a/yarn-project/prover-client/src/mocks/test_context.ts b/yarn-project/prover-client/src/mocks/test_context.ts index 9659b5cc4418..08bf74daabcf 100644 --- a/yarn-project/prover-client/src/mocks/test_context.ts +++ b/yarn-project/prover-client/src/mocks/test_context.ts @@ -59,6 +59,10 @@ export class TestContext { private nextBlockNumber = 1; private epochNumber = 1; private feePayerBalance: Fr; + // Running inbox rolling hash across the checkpoints built by this context (never resets: the protocol threads it + // from genesis). Each checkpoint's builder starts from it, and it advances by the checkpoint's messages, so a + // checkpoint built after a message-carrying one commits the correct continuation in its header. + private currentInboxRollingHash = Fr.ZERO; constructor( public worldState: MerkleTreeAdminDatabase, @@ -193,7 +197,7 @@ export class TestContext { const fork = await this.worldState.fork(); - // Build l1 to l2 messages. Appended unpadded at compact indices (AZIP-22 Fast Inbox); the mock assigns them all to + // Build l1 to l2 messages. Appended unpadded at compact indices; the mock assigns them all to // the checkpoint's first block, matching how the per-block driver slices them. const l1ToL2Messages = times(numL1ToL2Messages, i => new Fr(slotNumber * 100 + i)); await fork.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2Messages); @@ -241,11 +245,12 @@ export class TestContext { const cleanFork = await this.worldState.fork(); const previousCheckpointOutHashes = this.checkpointOutHashes; + const startInboxRollingHash = this.currentInboxRollingHash; const builder = LightweightCheckpointBuilder.startNewCheckpoint( checkpointNumber, { ...constants, timestamp }, previousCheckpointOutHashes, - Fr.ZERO, + startInboxRollingHash, cleanFork, ); @@ -272,6 +277,7 @@ export class TestContext { const checkpoint = await builder.completeCheckpoint(); this.checkpoints.push(checkpoint); this.checkpointOutHashes.push(checkpoint.getCheckpointOutHash()); + this.currentInboxRollingHash = checkpoint.header.inboxRollingHash; return { constants, @@ -280,6 +286,133 @@ export class TestContext { blocks, l1ToL2Messages, previousBlockHeader, + startInboxRollingHash, + }; + } + + /** + * Like {@link makeCheckpoint} but distributes the L1-to-L2 message bundle across the checkpoint's blocks + * (streaming Inbox): `l1ToL2MessagesPerBlock[i]` is block `i`'s own message slice, + * appended at compact indices in insertion order. Lets tests exercise checkpoints whose messages span more + * than one block, including a non-first block carrying a bundle — the single-block-per-checkpoint + * `makeCheckpoint` puts every message in the first block. `numTxsPerBlock` defaults to 1 because the builder + * rejects a non-first block with neither txs nor messages; a zero-tx entry whose slice is non-empty is valid + * and produces a message-only block. + */ + public async makeCheckpointWithMessagesPerBlock( + l1ToL2MessagesPerBlock: Fr[][], + { + numTxsPerBlock = 1, + makeProcessedTxOpts = () => ({}), + ...constantOpts + }: { + numTxsPerBlock?: number | number[]; + makeProcessedTxOpts?: ( + blockGlobalVariables: GlobalVariables, + txIndex: number, + ) => Partial[0]>; + } & Partial> = {}, + ) { + const numBlocks = l1ToL2MessagesPerBlock.length; + if (numBlocks === 0) { + throw new Error('Cannot make a checkpoint with 0 blocks.'); + } + + const checkpointIndex = this.nextCheckpointIndex++; + const checkpointNumber = this.nextCheckpointNumber; + this.nextCheckpointNumber++; + const slotNumber = checkpointNumber * 15; + + const constants = makeCheckpointConstants(slotNumber, constantOpts); + const l1ToL2Messages = l1ToL2MessagesPerBlock.flat(); + + const fork = await this.worldState.fork(); + + const startBlockNumber = this.nextBlockNumber; + const previousBlockHeader = this.getBlockHeader(BlockNumber(startBlockNumber - 1)); + const timestamp = BigInt(slotNumber * 26); + + const blockGlobalVariables = times(numBlocks, i => + makeGlobals(startBlockNumber + i, slotNumber, { + coinbase: constants.coinbase, + feeRecipient: constants.feeRecipient, + gasFees: constants.gasFees, + timestamp, + }), + ); + this.nextBlockNumber += numBlocks; + + // Build txs per block. Append the block's message slice to the fork before its txs so the per-block end + // state matches the builder's (message-tree and tx-effect trees are independent, so append order does not + // matter, but the cumulative message tree must reflect the slices already inserted). + let totalTxs = 0; + const blockEndStates: StateReference[] = []; + const blockTxs = await timesAsync(numBlocks, async blockIndex => { + await fork.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPerBlock[blockIndex]); + const newL1ToL2Snapshot = await getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, fork); + + const txIndexOffset = totalTxs; + const numTxs = typeof numTxsPerBlock === 'number' ? numTxsPerBlock : numTxsPerBlock[blockIndex]; + totalTxs += numTxs; + const txs = await timesAsync(numTxs, txIndex => + this.makeProcessedTx({ + seed: (txIndexOffset + txIndex + 1) * 321 + (checkpointIndex + 1) * 123456 + this.epochNumber * 0x99999, + globalVariables: blockGlobalVariables[blockIndex], + anchorBlockHeader: previousBlockHeader, + newL1ToL2Snapshot, + ...makeProcessedTxOpts(blockGlobalVariables[blockIndex], txIndexOffset + txIndex), + }), + ); + + const endState = await this.updateTrees(txs, fork); + blockEndStates.push(endState); + + return txs; + }); + + const cleanFork = await this.worldState.fork(); + const previousCheckpointOutHashes = this.checkpointOutHashes; + const startInboxRollingHash = this.currentInboxRollingHash; + const builder = LightweightCheckpointBuilder.startNewCheckpoint( + checkpointNumber, + { ...constants, timestamp }, + previousCheckpointOutHashes, + startInboxRollingHash, + cleanFork, + ); + + const blocks = []; + for (let i = 0; i < numBlocks; i++) { + const txs = blockTxs[i]; + const state = blockEndStates[i]; + + const { block } = await builder.addBlock(blockGlobalVariables[i], txs, l1ToL2MessagesPerBlock[i], { + expectedEndState: state, + insertTxsEffects: true, + }); + + const header = block.header; + this.headers.set(block.number, header); + + await this.worldState.handleL2BlockAndMessages(block, l1ToL2MessagesPerBlock[i]); + + blocks.push({ header, txs }); + } + + const checkpoint = await builder.completeCheckpoint(); + this.checkpoints.push(checkpoint); + this.checkpointOutHashes.push(checkpoint.getCheckpointOutHash()); + this.currentInboxRollingHash = checkpoint.header.inboxRollingHash; + + return { + constants, + checkpoint, + header: checkpoint.header, + blocks, + l1ToL2Messages, + l1ToL2MessagesPerBlock, + previousBlockHeader, + startInboxRollingHash, }; } diff --git a/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts b/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts index 3b17bc10cd31..1c340afa73cc 100644 --- a/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts +++ b/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts @@ -340,7 +340,7 @@ export const buildHeaderAndBodyFromTxs = runInSpan( noteHashRoot: partial.noteHashTree.root, nullifierRoot: partial.nullifierTree.root, publicDataRoot: partial.publicDataTree.root, - // Every block carries its own post-bundle l1-to-l2 message tree root (AZIP-22 Fast Inbox). + // Every block carries its own post-bundle l1-to-l2 message tree root. l1ToL2MessageRoot: l1ToL2MessageTree.root, txs: body.toTxBlobData(), }); @@ -401,7 +401,7 @@ export async function getSubtreeSiblingPath( /** * Returns the full-height frontier (left-sibling path) at a tree's next-available leaf index — the hint the append - * circuits re-hash against the snapshot root when appending leaves at a compact (unaligned) index (AZIP-22 Fast Inbox). + * circuits re-hash against the snapshot root when appending leaves at a compact (unaligned) index. */ export async function getFrontierSiblingPath(treeId: MerkleTreeId, db: MerkleTreeReadOperations): Promise { const nextAvailableLeafIndex = await db.getTreeInfo(treeId).then(t => t.size); diff --git a/yarn-project/prover-client/src/orchestrator/block-proving-state.ts b/yarn-project/prover-client/src/orchestrator/block-proving-state.ts index a3b3f27c66e1..f94fc06b7889 100644 --- a/yarn-project/prover-client/src/orchestrator/block-proving-state.ts +++ b/yarn-project/prover-client/src/orchestrator/block-proving-state.ts @@ -79,7 +79,7 @@ export class BlockProvingState { // The full state reference of the previous block, before this block's bundle is appended. Feeds the msgs-only // block root, whose zero-tx block has no tx constants to pin the previous state. private readonly previousState: StateReference, - // This block's L1-to-L2 message tree snapshot before and after its own bundle (AZIP-22 Fast Inbox). The start is + // This block's L1-to-L2 message tree snapshot before and after its own bundle. The start is // the previous block's end (block-merge continuity); the end is this block's own post-bundle snapshot. private readonly startL1ToL2MessageTreeSnapshot: AppendOnlyTreeSnapshot, public readonly newL1ToL2MessageTreeSnapshot: AppendOnlyTreeSnapshot, @@ -87,7 +87,7 @@ export class BlockProvingState { private readonly l1ToL2MessageFrontierHint: Tuple, // This block's own real L1-to-L2 message leaves (unpadded slice). private readonly l1ToL2Messages: Fr[], - // Message sponge threaded across the checkpoint's blocks (AZIP-22 Fast Inbox): the start is the previous block's + // Message sponge threaded across the checkpoint's blocks: the start is the previous block's // end sponge (empty for the first block), the end absorbs this block's own slice. Block merges assert the // continuity, so the end is exposed for the next block to inherit. private readonly startMsgSponge: L1ToL2MessageSponge, @@ -280,7 +280,7 @@ export class BlockProvingState { noteHashRoot: partial.noteHashTree.root, nullifierRoot: partial.nullifierTree.root, publicDataRoot: partial.publicDataTree.root, - // Every block carries its own post-bundle l1-to-l2 message tree root (AZIP-22 Fast Inbox). + // Every block carries its own post-bundle l1-to-l2 message tree root. l1ToL2MessageRoot: this.newL1ToL2MessageTreeSnapshot.root, }; } @@ -417,7 +417,7 @@ export class BlockProvingState { } /** - * The real-count message bundle this block appends (AZIP-22 Fast Inbox): the block's own message slice, inserted at + * The real-count message bundle this block appends: the block's own message slice, inserted at * compact indices and absorbed into its block-root message sponge. */ #getMessageBundle(): L1ToL2MessageBundle { @@ -428,7 +428,7 @@ export class BlockProvingState { /** * Full-height frontier hint for the bundle append: the left-sibling path at the block's compact start index, which - * the block-root circuit re-hashes against its start snapshot root (AZIP-22 Fast Inbox). + * the block-root circuit re-hashes against its start snapshot root. */ #getFrontierHint(): Tuple { return this.l1ToL2MessageFrontierHint; 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 1edfb7718947..04697e6e6249 100644 --- a/yarn-project/prover-client/src/orchestrator/checkpoint-proving-state.ts +++ b/yarn-project/prover-client/src/orchestrator/checkpoint-proving-state.ts @@ -52,7 +52,7 @@ export class CheckpointProvingState { this.firstBlockNumber = BlockNumber(headerOfLastBlockInPreviousCheckpoint.globalVariables.blockNumber + 1); } - /** The checkpoint's real L1-to-L2 messages (unpadded), consumed across its blocks (AZIP-22 Fast Inbox). */ + /** The checkpoint's real L1-to-L2 messages (unpadded), consumed across its blocks. */ public getL1ToL2Messages(): Fr[] { return this.l1ToL2Messages; } @@ -66,7 +66,7 @@ export class CheckpointProvingState { // The full state reference of the previous block (before this block's message bundle is appended). Feeds the // msgs-only block root, whose zero-tx block carries no tx constants to pin the previous state. previousState: StateReference, - // Per-block L1-to-L2 message state (AZIP-22 Fast Inbox): the block's start snapshot (its parent's end), its own + // Per-block L1-to-L2 message state: the block's start snapshot (its parent's end), its own // post-bundle end snapshot, the full-height frontier at the start index, and its own real message slice. startL1ToL2MessageTreeSnapshot: AppendOnlyTreeSnapshot, endL1ToL2MessageTreeSnapshot: AppendOnlyTreeSnapshot, @@ -85,7 +85,7 @@ export class CheckpointProvingState { ); } - // Thread the message sponge across the checkpoint's blocks (AZIP-22 Fast Inbox): each block starts from the + // Thread the message sponge across the checkpoint's blocks: each block starts from the // previous block's end sponge (empty for the first block) and absorbs its own real slice. The block merge and // checkpoint root circuits assert exactly this continuity (`right.start_msg_sponge == left.end_msg_sponge`, first // block starts empty, merged end equals the InboxParity sponge), so the end sponge is computed eagerly here for diff --git a/yarn-project/prover-client/src/orchestrator/checkpoint-sub-tree-orchestrator.test.ts b/yarn-project/prover-client/src/orchestrator/checkpoint-sub-tree-orchestrator.test.ts index c3e3b72fb3d8..554a8754cbc9 100644 --- a/yarn-project/prover-client/src/orchestrator/checkpoint-sub-tree-orchestrator.test.ts +++ b/yarn-project/prover-client/src/orchestrator/checkpoint-sub-tree-orchestrator.test.ts @@ -1,10 +1,10 @@ import { MAX_L2_TO_L1_MSGS_PER_TX } from '@aztec/constants'; import { EpochNumber } from '@aztec/foundation/branded-types'; -import { padArrayEnd } from '@aztec/foundation/collection'; +import { padArrayEnd, sum } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; import { EthAddress } from '@aztec/foundation/eth-address'; import { createLogger } from '@aztec/foundation/log'; -import { ScopedL2ToL1Message, computeBlockOutHash } from '@aztec/stdlib/messaging'; +import { L1ToL2MessageSponge, ScopedL2ToL1Message, computeBlockOutHash } from '@aztec/stdlib/messaging'; import { makeScopedL2ToL1Message } from '@aztec/stdlib/testing'; import { TestContext, makeTestDeferredJobQueue } from '../mocks/test_context.js'; @@ -219,4 +219,98 @@ describe('prover/orchestrator/checkpoint-sub-tree', () => { await subTree.stop(); } }); + + it('slices L1-to-L2 messages per block across a multi-block checkpoint', async () => { + // A checkpoint whose messages span more than one block: the first block carries a bundle, a middle block + // carries none (txs only), and the last block carries a bundle with zero txs (a message-only block, proven by + // the msgs-only block root). The sub-tree must append each block's own slice at compact indices with + // contiguous, non-overlapping per-block snapshots, and thread the message sponge across the blocks. + // The sub-tree result surfaces post-merge top-level nodes (at most two, for the binary + // checkpoint root), not one output per block. + const l1ToL2MessagesPerBlock = [[new Fr(1001), new Fr(1002)], [], [new Fr(1003), new Fr(1004), new Fr(1005)]]; + const numBlocks = l1ToL2MessagesPerBlock.length; + const { constants, blocks, l1ToL2Messages, previousBlockHeader } = await context.makeCheckpointWithMessagesPerBlock( + l1ToL2MessagesPerBlock, + { numTxsPerBlock: [1, 1, 0] }, + ); + expect(l1ToL2Messages.length).toBe(5); + + const subTree = await CheckpointSubTreeOrchestrator.start( + context.worldState, + context.prover, + EthAddress.ZERO, + chonkCache, + EpochNumber(1), + false, + makeTestDeferredJobQueue(), + constants, + l1ToL2Messages, + Fr.ZERO, + numBlocks, + previousBlockHeader, + ); + try { + const resultPromise = subTree.getSubTreeResult(); + + for (const [blockIndex, block] of blocks.entries()) { + const { blockNumber, timestamp } = block.header.globalVariables; + await subTree.startNewBlock(blockNumber, timestamp, block.txs.length, l1ToL2MessagesPerBlock[blockIndex]); + if (block.txs.length > 0) { + await subTree.addTxs(block.txs); + } + await subTree.setBlockCompleted(blockNumber, block.header); + } + + const result = await resultPromise; + // Three block roots reduce to two top-level outputs: a block-merge over blocks 0-1 and block 2's msgs-only + // root. Merge public inputs span their range: is_first_block propagates from the left child, the start + // sponge/state come from the left child and the end sponge/state from the right. + const expectedOutputBlockRanges = [[0, 1], [2]]; + expect(result.blockProofOutputs).toHaveLength(expectedOutputBlockRanges.length); + + // Order the outputs by position in the checkpoint (the archive tree grows by one leaf per block). + const ordered = [...result.blockProofOutputs].sort( + (a, b) => a.inputs.previousArchive.nextAvailableLeafIndex - b.inputs.previousArchive.nextAvailableLeafIndex, + ); + + // Walk the outputs in order, asserting the L1-to-L2 message tree partitions cleanly into per-output slices, + // with each output's start snapshot equal to the previous output's end snapshot (the "threaded" per-block + // L1-to-L2 tree state). + const baseLeaf = ordered[0].inputs.startState.l1ToL2MessageTree.nextAvailableLeafIndex; + let expectedStartLeaf = baseLeaf; + // The message sponge threads across the checkpoint's blocks: the first block starts from the empty sponge and + // each block absorbs exactly its own slice (the block merge and checkpoint root circuits assert this + // continuity against the InboxParity sponge). + const expectedSponge = L1ToL2MessageSponge.empty(); + for (const [i, output] of ordered.entries()) { + const inputs = output.inputs; + const blockIndexes = expectedOutputBlockRanges[i]; + const startLeaf = inputs.startState.l1ToL2MessageTree.nextAvailableLeafIndex; + const endLeaf = inputs.endState.l1ToL2MessageTree.nextAvailableLeafIndex; + const sliceLen = sum(blockIndexes.map(b => l1ToL2MessagesPerBlock[b].length)); + + // Only the output covering the checkpoint's first block flags isFirstBlock. + expect(inputs.isFirstBlock).toBe(blockIndexes.includes(0)); + // Contiguous, non-overlapping slices: this output starts where the previous one ended (no gap/overlap). + expect(startLeaf).toBe(expectedStartLeaf); + // The tree grows by exactly the covered blocks' bundle sizes. + expect(endLeaf - startLeaf).toBe(sliceLen); + expectedStartLeaf = endLeaf; + + // Sponge continuity: this output starts from the previous one's end sponge and absorbs its blocks' slices. + expect(inputs.startMsgSponge.toBuffer()).toEqual(expectedSponge.toBuffer()); + for (const blockIndex of blockIndexes) { + await expectedSponge.absorb(l1ToL2MessagesPerBlock[blockIndex]); + } + expect(inputs.endMsgSponge.toBuffer()).toEqual(expectedSponge.toBuffer()); + } + + // Every message is accounted for with no gap or overlap across the checkpoint's blocks. + expect(expectedStartLeaf - baseLeaf).toBe(l1ToL2Messages.length); + // The last block's end sponge equals the checkpoint's InboxParity end sponge. + expect(result.inboxParityProof.inputs.endSponge.toBuffer()).toEqual(expectedSponge.toBuffer()); + } finally { + await subTree.stop(); + } + }); }); diff --git a/yarn-project/prover-client/src/orchestrator/checkpoint-sub-tree-orchestrator.ts b/yarn-project/prover-client/src/orchestrator/checkpoint-sub-tree-orchestrator.ts index 44fc85c2d508..21fe9ec47c81 100644 --- a/yarn-project/prover-client/src/orchestrator/checkpoint-sub-tree-orchestrator.ts +++ b/yarn-project/prover-client/src/orchestrator/checkpoint-sub-tree-orchestrator.ts @@ -292,7 +292,7 @@ export class CheckpointSubTreeOrchestrator extends ProvingScheduler { // msgs-only block root, whose zero-tx block carries no tx constants to pin the previous state. const previousState = await db.getStateReference(); - // Streaming Inbox (AZIP-22 Fast Inbox): insert this block's own real message slice at compact indices, capturing + // Streaming Inbox: insert this block's own real message slice at compact indices, capturing // the start snapshot + full-height frontier before the append and the block's own post-bundle end snapshot after. const startL1ToL2Snapshot = await getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, db); const l1ToL2FrontierHint = assertLength( @@ -537,7 +537,7 @@ export class CheckpointSubTreeOrchestrator extends ProvingScheduler { // Get archive sibling path before any block in this checkpoint lands. const lastArchiveSiblingPath = await getLastSiblingPath(MerkleTreeId.ARCHIVE, db); - // Streaming Inbox (AZIP-22 Fast Inbox): messages are inserted per block in `startNewBlock`, not the whole + // Streaming Inbox: messages are inserted per block in `startNewBlock`, not the whole // checkpoint up front. The message sponge is likewise threaded per block (each block's start sponge is the // previous block's end), so the last block's end sponge matches the checkpoint's single InboxParity proof. this.provingState = new CheckpointProvingState( diff --git a/yarn-project/prover-client/src/orchestrator/top-tree-orchestrator.test.ts b/yarn-project/prover-client/src/orchestrator/top-tree-orchestrator.test.ts index 8ef78c653a05..1d7e8aab5805 100644 --- a/yarn-project/prover-client/src/orchestrator/top-tree-orchestrator.test.ts +++ b/yarn-project/prover-client/src/orchestrator/top-tree-orchestrator.test.ts @@ -7,7 +7,7 @@ import { createLogger } from '@aztec/foundation/log'; import { promiseWithResolvers } from '@aztec/foundation/promise'; import { retryUntil } from '@aztec/foundation/retry'; import { sleep } from '@aztec/foundation/sleep'; -import { ScopedL2ToL1Message, computeEpochOutHash } from '@aztec/stdlib/messaging'; +import { ScopedL2ToL1Message, accumulateInboxRollingHash, computeEpochOutHash } from '@aztec/stdlib/messaging'; import { makeScopedL2ToL1Message } from '@aztec/stdlib/testing'; import { TestContext, makeTestDeferredJobQueue } from '../mocks/test_context.js'; @@ -42,20 +42,14 @@ describe('prover/orchestrator/top-tree', () => { await context.cleanup(); }); + /** The checkpoint fixture shape shared by `makeCheckpoint` and `makeCheckpointWithMessagesPerBlock`. */ + type CheckpointFixture = Awaited>; + /** - * Drives a single checkpoint through `CheckpointSubTreeOrchestrator` and returns - * the assembled `CheckpointTopTreeData` plus the originating checkpoint metadata. + * Drives a checkpoint fixture through `CheckpointSubTreeOrchestrator`, feeding block `i` the message slice + * `messagesPerBlock[i]`, and returns the assembled `CheckpointTopTreeData` plus the originating fixture. */ - async function driveSubTree(numBlocks: number, numTxsPerBlock: number, numL1ToL2Messages = 0, numL2ToL1Messages = 0) { - const fixture = await context.makeCheckpoint(numBlocks, { - numTxsPerBlock, - numL1ToL2Messages, - makeProcessedTxOpts: - numL2ToL1Messages > 0 - ? () => ({ privateOnly: false, avmAccumulatedData: { l2ToL1Msgs: makeL2ToL1Messages(numL2ToL1Messages) } }) - : undefined, - }); - + async function driveFixture(fixture: CheckpointFixture, messagesPerBlock: Fr[][]) { const subTree = await CheckpointSubTreeOrchestrator.start( context.worldState, context.prover, @@ -66,20 +60,15 @@ describe('prover/orchestrator/top-tree', () => { makeTestDeferredJobQueue(), fixture.constants, fixture.l1ToL2Messages, - Fr.ZERO, - numBlocks, + fixture.startInboxRollingHash, + fixture.blocks.length, fixture.previousBlockHeader, ); const resultPromise = subTree.getSubTreeResult(); for (const [blockIndex, block] of fixture.blocks.entries()) { const { blockNumber, timestamp } = block.header.globalVariables; - await subTree.startNewBlock( - blockNumber, - timestamp, - block.txs.length, - blockIndex === 0 ? fixture.l1ToL2Messages : [], - ); + await subTree.startNewBlock(blockNumber, timestamp, block.txs.length, messagesPerBlock[blockIndex]); if (block.txs.length > 0) { await subTree.addTxs(block.txs); } @@ -103,6 +92,33 @@ describe('prover/orchestrator/top-tree', () => { return { fixture, topTreeData }; } + /** + * Builds a checkpoint via `makeCheckpoint` (every message in the first block) and drives it through + * `CheckpointSubTreeOrchestrator`, returning the assembled `CheckpointTopTreeData` plus the fixture. + */ + async function driveSubTree(numBlocks: number, numTxsPerBlock: number, numL1ToL2Messages = 0, numL2ToL1Messages = 0) { + const fixture = await context.makeCheckpoint(numBlocks, { + numTxsPerBlock, + numL1ToL2Messages, + makeProcessedTxOpts: + numL2ToL1Messages > 0 + ? () => ({ privateOnly: false, avmAccumulatedData: { l2ToL1Msgs: makeL2ToL1Messages(numL2ToL1Messages) } }) + : undefined, + }); + const messagesPerBlock = fixture.blocks.map((_, i) => (i === 0 ? fixture.l1ToL2Messages : [])); + return await driveFixture(fixture, messagesPerBlock); + } + + /** + * Like {@link driveSubTree} but distributes the checkpoint's messages across its blocks (streaming Inbox): + * block `i` carries `l1ToL2MessagesPerBlock[i]` as its own slice. A zero-tx entry in `numTxsPerBlock` + * whose slice is non-empty produces a message-only block, proven by the msgs-only block root. + */ + async function driveSubTreeWithMessageSlices(l1ToL2MessagesPerBlock: Fr[][], numTxsPerBlock: number[]) { + const fixture = await context.makeCheckpointWithMessagesPerBlock(l1ToL2MessagesPerBlock, { numTxsPerBlock }); + return await driveFixture(fixture, l1ToL2MessagesPerBlock); + } + it('produces an epoch proof for a single-checkpoint, single-block, single-tx epoch', async () => { const { topTreeData } = await driveSubTree(1, 1); const challenges = await context.getFinalBlobChallenges(); @@ -169,6 +185,39 @@ describe('prover/orchestrator/top-tree', () => { } }); + it('produces an epoch proof when messages span blocks, including a message-only block', async () => { + // The streaming Inbox shapes, driven through the entire proving DAG at simulated-circuit + // fidelity: a checkpoint whose messages land in a non-first block, a zero-tx message-only block (proven by the + // msgs-only block root), a block merge above the three block roots, the two-input checkpoint root over per-block + // bundles, the single-block checkpoint root for the follow-on checkpoints, the checkpoint merge asserting inbox + // rolling-hash continuity across a message-carrying boundary, and the root rollup exposing the epoch's + // rolling-hash range. + const ckpt1Slices = [[new Fr(0x100), new Fr(0x101)], [], [new Fr(0x102), new Fr(0x103), new Fr(0x104)]]; + const a = await driveSubTreeWithMessageSlices(ckpt1Slices, [1, 1, 0]); + // A single-block checkpoint with messages after a message-carrying checkpoint: its parity chain starts from + // checkpoint 0's (non-zero) end rolling hash. + const b = await driveSubTree(1, 1, 2); + // A message-less checkpoint after message-carrying ones: the rolling hash must pass through unchanged. + const c = await driveSubTree(1, 1); + const challenges = await context.getFinalBlobChallenges(); + + const topTree = new TopTreeOrchestrator(context.prover, EthAddress.ZERO, makeTestDeferredJobQueue()); + try { + const result = await topTree.prove(EpochNumber(1), 3, challenges, [a.topTreeData, b.topTreeData, c.topTreeData]); + expect(result.proof).toBeDefined(); + expect(result.publicInputs).toBeDefined(); + + // The epoch's rolling-hash range binds the exact message sequence consumed, in block order, across all three + // checkpoints; L1 validates this range against the Inbox when the proof lands. + const epochMessages = [...a.fixture.l1ToL2Messages, ...b.fixture.l1ToL2Messages]; + expect(epochMessages.length).toBe(7); // sanity: the fixtures really did carry messages + expect(result.publicInputs.previousInboxRollingHash).toEqual(Fr.ZERO); + expect(result.publicInputs.endInboxRollingHash).toEqual(accumulateInboxRollingHash(Fr.ZERO, epochMessages)); + } finally { + await topTree.stop(); + } + }, 300_000); + it('pipelines: starts ckpt0 root rollup before ckpt1 sub-tree resolves', async () => { // Drive both sub-trees synchronously (still no top tree running). const a = await driveSubTree(1, 1); diff --git a/yarn-project/prover-node/src/job/checkpoint-prover.ts b/yarn-project/prover-node/src/job/checkpoint-prover.ts index 107861380147..312a21578b5a 100644 --- a/yarn-project/prover-node/src/job/checkpoint-prover.ts +++ b/yarn-project/prover-node/src/job/checkpoint-prover.ts @@ -331,7 +331,7 @@ export class CheckpointProver { } } - // Streaming Inbox (AZIP-22 Fast Inbox): the checkpoint's messages are consumed contiguously across its blocks; + // Streaming Inbox: the checkpoint's messages are consumed contiguously across its blocks; // each block's slice runs from its parent block's L1-to-L2 leaf count to its own (compact indices make leaf // count equal cumulative message count). const l1ToL2LeafCount = (block: L2Block) => Number(block.header.state.l1ToL2MessageTree.nextAvailableLeafIndex); @@ -502,7 +502,7 @@ export class CheckpointProver { private async createFork(blockNumber: BlockNumber, l1ToL2Messages: Fr[]) { const db = await this.deps.dbProvider.fork(blockNumber); - // Append the block's real message leaves unpadded at compact indices (AZIP-22 Fast Inbox). + // Append the block's real message leaves unpadded at compact indices. await db.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2Messages); return db; } diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 7ebf550790ec..d81e21929fe2 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -351,7 +351,7 @@ export class ProverNode implements L2BlockStreamEventHandler, ProverNodeApi, Tra const previousBlockNumber = BlockNumber(checkpoint.blocks[0].number - 1); const previousBlockHeader = await this.gatherPreviousBlockHeader(previousBlockNumber); const lastBlock = checkpoint.blocks.at(-1)!; - // Streaming Inbox (AZIP-22 Fast Inbox): the checkpoint's consumed messages are those between the parent + // Streaming Inbox: the checkpoint's consumed messages are those between the parent // checkpoint's consumed position and this checkpoint's last block, as a compact leaf-count range. The prover // slices them per block. const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2MessagesBetweenLeafCounts( diff --git a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts index ea8091f4363e..688e02db2978 100644 --- a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts @@ -113,7 +113,7 @@ type L1ProcessArgs = { attestationsAndSignersSignature: Signature; /** The fee asset price modifier in basis points (from oracle) */ feeAssetPriceModifier: bigint; - /** Sequence number of the Inbox bucket the header's rolling hash corresponds to (AZIP-22 Fast Inbox lookup aid). */ + /** Sequence number of the Inbox bucket the header's rolling hash corresponds to. */ bucketHint: bigint; }; diff --git a/yarn-project/sequencer-client/src/sequencer/automine/automine_sequencer.ts b/yarn-project/sequencer-client/src/sequencer/automine/automine_sequencer.ts index 4d2426ad5712..58fe374d0eb7 100644 --- a/yarn-project/sequencer-client/src/sequencer/automine/automine_sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/automine/automine_sequencer.ts @@ -470,7 +470,7 @@ export class AutomineSequencer { await using fork = await this.deps.worldState.fork(syncedToBlockNumber, { closeDelayMs: 0 }); - // Streaming Inbox (AZIP-22 Fast Inbox): automine builds a single-block checkpoint, so its one block is the + // Streaming Inbox: automine builds a single-block checkpoint, so its one block is the // checkpoint's final block; select its bundle from the newest lag-eligible bucket with the last-block censorship // floor. The parent total is the fork's L1-to-L2 leaf count (compact indexing), which resolves the parent bucket. const parentInfo = await fork.getTreeInfo(MerkleTreeId.L1_TO_L2_MESSAGE_TREE); diff --git a/yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.timing.test.ts b/yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.timing.test.ts index 95c477b8d69c..558059f6fd2e 100644 --- a/yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.timing.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.timing.test.ts @@ -449,7 +449,7 @@ describe('CheckpointProposalJob Timing Tests', () => { const mockFork = mock({ [Symbol.asyncDispose]: jest.fn().mockReturnValue(Promise.resolve()) as () => Promise, }); - // AZIP-22 streaming inbox: the checkpoint job resolves the parent Inbox bucket from the fork's + // Streaming inbox: the checkpoint job resolves the parent Inbox bucket from the fork's // L1-to-L2 tree leaf count. Default to an empty tree so it starts at the genesis bucket. mockFork.getTreeInfo.mockResolvedValue({ size: 0n } as TreeInfo); worldState.fork.mockResolvedValue(mockFork); diff --git a/yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.ts b/yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.ts index d4c8b8608919..5bb2f55958fa 100644 --- a/yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.ts +++ b/yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.ts @@ -1107,8 +1107,8 @@ export class CheckpointProposalJob implements Traceable { } /** - * Resolves where a streaming-Inbox checkpoint's consumption starts: the parent checkpoint's last-consumed bucket - * (AZIP-22 Fast Inbox). The parent's cumulative consumed total is the L1-to-L2 message tree leaf count of the fork + * Resolves where a streaming-Inbox checkpoint's consumption starts: the parent checkpoint's last-consumed bucket. + * The parent's cumulative consumed total is the L1-to-L2 message tree leaf count of the fork * this checkpoint builds on (compact indexing makes leaf count equal cumulative message count), which resolves the * parent bucket by total. Genesis is the `total = 0` case, resolving the genesis bucket 0. */ @@ -1398,7 +1398,7 @@ export class CheckpointProposalJob implements Traceable { const { indexWithinCheckpoint, blockNumber, buildDeadline, forceCreate } = opts; // A non-empty streaming Inbox bundle is work on its own: the block must be produced even with zero txs, - // regardless of minTxsPerBlock, so the messages get inserted (message-only block, AZIP-22 Fast Inbox). + // regardless of minTxsPerBlock, so the messages get inserted (message-only block). // Without a bundle, a non-first block needs at least one tx to avoid empty filler blocks even when // minTxsPerBlock is zero. const hasStreamingBundle = (opts.l1ToL2Messages?.length ?? 0) > 0; diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index b8461a33c26e..95a866e36a3e 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -168,7 +168,7 @@ describe('sequencer', () => { expect.any(Checkpoint), attestationsAndSigners, getSignatures()[0].signature, - // AZIP-22 streaming inbox: the checkpoint job passes the parent bucket hint (genesis => 0n). + // Streaming inbox: the checkpoint job passes the parent bucket hint (genesis => 0n). 0n, expect.objectContaining({ txTimeoutAt: expect.any(Date), @@ -289,7 +289,7 @@ describe('sequencer', () => { }, } satisfies WorldStateSynchronizerStatus), }); - // AZIP-22 streaming inbox: the checkpoint job forks world state and resolves the parent Inbox bucket + // Streaming inbox: the checkpoint job forks world state and resolves the parent Inbox bucket // from the fork's L1-to-L2 tree leaf count. Default to an empty tree so it starts at the genesis bucket. const mockFork = mock({ [Symbol.asyncDispose]: jest.fn().mockReturnValue(Promise.resolve()) as () => Promise, @@ -858,7 +858,7 @@ describe('sequencer', () => { expect.any(Checkpoint), attestationsAndSigners, getSignatures()[0].signature, - // AZIP-22 streaming inbox: the checkpoint job passes the parent bucket hint (genesis => 0n). + // Streaming inbox: the checkpoint job passes the parent bucket hint (genesis => 0n). 0n, expect.objectContaining({ txTimeoutAt: expect.any(Date), diff --git a/yarn-project/stdlib/src/block/l2_block.ts b/yarn-project/stdlib/src/block/l2_block.ts index efd6b4833efe..06cd9133ac56 100644 --- a/yarn-project/stdlib/src/block/l2_block.ts +++ b/yarn-project/stdlib/src/block/l2_block.ts @@ -133,7 +133,7 @@ export class L2Block { noteHashRoot: this.header.state.partial.noteHashTree.root, nullifierRoot: this.header.state.partial.nullifierTree.root, publicDataRoot: this.header.state.partial.publicDataTree.root, - // Every block carries its own post-bundle l1-to-l2 message tree root (AZIP-22 Fast Inbox). + // Every block carries its own post-bundle l1-to-l2 message tree root. l1ToL2MessageRoot: this.header.state.l1ToL2MessageTree.root, txs: this.body.toTxBlobData(), }; diff --git a/yarn-project/stdlib/src/checkpoint/checkpoint_data.ts b/yarn-project/stdlib/src/checkpoint/checkpoint_data.ts index a6456ca4dd14..f8ef256a686c 100644 --- a/yarn-project/stdlib/src/checkpoint/checkpoint_data.ts +++ b/yarn-project/stdlib/src/checkpoint/checkpoint_data.ts @@ -46,7 +46,7 @@ export type ProposedOnlyCheckpointData = { export type ProposedInboxConsumption = { /** * Cumulative Inbox message count consumed as of this checkpoint: its last block's L1-to-L2 message tree leaf count - * under compact indexing (AZIP-22 Fast Inbox). L1 records this per checkpoint and reads it back as the parent total + * under compact indexing. L1 records this per checkpoint and reads it back as the parent total * when validating the next checkpoint's consumption, so a simulation against a not-yet-mined parent must override * the parent's cell with it — otherwise the parent reads as having consumed nothing and the child's consumption * looks larger than it is. diff --git a/yarn-project/stdlib/src/deserialization/deserialization.test.ts b/yarn-project/stdlib/src/deserialization/deserialization.test.ts index 7bd23cc96f1f..85482da68ba4 100644 --- a/yarn-project/stdlib/src/deserialization/deserialization.test.ts +++ b/yarn-project/stdlib/src/deserialization/deserialization.test.ts @@ -23,7 +23,7 @@ describe('MAX_CAPACITY_BLOCKS_PER_CHECKPOINT', () => { // Largest checkpoint that fits in the blob budget with the most compact construction: // one (possibly empty) first block + (N - 1) single-nullifier blocks + a checkpoint-end marker. Every block - // spends the same block-end overhead (including the per-block l1-to-l2 root) post-flip (AZIP-22 Fast Inbox). + // spends the same block-end overhead, including the per-block l1-to-l2 root. // This is the real ceiling the proving system / L1 enforce — there is no explicit block-count cap. const blobBudget = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB; const maxBlocksThatFitInBlobs = diff --git a/yarn-project/stdlib/src/deserialization/index.ts b/yarn-project/stdlib/src/deserialization/index.ts index d132bce44df6..0448f0733ca8 100644 --- a/yarn-project/stdlib/src/deserialization/index.ts +++ b/yarn-project/stdlib/src/deserialization/index.ts @@ -22,7 +22,7 @@ export const MAX_COMMITTEE_SIZE = 2048; * * budget = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB = 6 * 4096 = 24,576 fields * - * per-block minimal blob footprint (every block carries the per-block l1-to-l2 root post-flip): + * per-block minimal blob footprint (every block carries a per-block l1-to-l2 root): * first block (may be empty) = 7 fields (NUM_BLOCK_END_BLOB_FIELDS) * every other (needs >= 1 tx) = 7 + 4 = 11 fields * (NUM_BLOCK_END_BLOB_FIELDS + a 4-field minimal tx: diff --git a/yarn-project/stdlib/src/interfaces/block-builder.ts b/yarn-project/stdlib/src/interfaces/block-builder.ts index 6222585ee4f9..6d10698dab4d 100644 --- a/yarn-project/stdlib/src/interfaces/block-builder.ts +++ b/yarn-project/stdlib/src/interfaces/block-builder.ts @@ -58,7 +58,7 @@ type BlockBuilderOptionsBase = PublicProcessorLimits & { minValidTxs: number; /** * L1-to-L2 message leaves this block consumes, inserted into the fork's L1-to-L2 message tree before the block - * header is built (AZIP-22 Fast Inbox streaming). Omitted when the block consumes nothing from the Inbox. + * header is built. Omitted when the block consumes nothing from the Inbox. */ l1ToL2Messages?: Fr[]; }; @@ -157,7 +157,7 @@ export interface ICheckpointsBuilder { /** * Opens a fresh checkpoint. The checkpoint's L1-to-L2 messages are not passed here: each block carries its own - * bundle in `buildBlock`'s `l1ToL2Messages` (AZIP-22 Fast Inbox streaming). + * bundle in `buildBlock`'s `l1ToL2Messages`. */ startCheckpoint( checkpointNumber: CheckpointNumber, diff --git a/yarn-project/stdlib/src/messaging/append_l1_to_l2_messages.ts b/yarn-project/stdlib/src/messaging/append_l1_to_l2_messages.ts index 2090e0a164c1..fd42d32097cd 100644 --- a/yarn-project/stdlib/src/messaging/append_l1_to_l2_messages.ts +++ b/yarn-project/stdlib/src/messaging/append_l1_to_l2_messages.ts @@ -4,8 +4,8 @@ import type { MerkleTreeWriteOperations } from '../interfaces/merkle_tree_operat import { MerkleTreeId } from '../trees/merkle_tree_id.js'; /** - * Appends a block's real L1→L2 message leaves (unpadded, at compact indices) to the L1→L2 message tree of `db` - * (AZIP-22 Fast Inbox). Use whenever a fork at "state before a block" needs to mirror what the world-state + * Appends a block's real L1→L2 message leaves (unpadded, at compact indices) to the L1→L2 message tree of `db`. + * Use whenever a fork at "state before a block" needs to mirror what the world-state * synchronizer inserts at sync time. */ export async function appendL1ToL2MessagesToTree(db: MerkleTreeWriteOperations, l1ToL2Messages: Fr[]): Promise { diff --git a/yarn-project/stdlib/src/messaging/l1_to_l2_message_bundle.ts b/yarn-project/stdlib/src/messaging/l1_to_l2_message_bundle.ts index 2034ab3fdb70..6704a5d70c0f 100644 --- a/yarn-project/stdlib/src/messaging/l1_to_l2_message_bundle.ts +++ b/yarn-project/stdlib/src/messaging/l1_to_l2_message_bundle.ts @@ -7,7 +7,7 @@ import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; /** * A block's L1-to-L2 message bundle: the real message leaves it inserts into the L1-to-L2 message tree and the count - * that drives both the compact (unpadded) tree append and the message-sponge absorb (AZIP-22 Fast Inbox). See the Noir + * that drives both the compact (unpadded) tree append and the message-sponge absorb. See the Noir * `L1ToL2MessageBundle`. */ export class L1ToL2MessageBundle { diff --git a/yarn-project/stdlib/src/tx/state_reference.ts b/yarn-project/stdlib/src/tx/state_reference.ts index d003ec0b2e6c..184b6a89c27f 100644 --- a/yarn-project/stdlib/src/tx/state_reference.ts +++ b/yarn-project/stdlib/src/tx/state_reference.ts @@ -103,9 +103,8 @@ export class StateReference { /** * Validates the partial-state trees have the expected number of leaves (multiple of number of insertions per tx). * - * The L1-to-L2 message tree is not checked: post-flip it grows by real message counts at compact (unaligned) - * indices, so its next-available leaf index is no longer a multiple of any per-block subtree size (AZIP-22 Fast - * Inbox). + * The L1-to-L2 message tree is not checked: it grows by real message counts at compact (unaligned) + * indices, so its next-available leaf index is no longer a multiple of any per-block subtree size. */ public validate() { if (this.partial.noteHashTree.nextAvailableLeafIndex % MAX_NOTE_HASHES_PER_TX !== 0) { diff --git a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts index 181190abab55..327895abe241 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts @@ -668,7 +668,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl txEffect.txHash = new TxHash(new Fr(blockNumber)); - // Post-flip TXE blocks carry no L1-to-L2 messages, so the message tree is left unadvanced (AZIP-22 Fast Inbox). + // TXE blocks carry no L1-to-L2 messages, so the message tree is left unadvanced. const l2Block = await makeTXEBlock(forkedWorldTrees, globals, [txEffect]); @@ -832,7 +832,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl txEffect.txHash = new TxHash(new Fr(blockNumber)); - // Post-flip TXE blocks carry no L1-to-L2 messages, so the message tree is left unadvanced (AZIP-22 Fast Inbox). + // TXE blocks carry no L1-to-L2 messages, so the message tree is left unadvanced. const l2Block = await makeTXEBlock(forkedWorldTrees, globals, [txEffect]); diff --git a/yarn-project/txe/src/state_machine/synchronizer.ts b/yarn-project/txe/src/state_machine/synchronizer.ts index 6f61646ffba1..78dabcc2e81d 100644 --- a/yarn-project/txe/src/state_machine/synchronizer.ts +++ b/yarn-project/txe/src/state_machine/synchronizer.ts @@ -33,7 +33,7 @@ export class TXESynchronizer implements WorldStateSynchronizer { } public async handleL2Block(block: L2Block, l1ToL2Messages: Fr[] = []) { - // Append the block's real message leaves unpadded at compact indices (AZIP-22 Fast Inbox). + // Append the block's real message leaves unpadded at compact indices. await this.nativeWorldStateService.handleL2BlockAndMessages(block, l1ToL2Messages); this.blockNumber = block.header.globalVariables.blockNumber; diff --git a/yarn-project/txe/src/utils/block_creation.ts b/yarn-project/txe/src/utils/block_creation.ts index 2354c0530558..101139363fa6 100644 --- a/yarn-project/txe/src/utils/block_creation.ts +++ b/yarn-project/txe/src/utils/block_creation.ts @@ -31,7 +31,7 @@ export async function insertTxEffectIntoWorldTrees( NULLIFIER_SUBTREE_HEIGHT, ); - // Append the block's real message leaves unpadded at compact indices (AZIP-22 Fast Inbox). + // Append the block's real message leaves unpadded at compact indices. await worldTrees.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2Messages); // We do not need to add public data writes because we apply them as we go. diff --git a/yarn-project/validator-client/src/checkpoint_builder.ts b/yarn-project/validator-client/src/checkpoint_builder.ts index 65c2ccdd0321..7f7c10dddbaa 100644 --- a/yarn-project/validator-client/src/checkpoint_builder.ts +++ b/yarn-project/validator-client/src/checkpoint_builder.ts @@ -364,7 +364,7 @@ export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder { * Opens a checkpoint, either starting fresh or resuming from existing blocks. * @param l1ToL2Messages - Messages the existing blocks already consumed, which seed the resumed checkpoint's * rolling hash. Must be empty when starting fresh: a fresh checkpoint takes its messages per block, via - * `buildBlock` (AZIP-22 Fast Inbox streaming). + * `buildBlock`. */ async openCheckpoint( checkpointNumber: CheckpointNumber, diff --git a/yarn-project/validator-client/src/proposal_handler.test.ts b/yarn-project/validator-client/src/proposal_handler.test.ts index 28e0c69ac6f0..43c6abf33888 100644 --- a/yarn-project/validator-client/src/proposal_handler.test.ts +++ b/yarn-project/validator-client/src/proposal_handler.test.ts @@ -802,7 +802,7 @@ describe('ProposalHandler checkpoint validation', () => { }); }); - // AZIP-22 Fast Inbox: a block proposal's L1-to-L2 bundle is derived from its bucket reference and gated by the four + // Streaming Inbox: a block proposal's L1-to-L2 bundle is derived from its bucket reference and gated by the four // acceptance checks, replacing the legacy per-checkpoint inHash comparison. describe('handleBlockProposal streaming inbox checks', () => { const bucket = (overrides: Partial = {}): InboxBucket => ({ @@ -902,7 +902,7 @@ describe('ProposalHandler checkpoint validation', () => { const result = await blockHandler.handleBlockProposal(proposal, {} as any, true); expect(result.isValid).toBe(true); - // The block re-executes with the derived per-block bundle (streaming is the only path post-flip). + // The block re-executes with the derived per-block bundle (streaming is the only path). expect(reexecuteSpy).toHaveBeenCalledWith( proposal, BlockNumber(INITIAL_L2_BLOCK_NUM), @@ -915,7 +915,7 @@ describe('ProposalHandler checkpoint validation', () => { }); }); - // AZIP-22 Fast Inbox: the checkpoint handler enforces the last-block minimum-consumption (censorship) rule before + // Streaming Inbox: the checkpoint handler enforces the last-block minimum-consumption (censorship) rule before // attesting. describe('checkpoint proposal last-block censorship', () => { /** Two-block checkpoint at slot 10 whose last block consumed through leaf count `lastBlockTotal`. */ diff --git a/yarn-project/validator-client/src/proposal_handler.ts b/yarn-project/validator-client/src/proposal_handler.ts index 7cd115ccd7f3..345dd23208a5 100644 --- a/yarn-project/validator-client/src/proposal_handler.ts +++ b/yarn-project/validator-client/src/proposal_handler.ts @@ -73,7 +73,7 @@ export type BlockProposalValidationFailureReason = | 'parent_block_not_found' | 'parent_block_wrong_slot' | 'in_hash_mismatch' - // Streaming Inbox (AZIP-22 Fast Inbox) per-block acceptance failures. + // Streaming Inbox per-block acceptance failures. | StreamingBlockCheckReason | 'global_variables_mismatch' | 'block_number_already_exists' @@ -122,7 +122,7 @@ export type CheckpointProposalValidationFailureReason = | 'checkpoint_header_mismatch' | 'archive_mismatch' | 'out_hash_mismatch' - // Streaming Inbox (AZIP-22 Fast Inbox) last-block censorship failure. + // Streaming Inbox last-block censorship failure. | 'inbox_consumption_insufficient' | 'checkpoint_validation_failed'; @@ -576,7 +576,7 @@ export class ProposalHandler { proposalInfo.checkpointNumber = checkpointNumber; // Resolve this block's L1-to-L2 message bundle from its proposal bucket reference, gated by the four streaming - // acceptance checks (AZIP-22 Fast Inbox). + // acceptance checks. const streamingResult = await this.runStreamingBlockChecks(proposal, blockNumber, parentBlock); if (!streamingResult.accepted) { this.log.warn(`Streaming Inbox block acceptance check failed, skipping processing`, { @@ -1003,7 +1003,7 @@ export class ProposalHandler { /** * Derives the ordered list of L1-to-L2 messages a checkpoint consumed across its blocks, from the Inbox buckets - * between the parent checkpoint's consumed position and the checkpoint's last block (AZIP-22 Fast Inbox). Empty when + * between the parent checkpoint's consumed position and the checkpoint's last block. Empty when * the checkpoint consumed nothing or its consumption cannot be resolved against the local Inbox view. */ private async deriveCheckpointConsumedMessages(blocks: L2Block[]): Promise { diff --git a/yarn-project/validator-client/src/validator.integration.test.ts b/yarn-project/validator-client/src/validator.integration.test.ts index dd5319139037..d12bb5608f50 100644 --- a/yarn-project/validator-client/src/validator.integration.test.ts +++ b/yarn-project/validator-client/src/validator.integration.test.ts @@ -49,7 +49,7 @@ describe('ValidatorClient Integration', () => { // Constants for L1 const l1Constants: L1RollupConstants = { // Non-zero genesis time so the slot-1 validation clock is well past INBOX_LAG_SECONDS; otherwise the streaming - // Inbox acceptance check rejects even a genesis-timestamp (0) bucket as `bucket_too_new` (AZIP-22 Fast Inbox). + // Inbox acceptance check rejects even a genesis-timestamp (0) bucket as `bucket_too_new`. l1GenesisTime: 1_700_000_000n, slotDuration: 24, epochDuration: 16, @@ -259,8 +259,8 @@ describe('ValidatorClient Integration', () => { }); // Resolve the Inbox bucket this block consumed through (keyed by its cumulative L1-to-L2 leaf count) and attach - // the reference, mirroring the sequencer's block-building loop which carries a bucketRef on every proposal - // (AZIP-22 Fast Inbox). Without it the validator's streaming acceptance check rejects the proposal as + // the reference, mirroring the sequencer's block-building loop which carries a bucketRef on every proposal. + // Without it the validator's streaming acceptance check rejects the proposal as // `bucket_unknown`. A block that consumed nothing resolves to the genesis (or reused parent) bucket. const blockTotal = BigInt(block.header.state.l1ToL2MessageTree.nextAvailableLeafIndex); const bucket = await proposer.archiver.getInboxBucketByTotalMsgCount(blockTotal); diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index aa0b9e07d4ed..1af680ff3218 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -458,7 +458,7 @@ describe('ValidatorClient', () => { Array.isArray(args) && args[0]?.offenseType === OffenseType.ATTESTED_TO_INVALID_CHECKPOINT_PROPOSAL, ); - // AZIP-22 Fast Inbox: an empty-consumption streaming setup. Proposals reference the genesis Inbox bucket, the + // Streaming Inbox: an empty-consumption streaming setup. Proposals reference the genesis Inbox bucket, the // parent block's L1-to-L2 leaf count equals its cumulative total (0), so the derived per-block bundle is empty. const genesisInboxBucket: InboxBucket = { seq: 0n, 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 e5d579c5e434..aeceac469d4e 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 @@ -102,7 +102,7 @@ describe('NativeWorldState', () => { const status = await ws.handleL2BlockAndMessages(block, messages); - // Messages are appended unpadded at compact indices (AZIP-22 Fast Inbox). + // Messages are appended unpadded at compact indices. expect(status.meta.messageTreeMeta.size).toBe(BigInt(numMessages)); const expectedNoteHashCount = txsPerBlock * MAX_NOTE_HASHES_PER_TX; @@ -189,7 +189,7 @@ describe('NativeWorldState', () => { it('advances the L1-to-L2 message tree per block, including on non-first blocks', async () => { const fork = await ws.fork(); - // Block 1 is first-in-checkpoint carrying 3 messages, appended unpadded at compact indices (AZIP-22 Fast Inbox). + // Block 1 is first-in-checkpoint carrying 3 messages, appended unpadded at compact indices. const { block: b1, messages: m1 } = await mockBlockWithIndex(BlockNumber(1), /*index=*/ 0, 1, fork, 3, 1); const s1 = await ws.handleL2BlockAndMessages(b1, m1); expect(s1.meta.messageTreeMeta.size).toBe(3n); diff --git a/yarn-project/world-state/src/native/native_world_state.ts b/yarn-project/world-state/src/native/native_world_state.ts index f3ac3aa40de3..432c70e566b2 100644 --- a/yarn-project/world-state/src/native/native_world_state.ts +++ b/yarn-project/world-state/src/native/native_world_state.ts @@ -265,7 +265,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase { public async handleL2BlockAndMessages(l2Block: L2Block, l1ToL2Messages: Fr[]): Promise { // Any block may carry an L1-to-L2 message bundle and transition the L1-to-L2 message tree by its real (unpadded, - // compact) leaves, matching how the circuits build the tree post-flip (AZIP-22 Fast Inbox). + // compact) leaves, matching how the circuits build the tree. const paddedL1ToL2Messages = l1ToL2Messages; // We have to pad the note hashes and nullifiers within tx effects because that's how the trees are built by diff --git a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts index 1d0c963bfe4a..eefe75f2a976 100644 --- a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts +++ b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts @@ -369,8 +369,8 @@ export class ServerWorldStateSynchronizer private async handleL2Blocks(l2Blocks: L2Block[]) { this.log.debug(`Handling L2 blocks ${l2Blocks[0].number} to ${l2Blocks.at(-1)!.number}`); - // Derive each block's real L1-to-L2 message bundle from the compact leaf-index range it inserted (AZIP-22 Fast - // Inbox): the messages between the parent block's L1-to-L2 tree leaf count and this block's, resolved via the + // Derive each block's real L1-to-L2 message bundle from the compact leaf-index range it inserted: + // the messages between the parent block's L1-to-L2 tree leaf count and this block's, resolved via the // Inbox buckets. Blocks in a batch are consecutive, so we track the running leaf count. const messagesForBlocks = new Map(); let prevLeafCount = await this.getL1ToL2LeafCountBefore(l2Blocks[0].number); diff --git a/yarn-project/world-state/src/test/utils.ts b/yarn-project/world-state/src/test/utils.ts index 0e9b49229f89..8870091b3951 100644 --- a/yarn-project/world-state/src/test/utils.ts +++ b/yarn-project/world-state/src/test/utils.ts @@ -50,7 +50,7 @@ export async function updateBlockState(block: L2Block, l1ToL2Messages: Fr[], for padArrayEnd(txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX), ); - // Post-flip every block appends its real message leaves unpadded at compact indices (AZIP-22 Fast Inbox). + // Every block appends its real message leaves unpadded at compact indices. const noteHashInsert = fork.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, noteHashesPadded); const messageInsert = fork.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2Messages); await Promise.all([publicDataInsert, nullifierInsert, noteHashInsert, messageInsert]);