You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Allowing deposits to trigger executing messages was descoped from the interop devnet. There is a desire to include this feature into scope for the production release. Without this feature, it is possible for a sequencer to censor a cross chain transaction, effectively burning the user’s funds. Not good. How to solve this?
There is an open question around the latency at which using a deposit to trigger an executing message may happen. The specific problem is that a force include deposit transaction may reference an invalid executing message. The main invariant of interop is that all executing messages must be valid - meaning that the identifier references the initiating message. If a deposit transaction is allowed to trigger an executing message, what are the tradeoffs between waiting for finality versus not waiting?
Not waiting for finality adds complexity around deposit-only blocks and requires synchrony assumptions. We wouldn't know if a reorg might occur for a deposit only block until we observe finality from remote chains. While technically feasible, it introduces complexity and ties preconfirmations deeply to the system's correct functioning. Since some organizations may not want to rely on pre-confirmation security, we shouldn't make it a consensus requirement. This is why waiting for finality is the better approach.
We generally need some sort of “finality oracle”. One approach of a finality oracle is waiting for the sequencing window. Going any faster than that would need some sort of proof that the data was posted and derives into the outputs that contain the required logs.
Solution: Preregistration
This design space involves preregistering the executing message before executing it. The preregistration stage needs to be easy to validate and needs to be possible via a deposit transaction.
Constraints & Design Goals
Derivation should not need to execute the deposit
It is technically possible to “optimistically execute” the deposits using a forking EVM and then filtering based the standard interop invariants but strictly enforcing 12h on top of the initiating message. This would essentially double the deposit cost in the system
Separate registration from execution
Allows one proof to be consumed many times
Reduce single points of failure
If the diff is bigger in the smart contracts, it better be worth it
Proposal
Add a new mapping named registered to the CrossL2Inbox that can authenticate executing messages.
Invariants:
Only deposits can populate the registered mapping
The call to populate the registered mapping must be a top level call, meaning the data must be in the data field of the transaction. This enables static analysis
The timestamp on the identifier must be older than the sequencing window
Instead of reverting when the call to validateMessage is in the deposit context, instead require that the hash of the identifier and message hash is in the registered mapping.
Should the invariants be enforced onchain or offchain?
They have to be enforced offchain, there is no way to enforce “only EOA” effectively
The derivation pipeline is updated to enforce the above invariants. Derivation maps TransactionDeposited events into DepositTx transactions. We add a special rule in derivation that matches on the TransactionDeposited event. If the target is the CrossL2Inbox and the calldata is exactly the right number of bytes and has a prefix of the 4byte selector of the register function, then it maps the from into the DEPOSITOR_ACCOUNT rather than from the msg.sender in the context of the OptimismPortal. If the executing message is invalid, then the deposit transaction will revert in its call to the CrossL2Inbox
Pseudocode for this logic is below:
deposits= []
receipts=eth.get_receipts(block.hash)
forreceiptinreceipts:
forloginreceipt.logs:
ifis_transaction_deposited(log):
deposit_tx=map_log(log)
ifis_register_deposit(deposit_tx):
id, hash=abi.decode(deposit_tx.data[0:4], (Identifier, bytes32))
client=clients[id.chain_id]
receipts=client.get_receipts_by_blocknumber(id.block_number)
log=filter_log_by_index(receipts, id.log_index)
serialized=serialize_log(log)
is_correct_hash=hash==keccak(serialized)
is_before_expiry=id.timestamp>next_l2_timestamp-EXPIRY_WINDOWis_after_sequencer_window=id.timestamp+next_l2_timestamp<SEQUENCER_WINDOWifis_correct_hashandis_before_expiryandis_after_sequencer_window:
deposit_tx.from=DEPOSITOR_ACCOUNTdeposits.append(deposit_tx)
defis_transaction_deposited(log):
# check correct number of topics# check topic[0] is correct# check log.data is abi encoded correctlydefis_register_deposit(tx):
is_correct_target=tx.to==CROSS_L2_INBOXis_correct_calldata_size=len(tx.calldata) ==REGISTER_SIZEis_correct_calldata_selector=tx.calldata[0:4] ==REGISTER_SELECTORis_correct_encoding=abi.decode(tx.calldata, (REGISTER_ABI))
returnis_correct_target&&is_correct_calldata_size&&is_correct_calldata_selector&&is_correct_encoding
We know that it is impossible to create a subcall from the DEPOSITOR_ACCOUNT into the register function, therefore we guarantee the invariant that the calldata can always be statically analyzed in the derivation pipeline.
One possible variant here is that the register deposit transaction is still required to be statically identifiable but the check for executing message validity does not happen synchronously - the register transaction is allowed to proceed regardless and the mapping of registered initiating messages updated on chain.
Nodes then have a 12 hour window to asynchronously check the validity of the registered message. When the second deposit transaction arrives to actually execute:
if there is no pre-registration the transaction reverts (but is included as-is)
if the pre-registration exists and is valid, the transaction executes (ideally we'd remove the pre-registration here but then it would need to be deposit tx specific)
if the pre-registration exists and is invalid, the deposit transaction is replaced with one that removes the invalid pre-registration
This allows the cross-chain dependency check to be executed asynchronously (even nodes that have snap sync'd could read the list of pre-registrations and begin checking them). The executing deposit message is still only executed once - it may be invalid and need to be reverted but the invalidity can be cached and the rollback should be cheap. Potentially you could even relax the restriction on the pre-registration message being statically analysable since it is now unconditionally included and nodes could simply look for the event emitted when a pre-registration occurs.
The major downside I see is the message validity check would now occur in the execution client for deposit transactions whereas I think previously all validity checks would have been in the L2 consensus client.
Allowing deposits to trigger executing messages was descoped from the interop devnet. There is a desire to include this feature into scope for the production release. Without this feature, it is possible for a sequencer to censor a cross chain transaction, effectively burning the user’s funds. Not good. How to solve this?
There is an open question around the latency at which using a deposit to trigger an executing message may happen. The specific problem is that a force include deposit transaction may reference an invalid executing message. The main invariant of interop is that all executing messages must be valid - meaning that the identifier references the initiating message. If a deposit transaction is allowed to trigger an executing message, what are the tradeoffs between waiting for finality versus not waiting?
Not waiting for finality adds complexity around deposit-only blocks and requires synchrony assumptions. We wouldn't know if a reorg might occur for a deposit only block until we observe finality from remote chains. While technically feasible, it introduces complexity and ties preconfirmations deeply to the system's correct functioning. Since some organizations may not want to rely on pre-confirmation security, we shouldn't make it a consensus requirement. This is why waiting for finality is the better approach.
We generally need some sort of “finality oracle”. One approach of a finality oracle is waiting for the sequencing window. Going any faster than that would need some sort of proof that the data was posted and derives into the outputs that contain the required logs.
Solution: Preregistration
This design space involves preregistering the executing message before executing it. The preregistration stage needs to be easy to validate and needs to be possible via a deposit transaction.
Constraints & Design Goals
Proposal
Add a new mapping named
registered
to theCrossL2Inbox
that can authenticate executing messages.Invariants:
registered
mappingregistered
mapping must be a top level call, meaning the data must be in the data field of the transaction. This enables static analysisInstead of reverting when the call to
validateMessage
is in the deposit context, instead require that the hash of the identifier and message hash is in theregistered
mapping.Should the invariants be enforced onchain or offchain?
The derivation pipeline is updated to enforce the above invariants. Derivation maps
TransactionDeposited
events intoDepositTx
transactions. We add a special rule in derivation that matches on theTransactionDeposited
event. If thetarget
is theCrossL2Inbox
and the calldata is exactly the right number of bytes and has a prefix of the 4byte selector of theregister
function, then it maps thefrom
into theDEPOSITOR_ACCOUNT
rather than from themsg.sender
in the context of theOptimismPortal
. If the executing message is invalid, then the deposit transaction will revert in its call to theCrossL2Inbox
Pseudocode for this logic is below:
We know that it is impossible to create a subcall from the
DEPOSITOR_ACCOUNT
into theregister
function, therefore we guarantee the invariant that the calldata can always be statically analyzed in the derivation pipeline.Pseudocode for the
register
function is below:The text was updated successfully, but these errors were encountered: