Skip to content

Commit 8a56dfa

Browse files
committed
feat(fast-inbox): enforce a minimum Inbox bucket-ring size of 512 (A-1377)
Raise the constructor guard from `_bucketRingSize > 1` to a floor of 512. 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 re-consumed after a prune are not overwritten first. 384 rounded up to the next power of two, kept at or below the production ring of 1024. Rework testRingWraparound to exercise a real wraparound against a 512-slot ring and add a negative test asserting construction below the floor reverts.
1 parent f7f96e0 commit 8a56dfa

2 files changed

Lines changed: 28 additions & 15 deletions

File tree

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ import {SafeCast} from "@oz/utils/math/SafeCast.sol";
1818
// protection on unconsumed buckets, not by growing the ring.
1919
uint256 constant INBOX_BUCKET_RING_SIZE = 1024;
2020

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 buckets re-consumed after a prune are not overwritten first. 384 rounded up
24+
// to the next power of two, kept at or below the production ring.
25+
uint256 constant MIN_BUCKET_RING_SIZE = 512;
26+
2127
/**
2228
* @title Inbox
2329
* @author Aztec Labs
@@ -75,7 +81,7 @@ contract Inbox is IInbox {
7581
require(_lag > 0, "LAG TOO SMALL");
7682
LAG = _lag;
7783

78-
require(_bucketRingSize > 1, "BUCKET RING TOO SMALL");
84+
require(_bucketRingSize >= MIN_BUCKET_RING_SIZE, "BUCKET RING TOO SMALL");
7985
BUCKET_RING_SIZE = _bucketRingSize;
8086

8187
state = InboxState({

l1-contracts/test/InboxBuckets.t.sol

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {Test} from "forge-std/Test.sol";
66
import {TestERC20} from "src/mock/TestERC20.sol";
77
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
88
import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol";
9+
import {MIN_BUCKET_RING_SIZE} from "@aztec/core/messagebridge/Inbox.sol";
910
import {InboxHarness} from "./harnesses/InboxHarness.sol";
1011
import {TestConstants} from "./harnesses/TestConstants.sol";
1112
import {Constants} from "@aztec/core/libraries/ConstantsGen.sol";
@@ -16,7 +17,6 @@ import {DataStructures} from "@aztec/core/libraries/DataStructures.sol";
1617
contract InboxBucketsTest is Test {
1718
uint256 internal constant FIRST_REAL_TREE_NUM = Constants.INITIAL_CHECKPOINT_NUMBER + TestConstants.AZTEC_INBOX_LAG;
1819
uint256 internal constant HEIGHT = 10;
19-
uint256 internal constant SMALL_RING_SIZE = 4;
2020

2121
InboxHarness internal inbox;
2222
uint256 internal version = 0;
@@ -194,39 +194,46 @@ contract InboxBucketsTest is Test {
194194
}
195195

196196
function testRingWraparound() public {
197-
InboxHarness smallRingInbox = _deployInbox(SMALL_RING_SIZE);
197+
InboxHarness ringInbox = _deployInbox(MIN_BUCKET_RING_SIZE);
198198
expectedRollingHash = 0;
199199

200-
// One bucket per L1 block; after SMALL_RING_SIZE + 1 buckets the ring has wrapped past bucket 1.
201-
for (uint256 i = 1; i <= SMALL_RING_SIZE + 1; i++) {
200+
// One bucket per L1 block; after MIN_BUCKET_RING_SIZE + 1 buckets the ring has wrapped past bucket 1.
201+
for (uint256 i = 1; i <= MIN_BUCKET_RING_SIZE + 1; i++) {
202202
vm.roll(block.number + 1);
203203
vm.warp(block.timestamp + 12);
204-
_send(smallRingInbox, i);
204+
_send(ringInbox, i);
205205
}
206206

207-
uint256 current = smallRingInbox.getCurrentBucketSeq();
208-
assertEq(current, SMALL_RING_SIZE + 1, "one bucket per block");
207+
uint256 current = ringInbox.getCurrentBucketSeq();
208+
assertEq(current, MIN_BUCKET_RING_SIZE + 1, "one bucket per block");
209209

210-
// Buckets 0 and 1 have been overwritten (their ring slots were reused by buckets 4 and 5).
210+
// Buckets 0 and 1 have been overwritten: their ring slots were reused by buckets
211+
// MIN_BUCKET_RING_SIZE and MIN_BUCKET_RING_SIZE + 1.
211212
vm.expectRevert(abi.encodeWithSelector(Errors.Inbox__BucketOutOfWindow.selector, 0, current));
212-
smallRingInbox.getBucket(0);
213+
ringInbox.getBucket(0);
213214
vm.expectRevert(abi.encodeWithSelector(Errors.Inbox__BucketOutOfWindow.selector, 1, current));
214-
smallRingInbox.getBucket(1);
215+
ringInbox.getBucket(1);
215216

216217
// The live window is intact, with per-bucket data at the right ring slots.
217218
uint64 previousTimestamp = 0;
218-
for (uint256 seq = current - SMALL_RING_SIZE + 1; seq <= current; seq++) {
219-
IInbox.InboxBucket memory bucket = smallRingInbox.getBucket(seq);
219+
for (uint256 seq = current - MIN_BUCKET_RING_SIZE + 1; seq <= current; seq++) {
220+
IInbox.InboxBucket memory bucket = ringInbox.getBucket(seq);
220221
assertEq(bucket.totalMsgCount, seq, "cumulative total");
221222
assertEq(bucket.msgCount, 1, "one message per bucket");
222223
assertGt(bucket.timestamp, previousTimestamp, "timestamps increase per bucket");
223224
previousTimestamp = bucket.timestamp;
224225
}
225-
assertEq(smallRingInbox.getBucket(current).rollingHash, expectedRollingHash, "chain matches reference");
226+
assertEq(ringInbox.getBucket(current).rollingHash, expectedRollingHash, "chain matches reference");
226227

227228
// Buckets ahead of the current one do not exist yet.
228229
vm.expectRevert(abi.encodeWithSelector(Errors.Inbox__BucketOutOfWindow.selector, current + 1, current));
229-
smallRingInbox.getBucket(current + 1);
230+
ringInbox.getBucket(current + 1);
231+
}
232+
233+
function testConstructorRevertsBelowRingFloor() public {
234+
IERC20 feeAsset = new TestERC20("Fee Asset", "FA", address(this));
235+
vm.expectRevert("BUCKET RING TOO SMALL");
236+
new InboxHarness(address(this), feeAsset, version, HEIGHT, TestConstants.AZTEC_INBOX_LAG, MIN_BUCKET_RING_SIZE - 1);
230237
}
231238

232239
// Gas cost of a message absorbed into an already-open bucket (the common per-message case): the

0 commit comments

Comments
 (0)