Skip to content

feat(fast-inbox): record the opening L1 block number and hash on each inbox bucket - #25350

Open
spalladino wants to merge 2 commits into
spl/a-1393-validator-wait-for-inbox-bucket-syncfrom
spl/inbox-bucket-l1-block-ref
Open

feat(fast-inbox): record the opening L1 block number and hash on each inbox bucket#25350
spalladino wants to merge 2 commits into
spl/a-1393-validator-wait-for-inbox-bucket-syncfrom
spl/inbox-bucket-l1-block-ref

Conversation

@spalladino

@spalladino spalladino commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Stacked on #25342

Groundwork, no behaviour change. The archiver already knew which L1 block opened each Inbox bucket — every
MessageSent log carries the block number and hash — but only the number reached the persisted bucket snapshot,
and neither field reached the InboxBucket type the archiver serves to the sequencer and the validator.

InboxBucket now carries l1BlockNumber and l1BlockHash (schema included, so they survive the JSON-RPC hop
for out-of-process archivers), and BucketSnapshot persists the hash alongside the number. That is what lets the
next PR ask "is the L1 block that opened this bucket still canonical, and does it have a child?" for the
descendant-confirmed eligibility rule, and what lets the archiver in the message-only stack compare a stored
bucket against the live chain.

Two smaller things came with it:

  • The snapshot records the block a bucket was opened in, taken from its first message. The Inbox keys buckets
    by block.timestamp, so on a chain where consecutive blocks can share a timestamp (anvil with manual mining)
    one bucket can span several L1 blocks; that is logged at warn and the opening block is kept. Production
    Ethereum timestamps are strictly increasing, so a bucket there never spans more than one block. The cost of
    recording only the opening block is that a reorg touching only a later co-timestamped block of the same bucket
    is invisible to the hash; both fields say so.
  • The archiver's message test doubles now derive a bucket's L1 block from its L1 timestamp rather than from its
    sequence number, with a deterministic, distinct hash per block. Rollover siblings that share a timestamp share
    an L1 block, as on chain, and code that confused a bucket sequence with an L1 block number now fails its tests.

InboxBucketRef, the reference carried on block proposals, is deliberately unchanged: a validator reads the
bucket's L1 block from its own archiver and never from the proposer.

Breaking store format. The bucket snapshot gains a field with no versioning or migration, so an existing
archiver database will not deserialize. That is fine here — this line has never shipped, and dev databases
resync from L1.

Gates: full yarn build, yarn format and yarn lint on the touched packages. Tests: archiver 575/575,
stdlib src/messaging + src/interfaces/archiver.test.ts 118/118, validator-client, sequencer-client
(including the anvil-backed l1_publisher.integration.test.ts), aztec-node
node_public_calls_simulator.test.ts, world-state integration.test.ts. No e2e.

Fixes A-1878

… inbox bucket

The archiver already knew which L1 block each Inbox bucket was opened in, but kept it to itself: the bucket
snapshot stored the block number only, and neither field reached the `InboxBucket` type it serves to the
sequencer and validator. Both are now carried end to end, so a caller can ask whether the L1 block that opened
a bucket is still canonical and whether it has a descendant.

The persisted bucket snapshot gains `l1BlockHash` right after `l1BlockNumber`, and a batch whose bucket spans
more than one L1 block is now rejected, since a bucket is opened and closed within a single L1 block and its
snapshot records exactly one. The wire type on block proposals (`InboxBucketRef`) is unchanged: validators read
the L1 block from their own archiver rather than trusting the proposer.
@spalladino
spalladino requested a review from just-mitch as a code owner August 29, 2026 11:07
…ing buckets that span blocks

The Inbox keys buckets by block.timestamp, so a chain that lets consecutive blocks share a timestamp (anvil with
manual mining) can spread one bucket over several L1 blocks. Rejecting that batch wedged the archiver's message
sync, since the error escapes the L1 synchronizer. The snapshot now takes the block number and hash off the
bucket's first message -- the block it was opened in, which is what the field is documented to hold -- and only
warns when the bucket's later messages come from another block.

The cost of recording only the opening block is that a reorg touching just a later co-timestamped block of the
same bucket is invisible to the stored hash; the InboxBucket fields and the rollback helper say so. Production
Ethereum timestamps are strictly increasing, so no bucket spans more than one block there.

Also decorrelates the archiver test doubles: a bucket's L1 block is now derived from its L1 timestamp rather
than from its sequence number, so rollover siblings sharing a timestamp share a block as they do on chain and
code that confuses the two fails its tests. The publisher integration harness mirrors each bucket's real opening
block from its MessageSent log instead of standing in the chain head, and the genesis sentinel bucket documents
that its zero timestamp, block and hash must never be looked up on L1.
@spalladino
spalladino force-pushed the spl/inbox-bucket-l1-block-ref branch from 2562879 to 78479ad Compare August 29, 2026 11:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant