Thanks to the Nethermind and Taiko teams for discussions and feedback.
TL;DR
Raid (“Retroactive Attestation of Inbox Data”) is a set of contracts that rollup inboxes can adopt to enable preconf-compatible based sequencing. Raid employs multiple levels of on-chain filtering to ensure that only blobs submitted by preconfers are considered canonical by rollup nodes. This is accomplished without an on-chain view of the beacon chain lookahead or modifications to the L1 like EIP-7917 that would make this vastly simpler.
A post with more context can be found here.
Problem
Rollups need an L1 entry-point for blobs to be published.
The OP Stack uses a random batchInbox
EOA address and filters for canonical blobs by verifying that all blob transactions were signed by the dedicated batchSubmitter
during their rollup’s derivation. Instead of a fixed address, based sequencing requires a dynamic batchSubmitter
address to rotate between the L1 proposers.
In Taiko, anyone can permissionlessly publish blobs to the TaikoInbox
contract which saves metadata necessary for proving in later stages. This is sufficient for based sequencing, but because anyone can publish at anytime, preconfers cannot make credible commitments about contentious state. Any preconfer issuing L2 execution preconfs is guaranteed to break their commitment if an adversary changes the rollup’s state before they publish.
What’s needed is a way to grant the preconfer a write-lock on the rollup up until their slot. The prevailing thought has been to expose a view of the beacon lookahead to the inbox contract and use it to filter blob proposals. While EIP-7917 addresses this in the future, the lookahead is currently a function of the beacon state and therefore is not directly provable via Merkle proofs against the EIP-4788 beacon block root. Instead, trustlessly exposing the lookahead on-chain requires pessimistically ZK-proving the beacon chain function or optimistically posting it and then slashing via fraud proof as in Nethermind’s approach.
Proposal
Raid addresses this problem with today’s tools but comes with one tradeoff: it is not compatible with real-time settlement and thus can be thought of as a pragmatic short-term solution until EIP-7917 is live.
- The Raid inbox tracks an
unsafeHead
andsafeHead
, which are references to blob publications - Anyone can permissionlessly publish to the inbox and specify if they want to replace or advance the
unsafeHead
- The inbox will first ensure that the contract caller is a preconfer that satisfies all preconditions (e.g., sufficient collateral / opted in to preconf protocol / etc)
- The inbox will verify a
validatorProof
- a Merkle proof against the EIP-4788 beacon block root that proves who the L1 proposer was during the lastunsafeHead
’s publication - If the contract caller tried to replace, the
validatorProof
proves the sender ofunsafeHead
did not publish during their L1 slot, then replaces theunsafeHead
with their own - If the contract caller tried to advance, the
validatorProof
proves the sender ofunsafeHead
did publish during their L1 slot, then promotes theunsafeHead
to thesafeHead
and replaces theunsafeHead
with their own - Rollup nodes use the historical
safeHeads
when deriving the rollup’s state
Example Flow
Slot N
- We assume the rollup starts in it’s genesis state
- blobProposer_0 is a valid preconfer that publishes B_0 with
replaceUnsafeHead = true
- B_0 is published to the
PublicationFeed
contract and returns a publicationId_{B_0} - Since
replaceUnsafeHead
is true and there is no previousunsafeHead
, publicationId_{B_0} becomes the newunsafeHead
- Rollup nodes don’t update their state as there’s no new
safeHead
Slot N+1
- blobProposer_1 is an invalid preconfer so publishing B_1 reverts
- blobProposer_2 is a valid preconfer that publishes B_2 with
replaceUnsafeHead = false
- Since
replaceUnsafeHead
is false, blobProposer_2'svalidatorProof
proves that publicationId_{B_0} was indeed submitted during blobProposer_0's slot - Assuming
validatorProof
is valid, publicationId_{B_0} is promoted to thesafeHead
, and publicationId_{B_2} becomes the newunsafeHead
- Rollup nodes receive a
NewSafeHead
event and process B_0 to update their local L2 state
Slot N+2
- blobProposer_3 is a valid preconfer that publishes B_3 with
replaceUnsafeHead = true
- Since
replaceUnsafeHead
is true, blobProposer_3'svalidatorProof
proves that it was not blobProposer_2's slot when they published B_2 - Assuming
validatorProof
is valid, publicationId_{B_2} is replaced by publicationId_{B_3} as theunsafeHead
- Rollup nodes don’t update their state as there’s no new
safeHead
Slot N+3
- blobProposer_4 is a valid preconfer that publishes B_4 with
replaceUnsafeHead = false
- Since
replaceUnsafeHead
is false, blobProposer_4'svalidatorProof
proves that publicationId_{B_3} was indeed submitted during blobProposer_3's slot - Assuming
validatorProof
is valid, publicationId_{B_3} is promoted to thesafeHead
, and publicationId_{B_4} becomes the newunsafeHead
- Rollup nodes receive a
NewSafeHead
event and process B_3 to update their local L2 state
Assumptions
- since each publication either replaces or promotes the previous
unsafeHead
, only one publication can be added per block to allow time for the beacon block root to become available on-chain - following the previous assumption, a rational L1 proposer would ensure only their publication transaction lands
- publications must happen at least daily to ensure consecutive proposals can access the required beacon block roots (only the last 8091 are accessible on-chain).