From 2843f9caaf983463bacb13afe810c6112f17e73c Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Sat, 18 Jul 2026 14:19:04 -0300 Subject: [PATCH 01/10] chore(fast-inbox): delete legacy L1 inbox path (A-1386) Remove the frontier-tree machinery, legacy consume(), the LAG/inboxLag plumbing and the dead MessageSent event field now that propose enforces streaming-inbox consumption (AZIP-22 Fast Inbox). Inbox / IInbox: - Drop the frontier tree forest (trees mapping, forest, HEIGHT/SIZE/EMPTY_ROOT), the per-message tree insert, getRoot(), and consume(). - Drop the LAG immutable and the _lag/_height constructor args; the constructor now takes only (rollup, feeAsset, version, bucketRingSize). - Source the compact message index from the current bucket's running total instead of a parallel counter; drop InboxState.inProgress and getInProgress(). - Drop the meaningless checkpointNumber (former inProgress) from MessageSent. - Keep the 128-bit rolling hash, InboxState.{rollingHash,totalMessagesInserted} and getState()/getTotalMessagesInserted(): the node still consumes them for message sync and L1-reorg detection until that path is retired. Rollup / config / errors: - Remove RollupConfigInput.inboxLag and the RollupCore constructor pass-through; drop Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT from the Inbox construction. - Sweep orphaned errors Inbox__Unauthorized, Inbox__MustBuildBeforeConsume and Rollup__InvalidInHash; refresh the now-stale ProposeLib NatSpec. Tests: delete the frontier/consume suites and fuzz, re-home the bucket/rolling-hash coverage, update the propose-path fixtures (inHash is an unconstrained hint now), and fix the flip-stranded fee-portal/token-portal index and event expectations. FrontierLib is kept: it still backs the base-parity and merkle Frontier tests. --- .../script/deploy/RollupConfiguration.sol | 1 - l1-contracts/src/core/RollupCore.sol | 14 +- l1-contracts/src/core/interfaces/IRollup.sol | 1 - .../core/interfaces/messagebridge/IInbox.sol | 50 ++----- l1-contracts/src/core/libraries/Errors.sol | 3 - .../src/core/libraries/rollup/ProposeLib.sol | 8 +- l1-contracts/src/core/messagebridge/Inbox.sol | 124 +++------------- l1-contracts/test/Inbox.t.sol | 112 +-------------- l1-contracts/test/InboxBuckets.t.sol | 17 +-- l1-contracts/test/Rollup.t.sol | 27 ---- l1-contracts/test/RollupFieldRange.t.sol | 3 +- l1-contracts/test/base/RollupBase.sol | 5 +- l1-contracts/test/builder/RollupBuilder.sol | 5 - .../fee_portal/depositToAztecPublic.t.sol | 133 +----------------- l1-contracts/test/harnesses/InboxHarness.sol | 35 +---- l1-contracts/test/harnesses/TestConstants.sol | 2 - l1-contracts/test/portals/TokenPortal.t.sol | 8 +- .../test/rollup/ProposeInboxConsumption.t.sol | 3 +- .../ValidatorSelection.t.sol | 4 +- 19 files changed, 62 insertions(+), 493 deletions(-) diff --git a/l1-contracts/script/deploy/RollupConfiguration.sol b/l1-contracts/script/deploy/RollupConfiguration.sol index f19c5e9a620e..d70ddf1be185 100644 --- a/l1-contracts/script/deploy/RollupConfiguration.sol +++ b/l1-contracts/script/deploy/RollupConfiguration.sol @@ -110,7 +110,6 @@ contract RollupConfiguration is IRollupConfiguration, Test { config.targetCommitteeSize = vm.envUint("AZTEC_TARGET_COMMITTEE_SIZE"); config.lagInEpochsForValidatorSet = vm.envUint("AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET"); config.lagInEpochsForRandao = vm.envUint("AZTEC_LAG_IN_EPOCHS_FOR_RANDAO"); - config.inboxLag = vm.envUint("AZTEC_INBOX_LAG"); config.aztecProofSubmissionEpochs = vm.envUint("AZTEC_PROOF_SUBMISSION_EPOCHS"); config.localEjectionThreshold = vm.envUint("AZTEC_LOCAL_EJECTION_THRESHOLD"); config.slashingQuorum = vm.envOr("AZTEC_SLASHING_QUORUM", slashingRoundSize / 2 + 1); diff --git a/l1-contracts/src/core/RollupCore.sol b/l1-contracts/src/core/RollupCore.sol index ac0a89dfa0d5..5d8f33eca314 100644 --- a/l1-contracts/src/core/RollupCore.sol +++ b/l1-contracts/src/core/RollupCore.sol @@ -15,7 +15,6 @@ import {IStakingCore} from "@aztec/core/interfaces/IStaking.sol"; import {IValidatorSelectionCore} from "@aztec/core/interfaces/IValidatorSelection.sol"; import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; -import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {CommitteeAttestations} from "@aztec/core/libraries/rollup/AttestationLib.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {EpochProofExtLib} from "@aztec/core/libraries/rollup/EpochProofExtLib.sol"; @@ -620,18 +619,7 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali rollupStore.config.epochProofVerifier = _epochProofVerifier; rollupStore.config.version = _config.version; - IInbox inbox = IInbox( - address( - new Inbox( - address(this), - _feeAsset, - _config.version, - Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT, - _config.inboxLag, - INBOX_BUCKET_RING_SIZE - ) - ) - ); + IInbox inbox = IInbox(address(new Inbox(address(this), _feeAsset, _config.version, INBOX_BUCKET_RING_SIZE))); rollupStore.config.inbox = inbox; rollupStore.config.outbox = IOutbox(address(new Outbox(address(this), _config.version))); diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 6a347b11d8d5..4f94b12be7d5 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -84,7 +84,6 @@ struct RollupConfigInput { RewardBoostConfig rewardBoostConfig; StakingQueueConfig stakingQueueConfig; uint256 localEjectionThreshold; - uint256 inboxLag; } struct RollupConfig { diff --git a/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol b/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol index 8a9366b1b0c5..6c99ea496b84 100644 --- a/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol +++ b/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol @@ -5,8 +5,8 @@ pragma solidity >=0.8.27; import {DataStructures} from "../../libraries/DataStructures.sol"; // Maximum number of messages a single bucket can hold before further messages in the same L1 block spill over -// into the next bucket. Matches the number of L1 to L2 messages a single L2 block can insert once the streaming -// inbox is live, so any one bucket is always consumable by one block. +// into the next bucket. Matches the number of L1 to L2 messages a single L2 block can insert, so any one bucket +// is always consumable by one block. uint256 constant MAX_MSGS_PER_BUCKET = 256; /** @@ -16,16 +16,14 @@ uint256 constant MAX_MSGS_PER_BUCKET = 256; */ interface IInbox { struct InboxState { - // Rolling hash of all messages inserted into the inbox. - // Used by clients to check for consistency. - // TODO: remove once the streaming inbox (AZIP-22 Fast Inbox) flips on and clients rely on the - // consensus rolling hash tracked in the buckets instead. + // Legacy 128-bit keccak rolling hash of all messages inserted into the inbox. Consumed only by the + // node for message sync and L1-reorg detection. + // TODO: remove once the node relies on the full-width consensus rolling hash tracked in the buckets + // instead (AZIP-22 Fast Inbox). bytes16 rollingHash; - // This value is not used much by the contract, but it is useful for synching the node faster - // as it can more easily figure out if it can just skip looking for events for a time period. + // Cumulative number of messages inserted into the inbox. Useful for synching the node faster as it can + // more easily figure out if it can just skip looking for events for a time period. uint64 totalMessagesInserted; - // Number of a tree which is currently being filled - uint64 inProgress; } /** @@ -50,20 +48,14 @@ interface IInbox { /** * @notice Emitted when a message is sent - * @param checkpointNumber - The checkpoint number in which the message is included - * @param index - The index of the message in the L1 to L2 messages tree + * @param index - The compact cumulative index of the message in the Inbox insertion order * @param hash - The hash of the message - * @param rollingHash - The rolling hash of all messages inserted into the inbox + * @param rollingHash - The legacy 128-bit rolling hash of all messages inserted into the inbox * @param inboxRollingHash - The consensus rolling hash (truncated sha256 chain) after this message * @param bucketSeq - The sequence number of the bucket this message was absorbed into */ event MessageSent( - uint256 indexed checkpointNumber, - uint256 index, - bytes32 indexed hash, - bytes16 rollingHash, - bytes32 inboxRollingHash, - uint256 bucketSeq + uint256 index, bytes32 indexed hash, bytes16 rollingHash, bytes32 inboxRollingHash, uint256 bucketSeq ); // docs:start:send_l1_to_l2_message @@ -74,37 +66,19 @@ interface IInbox { * @param _content - The content of the message (application specific) * @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed * on L2) - * @return The key of the message in the set and its leaf index in the tree + * @return The key of the message in the set and its compact cumulative index */ function sendL2Message(DataStructures.L2Actor memory _recipient, bytes32 _content, bytes32 _secretHash) external returns (bytes32, uint256); // docs:end:send_l1_to_l2_message - // docs:start:consume - /** - * @notice Consumes the current tree, and starts a new one if needed - * @dev Only callable by the rollup contract - * @dev In the first iteration we return empty tree root because first checkpoint's messages tree is always - * empty because there has to be a 1 checkpoint lag to prevent sequencer DOS attacks - * - * @param _toConsume - The checkpoint number to consume - * - * @return The root of the consumed tree - */ - function consume(uint256 _toConsume) external returns (bytes32); - // docs:end:consume - function getFeeAssetPortal() external view returns (address); - function getRoot(uint256 _checkpointNumber) external view returns (bytes32); - function getState() external view returns (InboxState memory); function getTotalMessagesInserted() external view returns (uint64); - function getInProgress() external view returns (uint64); - /** * @notice Returns the sequence number of the bucket currently accumulating messages * @return The current bucket sequence number diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index 83227e372e55..e78dcfa9cafd 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -22,12 +22,10 @@ library Errors { error DevNet__InvalidProposer(address expected, address actual); // 0x11e6e6f7 // Inbox - error Inbox__Unauthorized(); // 0xe5336a6b error Inbox__ActorTooLarge(bytes32 actor); // 0xa776a06e error Inbox__VersionMismatch(uint256 expected, uint256 actual); // 0x47452014 error Inbox__ContentTooLarge(bytes32 content); // 0x47452014 error Inbox__SecretHashTooLarge(bytes32 secretHash); // 0xecde7e2c - error Inbox__MustBuildBeforeConsume(); // 0xc4901999 error Inbox__BucketOutOfWindow(uint256 seq, uint256 current); // 0xfee255b7 // Outbox @@ -58,7 +56,6 @@ library Errors { error Rollup__InvalidCheckpointHeader(bytes32 expected, bytes32 actual); error Rollup__InvalidCheckpointHeaderCount(uint256 expected, uint256 actual); error Rollup__InvalidCheckpointNumber(uint256 expected, uint256 actual); // 0xd1ba9bfa - error Rollup__InvalidInHash(bytes32 expected, bytes32 actual); // 0xcd6f4233 error Rollup__InvalidInboxRollingHash(bytes32 expected, bytes32 actual); // 0xed1f7bb5 error Rollup__InvalidPreviousInboxRollingHash(bytes32 expected, bytes32 actual); // 0x2fe7cae5 error Rollup__InvalidEndInboxRollingHash(bytes32 expected, bytes32 actual); // 0x4a9cdb72 diff --git a/l1-contracts/src/core/libraries/rollup/ProposeLib.sol b/l1-contracts/src/core/libraries/rollup/ProposeLib.sol index 23dd2aeb7d20..877295b021b8 100644 --- a/l1-contracts/src/core/libraries/rollup/ProposeLib.sol +++ b/l1-contracts/src/core/libraries/rollup/ProposeLib.sol @@ -142,7 +142,7 @@ library ProposeLib { * - Checkpoint header validations (see validateHeader function for details) * - Proposer signature is valid for designated slot proposer: * Errors.ValidatorSelection__MissingProposerSignature - * - Inbox hash matches expected value: Errors.Rollup__InvalidInHash + * - Streaming Inbox consumption is valid: Errors.Rollup__InvalidInboxRollingHash * - Archive root is within the scalar field: Errors.Rollup__FieldElementOutOfRange * * Validations NOT performed: @@ -154,7 +154,7 @@ library ProposeLib { * - Store archive root for the new checkpoint number * - Store checkpoint metadata in circular storage (TempCheckpointLog) * - Update L1 gas fee oracle - * - Consume inbox messages + * - Validate streaming Inbox consumption against the parent checkpoint * - Setup epoch for validator selection (first block of the epoch) * * @param _args - The arguments to propose the checkpoint @@ -401,8 +401,8 @@ library ProposeLib { /** * @notice Validates a checkpoint's Inbox consumption against the streaming inbox buckets and returns how - * far consumption has reached. Not yet called from propose(): the legacy `consume()`/`inHash` flow - * remains the enforced path until the streaming inbox (AZIP-22 Fast Inbox) flips on. + * far consumption has reached. Called from propose() as the enforced consumption path (AZIP-22 Fast + * Inbox). * * @dev Read-only; performs no Inbox write. Checks, in order: * 1. The checkpoint header's `inboxRollingHash` must equal the rolling hash snapshotted in the Inbox diff --git a/l1-contracts/src/core/messagebridge/Inbox.sol b/l1-contracts/src/core/messagebridge/Inbox.sol index 4258afb2cc64..183164869dcd 100644 --- a/l1-contracts/src/core/messagebridge/Inbox.sol +++ b/l1-contracts/src/core/messagebridge/Inbox.sol @@ -5,7 +5,6 @@ pragma solidity >=0.8.27; import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; import {IInbox, MAX_MSGS_PER_BUCKET} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; -import {FrontierLib} from "@aztec/core/libraries/crypto/FrontierLib.sol"; import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; @@ -31,66 +30,36 @@ uint256 constant MIN_BUCKET_RING_SIZE = 512; */ contract Inbox is IInbox { using Hash for DataStructures.L1ToL2Msg; - using FrontierLib for FrontierLib.Forest; - using FrontierLib for FrontierLib.Tree; address public immutable ROLLUP; uint256 public immutable VERSION; address public immutable FEE_ASSET_PORTAL; - uint256 public immutable LAG; - uint256 public immutable BUCKET_RING_SIZE; - uint256 internal immutable HEIGHT; - uint256 internal immutable SIZE; - bytes32 internal immutable EMPTY_ROOT; // The root of an empty frontier tree - - // Practically immutable value as we only set it in the constructor. - FrontierLib.Forest internal forest; - - mapping(uint256 checkpointNumber => FrontierLib.Tree tree) public trees; + // Legacy 128-bit keccak rolling hash over every inserted message leaf. Consumed only by the node's + // message sync and L1-reorg detection; retained until those switch to the full-width consensus rolling + // hash tracked in the buckets (AZIP-22 Fast Inbox). + bytes16 internal messagesRollingHash; - InboxState internal state; - - // Ring of rolling-hash buckets, keyed by `bucketSeq % BUCKET_RING_SIZE`. Inert for the legacy - // frontier-tree flow; consumed by the streaming inbox checks at `propose` (AZIP-22 Fast Inbox). + // Ring of rolling-hash buckets, keyed by `bucketSeq % BUCKET_RING_SIZE`. Consumed by the streaming inbox + // checks at `propose` (AZIP-22 Fast Inbox). mapping(uint256 ringIndex => InboxBucket bucket) internal buckets; uint64 internal currentBucketSeq; - constructor( - address _rollup, - IERC20 _feeAsset, - uint256 _version, - uint256 _height, - uint256 _lag, - uint256 _bucketRingSize - ) { + constructor(address _rollup, IERC20 _feeAsset, uint256 _version, uint256 _bucketRingSize) { ROLLUP = _rollup; VERSION = _version; - HEIGHT = _height; - SIZE = 2 ** _height; - - require(_lag > 0, "LAG TOO SMALL"); - LAG = _lag; - require(_bucketRingSize >= MIN_BUCKET_RING_SIZE, "BUCKET RING TOO SMALL"); BUCKET_RING_SIZE = _bucketRingSize; - state = InboxState({ - rollingHash: 0, totalMessagesInserted: 0, inProgress: SafeCast.toUint64(Constants.INITIAL_CHECKPOINT_NUMBER + LAG) - }); - // Genesis bucket: a checkpoint consuming no messages references the same bucket as its parent, so the // first checkpoint against an empty Inbox references this one and no base case leaks into `propose`. buckets[0] = InboxBucket({rollingHash: 0, totalMsgCount: 0, timestamp: SafeCast.toUint64(block.timestamp), msgCount: 0}); - forest.initialize(_height); - EMPTY_ROOT = trees[type(uint256).max].root(forest, HEIGHT, SIZE); - FEE_ASSET_PORTAL = address(new FeeJuicePortal(IRollup(_rollup), _feeAsset, IInbox(this), VERSION)); } @@ -104,7 +73,7 @@ contract Inbox is IInbox { * @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed * on L2) * - * @return Hash of the sent message and its leaf index in the tree. + * @return Hash of the sent message and its compact cumulative index. */ function sendL2Message(DataStructures.L2Actor memory _recipient, bytes32 _content, bytes32 _secretHash) external @@ -116,24 +85,11 @@ contract Inbox is IInbox { require(uint256(_content) <= Constants.MAX_FIELD_VALUE, Errors.Inbox__ContentTooLarge(_content)); require(uint256(_secretHash) <= Constants.MAX_FIELD_VALUE, Errors.Inbox__SecretHashTooLarge(_secretHash)); - // Is this the best way to read a packed struct into local variables in a single SLOAD - // without having to use assembly and manual unpacking? - InboxState memory _state = state; - bytes16 rollingHash = _state.rollingHash; - uint64 totalMessagesInserted = _state.totalMessagesInserted; - uint64 inProgress = _state.inProgress; - - FrontierLib.Tree storage currentTree = trees[inProgress]; - - if (currentTree.isFull(SIZE)) { - inProgress += 1; - currentTree = trees[inProgress]; - } - // 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; + // insertion order, equal to the number of messages inserted before it. It matches the streaming L1-to-L2 tree's + // leaf count, so consumers do not need per-checkpoint tree geometry. Sourced from the current bucket's running + // total, which the absorb below then advances (a rollover carries the total forward unchanged). + uint256 index = _totalMessagesInserted(); // If the sender is the fee asset portal, we use a magic address to simpler have it initialized at genesis. // We assume that no-one will know the private key for this address and that the precompile won't change to @@ -149,69 +105,26 @@ contract Inbox is IInbox { }); bytes32 leaf = message.sha256ToField(); - currentTree.insertLeaf(leaf); - bytes16 updatedRollingHash = bytes16(keccak256(abi.encodePacked(rollingHash, leaf))); - state = InboxState({ - rollingHash: updatedRollingHash, totalMessagesInserted: totalMessagesInserted + 1, inProgress: inProgress - }); + messagesRollingHash = bytes16(keccak256(abi.encodePacked(messagesRollingHash, leaf))); (uint64 bucketSeq, bytes32 inboxRollingHash) = _absorbIntoBucket(leaf); - emit MessageSent(inProgress, index, leaf, updatedRollingHash, inboxRollingHash, bucketSeq); + emit MessageSent(index, leaf, messagesRollingHash, inboxRollingHash, bucketSeq); return (leaf, index); } - /** - * @notice Consumes the current tree, and starts a new one if needed - * - * @dev Only callable by the rollup contract - * @dev In the first iteration we return empty tree root because first checkpoint's messages tree is always - * empty because there has to be a 1 checkpoint lag to prevent sequencer DOS attacks - * - * @param _toConsume - The checkpoint number to consume - * - * @return The root of the consumed tree - */ - function consume(uint256 _toConsume) external override(IInbox) returns (bytes32) { - require(msg.sender == ROLLUP, Errors.Inbox__Unauthorized()); - - uint64 inProgress = state.inProgress; - require(_toConsume < inProgress, Errors.Inbox__MustBuildBeforeConsume()); - - bytes32 root = EMPTY_ROOT; - if (_toConsume > Constants.INITIAL_CHECKPOINT_NUMBER) { - root = trees[_toConsume].root(forest, HEIGHT, SIZE); - } - - // Once consumption reaches the current lag boundary, open the next checkpoint - // so new inserts keep a full LAG-sized buffer ahead of the rollup. - if (_toConsume + LAG == inProgress) { - state.inProgress = inProgress + 1; - } - - return root; - } - function getFeeAssetPortal() external view override(IInbox) returns (address) { return FEE_ASSET_PORTAL; } - function getRoot(uint256 _checkpointNumber) external view override(IInbox) returns (bytes32) { - return trees[_checkpointNumber].root(forest, HEIGHT, SIZE); - } - function getState() external view override(IInbox) returns (InboxState memory) { - return state; + return InboxState({rollingHash: messagesRollingHash, totalMessagesInserted: _totalMessagesInserted()}); } function getTotalMessagesInserted() external view override(IInbox) returns (uint64) { - return state.totalMessagesInserted; - } - - function getInProgress() external view override(IInbox) returns (uint64) { - return state.inProgress; + return _totalMessagesInserted(); } function getCurrentBucketSeq() external view override(IInbox) returns (uint64) { @@ -264,4 +177,9 @@ contract Inbox is IInbox { return (bucketSeq, bucket.rollingHash); } + + // Cumulative number of messages inserted into the Inbox, tracked by the current bucket's running total. + function _totalMessagesInserted() internal view returns (uint64) { + return buckets[currentBucketSeq % BUCKET_RING_SIZE].totalMsgCount; + } } diff --git a/l1-contracts/test/Inbox.t.sol b/l1-contracts/test/Inbox.t.sol index 7f9e36d4c1e4..293a1e28c78e 100644 --- a/l1-contracts/test/Inbox.t.sol +++ b/l1-contracts/test/Inbox.t.sol @@ -17,23 +17,13 @@ import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; contract InboxTest is Test { using Hash for DataStructures.L1ToL2Msg; - uint256 internal constant FIRST_REAL_TREE_NUM = Constants.INITIAL_CHECKPOINT_NUMBER + TestConstants.AZTEC_INBOX_LAG; - // We set low depth (5) to ensure we sufficiently test the tree transitions - uint256 internal constant HEIGHT = 5; - uint256 internal constant SIZE = 2 ** HEIGHT; - InboxHarness internal inbox; uint256 internal version = 0; - uint256 internal checkpointNumber = Constants.INITIAL_CHECKPOINT_NUMBER; - bytes32 internal emptyTreeRoot; function setUp() public { address rollup = address(this); IERC20 feeAsset = new TestERC20("Fee Asset", "FA", address(this)); - inbox = new InboxHarness( - rollup, feeAsset, version, HEIGHT, TestConstants.AZTEC_INBOX_LAG, TestConstants.AZTEC_INBOX_BUCKET_RING_SIZE - ); - emptyTreeRoot = inbox.getEmptyRoot(); + inbox = new InboxHarness(rollup, feeAsset, version, TestConstants.AZTEC_INBOX_BUCKET_RING_SIZE); } function _fakeMessage() internal view returns (DataStructures.L1ToL2Msg memory) { @@ -48,10 +38,6 @@ contract InboxTest is Test { }); } - function _divideAndRoundUp(uint256 a, uint256 b) internal pure returns (uint256) { - return (a + b - 1) / b; - } - function _boundMessage(DataStructures.L1ToL2Msg memory _message, uint256 _globalLeafIndex) internal view @@ -73,25 +59,7 @@ contract InboxTest is Test { return _message; } - // Since there is a LAG checkpoint lag between tree to be consumed and tree in progress the following invariant should - // never be violated - modifier checkInvariant() { - _; - assertLt(checkpointNumber, inbox.getInProgress()); - } - - function testRevertIfNotConsumingFromRollup() public { - vm.prank(address(0x1)); - vm.expectRevert(Errors.Inbox__Unauthorized.selector); - inbox.consume(checkpointNumber); - } - - function testRevertIFConsumingInFuture() public { - vm.expectRevert(Errors.Inbox__MustBuildBeforeConsume.selector); - inbox.consume(checkpointNumber + 1000); - } - - function testFuzzInsert(DataStructures.L1ToL2Msg memory _message) public checkInvariant { + function testFuzzInsert(DataStructures.L1ToL2Msg memory _message) public { Inbox.InboxState memory stateBefore = inbox.getState(); // Compact cumulative index: the message's index is the count inserted before it. uint256 globalLeafIndex = stateBefore.totalMessagesInserted; @@ -102,9 +70,7 @@ contract InboxTest is Test { bytes32 expectedInboxRollingHash = Hash.accumulateInboxRollingHash(bytes32(0), leaf); vm.expectEmit(true, true, true, true); // event we expect - emit IInbox.MessageSent( - FIRST_REAL_TREE_NUM, globalLeafIndex, leaf, expectedRollingHash, expectedInboxRollingHash, 1 - ); + emit IInbox.MessageSent(globalLeafIndex, leaf, expectedRollingHash, expectedInboxRollingHash, 1); // event we will get (bytes32 insertedLeaf, uint256 insertedIndex) = inbox.sendL2Message(message.recipient, message.content, message.secretHash); @@ -115,18 +81,14 @@ contract InboxTest is Test { Inbox.InboxState memory stateAfter = inbox.getState(); assertEq(stateBefore.totalMessagesInserted + 1, stateAfter.totalMessagesInserted); assertEq(expectedRollingHash, stateAfter.rollingHash); - assertEq(stateBefore.inProgress, stateAfter.inProgress); } - function testSendDuplicateL2Messages() public checkInvariant { + function testSendDuplicateL2Messages() public { DataStructures.L1ToL2Msg memory message = _fakeMessage(); (bytes32 leaf1, uint256 index1) = inbox.sendL2Message(message.recipient, message.content, message.secretHash); (bytes32 leaf2, uint256 index2) = inbox.sendL2Message(message.recipient, message.content, message.secretHash); (bytes32 leaf3, uint256 index3) = inbox.sendL2Message(message.recipient, message.content, message.secretHash); - // Only 1 tree should be non-zero - assertEq(inbox.getNumTrees(), TestConstants.AZTEC_INBOX_LAG); - // All the leaves should be different since the index gets mixed in assertNotEq(leaf1, leaf2); assertNotEq(leaf2, leaf3); @@ -163,70 +125,4 @@ contract InboxTest is Test { vm.expectRevert(abi.encodeWithSelector(Errors.Inbox__SecretHashTooLarge.selector, message.secretHash)); inbox.sendL2Message(message.recipient, message.content, message.secretHash); } - - function testFuzzSendAndConsume( - DataStructures.L1ToL2Msg[] memory _messagesFirstBatch, - DataStructures.L1ToL2Msg[] memory _messagesSecondBatch, - uint256 _numTreesToConsumeFirstBatch, - uint256 _numTreesToConsumeSecondBatch - ) public { - // Send first batch of messages - _send(_messagesFirstBatch); - - // Consume first few trees - _consume(_numTreesToConsumeFirstBatch); - - // Send second batch of messages - _send(_messagesSecondBatch); - - // Consume second batch of trees - _consume(_numTreesToConsumeSecondBatch); - } - - function _send(DataStructures.L1ToL2Msg[] memory _messages) internal checkInvariant { - bytes32 toConsumeRoot = inbox.getToConsumeRoot(checkpointNumber); - - // We send the messages and then check that toConsume root did not change. - for (uint256 i = 0; i < _messages.length; i++) { - DataStructures.L1ToL2Msg memory message = _boundMessage(_messages[i], inbox.getNextMessageIndex()); - - // We check whether a new tree is correctly initialized when the one in progress is full - uint256 numTrees = inbox.getNumTrees(); - uint256 expectedNumTrees = inbox.treeInProgressFull() ? numTrees + 1 : numTrees; - - inbox.sendL2Message(message.recipient, message.content, message.secretHash); - - assertEq(inbox.getNumTrees(), expectedNumTrees, "Unexpected number of trees"); - } - - // Root of a tree waiting to be consumed should not change because we introduced a LAG checkpoint lag to prevent - // sequencer DOS attacks - assertEq( - inbox.getToConsumeRoot(checkpointNumber), toConsumeRoot, "Root of a tree waiting to be consumed should not change" - ); - } - - function _consume(uint256 _numTreesToConsume) internal checkInvariant { - uint256 initialNumTrees = inbox.getNumTrees(); - // We use (initialNumTrees * 2) as upper bound here because we want to test the case where we go beyond - // the currently initalized number of trees. When consuming the newly initialized trees we should get zero roots. - uint256 numTreesToConsume = bound(_numTreesToConsume, 1, initialNumTrees * 2); - - // Now we consume the trees - for (uint256 i = 0; i < numTreesToConsume; i++) { - uint256 numTrees = inbox.getNumTrees(); - uint256 expectedNumTrees = - (checkpointNumber + TestConstants.AZTEC_INBOX_LAG == inbox.getInProgress()) ? numTrees + 1 : numTrees; - bytes32 root = inbox.consume(checkpointNumber); - - // We check whether a new tree is correctly initialized when the one which was in progress was set as to consume - assertEq(inbox.getNumTrees(), expectedNumTrees, "Unexpected number of trees"); - - // If we go beyong the number of trees initialized before consuming we should get empty root - if (i > initialNumTrees) { - assertEq(root, emptyTreeRoot, "Root of a newly initialized tree not empty"); - } - checkpointNumber += 1; - } - } } diff --git a/l1-contracts/test/InboxBuckets.t.sol b/l1-contracts/test/InboxBuckets.t.sol index 5f23173c2416..3be204a67f94 100644 --- a/l1-contracts/test/InboxBuckets.t.sol +++ b/l1-contracts/test/InboxBuckets.t.sol @@ -9,15 +9,11 @@ import {IInbox, MAX_MSGS_PER_BUCKET} from "@aztec/core/interfaces/messagebridge/ import {MIN_BUCKET_RING_SIZE} from "@aztec/core/messagebridge/Inbox.sol"; import {InboxHarness} from "./harnesses/InboxHarness.sol"; import {TestConstants} from "./harnesses/TestConstants.sol"; -import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; contract InboxBucketsTest is Test { - uint256 internal constant FIRST_REAL_TREE_NUM = Constants.INITIAL_CHECKPOINT_NUMBER + TestConstants.AZTEC_INBOX_LAG; - uint256 internal constant HEIGHT = 10; - InboxHarness internal inbox; uint256 internal version = 0; bytes32 internal expectedRollingHash; @@ -28,7 +24,7 @@ contract InboxBucketsTest is Test { function _deployInbox(uint256 _ringSize) internal returns (InboxHarness) { IERC20 feeAsset = new TestERC20("Fee Asset", "FA", address(this)); - return new InboxHarness(address(this), feeAsset, version, HEIGHT, TestConstants.AZTEC_INBOX_LAG, _ringSize); + return new InboxHarness(address(this), feeAsset, version, _ringSize); } function _send(InboxHarness _inbox, uint256 _salt) internal returns (bytes32) { @@ -140,7 +136,7 @@ contract InboxBucketsTest is Test { bytes32 inboxRollingHash = Hash.accumulateInboxRollingHash(bytes32(0), leaf); vm.expectEmit(true, true, true, true, address(inbox)); - emit IInbox.MessageSent(FIRST_REAL_TREE_NUM, message.index, leaf, legacyHash, inboxRollingHash, 1); + emit IInbox.MessageSent(message.index, leaf, legacyHash, inboxRollingHash, 1); inbox.sendL2Message(recipient, content, secretHash); } @@ -234,7 +230,7 @@ contract InboxBucketsTest is Test { function testConstructorRevertsBelowRingFloor() public { IERC20 feeAsset = new TestERC20("Fee Asset", "FA", address(this)); vm.expectRevert("BUCKET RING TOO SMALL"); - new InboxHarness(address(this), feeAsset, version, HEIGHT, TestConstants.AZTEC_INBOX_LAG, MIN_BUCKET_RING_SIZE - 1); + new InboxHarness(address(this), feeAsset, version, MIN_BUCKET_RING_SIZE - 1); } // Gas cost of a message absorbed into an already-open bucket (the common per-message case): the @@ -250,7 +246,7 @@ contract InboxBucketsTest is Test { } // Gas cost of the first message of a new L1 block: a larger timestamp opens the next bucket, - // writing a fresh ring slot on top of the per-message frontier-tree insert. + // writing a fresh ring slot. function testGasSendFirstMessageOfNewBlock() public { _send(inbox, 0); assertEq(inbox.getCurrentBucketSeq(), 1, "warmup opened bucket 1"); @@ -279,9 +275,8 @@ contract InboxBucketsTest is Test { assertEq(inbox.getCurrentBucketSeq(), 2, "rollover opened bucket 2"); } - // Gas cost of the first-ever message against a freshly deployed Inbox: the state struct, bucket 1, and the - // first frontier-tree slots are all written cold. This is the cold-storage case, not the global worst-case - // insert — later frontier indices with more levels to hash can cost more. + // Gas cost of the first-ever message against a freshly deployed Inbox: the rolling-hash slot and bucket 1 + // are written cold. This is the cold-storage case for the first message. function testGasSendFirstEverMessage() public { assertEq(inbox.getCurrentBucketSeq(), 0, "no message sent yet"); diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 830e62d33c9f..685ee2124b95 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -171,18 +171,6 @@ contract RollupTest is RollupBase { function testPrune() public setUpFor("mixed_checkpoint_1") { _proposeCheckpoint("mixed_checkpoint_1", 1); - assertEq( - inbox.getInProgress(), - // Post-flip the Inbox `consume()` is no longer called at propose, so the frontier in-progress tree no longer - // advances per checkpoint; it stays at its initial value until a tree fills (AZIP-22 Fast Inbox). - Constants.INITIAL_CHECKPOINT_NUMBER + TestConstants.AZTEC_INBOX_LAG, - "Invalid in progress" - ); - - // @note Fetch the inbox root of checkpoint 2. This should be frozen when checkpoint 1 is proposed. - // Even if we end up reverting checkpoint 1, we should still see the same root in the inbox. - bytes32 inboxRoot2 = inbox.getRoot(2); - CheckpointLog memory checkpoint = rollup.getCheckpoint(1); Slot prunableAt = checkpoint.slotNumber + Epoch.wrap(2).toSlots(); @@ -193,13 +181,6 @@ contract RollupTest is RollupBase { assertEq(rollup.getProvenCheckpointNumber(), 0, "Invalid proven checkpoint number"); rollup.prune(); - assertEq( - inbox.getInProgress(), - // Post-flip the Inbox `consume()` is no longer called at propose, so the frontier in-progress tree no longer - // advances per checkpoint; it stays at its initial value until a tree fills (AZIP-22 Fast Inbox). - Constants.INITIAL_CHECKPOINT_NUMBER + TestConstants.AZTEC_INBOX_LAG, - "Invalid in progress" - ); assertEq(rollup.getPendingCheckpointNumber(), 0, "Invalid pending checkpoint number"); assertEq(rollup.getProvenCheckpointNumber(), 0, "Invalid proven checkpoint number"); @@ -211,14 +192,6 @@ contract RollupTest is RollupBase { // @note We prune the pending chain as part of the propose call. _proposeCheckpoint("empty_checkpoint_1", Slot.unwrap(prunableAt)); - assertEq( - inbox.getInProgress(), - // Post-flip the Inbox `consume()` is no longer called at propose, so the frontier in-progress tree no longer - // advances per checkpoint; it stays at its initial value until a tree fills (AZIP-22 Fast Inbox). - Constants.INITIAL_CHECKPOINT_NUMBER + TestConstants.AZTEC_INBOX_LAG, - "Invalid in progress" - ); - assertEq(inbox.getRoot(2), inboxRoot2, "Invalid inbox root"); assertEq(rollup.getPendingCheckpointNumber(), 1, "Invalid pending checkpoint number"); assertEq(rollup.getProvenCheckpointNumber(), 0, "Invalid proven checkpoint number"); } diff --git a/l1-contracts/test/RollupFieldRange.t.sol b/l1-contracts/test/RollupFieldRange.t.sol index 3a910ac3e9d6..9763efa0ae4e 100644 --- a/l1-contracts/test/RollupFieldRange.t.sol +++ b/l1-contracts/test/RollupFieldRange.t.sol @@ -164,7 +164,8 @@ contract RollupFieldRangeTest is RollupBase { vm.warp(max(block.timestamp, Timestamp.unwrap(ts))); _populateInbox(full.populate.sender, full.populate.recipient, full.populate.l1ToL2Content); - header.inHash = rollup.getInbox().getRoot(full.checkpoint.checkpointNumber); + // The header's inHash field is an unconstrained pass-through hint post-flip; propose does not check it. + header.inHash = bytes32(0); header.gasFees.feePerL2Gas = SafeCast.toUint128(rollup.getManaMinFeeAt(ts, true)); // Every field the range check guards, set to the maximal in-range value. diff --git a/l1-contracts/test/base/RollupBase.sol b/l1-contracts/test/base/RollupBase.sol index 4f50e6b3c4eb..f7080e4cc617 100644 --- a/l1-contracts/test/base/RollupBase.sol +++ b/l1-contracts/test/base/RollupBase.sol @@ -171,9 +171,8 @@ contract RollupBase is DecoderBase { // We jump to the time of the block, always past the L1 block the messages above landed in. vm.warp(max(block.timestamp + 1, Timestamp.unwrap(full.checkpoint.header.timestamp))); - // 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); + // The header's inHash field is an unconstrained pass-through hint post-flip; propose does not check it. + full.checkpoint.header.inHash = bytes32(0); // Streaming Inbox: reference the newest bucket so the checkpoint consumes all messages // seeded above and the mandatory-consumption assert is trivially satisfied (a wrong ref could only revert). uint256 bucketHint = rollup.getInbox().getCurrentBucketSeq(); diff --git a/l1-contracts/test/builder/RollupBuilder.sol b/l1-contracts/test/builder/RollupBuilder.sol index 6d01ae12d3bc..e32fa8f97f58 100644 --- a/l1-contracts/test/builder/RollupBuilder.sol +++ b/l1-contracts/test/builder/RollupBuilder.sol @@ -164,11 +164,6 @@ contract RollupBuilder is Test { return this; } - function setInboxLag(uint256 _inboxLag) public returns (RollupBuilder) { - config.rollupConfigInput.inboxLag = _inboxLag; - return this; - } - function setSlotDuration(uint256 _slotDuration) public returns (RollupBuilder) { config.rollupConfigInput.aztecSlotDuration = _slotDuration; return this; diff --git a/l1-contracts/test/fee_portal/depositToAztecPublic.t.sol b/l1-contracts/test/fee_portal/depositToAztecPublic.t.sol index bdda37eb7990..25da1722339b 100644 --- a/l1-contracts/test/fee_portal/depositToAztecPublic.t.sol +++ b/l1-contracts/test/fee_portal/depositToAztecPublic.t.sol @@ -9,8 +9,6 @@ import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol"; import {Rollup} from "@aztec/core/Rollup.sol"; -import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; -import {TestConstants} from "../harnesses/TestConstants.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; @@ -30,23 +28,6 @@ contract DepositToAztecPublic is Test { address internal constant MAGIC_FEE_JUICE_ADDRESS = address(uint160(Constants.FEE_JUICE_ADDRESS)); - struct InboxLagTestState { - Rollup testRollup; - FeeJuicePortal testFeeJuicePortal; - TestERC20 testToken; - Inbox testInbox; - uint256 lag; - uint256 SIZE; - uint256 initialInProgress; - bytes32 to; - bytes32 secretHash1; - bytes32 secretHash2; - uint256 amount; - uint256 expectedIndex1; - uint256 expectedIndex2; - uint256 expectedInProgress; - } - function setUp() public { RollupBuilder builder = new RollupBuilder(address(this)); builder.deploy(); @@ -82,8 +63,7 @@ contract DepositToAztecPublic is Test { uint256 amount = 100 ether; Inbox inbox = Inbox(address(Rollup(address(registry.getCanonicalRollup())).getInbox())); - // Compact cumulative index (AZIP-22 Fast Inbox): the first message against a fresh Inbox has index 0 - // (equal to the number of messages inserted before it), regardless of the inbox lag. + // Compact cumulative index (AZIP-22 Fast Inbox): the first message against a fresh Inbox has index 0. uint256 expectedIndex = 0; // The purpose of including the function selector is to make the message unique to that specific call. Note that @@ -107,10 +87,9 @@ contract DepositToAztecPublic is Test { assertEq(inbox.getTotalMessagesInserted(), 0); bytes16 expectedHash = bytes16(keccak256(abi.encodePacked(inbox.getState().rollingHash, expectedKey))); - uint256 expectedInProgress = Constants.INITIAL_CHECKPOINT_NUMBER + TestConstants.AZTEC_INBOX_LAG; bytes32 expectedInboxRollingHash = Hash.accumulateInboxRollingHash(bytes32(0), expectedKey); vm.expectEmit(true, true, true, true, address(inbox)); - emit IInbox.MessageSent(expectedInProgress, expectedIndex, expectedKey, expectedHash, expectedInboxRollingHash, 1); + emit IInbox.MessageSent(expectedIndex, expectedKey, expectedHash, expectedInboxRollingHash, 1); vm.expectEmit(true, true, true, true, address(feeJuicePortal)); emit IFeeJuicePortal.DepositToAztecPublic(to, amount, secretHash, expectedKey, expectedIndex); @@ -120,112 +99,4 @@ contract DepositToAztecPublic is Test { assertEq(key, expectedKey); assertEq(index, expectedIndex); } - - function testFuzz_InboxLag(uint256 _lag) external { - // Bound lag to reasonable values (1-10) to test different lag configurations - uint256 lag = bound(_lag, 1, 10); - - RollupBuilder builder = new RollupBuilder(address(this)); - builder.setInboxLag(lag); - builder.deploy(); - - InboxLagTestState memory state = InboxLagTestState({ - testRollup: builder.getConfig().rollup, - testFeeJuicePortal: FeeJuicePortal(address(builder.getConfig().rollup.getFeeAssetPortal())), - testToken: builder.getConfig().testERC20, - testInbox: Inbox(address(builder.getConfig().rollup.getInbox())), - lag: lag, - SIZE: 2 ** Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT, - initialInProgress: 0, // Will be set below - to: bytes32(0x0), - secretHash1: bytes32(uint256(0x01)), - secretHash2: bytes32(uint256(0x02)), - amount: 100 ether, - expectedIndex1: 0, // Will be set below - expectedIndex2: 0, // Will be set below - expectedInProgress: Constants.INITIAL_CHECKPOINT_NUMBER + lag - }); - - // Verify initial state: inProgress should be INITIAL_CHECKPOINT_NUMBER + lag - assertEq( - state.testInbox.getInProgress(), - state.expectedInProgress, - "Initial inProgress should be INITIAL_CHECKPOINT_NUMBER + lag" - ); - state.initialInProgress = state.testInbox.getInProgress(); - // Compact cumulative index (AZIP-22 Fast Inbox): messages are indexed by insertion order from 0, - // independent of the lag-based tree geometry. - state.expectedIndex1 = 0; - state.expectedIndex2 = 1; - - vm.prank(state.testToken.owner()); - state.testToken.mint(address(this), state.amount * 2); - state.testToken.approve(address(state.testFeeJuicePortal), state.amount * 2); - - // Send first message - (, uint256 index1) = state.testFeeJuicePortal.depositToAztecPublic(state.to, state.amount, state.secretHash1); - assertEq(index1, state.expectedIndex1, "First message index should be 0 (compact cumulative)"); - assertEq( - state.testInbox.getInProgress(), - state.expectedInProgress, - "inProgress should not change after sending first message" - ); - assertEq(state.testInbox.getTotalMessagesInserted(), 1, "Should have 1 message after first deposit"); - - // Send second message - (, uint256 index2) = state.testFeeJuicePortal.depositToAztecPublic(state.to, state.amount, state.secretHash2); - assertEq(index2, state.expectedIndex2, "Second message index should be 1 (compact cumulative)"); - assertEq( - state.testInbox.getInProgress(), - state.expectedInProgress, - "inProgress should not change after sending second message" - ); - assertEq(state.testInbox.getTotalMessagesInserted(), 2, "Should have 2 messages after second deposit"); - - // Test consume logic: consume checkpoints to verify lag behavior - // Initially: inProgress = INITIAL_CHECKPOINT_NUMBER + lag - // When checkpoint N is consumed: - // - If N + lag == inProgress, then inProgress advances to inProgress + 1 - // - Otherwise, inProgress stays the same - - // Consume checkpoints starting from INITIAL_CHECKPOINT_NUMBER - // We need to consume them sequentially to test the lag behavior - for ( - uint256 checkpointNum = Constants.INITIAL_CHECKPOINT_NUMBER; - checkpointNum < state.initialInProgress; - checkpointNum++ - ) { - uint256 inProgressBefore = state.testInbox.getInProgress(); - - // Call consume as the rollup - vm.prank(address(state.testRollup)); - bytes32 root = state.testInbox.consume(checkpointNum); - - uint256 inProgressAfter = state.testInbox.getInProgress(); - bool shouldAdvance = (checkpointNum + state.lag == inProgressBefore); - - if (shouldAdvance) { - assertEq( - inProgressAfter, inProgressBefore + 1, "inProgress should advance when checkpointNum + lag == inProgress" - ); - } else { - assertEq( - inProgressAfter, inProgressBefore, "inProgress should not advance when checkpointNum + lag != inProgress" - ); - } - - // Verify root is correct for non-initial checkpoints - if (checkpointNum > Constants.INITIAL_CHECKPOINT_NUMBER) { - assertEq(root, state.testInbox.getRoot(checkpointNum), "Root should match getRoot for checkpoint"); - } - } - - // After consuming all checkpoints up to the lag point, inProgress should have advanced - uint256 finalInProgress = state.testInbox.getInProgress(); - assertEq( - finalInProgress, - state.initialInProgress + state.lag, - "Final inProgress should advance by lag after consuming all checkpoints" - ); - } } diff --git a/l1-contracts/test/harnesses/InboxHarness.sol b/l1-contracts/test/harnesses/InboxHarness.sol index b7489a0d4a9f..983710628b48 100644 --- a/l1-contracts/test/harnesses/InboxHarness.sol +++ b/l1-contracts/test/harnesses/InboxHarness.sol @@ -4,43 +4,14 @@ pragma solidity >=0.8.27; import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; -import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; -import {FrontierLib} from "@aztec/core/libraries/crypto/FrontierLib.sol"; contract InboxHarness is Inbox { - using FrontierLib for FrontierLib.Tree; - - constructor(address _rollup, IERC20 _feeAsset, uint256 _version, uint256 _height, uint256 _lag, uint256 _ringSize) - Inbox(_rollup, _feeAsset, _version, _height, _lag, _ringSize) + constructor(address _rollup, IERC20 _feeAsset, uint256 _version, uint256 _ringSize) + Inbox(_rollup, _feeAsset, _version, _ringSize) {} - function getSize() external view returns (uint256) { - return SIZE; - } - - function getEmptyRoot() external view returns (bytes32) { - return EMPTY_ROOT; - } - - function treeInProgressFull() external view returns (bool) { - return trees[state.inProgress].isFull(SIZE); - } - - function getToConsumeRoot(uint256 _toConsume) external view returns (bytes32) { - bytes32 root = EMPTY_ROOT; - if (_toConsume > Constants.INITIAL_CHECKPOINT_NUMBER) { - root = trees[_toConsume].root(forest, HEIGHT, SIZE); - } - return root; - } - - function getNumTrees() external view returns (uint256) { - // -INITIAL_CHECKPOINT_NUMBER because tree number INITIAL_CHECKPOINT_NUMBER is not real - return state.inProgress - Constants.INITIAL_CHECKPOINT_NUMBER; - } - function getNextMessageIndex() external view returns (uint256) { // Compact cumulative index: the next message's index is the count inserted so far. - return state.totalMessagesInserted; + return _totalMessagesInserted(); } } diff --git a/l1-contracts/test/harnesses/TestConstants.sol b/l1-contracts/test/harnesses/TestConstants.sol index 44aee456e1d4..dceae35f5020 100644 --- a/l1-contracts/test/harnesses/TestConstants.sol +++ b/l1-contracts/test/harnesses/TestConstants.sol @@ -25,7 +25,6 @@ library TestConstants { uint256 internal constant AZTEC_TARGET_COMMITTEE_SIZE = 48; uint256 internal constant AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET = 3; uint256 internal constant AZTEC_LAG_IN_EPOCHS_FOR_RANDAO = 2; - uint256 internal constant AZTEC_INBOX_LAG = 2; uint256 internal constant AZTEC_INBOX_BUCKET_RING_SIZE = 1024; uint256 internal constant AZTEC_PROOF_SUBMISSION_EPOCHS = 1; uint256 internal constant AZTEC_SLASHING_QUORUM = 17; // Must be > ROUND_SIZE / 2 (ROUND_SIZE derived from @@ -133,7 +132,6 @@ library TestConstants { config.slashAmounts = slashAmounts; config.slasherEnabled = false; config.localEjectionThreshold = 0; - config.inboxLag = AZTEC_INBOX_LAG; // For the version we derive it based on the config (with a 0 version) // TODO(https://linear.app/aztec-labs/issue/TMNT-139/version-at-deployment) diff --git a/l1-contracts/test/portals/TokenPortal.t.sol b/l1-contracts/test/portals/TokenPortal.t.sol index ef51998626b8..be63d4995778 100644 --- a/l1-contracts/test/portals/TokenPortal.t.sol +++ b/l1-contracts/test/portals/TokenPortal.t.sol @@ -11,7 +11,6 @@ import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; -import {TestConstants} from "../harnesses/TestConstants.sol"; import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; // Interfaces @@ -34,9 +33,6 @@ contract TokenPortalTest is Test { event MessageConsumed(bytes32 indexed messageHash, address indexed recipient); - uint256 internal constant FIRST_REAL_TREE_NUM = Constants.INITIAL_CHECKPOINT_NUMBER + TestConstants.AZTEC_INBOX_LAG; - uint256 internal constant L1_TO_L2_MSG_SUBTREE_SIZE = 2 ** Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT; - Registry internal registry; RewardDistributor internal rewardDistributor; Inbox internal inbox; @@ -129,7 +125,7 @@ contract TokenPortalTest is Test { // Check the event was emitted vm.expectEmit(true, true, true, true); // event we expect - emit IInbox.MessageSent(FIRST_REAL_TREE_NUM, expectedIndex, expectedLeaf, expectedHash, expectedInboxRollingHash, 1); + emit IInbox.MessageSent(expectedIndex, expectedLeaf, expectedHash, expectedInboxRollingHash, 1); // event we will get // Perform op @@ -158,7 +154,7 @@ contract TokenPortalTest is Test { // Check the event was emitted vm.expectEmit(true, true, true, true); // event we expect - emit IInbox.MessageSent(FIRST_REAL_TREE_NUM, expectedIndex, expectedLeaf, expectedHash, expectedInboxRollingHash, 1); + emit IInbox.MessageSent(expectedIndex, expectedLeaf, expectedHash, expectedInboxRollingHash, 1); // Perform op (bytes32 leaf, uint256 index) = tokenPortal.depositToAztecPublic(to, amount, secretHashForL2MessageConsumption); diff --git a/l1-contracts/test/rollup/ProposeInboxConsumption.t.sol b/l1-contracts/test/rollup/ProposeInboxConsumption.t.sol index 7d1e8a00c032..c0539cc09118 100644 --- a/l1-contracts/test/rollup/ProposeInboxConsumption.t.sol +++ b/l1-contracts/test/rollup/ProposeInboxConsumption.t.sol @@ -40,7 +40,6 @@ contract ProposeInboxConsumptionTest is Test { uint256 internal constant GENESIS_TIME = 100_000; uint256 internal constant SLOT_DURATION = 36; uint256 internal constant EPOCH_DURATION = 32; - uint256 internal constant HEIGHT = 10; Slot internal constant SLOT = Slot.wrap(10); @@ -61,7 +60,7 @@ contract ProposeInboxConsumptionTest is Test { function _deployInbox(uint256 _ringSize) internal returns (InboxHarness) { IERC20 feeAsset = new TestERC20("Fee Asset", "FA", address(this)); - return new InboxHarness(address(rollup), feeAsset, version, HEIGHT, TestConstants.AZTEC_INBOX_LAG, _ringSize); + return new InboxHarness(address(rollup), feeAsset, version, _ringSize); } function _send(uint256 _salt) internal { diff --git a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol index 19da2d10ca76..80820932ec1b 100644 --- a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol +++ b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol @@ -546,8 +546,8 @@ contract ValidatorSelectionTest is ValidatorSelectionTestBase { { uint128 manaMinFee = SafeCast.toUint128(rollup.getManaMinFeeAt(Timestamp.wrap(block.timestamp), true)); - bytes32 inHash = inbox.getRoot(full.checkpoint.checkpointNumber); - header.inHash = inHash; + // The header's inHash field is an unconstrained pass-through hint post-flip; propose does not check it. + header.inHash = bytes32(0); header.gasFees.feePerL2Gas = manaMinFee; } From dcbda8c36a99ac63eb45779863d912e1792b8b5c Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Sat, 18 Jul 2026 14:19:18 -0300 Subject: [PATCH 02/10] chore(fast-inbox): follow the L1 inbox cleanup through the TS clients (A-1386) Track the regenerated Inbox ABI after the legacy L1 path was removed (AZIP-22 Fast Inbox): - ethereum InboxContract: drop getLag() (the LAG getter is gone) and the MessageSent checkpointNumber decode; getState() no longer reads inProgress and InboxContractState.treeInProgress becomes optional (no longer tracked on-chain). - Stop threading inboxLag through queries/getL1ContractsConfig and the L1 deploy env; the broader AZTEC_INBOX_LAG config removal is deferred to the node cleanup. - archiver decode: derive the message's checkpoint number from the compact index (the event no longer carries it), keeping the legacy per-checkpoint store shape. - Remove the obsolete advanceInboxInProgress cheat code and its inbox-drift tests; drop the orphaned archiver inHash-mismatch sync test (the cross-check was removed at the flip). Add the flip-stranded bucketHint to the propose-call test fixtures. --- .../src/l1/calldata_retriever.test.ts | 6 +- .../archiver/src/l1/data_retrieval.ts | 5 +- .../archiver/src/test/fake_l1_state.ts | 1 - .../src/single-node/bot/bot.test.ts | 29 +----- yarn-project/ethereum/src/contracts/inbox.ts | 21 ++-- .../ethereum/src/deploy_aztec_l1_contracts.ts | 1 - yarn-project/ethereum/src/queries.ts | 8 +- .../src/test/rollup_cheat_codes.test.ts | 95 ------------------- .../ethereum/src/test/rollup_cheat_codes.ts | 41 -------- 9 files changed, 20 insertions(+), 187 deletions(-) delete mode 100644 yarn-project/ethereum/src/test/rollup_cheat_codes.test.ts diff --git a/yarn-project/archiver/src/l1/calldata_retriever.test.ts b/yarn-project/archiver/src/l1/calldata_retriever.test.ts index 10a2103cc253..35dbd1b4179a 100644 --- a/yarn-project/archiver/src/l1/calldata_retriever.test.ts +++ b/yarn-project/archiver/src/l1/calldata_retriever.test.ts @@ -140,7 +140,7 @@ describe('CalldataRetriever', () => { archive, oracleInput: { feeAssetPriceModifier: BigInt(0) }, header: viemHeader, - bucketHint: 0n, + bucketHint: BigInt(0), }, attestations, signers, @@ -369,7 +369,11 @@ describe('CalldataRetriever', () => { archive, oracleInput: { feeAssetPriceModifier }, header, +<<<<<<< HEAD bucketHint: 0n, +======= + bucketHint: BigInt(0), +>>>>>>> e74ca75577 (chore(fast-inbox): follow the L1 inbox cleanup through the TS clients (A-1386)) }, attestations, [], // signers diff --git a/yarn-project/archiver/src/l1/data_retrieval.ts b/yarn-project/archiver/src/l1/data_retrieval.ts index c6f7c8b8941b..358648e7d306 100644 --- a/yarn-project/archiver/src/l1/data_retrieval.ts +++ b/yarn-project/archiver/src/l1/data_retrieval.ts @@ -24,6 +24,7 @@ import { type Logger, createLogger } from '@aztec/foundation/log'; import { RollupAbi } from '@aztec/l1-artifacts'; import { Body, CommitteeAttestation, L2Block } from '@aztec/stdlib/block'; import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint'; +import { InboxLeaf } from '@aztec/stdlib/messaging'; import { Proof } from '@aztec/stdlib/proofs'; import { CheckpointHeader } from '@aztec/stdlib/rollup'; import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees'; @@ -391,7 +392,9 @@ function mapLogInboxMessage(log: MessageSentLog): InboxMessage { leaf: log.args.leaf, l1BlockNumber: log.l1BlockNumber, l1BlockHash: log.l1BlockHash, - checkpointNumber: log.args.checkpointNumber, + // The Inbox no longer emits a checkpoint number (AZIP-22 Fast Inbox). Derive it from the compact index for the + // legacy per-checkpoint message store, which the node still keeps until it drops the per-checkpoint flow. + checkpointNumber: InboxLeaf.checkpointNumberFromIndex(log.args.index), rollingHash: log.args.rollingHash, inboxRollingHash: log.args.inboxRollingHash, bucketSeq: log.args.bucketSeq, diff --git a/yarn-project/archiver/src/test/fake_l1_state.ts b/yarn-project/archiver/src/test/fake_l1_state.ts index e7354880213d..97d05a98999c 100644 --- a/yarn-project/archiver/src/test/fake_l1_state.ts +++ b/yarn-project/archiver/src/test/fake_l1_state.ts @@ -702,7 +702,6 @@ export class FakeL1State { l1TransactionHash: `0x${msg.l1BlockNumber.toString(16)}` as `0x${string}`, l1BlockTimestamp: this.getTimestampAtL1Block(msg.l1BlockNumber), args: { - checkpointNumber: msg.checkpointNumber, index: msg.index, leaf: msg.leaf, rollingHash: msg.rollingHash, diff --git a/yarn-project/end-to-end/src/single-node/bot/bot.test.ts b/yarn-project/end-to-end/src/single-node/bot/bot.test.ts index 75a54e5b92f2..48831ebfebc4 100644 --- a/yarn-project/end-to-end/src/single-node/bot/bot.test.ts +++ b/yarn-project/end-to-end/src/single-node/bot/bot.test.ts @@ -29,9 +29,8 @@ import { NO_REORG_SUBMISSION_EPOCHS } from '../setup.js'; // Tests the transaction bot implementations (transfer bot, AMM bot, cross-chain bot). // Uses setup(0, PIPELINING_SETUP_OPTS + aztecProofSubmissionEpochs:NO_REORG_SUBMISSION_EPOCHS) with one node, production // sequencer (ethereumSlotDuration=4s, aztecSlotDuration=12s, proofSubEpochs=NO_REORG_SUBMISSION_EPOCHS, minTxsPerBlock=0; -// aztecEpochDuration is the setup() default). The bridge-resume, setup-via-bridging, and -// cross-chain-bot subsuites actively drive L1 cross-chain bridging: fee-juice portal deposits, -// advanceInboxInProgress, and L2→L1 messages via CrossChainBot. +// aztecEpochDuration is the setup() default). The bridge-resume and cross-chain-bot subsuites actively +// drive L1 cross-chain bridging: fee-juice portal deposits and L2→L1 messages via CrossChainBot. describe('single-node/bot/bot', () => { let wallet: EmbeddedWallet; let aztecNode: AztecNode; @@ -270,30 +269,6 @@ describe('single-node/bot/bot', () => { }); }); - // Tests that Bot.create succeeds after the inbox drifts away from the rollup contract. - // Actively drives L1 via advanceInboxInProgress. - describe('setup via bridging funds cross-chain', () => { - beforeAll(() => { - config = { - ...getBotDefaultConfig(), - followChain: 'PROPOSED', - botMode: 'transfer', - senderPrivateKey: new SecretValue(Fr.random()), - l1PrivateKey: getPrivateKey(), - l1RpcUrls, - flushSetupTransactions: true, - }; - }); - - // See 'can consume L1 to L2 message in %s after inbox drifts away from the rollup' - // in end-to-end/src/e2e_cross_chain_messaging/l1_to_l2.test.ts for context on this test. - // Advances inbox 4 slots then creates Bot; verifies it completes setup without error. - it('creates bot after inbox drift', async () => { - await cheatCodes.rollup.advanceInboxInProgress(4); - await Bot.create(config, wallet, aztecNode, aztecNodeAdmin, new BotStore(await openTmpStore('bot'))); - }, 300_000); - }); - // Tests the CrossChainBot: seeds L1→L2 messages and on each tick consumes one while seeding // a replacement. Actively drives L1 portal contracts. describe('cross-chain-bot', () => { diff --git a/yarn-project/ethereum/src/contracts/inbox.ts b/yarn-project/ethereum/src/contracts/inbox.ts index 1d2a732ace8d..70ce019de4d8 100644 --- a/yarn-project/ethereum/src/contracts/inbox.ts +++ b/yarn-project/ethereum/src/contracts/inbox.ts @@ -1,6 +1,5 @@ import { asyncPool } from '@aztec/foundation/async-pool'; import { maxBigint } from '@aztec/foundation/bigint'; -import { CheckpointNumber } from '@aztec/foundation/branded-types'; import { Buffer16, Buffer32 } from '@aztec/foundation/buffer'; import { Fr } from '@aztec/foundation/curves/bn254'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -19,11 +18,11 @@ import { checkBlockTag } from './utils.js'; export type MessageSentArgs = { index: bigint; leaf: Fr; - checkpointNumber: CheckpointNumber; + /** Legacy 128-bit keccak rolling hash of all messages inserted up to and including this one. */ rollingHash: Buffer16; - /** Consensus rolling hash (truncated sha256 chain) after this message (AZIP-22 Fast Inbox). Not yet consumed by the node. */ + /** Consensus rolling hash (truncated sha256 chain) after this message (AZIP-22 Fast Inbox). */ inboxRollingHash: Fr; - /** Sequence number of the Inbox bucket this message was absorbed into (AZIP-22 Fast Inbox). Not yet consumed by the node. */ + /** Sequence number of the Inbox bucket this message was absorbed into (AZIP-22 Fast Inbox). */ bucketSeq: bigint; }; @@ -68,18 +67,12 @@ export class InboxContract { return this.inbox; } - public async getLag(opts: { blockTag?: BlockTag; blockNumber?: bigint } = {}): Promise { - await checkBlockTag(opts.blockNumber, this.client); - return await this.inbox.read.LAG(opts); - } - public async getState(opts: { blockTag?: BlockTag; blockNumber?: bigint } = {}): Promise { await checkBlockTag(opts.blockNumber, this.client); const state = await this.inbox.read.getState(opts); return { totalMessagesInserted: state.totalMessagesInserted, messagesRollingHash: Buffer16.fromString(state.rollingHash), - treeInProgress: state.inProgress, }; } @@ -135,7 +128,6 @@ export class InboxContract { args: { index?: bigint; hash?: `0x${string}`; - checkpointNumber?: bigint; rollingHash?: `0x${string}`; inboxRollingHash?: `0x${string}`; bucketSeq?: bigint; @@ -151,7 +143,6 @@ export class InboxContract { args: { index: log.args.index!, leaf: Fr.fromString(log.args.hash!), - checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber!), rollingHash: Buffer16.fromString(log.args.rollingHash!), inboxRollingHash: Fr.fromString(log.args.inboxRollingHash!), bucketSeq: log.args.bucketSeq!, @@ -163,5 +154,9 @@ export class InboxContract { export type InboxContractState = { totalMessagesInserted: bigint; messagesRollingHash: Buffer16; - treeInProgress: bigint; + /** + * Checkpoint currently accumulating messages, when known. No longer tracked on-chain post-flip (AZIP-22 Fast + * Inbox); used only by the legacy per-checkpoint message-readiness gate. + */ + treeInProgress?: bigint; }; diff --git a/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts b/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts index f62c005c0949..df59d422a678 100644 --- a/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts @@ -581,7 +581,6 @@ export function getDeployRollupForUpgradeEnvVars( AZTEC_TARGET_COMMITTEE_SIZE: args.aztecTargetCommitteeSize.toString(), AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET: args.lagInEpochsForValidatorSet.toString(), AZTEC_LAG_IN_EPOCHS_FOR_RANDAO: args.lagInEpochsForRandao.toString(), - AZTEC_INBOX_LAG: args.inboxLag?.toString(), AZTEC_PROOF_SUBMISSION_EPOCHS: args.aztecProofSubmissionEpochs.toString(), AZTEC_LOCAL_EJECTION_THRESHOLD: args.localEjectionThreshold.toString(), AZTEC_SLASHING_LIFETIME_IN_ROUNDS: args.slashingLifetimeInRounds.toString(), diff --git a/yarn-project/ethereum/src/queries.ts b/yarn-project/ethereum/src/queries.ts index fde4ceb0561d..ca5a7cfdcc02 100644 --- a/yarn-project/ethereum/src/queries.ts +++ b/yarn-project/ethereum/src/queries.ts @@ -5,7 +5,6 @@ import { BaseError, type Block } from 'viem'; import { DefaultL1ContractsConfig, type L1ContractsConfig } from './config.js'; import { ReadOnlyGovernanceContract } from './contracts/governance.js'; import { GovernanceProposerContract } from './contracts/governance_proposer.js'; -import { InboxContract } from './contracts/inbox.js'; import { RollupContract } from './contracts/rollup.js'; import type { ViemClient, ViemPublicClient } from './types.js'; @@ -78,7 +77,7 @@ export async function getL1ContractsConfig( publicClient: ViemPublicClient, addresses: { governanceAddress: EthAddress; rollupAddress?: EthAddress }, ): Promise< - Omit & { + Omit & { l1StartBlock: bigint; l1GenesisTime: bigint; rollupVersion: number; @@ -92,8 +91,6 @@ export async function getL1ContractsConfig( const rollup = new RollupContract(publicClient, rollupAddress.toString()); const slasherProposer = await rollup.getSlashingProposer(); const slasher = await rollup.getSlasherContract(); - const rollupAddresses = await rollup.getRollupAddresses(); - const inboxContract = new InboxContract(publicClient, rollupAddresses.inboxAddress.toString()); const [ l1StartBlock, @@ -104,7 +101,6 @@ export async function getL1ContractsConfig( aztecTargetCommitteeSize, lagInEpochsForValidatorSet, lagInEpochsForRandao, - inboxLag, activationThreshold, ejectionThreshold, localEjectionThreshold, @@ -132,7 +128,6 @@ export async function getL1ContractsConfig( rollup.getTargetCommitteeSize(), rollup.getLagInEpochsForValidatorSet(), rollup.getLagInEpochsForRandao(), - inboxContract.getLag(), rollup.getActivationThreshold(), rollup.getEjectionThreshold(), rollup.getLocalEjectionThreshold(), @@ -162,7 +157,6 @@ export async function getL1ContractsConfig( aztecTargetCommitteeSize: Number(aztecTargetCommitteeSize), lagInEpochsForValidatorSet: Number(lagInEpochsForValidatorSet), lagInEpochsForRandao: Number(lagInEpochsForRandao), - inboxLag: Number(inboxLag), governanceProposerQuorum: Number(governanceProposerQuorum), governanceProposerRoundSize: Number(governanceProposerRoundSize), governanceVotingDuration: DefaultL1ContractsConfig.governanceVotingDuration, diff --git a/yarn-project/ethereum/src/test/rollup_cheat_codes.test.ts b/yarn-project/ethereum/src/test/rollup_cheat_codes.test.ts deleted file mode 100644 index 454549dfe680..000000000000 --- a/yarn-project/ethereum/src/test/rollup_cheat_codes.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { getPublicClient } from '@aztec/ethereum/client'; -import { Fr } from '@aztec/foundation/curves/bn254'; -import { createLogger } from '@aztec/foundation/log'; -import { DateProvider } from '@aztec/foundation/timer'; -import { InboxAbi } from '@aztec/l1-artifacts/InboxAbi'; - -import { foundry } from 'viem/chains'; - -import { DefaultL1ContractsConfig } from '../config.js'; -import { deployAztecL1Contracts } from '../deploy_aztec_l1_contracts.js'; -import type { ViemClient } from '../types.js'; -import { EthCheatCodes } from './eth_cheat_codes.js'; -import { RollupCheatCodes } from './rollup_cheat_codes.js'; -import type { Anvil } from './start_anvil.js'; -import { startAnvil } from './start_anvil.js'; - -describe('RollupCheatCodes', () => { - let anvil: Anvil; - let rpcUrl: string; - let privateKey: `0x${string}`; - let publicClient: ViemClient; - let cheatCodes: EthCheatCodes; - let rollupCheatCodes: RollupCheatCodes; - - let vkTreeRoot: Fr; - let protocolContractsHash: Fr; - let deployedL1Contracts: Awaited>; - - beforeAll(async () => { - // this is the 6th address that gets funded by the junk mnemonic - privateKey = '0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba'; - vkTreeRoot = Fr.random(); - protocolContractsHash = Fr.random(); - - ({ anvil, rpcUrl } = await startAnvil()); - - publicClient = getPublicClient({ l1RpcUrls: [rpcUrl], l1ChainId: 31337 }); - cheatCodes = new EthCheatCodes([rpcUrl], new DateProvider()); - - deployedL1Contracts = await deployAztecL1Contracts(rpcUrl, privateKey, foundry.id, { - ...DefaultL1ContractsConfig, - vkTreeRoot, - protocolContractsHash, - genesisArchiveRoot: Fr.random(), - realVerifier: false, - }); - - rollupCheatCodes = RollupCheatCodes.create([rpcUrl], deployedL1Contracts.l1ContractAddresses, new DateProvider()); - }); - - afterAll(async () => { - await cheatCodes.setIntervalMining(0); - await anvil?.stop().catch(err => createLogger('cleanup').error(err)); - }); - - describe('advanceInboxInProgress', () => { - it('should advance the inbox inProgress field correctly', async () => { - const inboxAddress = deployedL1Contracts.l1ContractAddresses.inboxAddress.toString(); - - // Read initial state directly from contract - const initialState = await publicClient.readContract({ - address: inboxAddress as `0x${string}`, - abi: InboxAbi, - functionName: 'getState', - }); - - const initialInProgress = initialState.inProgress; - const initialRollingHash = initialState.rollingHash; - const initialTotalMessagesInserted = initialState.totalMessagesInserted; - - // Advance the inbox inProgress by a large amount - const advanceBy = 1000n; - const newInProgress = await rollupCheatCodes.advanceInboxInProgress(advanceBy); - - // Read state after advancement - const finalState = await publicClient.readContract({ - address: inboxAddress as `0x${string}`, - abi: InboxAbi, - functionName: 'getState', - }); - - const finalInProgress = finalState.inProgress; - const finalRollingHash = finalState.rollingHash; - const finalTotalMessagesInserted = finalState.totalMessagesInserted; - - // Check that the advancement worked - expect(newInProgress).toBe(initialInProgress + advanceBy); - expect(finalInProgress).toBe(initialInProgress + advanceBy); - - // Check that all other fields remain unchanged - expect(finalRollingHash).toBe(initialRollingHash); - expect(finalTotalMessagesInserted).toBe(initialTotalMessagesInserted); - }); - }); -}); diff --git a/yarn-project/ethereum/src/test/rollup_cheat_codes.ts b/yarn-project/ethereum/src/test/rollup_cheat_codes.ts index 9aa17c44994d..e2823cd86c2c 100644 --- a/yarn-project/ethereum/src/test/rollup_cheat_codes.ts +++ b/yarn-project/ethereum/src/test/rollup_cheat_codes.ts @@ -295,47 +295,6 @@ export class RollupCheatCodes { ); } - /** - * Overrides the inProgress field of the Inbox contract state - * @param howMuch - How many checkpoints to move it forward - */ - public advanceInboxInProgress(howMuch: number | bigint): Promise { - return this.ethCheatCodes.execWithPausedAnvil(async () => { - // Storage slot 2 contains the InboxState struct - const inboxStateSlot = 2n; - - // Get inbox and its current state values - const inboxAddress = await this.rollup.read.getInbox(); - const currentStateValue = await this.ethCheatCodes.load(EthAddress.fromString(inboxAddress), inboxStateSlot); - - // Extract current values from the packed storage slot - // Storage layout: rollingHash (128 bits) | totalMessagesInserted (64 bits) | inProgress (64 bits) - const currentRollingHash = currentStateValue & ((1n << 128n) - 1n); - const currentTotalMessages = (currentStateValue >> 128n) & ((1n << 64n) - 1n); - const currentInProgress = currentStateValue >> 192n; - const newInProgress = currentInProgress + BigInt(howMuch); - - // Pack new values: rollingHash (low 128 bits) | totalMessages (middle 64 bits) | inProgress (high 64 bits) - const newValue = (BigInt(newInProgress) << 192n) | (currentTotalMessages << 128n) | currentRollingHash; - - await this.ethCheatCodes.store(EthAddress.fromString(inboxAddress), inboxStateSlot, newValue, { - silent: true, - }); - - this.logger.warn(`Inbox inProgress advanced from ${currentInProgress} to ${newInProgress}`, { - inbox: inboxAddress, - oldValue: '0x' + currentStateValue.toString(16), - newValue: '0x' + newValue.toString(16), - rollingHash: currentRollingHash, - totalMessages: currentTotalMessages, - oldInProgress: currentInProgress, - newInProgress, - }); - - return newInProgress; - }); - } - public insertOutbox(epoch: EpochNumber, numCheckpointsInEpoch: number, outHash: bigint) { return this.ethCheatCodes.execWithPausedAnvil(async () => { const outboxAddress = await this.rollup.read.getOutbox(); From 6d011ea772b6fa4d2a577671bac800fc39ce5125 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Sat, 18 Jul 2026 22:10:48 -0300 Subject: [PATCH 03/10] chore(fast-inbox): drop unused cheatCodes from bot e2e after inbox-drift test removal (A-1386) Deleting the inbox-drift bot test left the suite's cheatCodes variable (and its import) unused, tripping lint. --- yarn-project/end-to-end/src/single-node/bot/bot.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/yarn-project/end-to-end/src/single-node/bot/bot.test.ts b/yarn-project/end-to-end/src/single-node/bot/bot.test.ts index 48831ebfebc4..b6b55845d16d 100644 --- a/yarn-project/end-to-end/src/single-node/bot/bot.test.ts +++ b/yarn-project/end-to-end/src/single-node/bot/bot.test.ts @@ -2,7 +2,6 @@ import { getInitialTestAccountsData } from '@aztec/accounts/testing'; import { Fr } from '@aztec/aztec.js/fields'; import type { AztecNode } from '@aztec/aztec.js/node'; import { MinedTxReceipt, type TxReceipt } from '@aztec/aztec.js/tx'; -import type { CheatCodes } from '@aztec/aztec/testing'; import { AmmBot, Bot, @@ -36,7 +35,6 @@ describe('single-node/bot/bot', () => { let aztecNode: AztecNode; let teardown: () => Promise; let aztecNodeAdmin: AztecNodeAdmin | undefined; - let cheatCodes: CheatCodes; let config: BotConfig; let l1RpcUrls: string[]; @@ -51,7 +49,6 @@ describe('single-node/bot/bot', () => { teardown, aztecNode, aztecNodeAdmin, - cheatCodes, config: { l1RpcUrls }, } = setupResult); wallet = await testSpan('setup:wallet', () => EmbeddedWallet.create(aztecNode, { ephemeral: true })); From 5d0534d54f5c6c24f23861ef8794f33a2e32a0c9 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Sun, 19 Jul 2026 15:01:38 -0300 Subject: [PATCH 04/10] fix(fast-inbox): drop leftover conflict markers in calldata retriever test (A-1386) --- yarn-project/archiver/src/l1/calldata_retriever.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/yarn-project/archiver/src/l1/calldata_retriever.test.ts b/yarn-project/archiver/src/l1/calldata_retriever.test.ts index 35dbd1b4179a..cc589173dbf9 100644 --- a/yarn-project/archiver/src/l1/calldata_retriever.test.ts +++ b/yarn-project/archiver/src/l1/calldata_retriever.test.ts @@ -369,11 +369,7 @@ describe('CalldataRetriever', () => { archive, oracleInput: { feeAssetPriceModifier }, header, -<<<<<<< HEAD - bucketHint: 0n, -======= bucketHint: BigInt(0), ->>>>>>> e74ca75577 (chore(fast-inbox): follow the L1 inbox cleanup through the TS clients (A-1386)) }, attestations, [], // signers From 4f1026217164242a558fdd03f9f62e1148653750 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Sun, 19 Jul 2026 17:45:32 -0300 Subject: [PATCH 05/10] docs(fast-inbox): drop checkpointNumber from the example Inbox ABIs (A-1386) The legacy L1 inbox cleanup removes checkpointNumber from the MessageSent event, changing its topic again; keep the hardcoded example ABIs in lockstep. --- docs/examples/ts/aave_bridge/index.ts | 1 - docs/examples/ts/example_swap/index.ts | 1 - docs/examples/ts/token_bridge/index.ts | 1 - 3 files changed, 3 deletions(-) diff --git a/docs/examples/ts/aave_bridge/index.ts b/docs/examples/ts/aave_bridge/index.ts index db53401fcc00..d274a5726ea4 100644 --- a/docs/examples/ts/aave_bridge/index.ts +++ b/docs/examples/ts/aave_bridge/index.ts @@ -347,7 +347,6 @@ const INBOX_ABI = [ type: "event", name: "MessageSent", inputs: [ - { name: "checkpointNumber", type: "uint256", indexed: true }, { name: "index", type: "uint256", indexed: false }, { name: "hash", type: "bytes32", indexed: true }, { name: "rollingHash", type: "bytes16", indexed: false }, diff --git a/docs/examples/ts/example_swap/index.ts b/docs/examples/ts/example_swap/index.ts index 099c8cea3878..f12f638034a2 100644 --- a/docs/examples/ts/example_swap/index.ts +++ b/docs/examples/ts/example_swap/index.ts @@ -259,7 +259,6 @@ const INBOX_ABI = [ type: "event", name: "MessageSent", inputs: [ - { name: "checkpointNumber", type: "uint256", indexed: true }, { name: "index", type: "uint256", indexed: false }, { name: "hash", type: "bytes32", indexed: true }, { name: "rollingHash", type: "bytes16", indexed: false }, diff --git a/docs/examples/ts/token_bridge/index.ts b/docs/examples/ts/token_bridge/index.ts index 02f8976bf7e7..aa5ea9c23de9 100644 --- a/docs/examples/ts/token_bridge/index.ts +++ b/docs/examples/ts/token_bridge/index.ts @@ -165,7 +165,6 @@ const INBOX_ABI = [ type: "event", name: "MessageSent", inputs: [ - { name: "checkpointNumber", type: "uint256", indexed: true }, { name: "index", type: "uint256", indexed: false }, { name: "hash", type: "bytes32", indexed: true }, { name: "rollingHash", type: "bytes16", indexed: false }, From 903b18557764ff7104a0450eee77a4bb2de1454d Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 20 Jul 2026 11:03:32 -0300 Subject: [PATCH 06/10] fix(fast-inbox): drop removed consume/getRoot/getInProgress from inbox docs (A-1386) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The legacy-inbox cleanup removed IInbox.consume(), getRoot(), getInProgress() and InboxState.inProgress, but inbox.md still documented them — the #include_code for consume broke the docs build (identifier not found) once the lint gate stopped masking it. Drop the stale view-function rows, the internal consume() section, and the in-progress mention in getState; the surviving send_l1_to_l2_message include still resolves. --- .../ethereum-aztec-messaging/inbox.md | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/docs/docs-developers/docs/foundational-topics/ethereum-aztec-messaging/inbox.md b/docs/docs-developers/docs/foundational-topics/ethereum-aztec-messaging/inbox.md index fcf43a73c32c..d3d818b629c5 100644 --- a/docs/docs-developers/docs/foundational-topics/ethereum-aztec-messaging/inbox.md +++ b/docs/docs-developers/docs/foundational-topics/ethereum-aztec-messaging/inbox.md @@ -35,34 +35,10 @@ These functions allow you to query the current state of the Inbox. | Function | Returns | Description | | -------------------------- | ----------------- | ------------------------------------------------ | -| `getRoot(uint256)` | `bytes32` | Returns the root of a message tree for a given checkpoint number. | -| `getState()` | `InboxState` | Returns the current inbox state (rolling hash, total messages inserted, in-progress checkpoint). | +| `getState()` | `InboxState` | Returns the current inbox state (rolling hash, total messages inserted). | | `getTotalMessagesInserted()` | `uint64` | Returns the total number of messages inserted into the inbox. | -| `getInProgress()` | `uint64` | Returns the checkpoint number currently being filled. | | `getFeeAssetPortal()` | `address` | Returns the address of the Fee Juice portal. | -## Internal functions - -:::note -The following functions are only callable by the Rollup contract and are documented here for completeness. -::: - -### `consume()` - -Consumes a message tree for a given checkpoint number. - -#include_code consume l1-contracts/src/core/interfaces/messagebridge/IInbox.sol solidity - -| Name | Type | Description | -| ----------- | --------- | ---------------------------------------- | -| _toConsume | `uint256` | The checkpoint number to consume. | -| ReturnValue | `bytes32` | The root of the consumed message tree. | - -#### Edge cases - -- Will revert with `Inbox__Unauthorized()` if `msg.sender != ROLLUP`. -- Will revert with `Inbox__MustBuildBeforeConsume()` if trying to consume a checkpoint that hasn't been built yet. - ## Related pages - [Outbox](./outbox.md) - L2 to L1 message passing From 97a8256778283211fe3842d47511853d8eff669c Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 21 Jul 2026 19:00:13 -0300 Subject: [PATCH 07/10] docs(fast-inbox): correct PublicInputArgs inbox rolling-hash validation comment (A-1386) --- l1-contracts/src/core/interfaces/IRollup.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 4f94b12be7d5..d702aca920d8 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -27,9 +27,10 @@ struct PublicInputArgs { bytes32 previousArchive; bytes32 endArchive; bytes32 outHash; - // Inbox rolling-hash chain segment consumed across the proven epoch (AZIP-22 Fast Inbox). Deliberately UNVALIDATED - // until the Fast Inbox flip, when they get checked against per-checkpoint records written at propose; for now they - // are only passed through to the proof's public inputs. + // Inbox rolling-hash chain segment consumed across the proven epoch (AZIP-22 Fast Inbox). previousInboxRollingHash + // is anchored at proof submission against the rolling hash recorded at propose for checkpoint start - 1; the end + // boundary is validated transitively, since the stored checkpoint header hashes (pinned by verifyHeaders) cover + // the rolling hash the epoch ends on. bytes32 previousInboxRollingHash; bytes32 endInboxRollingHash; address proverId; From c0875f8fd53c6670818c132f87b9cc25bc22740b Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 27 Jul 2026 13:49:45 -0300 Subject: [PATCH 08/10] docs(fast-inbox): PublicInputArgs both rolling-hash boundaries are checked (A-1386) The end boundary is now compared against the record written at propose, not only pinned transitively through the stored header hashes. --- l1-contracts/src/core/interfaces/IRollup.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index d702aca920d8..db5fa4a34e85 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -27,10 +27,8 @@ struct PublicInputArgs { bytes32 previousArchive; bytes32 endArchive; bytes32 outHash; - // Inbox rolling-hash chain segment consumed across the proven epoch (AZIP-22 Fast Inbox). previousInboxRollingHash - // is anchored at proof submission against the rolling hash recorded at propose for checkpoint start - 1; the end - // boundary is validated transitively, since the stored checkpoint header hashes (pinned by verifyHeaders) cover - // the rolling hash the epoch ends on. + // Inbox rolling-hash chain segment consumed across the proven epoch (AZIP-22 Fast Inbox). Both boundaries are + // anchored at proof submission against the rolling hashes recorded at propose for checkpoints start - 1 and end. bytes32 previousInboxRollingHash; bytes32 endInboxRollingHash; address proverId; From 6d19511008f10346086782dbf62c7b298b132546 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 27 Jul 2026 14:41:00 -0300 Subject: [PATCH 09/10] test(fast-inbox): fuzz the Inbox send path across L1 blocks and rollovers (A-1386) The legacy send-and-consume fuzz went away with the frontier trees, leaving the bucket suite fully deterministic apart from the single-message field fuzz. This adds randomized send-path coverage: message batches spread over several L1 blocks, with one batch per run exceeding the per-bucket cap so rollover is always exercised. Each run re-derives the whole ring from the returned message leaves and the per-block batch sizes, asserting the rolling-hash chain, the cumulative totals, the per-bucket cap, and that bucket boundaries track L1 blocks. Consume-side interleaving and ring wraparound stay out of scope. --- l1-contracts/test/InboxBucketsFuzz.t.sol | 199 +++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 l1-contracts/test/InboxBucketsFuzz.t.sol diff --git a/l1-contracts/test/InboxBucketsFuzz.t.sol b/l1-contracts/test/InboxBucketsFuzz.t.sol new file mode 100644 index 000000000000..11a5b826eeb1 --- /dev/null +++ b/l1-contracts/test/InboxBucketsFuzz.t.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {Test} from "forge-std/Test.sol"; +import {TestERC20} from "src/mock/TestERC20.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; +import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; +import {InboxHarness} from "./harnesses/InboxHarness.sol"; +import {TestConstants} from "./harnesses/TestConstants.sol"; +import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; +import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; +import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; + +/** + * Randomized coverage of the Inbox send path: batches of messages spread over several L1 blocks, with batches + * large enough to exceed the per-bucket cap and roll over. Every run re-derives the whole bucket ring from the + * message leaves the Inbox returned and the per-L1-block batch sizes, so an accumulation, rollover, or + * snapshot-boundary bug shows up as a mismatch against the model rather than as a missing assertion. The + * hand-written boundary cases live in InboxBuckets.t.sol; consumption and ring wraparound are out of scope. + */ +contract InboxBucketsFuzzTest is Test { + // Number of L1 blocks a multi-block run spans. Small enough that the bucket ring never wraps. + uint256 internal constant L1_BLOCKS = 6; + + /// Where one L1 block's batch of messages landed: the contiguous run of buckets it opened. + struct BlockBatch { + uint256 timestamp; + uint256 firstSeq; + uint256 lastSeq; + uint256 msgCount; + } + + InboxHarness internal inbox; + uint256 internal version = 0; + uint256 internal cap; + + // Every leaf the Inbox returned, in insertion order. The rolling-hash chain is recomputed from these. + bytes32[] internal leaves; + + // One entry per L1 block that sent at least one message, in block order. + BlockBatch[] internal batches; + + function setUp() public { + IERC20 feeAsset = new TestERC20("Fee Asset", "FA", address(this)); + inbox = new InboxHarness(address(this), feeAsset, version, TestConstants.AZTEC_INBOX_BUCKET_RING_SIZE); + cap = inbox.MAX_MSGS_PER_BUCKET(); + } + + /// forge-config: default.fuzz.runs = 128 + function testFuzzBucketsAcrossL1Blocks( + uint8[L1_BLOCKS] memory _msgsPerBlock, + uint16[L1_BLOCKS] memory _blockGaps, + uint8 _rolloverBlock, + uint16 _rolloverCount, + uint256 _seed + ) public { + // One block per run always crosses the per-bucket cap, so every run exercises a rollover alongside the + // ordinary same-block accumulation and new-block bucket opening. + uint256 rolloverBlock = bound(_rolloverBlock, 0, L1_BLOCKS - 1); + uint256 rolloverCount = bound(_rolloverCount, cap + 1, cap + 8); + + for (uint256 b = 0; b < L1_BLOCKS; b++) { + if (b > 0) { + vm.roll(block.number + 1); + vm.warp(block.timestamp + bound(_blockGaps[b], 1, 3600)); + } + _sendBlock(b == rolloverBlock ? rolloverCount : bound(_msgsPerBlock[b], 0, 12), _seed); + } + + _assertBucketRing(); + } + + /// forge-config: default.fuzz.runs = 32 + function testFuzzRolloverChainWithinOneL1Block(uint16 _msgCount, uint256 _seed) public { + // Several buckets' worth of messages in a single L1 block: each message arriving at a full bucket opens the + // next ring slot at the same timestamp, so the block owns a run of buckets rather than one. + _sendBlock(bound(_msgCount, cap + 1, 2 * cap + 5), _seed); + _assertBucketRing(); + } + + // Sends `_count` messages in the current L1 block and checks what the batch did to the ring: it opened one + // bucket per cap-sized slice, every bucket it opened carries this block's timestamp, and the bucket the + // previous L1 block left behind is frozen. + function _sendBlock(uint256 _count, uint256 _seed) internal { + uint256 seqBefore = inbox.getCurrentBucketSeq(); + IInbox.InboxBucket memory frozen = inbox.getBucket(seqBefore); + uint256 totalBefore = leaves.length; + + for (uint256 i = 0; i < _count; i++) { + _send(_seed); + } + + uint256 seqAfter = inbox.getCurrentBucketSeq(); + assertEq(seqAfter, seqBefore + (_count + cap - 1) / cap, "buckets opened by this L1 block"); + _assertBucketEq(inbox.getBucket(seqBefore), frozen, "bucket left by the previous L1 block is frozen"); + + if (_count == 0) { + return; + } + + for (uint256 seq = seqBefore + 1; seq <= seqAfter; seq++) { + IInbox.InboxBucket memory bucket = inbox.getBucket(seq); + uint256 sliceStart = (seq - seqBefore - 1) * cap; + uint256 sliceSize = _count - sliceStart < cap ? _count - sliceStart : cap; + assertEq(bucket.msgCount, sliceSize, "messages absorbed into bucket"); + assertEq(bucket.timestamp, block.timestamp, "bucket carries the sending L1 block's timestamp"); + assertEq(bucket.totalMsgCount, totalBefore + sliceStart + sliceSize, "bucket cumulative total"); + } + + batches.push(BlockBatch({timestamp: block.timestamp, firstSeq: seqBefore + 1, lastSeq: seqAfter, msgCount: _count})); + } + + // Sends one message with fuzz-derived contents, checking the Inbox agrees on its leaf and compact index. + function _send(uint256 _seed) internal { + uint256 index = leaves.length; + DataStructures.L2Actor memory recipient = DataStructures.L2Actor({actor: _field(_seed, index, 0), version: version}); + bytes32 content = _field(_seed, index, 1); + bytes32 secretHash = _field(_seed, index, 2); + + (bytes32 leaf, uint256 insertedIndex) = inbox.sendL2Message(recipient, content, secretHash); + + // The leaf commits to the message and to the compact index the Inbox assigned it, so recomputing it from the + // sent contents pins both, and the chain recomputed from these leaves is anchored to the message stream. + bytes32 expectedLeaf = Hash.sha256ToField( + DataStructures.L1ToL2Msg({ + sender: DataStructures.L1Actor(address(this), block.chainid), + recipient: recipient, + content: content, + secretHash: secretHash, + index: index + }) + ); + assertEq(insertedIndex, index, "compact message index"); + assertEq(leaf, expectedLeaf, "message leaf"); + + leaves.push(leaf); + } + + // Re-derives every bucket in the ring from the recorded leaves and per-L1-block batches. + function _assertBucketRing() internal { + uint256 current = inbox.getCurrentBucketSeq(); + bytes32 rollingHash = 0; + uint256 counted = 0; + uint256 previousTimestamp = 0; + + for (uint256 seq = 1; seq <= current; seq++) { + IInbox.InboxBucket memory bucket = inbox.getBucket(seq); + assertGt(bucket.msgCount, 0, "bucket holds at least one message"); + assertLe(bucket.msgCount, cap, "per-bucket cap"); + + for (uint256 i = 0; i < bucket.msgCount; i++) { + rollingHash = Hash.accumulateInboxRollingHash(rollingHash, leaves[counted + i]); + } + counted += bucket.msgCount; + + assertEq(bucket.rollingHash, rollingHash, "bucket rolling hash recomputed from the message leaves"); + assertEq(bucket.totalMsgCount, counted, "cumulative total is the sum of the per-bucket counts"); + assertGe(bucket.timestamp, previousTimestamp, "bucket timestamps never go backwards"); + previousTimestamp = bucket.timestamp; + } + + assertEq(counted, leaves.length, "every message sent is in a bucket"); + assertEq(inbox.getTotalMessagesInserted(), leaves.length, "inbox total matches messages sent"); + + // Bucket boundaries align with L1 blocks: each block owns a contiguous run of buckets holding exactly the + // messages it sent, all stamped with its own timestamp, so no later block extended an earlier block's bucket. + uint256 expectedFirstSeq = 1; + for (uint256 b = 0; b < batches.length; b++) { + BlockBatch memory batch = batches[b]; + assertEq(batch.firstSeq, expectedFirstSeq, "L1 block's buckets follow the previous block's"); + + uint256 batchCount = 0; + for (uint256 seq = batch.firstSeq; seq <= batch.lastSeq; seq++) { + IInbox.InboxBucket memory bucket = inbox.getBucket(seq); + assertEq(bucket.timestamp, batch.timestamp, "bucket timestamp is its L1 block's"); + batchCount += bucket.msgCount; + } + + assertEq(batchCount, batch.msgCount, "L1 block's messages are all in that block's buckets"); + expectedFirstSeq = batch.lastSeq + 1; + } + assertEq(expectedFirstSeq - 1, current, "no buckets outside the recorded L1 blocks"); + } + + function _assertBucketEq(IInbox.InboxBucket memory _actual, IInbox.InboxBucket memory _expected, string memory _err) + internal + { + assertEq(_actual.rollingHash, _expected.rollingHash, _err); + assertEq(_actual.totalMsgCount, _expected.totalMsgCount, _err); + assertEq(_actual.timestamp, _expected.timestamp, _err); + assertEq(_actual.msgCount, _expected.msgCount, _err); + } + + // Fuzz-derived field element, distinct per message and per message field. + function _field(uint256 _seed, uint256 _index, uint256 _tag) internal pure returns (bytes32) { + return bytes32(uint256(keccak256(abi.encodePacked(_seed, _index, _tag))) % Constants.P); + } +} From 7239dcb07e788aec7af66deb3805094beba494da Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 27 Jul 2026 19:42:50 -0300 Subject: [PATCH 10/10] test(fast-inbox): read the per-bucket cap from the shared constant (A-1386) The cap moved to file scope in Inbox.sol so the propose-side settled-bucket check can share one definition with the Inbox; the fuzz harness still read it through the removed contract getter. --- l1-contracts/test/InboxBucketsFuzz.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/l1-contracts/test/InboxBucketsFuzz.t.sol b/l1-contracts/test/InboxBucketsFuzz.t.sol index 11a5b826eeb1..7378a1a8e6c2 100644 --- a/l1-contracts/test/InboxBucketsFuzz.t.sol +++ b/l1-contracts/test/InboxBucketsFuzz.t.sol @@ -5,7 +5,7 @@ pragma solidity >=0.8.27; import {Test} from "forge-std/Test.sol"; import {TestERC20} from "src/mock/TestERC20.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; -import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; +import {IInbox, MAX_MSGS_PER_BUCKET} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; import {InboxHarness} from "./harnesses/InboxHarness.sol"; import {TestConstants} from "./harnesses/TestConstants.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; @@ -44,7 +44,7 @@ contract InboxBucketsFuzzTest is Test { function setUp() public { IERC20 feeAsset = new TestERC20("Fee Asset", "FA", address(this)); inbox = new InboxHarness(address(this), feeAsset, version, TestConstants.AZTEC_INBOX_BUCKET_RING_SIZE); - cap = inbox.MAX_MSGS_PER_BUCKET(); + cap = MAX_MSGS_PER_BUCKET; } /// forge-config: default.fuzz.runs = 128