Skip to content

feat(fast-inbox): commit the inbox bucket timestamp in every rolling-hash link - #25327

Open
spalladino wants to merge 2 commits into
spl/inbox-block-bucket-boundaryfrom
spl/a-1758-inbox-rolling-hash-timestamp
Open

feat(fast-inbox): commit the inbox bucket timestamp in every rolling-hash link#25327
spalladino wants to merge 2 commits into
spl/inbox-block-bucket-boundaryfrom
spl/a-1758-inbox-rolling-hash-timestamp

Conversation

@spalladino

@spalladino spalladino commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Every link of the Inbox rolling hash now also absorbs the bucket's L1 timestamp, so an L1 reorg that re-times a bucket without changing its messages or packing is detected by the archiver. This is stage 4 of the Inbox Domain Separators walkthrough.

Context

The archiver decides it is in sync with the Inbox by comparing two numbers against L1: the total message count and the rolling hash. After #25319 the hash covers the messages, their order and how they are grouped into buckets — but not the bucket timestamps. So this reorg was invisible:

Fork the node saw                          Canonical fork after the reorg
L1 block 100 (t=1200): A, B → bucket #7    L1 block 100' (t=1200): A, B → bucket #7 (ts 1200)
L1 block 101 (t=1212): C    → bucket #8    L1 block 101' (t=1224): C    → bucket #8 (ts 1224)

Same messages, same buckets, same count, same hash. The node keeps ts 1212 for bucket #8 while L1 says 1224. A bucket's timestamp is what the censorship cutoff is compared against, so the stale node believes #8 is mandatory under a cutoff of 1220 while L1 says it is optional, and it is the recency key the sequencer's bucket selection walks. Nodes split their attestations for no good reason, and rolling back does not help because the rollback looks for the last message whose hash still matches, and they all do.

Approach

With the timestamp in every link, a re-timed bucket changes its hash from its first message onward. The archiver's existing sync check fails, its existing rollback finds the exact last common message, and it re-downloads only what changed. No new node logic. After this change the rolling hash commits to everything in the L1 InboxBucket struct that consensus reads: leaves, order, boundaries, timestamp.

before:  h' = sha256ToField(sep ‖ h ‖ leaf)                       // 68 bytes
after:   h' = sha256ToField(sep ‖ h ‖ leaf ‖ timestamp_u64_be)    // 76 bytes
  • sep is unchanged (…_BUCKET_START on a bucket's first leaf, …_ROLLING_HASH otherwise). It stays because a full bucket spills into a second one in the same L1 block, so rollover siblings share a timestamp and the timestamp alone cannot mark that boundary; feat(fast-inbox): require every block to start consuming at an inbox bucket boundary #25322's block-alignment flags are also sound only because the separator anchors them to L1's hash.
  • Both preimage lengths are two sha256 blocks and three precompile words, so hashing cost is unchanged. inbox_parity_{64,256,1024} are +0.24% gates (21.75 gates per lane); block roots and the L1ToL2MessageSponge are untouched.

Gas

./bootstrap.sh gas_report and ./bootstrap.sh gas_benchmark re-run on this branch:

before after
Inbox.sendL2Message min/median 43,360 43,419 (+59)
Inbox.sendL2Message max 102,144 102,203 (+59)
Inbox bytecode size 6,859 B 6,885 B
Rollup bytecode size 43,872 B 43,898 B
propose / submitEpochRootProof benchmarks (gas_benchmark.md) unchanged

The +59 gas is abi.encodePacked of the extra uint64 plus the wider memory copy; the sha256 precompile cost is the same for 68 and 76 bytes. The rollup side never hashes the chain on L1, so the checkpoint benchmarks do not move.

  • Implemented identically on L1 (Hash.accumulateInboxRollingHash, Inbox._absorbIntoBucket passes bucket.timestamp), Noir (accumulate_sha256_with_separator_and_u64; InboxParityPrivateInputs.bucket_timestamps: [u64; S], padding lanes asserted zero) and TS (updateInboxRollingHash takes the timestamp and rejects values outside uint64). Shared vectors from inbox_rolling_hash_vectors.py are pinned on all three sides, including timestamps 0 and 2^64-1 and a same-leaves/different-timestamp pair.
  • InboxMessageBundle carries the timestamp once per bucket ({ timestamp, leaves }[]) and expands it per lane only when building parity inputs. Rollup sample inputs and the three parity VKs are regenerated.
  • Putting the timestamp in the message leaf instead was rejected: the leaf is the message's identity, so that would add a sha256 block to every consuming app circuit and change consume_l1_to_l2_message, every portal, FeeJuice claims, the MessageSent ABI and the SDK.
  • Archiver reorg coverage: FakeL1State gains recomputeMessageBuckets() and setL1BlockTimestamp(); new sync tests for a packing reorg, a bucket merge, and a timestamp-only reorg (red before this change), plus bucket-snapshot assertions on the existing reorg tests. A validator test pins that a proposal built on a stale bucket timestamp is rejected as bucket_hash_mismatch.

API changes

  • Breaking for anyone computing the Inbox rolling hash off-chain: the preimage gains the 8-byte big-endian bucket timestamp. Fresh contracts per release, no migration.
  • InboxMessageBundle (archiver getL1ToL2MessagesBetweenBuckets and friends) changes from Fr[][] to { timestamp: bigint; leaves: Fr[] }[]; EpochProvingJobData and InboxParityPrivateInputs serialization change accordingly (both formats are unversioned, as before).

Stacked on #25322.

Fixes A-1758

@spalladino spalladino added the ci-draft Run CI on draft PRs. label Aug 27, 2026
@spalladino
spalladino marked this pull request as ready for review August 27, 2026 16:13
@spalladino spalladino removed the ci-draft Run CI on draft PRs. label Aug 27, 2026
@spalladino
spalladino force-pushed the spl/a-1758-inbox-rolling-hash-timestamp branch from b780299 to f1cfcfe Compare August 28, 2026 22:07
@spalladino
spalladino changed the base branch from spl/checkpoint-builder-insert-messages-before-txs to spl/inbox-block-bucket-boundary August 28, 2026 22:07
@spalladino
spalladino force-pushed the spl/a-1758-inbox-rolling-hash-timestamp branch from f1cfcfe to 21f8e29 Compare August 29, 2026 11:12
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