feat(fast-inbox): rolling-hash buckets in the Inbox alongside frontier trees (A-1377)#24771
Open
spalladino wants to merge 5 commits into
Open
feat(fast-inbox): rolling-hash buckets in the Inbox alongside frontier trees (A-1377)#24771spalladino wants to merge 5 commits into
spalladino wants to merge 5 commits into
Conversation
This was referenced Jul 17, 2026
spalladino
force-pushed
the
spl/a-1427-inbox-parity
branch
from
July 17, 2026 19:38
c18b69c to
1872323
Compare
spalladino
force-pushed
the
spl/a-1377-inbox-buckets
branch
from
July 17, 2026 19:38
630c509 to
20656f8
Compare
spalladino
force-pushed
the
spl/a-1427-inbox-parity
branch
from
July 17, 2026 20:04
1872323 to
347a6e1
Compare
spalladino
force-pushed
the
spl/a-1377-inbox-buckets
branch
from
July 17, 2026 20:04
20656f8 to
d12875f
Compare
spalladino
force-pushed
the
spl/a-1377-inbox-buckets
branch
from
July 17, 2026 23:29
d12875f to
68714ab
Compare
spalladino
changed the base branch from
spl/a-1427-inbox-parity
to
spl/a-1432-same-block-msgs
July 17, 2026 23:29
…amp-key note (A-1377)
…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.
spalladino
force-pushed
the
spl/a-1377-inbox-buckets
branch
from
July 18, 2026 05:22
ec1326a to
db14374
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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'sinboxRollingHashfield, the two epoch-proof public inputs, and the regenerated checkpoint fixtures — so this PR adds only the bucket machinery on top.What
sendL2Messageadditionally maintains the consensus rolling hash — the truncated-per-link sha256 chain the circuits recompute (h' = sha256ToField(h || leaf), genesis zero; newHash.accumulateInboxRollingHash) — and snapshots it into a fixed-size ring of buckets. Frontier trees,LAG, the legacyconsume()flow, and the legacybytes16keccak 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:
{rollingHash | totalMsgCount: uint64, timestamp: uint64, msgCount: uint32}packs into two slots;totalMsgCountis the Inbox-wide cumulative count (what the censorship cap-escape check reads),timestampis the bucket's opening L1 block timestamp (recency checks are done in seconds),msgCountsits in slot 2's spare bits so the per-bucket cap costs no extra storage access.seq % BUCKET_RING_SIZE), 1024 entries in production, sized as an immutable constructor parameter.getBucket(seq)reverts withInbox__BucketOutOfWindowoutside 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).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.{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 atpropose.MessageSentgainsbytes32 inboxRollingHashanduint256 bucketSeqafter the legacy args.Archiver / TS
The generated
InboxAbipicks up the new event signature at build time;MessageSentArgsand the decode inethereum/src/contracts/inbox.tscarry the two new fields, and the archiver keeps relying only on the legacy args for now (InboxMessageunchanged). 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
InboxBuckets.t.solcovers 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.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 testat the current stack top: 890 passed, 0 failed, 3 skipped (thetestGetEpochProofPublicInputsVerifiesHeadersfailure previously inherited from the base has been fixed by the rebased base branches). The 13InboxBuckets.t.soltests all pass (9 behavioral + 4 gas measurements). ExistingMessageSentexpectations in Inbox/TokenPortal/FeeJuicePortal tests updated for the extended event.@aztec/l1-artifactsInboxAbipicks up the two new event fields, and@aztec/ethereumtypechecks 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.solcover the bucket write paths (inlinegasleft()deltas, matching theRollupGetters.t.solconvention). These feed the capacity analysis (max messages per L1 block from gas):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 increasesblock.timestampstrictly 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 > 1to 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.testRingWraparoundnow exercises a real wraparound against a 512-slot ring, and a newtestConstructorRevertsBelowRingFloorasserts that constructing below the floor reverts. The full capacity/liveness analysis behind this number feeds the AZIP-22 review.