Reducing L1→L2 inclusion latency via rolling-hash Inbox #53
spalladino
started this conversation in
AZIP Proposals
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Goal: cut L1→L2 inclusion latency. A user sends a message to the Inbox on L1, and a proposer includes it in the next L2 block ~seconds later, instead of waiting a full checkpoint.
Today the Inbox builds one Merkle tree per checkpoint, and the rollup consumes that tree's root once per checkpoint. We replace the per-checkpoint tree with a rolling hash that the rollup can consume up to any recent point, which enables per-block inclusion.
Inbox (L1)
Storage
Drop the per-checkpoint tree. Keep only a rolling hash, snapshotted once per L1 block. Storage is a ring buffer to bound cost; if it fills, new messages are rejected.
Consuming
consumeadvances a "last consumed" pointer up to a caller-chosen timestamp — i.e. "the checkpoint being proposed has now processed all inbox messages up to timestamp X". It releases the freed ring slots and returns the rolling hash at that point, which the rollup binds into the checkpoint header (same shape as today's root check, whereconsumereturns the tree root and we assert the header matches).To keep it censorship-resistant, the contract forces that timestamp to be recent; otherwise a proposer could keep advancing the chain while never consuming messages at all.
Tension to resolve: circuits cap how many messages a checkpoint can prove, so the recency bound (
MAX_CONSUME_LAG) must not force the rollup to consume more than that cap. Balancing the two likely means tracking the message count per L1 block (or cumulative), so the contract can reason about volume and not just time. See Open questions.Nodes
Archiver
Syncs L1→L2 messages as today, now tagging each with its L1 timestamp, and serves range queries: "all messages within
[tsA, tsB]". It could also track how far consumption has advanced (across the proposed / checkpointed / proven chains); if it does, it can answer "messages to include in the next block" directly — though whether that responsibility belongs in the archiver is open.Proposer
When building any block (not just the first of a checkpoint), pull the next batch of messages since the last consumed tip, capped at
MAX_MSGS_PER_BLOCK.Validators
On receiving a proposal, recompute the rolling hash from their own archiver and accept the block if it matches the one in the header.
Block processing
Messages are inserted into the L1→L2 tree before txs run, so txs in the same block can consume them. The block header now carries both:
Circuits
Previously, parity circuits proved the Pedersen subtree root matched the SHA256 root returned from the Inbox. Two options now:
Option 1 — prove per block. Each block proves its rolling hash is consistent with the messages inserted into the tree (inhash ↔ stateref, like parity did per checkpoint). Can live in the block root, or in a dedicated parity circuit — the latter avoids paying
MAX_MSGS_PER_BLOCKSHA256 hashes in every block root.Option 2 — prove per checkpoint. Only the checkpoint's last block reconciles its inhash/stateref against L1, reusing today's parity circuits almost unchanged. Intermediate block rolling hashes are not proven consistent — acceptable as long as the final one matches L1.
We do not validate that individual blocks' consumed messages match the Inbox. We could pass per-block message roots to
propose, but that costs one SLOAD per block. Skipping is fine: what matters is that the checkpoint's messages are all real, in order, and none skipped — how they split across blocks doesn't.Open questions
MAX_CONSUME_LAGso it never forces consuming more than the circuits' per-checkpoint cap. May require tracking per-L1-block message counts.Beta Was this translation helpful? Give feedback.
All reactions