feat(fast-inbox): record the opening L1 block number and hash on each inbox bucket - #25350
Open
spalladino wants to merge 2 commits into
Open
Conversation
… 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.
…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
force-pushed
the
spl/inbox-bucket-l1-block-ref
branch
from
August 29, 2026 11:07
2562879 to
78479ad
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.
Stacked on #25342
Groundwork, no behaviour change. The archiver already knew which L1 block opened each Inbox bucket — every
MessageSentlog carries the block number and hash — but only the number reached the persisted bucket snapshot,and neither field reached the
InboxBuckettype the archiver serves to the sequencer and the validator.InboxBucketnow carriesl1BlockNumberandl1BlockHash(schema included, so they survive the JSON-RPC hopfor out-of-process archivers), and
BucketSnapshotpersists the hash alongside the number. That is what lets thenext 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:
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
warnand the opening block is kept. ProductionEthereum 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.
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 thebucket'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 formatandyarn linton the touched packages. Tests:archiver575/575,stdlibsrc/messaging+src/interfaces/archiver.test.ts118/118,validator-client,sequencer-client(including the anvil-backed
l1_publisher.integration.test.ts),aztec-nodenode_public_calls_simulator.test.ts,world-stateintegration.test.ts. No e2e.Fixes A-1878