Skip to content

Commit 1c18728

Browse files
committed
feat(fast-inbox): rolling-hash buckets in the Inbox alongside frontier trees (A-1377)
AZIP-22 Fast Inbox, FI-07. First L1 PR of the series; a follow-up stacks the `propose`-side streaming validation on top of this branch. Stacked on #24781 (`spl/a-1432-same-block-msgs`), the tip of the Fast Inbox circuits stack (#24587#24600#24603#24612#24759#24781). That stack already carries the small L1 changes this feature builds on — the header's `inboxRollingHash` field, the two epoch-proof public inputs, and the regenerated checkpoint fixtures — so this PR adds only the bucket machinery on top. ## What `sendL2Message` additionally maintains the **consensus rolling hash** — the truncated-per-link sha256 chain the circuits recompute (`h' = sha256ToField(h || leaf)`, genesis zero; new `Hash.accumulateInboxRollingHash`) — and snapshots it into a fixed-size ring of **buckets**. Frontier trees, `LAG`, the legacy `consume()` flow, and the legacy `bytes16` keccak rolling hash are untouched (the legacy hash now carries a TODO to remove it at cleanup); buckets are completely inert for the legacy flow. Per the pinned design decisions: - **Bucket struct** `{rollingHash | totalMsgCount: uint64, timestamp: uint64, msgCount: uint32}` packs into two slots; `totalMsgCount` is the Inbox-wide cumulative count (what the censorship cap-escape check reads), `timestamp` is the bucket's opening L1 block timestamp (recency checks are done in seconds), `msgCount` sits in slot 2's spare bits so the per-bucket cap costs no extra storage access. - **Ring** indexed by dense bucket sequence number (`seq % BUCKET_RING_SIZE`), 1024 entries in production, sized as an immutable constructor parameter. `getBucket(seq)` reverts with `Inbox__BucketOutOfWindow` outside the live window (`seq <= current < seq + ringSize`, same idiom as the Rollup's roundabout). Overwrite protection for unconsumed buckets is deliberately **not** enforced yet (happy path assumes the ring never wraps into live data). - **Bucket boundaries**: a bucket only holds messages from a single L1 block; the first message of a new block opens the next bucket, and the 257th message within one block (`MAX_MSGS_PER_BUCKET = 256`, the post-flip per-L2-block insertion cap) **rolls over** into the next bucket with the same timestamp. New buckets inherit the rolling hash and cumulative count, so the chain is continuous across buckets. - **Genesis base case**: bucket 0 is `{0, 0, deployTime}` and never absorbs (even for a message sent in the deployment block), so a checkpoint consuming nothing against an empty Inbox can always reference a bucket matching its parent's chain position — no special case at `propose`. - **Event**: `MessageSent` gains `bytes32 inboxRollingHash` and `uint256 bucketSeq` after the legacy args. ## Archiver / TS The generated `InboxAbi` picks up the new event signature at build time; `MessageSentArgs` and the decode in `ethereum/src/contracts/inbox.ts` carry the two new fields, and the archiver keeps relying only on the legacy args for now (`InboxMessage` unchanged). The archiver test fake fills the new fields with placeholders. Nothing on this branch consumes the new values yet — the node-side cross-check of its TS rolling hash against L1-emitted values comes with the archiver bucket-sync work. ## Testing - New `InboxBuckets.t.sol` covers the roadmap done-when: accumulation within a block, snapshot freezing at block boundaries with chain continuity into the next bucket, 256-cap rollover within one block, ring wraparound + out-of-window reverts on a small ring, the genesis bucket (including a first message in the deploy block), and event contents. - **Shared rolling-hash test vectors** pinned in #24587's noir tests (`chain(0,[11])`, `chain(0,[11,22,33])`, `chain(0,[1..=256])`, `chain(0x2a,[7])`, `chain(0x2a,[7,8])`) are asserted against the L1 implementation, so L1, TS, and the circuits provably compute the same chain. - `forge test` at the current stack top: 890 passed, 0 failed, 3 skipped (the `testGetEpochProofPublicInputsVerifiesHeaders` failure previously inherited from the base has been fixed by the rebased base branches). The 13 `InboxBuckets.t.sol` tests all pass (9 behavioral + 4 gas measurements). Existing `MessageSent` expectations in Inbox/TokenPortal/FeeJuicePortal tests updated for the extended event. - TS: the regenerated `@aztec/l1-artifacts` `InboxAbi` picks up the two new event fields, and `@aztec/ethereum` typechecks clean against it. `@aztec/archiver`'s full typecheck is blocked only by stale Noir-generated artifacts in a transitive dependency (base-branch circuit changes that require the Noir toolchain, not run in this workspace), so it runs in CI; the archiver change is decode-only and mechanical. ## Gas (`sendL2Message`) Four Forge gas measurements in `InboxBuckets.t.sol` cover the bucket write paths (inline `gasleft()` deltas, matching the `RollupGetters.t.sol` convention). These feed the capacity analysis (max messages per L1 block from gas): | Scenario | Gas | |---|---| | Absorb into an already-open bucket (common per-message case) | 34,468 | | First message of a new L1 block (opens a bucket via timestamp) | 78,958 | | Rollover opening mid-block (256-cap reached, same timestamp) | 55,593 | | First-ever message (cold state struct + bucket 1) | 128,118 | Caveat for downstream use: these are **warm execution gas including the CALL overhead** from the test harness. They exclude the 21k intrinsic tx cost, calldata gas, and the cold-access surcharge a standalone EOA transaction pays on its first touch of each slot — the capacity analysis must add those separately. The first-ever figure is the cold-storage case for the bucket/state slots, **not** the global worst-case insert: later frontier-tree leaf indices with more levels to hash can cost more. Also documents the timestamp-key assumption in `_absorbIntoBucket` (post-merge Ethereum increases `block.timestamp` strictly per block; anvil manual-mining can collapse two blocks into one bucket, which is harmless because the consumption cutoff is timestamp-based). ## Ring-size floor Raises the constructor guard from `_bucketRingSize > 1` to a floor of **512** (`MIN_BUCKET_RING_SIZE`); production stays at 1024. Rationale: the ring must cover the longest stall the chain recovers from on its own — the prune-and-repropose window of 64 checkpoints (2 epochs = 384 L1 blocks) at the natural one-bucket-per-L1-block cadence, so buckets it re-consumes after a prune are not overwritten first — 384 rounded up to the next power of two, kept at or below the production ring. `testRingWraparound` now exercises a real wraparound against a 512-slot ring, and a new `testConstructorRevertsBelowRingFloor` asserts that constructing below the floor reverts. The full capacity/liveness analysis behind this number feeds the AZIP-22 review. Replaces #24771.
1 parent 07de2ad commit 1c18728

16 files changed

Lines changed: 494 additions & 13 deletions

File tree

docs/examples/ts/aave_bridge/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,8 @@ const INBOX_ABI = [
351351
{ name: "index", type: "uint256", indexed: false },
352352
{ name: "hash", type: "bytes32", indexed: true },
353353
{ name: "rollingHash", type: "bytes16", indexed: false },
354+
{ name: "inboxRollingHash", type: "bytes32", indexed: false },
355+
{ name: "bucketSeq", type: "uint256", indexed: false },
354356
],
355357
},
356358
] as const;

docs/examples/ts/example_swap/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,8 @@ const INBOX_ABI = [
263263
{ name: "index", type: "uint256", indexed: false },
264264
{ name: "hash", type: "bytes32", indexed: true },
265265
{ name: "rollingHash", type: "bytes16", indexed: false },
266+
{ name: "inboxRollingHash", type: "bytes32", indexed: false },
267+
{ name: "bucketSeq", type: "uint256", indexed: false },
266268
],
267269
},
268270
] as const;

docs/examples/ts/token_bridge/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ const INBOX_ABI = [
169169
{ name: "index", type: "uint256", indexed: false },
170170
{ name: "hash", type: "bytes32", indexed: true },
171171
{ name: "rollingHash", type: "bytes16", indexed: false },
172+
{ name: "inboxRollingHash", type: "bytes32", indexed: false },
173+
{ name: "bucketSeq", type: "uint256", indexed: false },
172174
],
173175
},
174176
] as const;

l1-contracts/src/core/RollupCore.sol

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {ProposeArgs} from "@aztec/core/libraries/rollup/ProposeLib.sol";
2727
import {STFLib, GenesisState} from "@aztec/core/libraries/rollup/STFLib.sol";
2828
import {StakingLib} from "@aztec/core/libraries/rollup/StakingLib.sol";
2929
import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
30-
import {Inbox} from "@aztec/core/messagebridge/Inbox.sol";
30+
import {Inbox, INBOX_BUCKET_RING_SIZE} from "@aztec/core/messagebridge/Inbox.sol";
3131
import {Outbox} from "@aztec/core/messagebridge/Outbox.sol";
3232
import {ISlasher} from "@aztec/core/slashing/Slasher.sol";
3333
import {GSE} from "@aztec/governance/GSE.sol";
@@ -621,7 +621,14 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
621621

622622
IInbox inbox = IInbox(
623623
address(
624-
new Inbox(address(this), _feeAsset, _config.version, Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT, _config.inboxLag)
624+
new Inbox(
625+
address(this),
626+
_feeAsset,
627+
_config.version,
628+
Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT,
629+
_config.inboxLag,
630+
INBOX_BUCKET_RING_SIZE
631+
)
625632
)
626633
);
627634

l1-contracts/src/core/interfaces/messagebridge/IInbox.sol

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ interface IInbox {
1313
struct InboxState {
1414
// Rolling hash of all messages inserted into the inbox.
1515
// Used by clients to check for consistency.
16+
// TODO: remove once the streaming inbox (AZIP-22 Fast Inbox) flips on and clients rely on the
17+
// consensus rolling hash tracked in the buckets instead.
1618
bytes16 rollingHash;
1719
// This value is not used much by the contract, but it is useful for synching the node faster
1820
// as it can more easily figure out if it can just skip looking for events for a time period.
@@ -21,14 +23,43 @@ interface IInbox {
2123
uint64 inProgress;
2224
}
2325

26+
/**
27+
* @notice Snapshot of the consensus rolling hash over the messages inserted into the Inbox, stored in a
28+
* fixed-size ring indexed by a dense bucket sequence number (`seq % ringSize`). A bucket only accumulates
29+
* messages sent within a single L1 block, so its final state is the chain position as of the end of that
30+
* block; the censorship check at `propose` compares the checkpoint header's rolling hash against these
31+
* snapshots (AZIP-22 Fast Inbox).
32+
*/
33+
struct InboxBucket {
34+
// Rolling hash after the last message absorbed into this bucket. Each link is
35+
// `sha256ToField(previousRollingHash || leaf)`; the genesis value is zero.
36+
bytes32 rollingHash;
37+
// Cumulative number of messages inserted into the Inbox up to and including this bucket.
38+
uint64 totalMsgCount;
39+
// L1 block timestamp at which this bucket was opened. Recency comparisons (message lag,
40+
// censorship cutoff) are done in seconds against this value.
41+
uint64 timestamp;
42+
// Number of messages absorbed into this bucket, capped at the per-bucket maximum.
43+
uint32 msgCount;
44+
}
45+
2446
/**
2547
* @notice Emitted when a message is sent
2648
* @param checkpointNumber - The checkpoint number in which the message is included
2749
* @param index - The index of the message in the L1 to L2 messages tree
2850
* @param hash - The hash of the message
2951
* @param rollingHash - The rolling hash of all messages inserted into the inbox
52+
* @param inboxRollingHash - The consensus rolling hash (truncated sha256 chain) after this message
53+
* @param bucketSeq - The sequence number of the bucket this message was absorbed into
3054
*/
31-
event MessageSent(uint256 indexed checkpointNumber, uint256 index, bytes32 indexed hash, bytes16 rollingHash);
55+
event MessageSent(
56+
uint256 indexed checkpointNumber,
57+
uint256 index,
58+
bytes32 indexed hash,
59+
bytes16 rollingHash,
60+
bytes32 inboxRollingHash,
61+
uint256 bucketSeq
62+
);
3263

3364
// docs:start:send_l1_to_l2_message
3465
/**
@@ -68,4 +99,18 @@ interface IInbox {
6899
function getTotalMessagesInserted() external view returns (uint64);
69100

70101
function getInProgress() external view returns (uint64);
102+
103+
/**
104+
* @notice Returns the sequence number of the bucket currently accumulating messages
105+
* @return The current bucket sequence number
106+
*/
107+
function getCurrentBucketSeq() external view returns (uint64);
108+
109+
/**
110+
* @notice Returns the bucket with the given sequence number
111+
* @dev Reverts if the bucket is ahead of the current one or has already been overwritten in the ring
112+
* @param _seq - The bucket sequence number
113+
* @return The bucket
114+
*/
115+
function getBucket(uint256 _seq) external view returns (InboxBucket memory);
71116
}

l1-contracts/src/core/libraries/Errors.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ library Errors {
2828
error Inbox__ContentTooLarge(bytes32 content); // 0x47452014
2929
error Inbox__SecretHashTooLarge(bytes32 secretHash); // 0xecde7e2c
3030
error Inbox__MustBuildBeforeConsume(); // 0xc4901999
31+
error Inbox__BucketOutOfWindow(uint256 seq, uint256 current); // 0xfee255b7
3132

3233
// Outbox
3334
error Outbox__Unauthorized(); // 0x2c9490c2

l1-contracts/src/core/libraries/crypto/Hash.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,16 @@ library Hash {
4949
function sha256ToField(bytes memory _data) internal pure returns (bytes32) {
5050
return bytes32(bytes.concat(new bytes(1), bytes31(sha256(_data))));
5151
}
52+
53+
/**
54+
* @notice Advances the Inbox consensus rolling hash by one message leaf
55+
* @dev Truncated at every link so the value is always a field element; the rollup circuits recompute the
56+
* identical chain over the message leaves they insert (AZIP-22 Fast Inbox). The genesis value is zero.
57+
* @param _rollingHash - The current rolling hash
58+
* @param _leaf - The message leaf to absorb
59+
* @return The updated rolling hash
60+
*/
61+
function accumulateInboxRollingHash(bytes32 _rollingHash, bytes32 _leaf) internal pure returns (bytes32) {
62+
return sha256ToField(abi.encodePacked(_rollingHash, _leaf));
63+
}
5264
}

l1-contracts/src/core/messagebridge/Inbox.sol

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ import {FeeJuicePortal} from "@aztec/core/messagebridge/FeeJuicePortal.sol";
1313
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
1414
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
1515

16+
// Number of buckets in the rolling-hash ring. Sized far beyond normal consumption lag (the censorship
17+
// cutoff bounds it to roughly one build frame); outages longer than the ring are handled by overwrite
18+
// protection on unconsumed buckets, not by growing the ring.
19+
uint256 constant INBOX_BUCKET_RING_SIZE = 1024;
20+
21+
// Constructor floor for the bucket ring. The ring must cover the longest stall the chain recovers from on
22+
// its own: the prune-and-repropose window of 64 checkpoints (2 epochs = 384 L1 blocks) at the natural cadence
23+
// of one bucket per L1 block, so the buckets it must re-consume after a prune have not been overwritten. 384
24+
// rounded up to the next power of two, kept at or below the production ring.
25+
uint256 constant MIN_BUCKET_RING_SIZE = 512;
26+
1627
/**
1728
* @title Inbox
1829
* @author Aztec Labs
@@ -23,12 +34,19 @@ contract Inbox is IInbox {
2334
using FrontierLib for FrontierLib.Forest;
2435
using FrontierLib for FrontierLib.Tree;
2536

37+
// Maximum number of messages a single bucket can hold before further messages in the same L1 block
38+
// spill over into the next bucket. Matches the number of L1 to L2 messages a single L2 block can
39+
// insert once the streaming inbox is live, so any one bucket is always consumable by one block.
40+
uint256 public constant MAX_MSGS_PER_BUCKET = 256;
41+
2642
address public immutable ROLLUP;
2743
uint256 public immutable VERSION;
2844
address public immutable FEE_ASSET_PORTAL;
2945

3046
uint256 public immutable LAG;
3147

48+
uint256 public immutable BUCKET_RING_SIZE;
49+
3250
uint256 internal immutable HEIGHT;
3351
uint256 internal immutable SIZE;
3452
bytes32 internal immutable EMPTY_ROOT; // The root of an empty frontier tree
@@ -40,7 +58,20 @@ contract Inbox is IInbox {
4058

4159
InboxState internal state;
4260

43-
constructor(address _rollup, IERC20 _feeAsset, uint256 _version, uint256 _height, uint256 _lag) {
61+
// Ring of rolling-hash buckets, keyed by `bucketSeq % BUCKET_RING_SIZE`. Inert for the legacy
62+
// frontier-tree flow; consumed by the streaming inbox checks at `propose` (AZIP-22 Fast Inbox).
63+
mapping(uint256 ringIndex => InboxBucket bucket) internal buckets;
64+
65+
uint64 internal currentBucketSeq;
66+
67+
constructor(
68+
address _rollup,
69+
IERC20 _feeAsset,
70+
uint256 _version,
71+
uint256 _height,
72+
uint256 _lag,
73+
uint256 _bucketRingSize
74+
) {
4475
ROLLUP = _rollup;
4576
VERSION = _version;
4677

@@ -50,10 +81,18 @@ contract Inbox is IInbox {
5081
require(_lag > 0, "LAG TOO SMALL");
5182
LAG = _lag;
5283

84+
require(_bucketRingSize >= MIN_BUCKET_RING_SIZE, "BUCKET RING TOO SMALL");
85+
BUCKET_RING_SIZE = _bucketRingSize;
86+
5387
state = InboxState({
5488
rollingHash: 0, totalMessagesInserted: 0, inProgress: SafeCast.toUint64(Constants.INITIAL_CHECKPOINT_NUMBER + LAG)
5589
});
5690

91+
// Genesis bucket: a checkpoint consuming no messages references the same bucket as its parent, so the
92+
// first checkpoint against an empty Inbox references this one and no base case leaks into `propose`.
93+
buckets[0] =
94+
InboxBucket({rollingHash: 0, totalMsgCount: 0, timestamp: SafeCast.toUint64(block.timestamp), msgCount: 0});
95+
5796
forest.initialize(_height);
5897
EMPTY_ROOT = trees[type(uint256).max].root(forest, HEIGHT, SIZE);
5998

@@ -122,7 +161,9 @@ contract Inbox is IInbox {
122161
rollingHash: updatedRollingHash, totalMessagesInserted: totalMessagesInserted + 1, inProgress: inProgress
123162
});
124163

125-
emit MessageSent(inProgress, index, leaf, updatedRollingHash);
164+
(uint64 bucketSeq, bytes32 inboxRollingHash) = _absorbIntoBucket(leaf);
165+
166+
emit MessageSent(inProgress, index, leaf, updatedRollingHash, inboxRollingHash, bucketSeq);
126167

127168
return (leaf, index);
128169
}
@@ -177,4 +218,55 @@ contract Inbox is IInbox {
177218
function getInProgress() external view override(IInbox) returns (uint64) {
178219
return state.inProgress;
179220
}
221+
222+
function getCurrentBucketSeq() external view override(IInbox) returns (uint64) {
223+
return currentBucketSeq;
224+
}
225+
226+
function getBucket(uint256 _seq) external view override(IInbox) returns (InboxBucket memory) {
227+
uint256 current = currentBucketSeq;
228+
require(_seq <= current && current - _seq < BUCKET_RING_SIZE, Errors.Inbox__BucketOutOfWindow(_seq, current));
229+
return buckets[_seq % BUCKET_RING_SIZE];
230+
}
231+
232+
/**
233+
* @notice Absorbs a message leaf into the consensus rolling hash and snapshots it into the bucket ring
234+
*
235+
* @dev A bucket only holds messages from a single L1 block, up to MAX_MSGS_PER_BUCKET; the first message
236+
* of a new L1 block — or the message after a full bucket, spilling over within the same block — opens the
237+
* next bucket, inheriting the rolling hash and cumulative count. Bucket 0 is the pristine genesis base
238+
* case and never absorbs. Opening a bucket overwrites the ring entry from BUCKET_RING_SIZE buckets ago;
239+
* protection against overwriting unconsumed buckets is not enforced yet.
240+
*
241+
* @param _leaf - The message leaf to absorb
242+
*
243+
* @return The sequence number of the bucket the leaf was absorbed into and the updated rolling hash
244+
*/
245+
function _absorbIntoBucket(bytes32 _leaf) internal returns (uint64, bytes32) {
246+
uint64 bucketSeq = currentBucketSeq;
247+
InboxBucket memory bucket = buckets[bucketSeq % BUCKET_RING_SIZE];
248+
249+
// Buckets are keyed by L1 block timestamp: a strictly larger timestamp opens a new bucket (a full bucket
250+
// also rolls over within the same block). Post-merge Ethereum increases block.timestamp strictly per block,
251+
// so messages from different L1 blocks always land in different buckets. Under anvil with manual mining two
252+
// blocks can share a timestamp and therefore a bucket; this is harmless because the consumption cutoff is
253+
// computed over timestamps, so co-timestamped blocks are indistinguishable to it.
254+
if (bucketSeq == 0 || bucket.timestamp < block.timestamp || bucket.msgCount == MAX_MSGS_PER_BUCKET) {
255+
bucketSeq += 1;
256+
currentBucketSeq = bucketSeq;
257+
bucket = InboxBucket({
258+
rollingHash: bucket.rollingHash,
259+
totalMsgCount: bucket.totalMsgCount,
260+
timestamp: SafeCast.toUint64(block.timestamp),
261+
msgCount: 0
262+
});
263+
}
264+
265+
bucket.rollingHash = Hash.accumulateInboxRollingHash(bucket.rollingHash, _leaf);
266+
bucket.totalMsgCount += 1;
267+
bucket.msgCount += 1;
268+
buckets[bucketSeq % BUCKET_RING_SIZE] = bucket;
269+
270+
return (bucketSeq, bucket.rollingHash);
271+
}
180272
}

l1-contracts/test/Inbox.t.sol

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ contract InboxTest is Test {
3030
function setUp() public {
3131
address rollup = address(this);
3232
IERC20 feeAsset = new TestERC20("Fee Asset", "FA", address(this));
33-
inbox = new InboxHarness(rollup, feeAsset, version, HEIGHT, TestConstants.AZTEC_INBOX_LAG);
33+
inbox = new InboxHarness(
34+
rollup, feeAsset, version, HEIGHT, TestConstants.AZTEC_INBOX_LAG, TestConstants.AZTEC_INBOX_BUCKET_RING_SIZE
35+
);
3436
emptyTreeRoot = inbox.getEmptyRoot();
3537
}
3638

@@ -96,9 +98,12 @@ contract InboxTest is Test {
9698

9799
bytes32 leaf = message.sha256ToField();
98100
bytes16 expectedRollingHash = bytes16(keccak256(abi.encodePacked(stateBefore.rollingHash, leaf)));
101+
bytes32 expectedInboxRollingHash = Hash.accumulateInboxRollingHash(bytes32(0), leaf);
99102
vm.expectEmit(true, true, true, true);
100103
// event we expect
101-
emit IInbox.MessageSent(FIRST_REAL_TREE_NUM, globalLeafIndex, leaf, expectedRollingHash);
104+
emit IInbox.MessageSent(
105+
FIRST_REAL_TREE_NUM, globalLeafIndex, leaf, expectedRollingHash, expectedInboxRollingHash, 1
106+
);
102107
// event we will get
103108
(bytes32 insertedLeaf, uint256 insertedIndex) =
104109
inbox.sendL2Message(message.recipient, message.content, message.secretHash);

0 commit comments

Comments
 (0)