by @paco0x
Name | Type | Address |
---|---|---|
L1 Attribute Depositor | EOA | https://goerli-optimism.etherscan.io/address/0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001 |
L1Block | Pre-deploy | https://goerli-optimism.etherscan.io/address/0x4200000000000000000000000000000000000015 |
L2ToL1MessagePasser | Pre-deploy | https://goerli-optimism.etherscan.io/address/0x4200000000000000000000000000000000000016 |
L2CrossDomainMessenger | Pre-deploy | https://goerli-optimism.etherscan.io/address/0x4200000000000000000000000000000000000007 |
L2StandardBridge | Pre-deploy | https://goerli-optimism.etherscan.io/address/0x4200000000000000000000000000000000000010 |
L2ERC721Bridge | Pre-deploy | https://goerli-optimism.etherscan.io/address/0x4200000000000000000000000000000000000014 |
OptimismMintableERC20Factory | Pre-deploy | https://goerli-optimism.etherscan.io/address/0x4200000000000000000000000000000000000012 |
OptimismMintableERC721Factory | Pre-deploy | https://goerli-optimism.etherscan.io/address/0x4200000000000000000000000000000000000017 |
Pre-deployed contracts are deployed in genesis block by directly setting the state DB: https://github.com/ethereum-optimism/optimism/blob/c21476c1874702da8c0817d5c7c9de5b8066eb4b/op-chain-ops/genesis/setters.go#L226
- op-geth is a slightly modified version of geth, the Execution Engine in Optimism Bedrock, used to produce L2 blocks. You can find all the changes at https://op-geth.optimism.io/.
- op-node is the rollup node, which is a standalone stateless binary.
- It receives L2 user transactions and converts the deposit data into payload attributes for the Engine API.
- It submits the payload attributes to the Engine API, where they are converted into blocks and added to the canonical chain.
- It can also derive the L2 chain from L1 deposits & batchInbox.
- It generates L2 outputRoots for submission or verification.
- op-batcher submits the L2 transactions to DA (currently, L1 calldata) through batcher transactions.
- op-proposer periodically submits the L2 state root to the L1 contract for verification and withdrawal proving.
Before Bedrock, the blockchain had a variable block time and one transaction per block.
With Bedrock, the block time is fixed at 2 seconds and can contain multiple transactions.
Advantages of this change include:
- Consistency with the Ethereum mainnet after POS.
- Convenience for contracts that use block numbers rather than timestamps to keep track of time; for example, the
MasterChef
contract. Block numbers are harder to manipulate than block timestamps. - Reduced overhead for storing the blockchain.
- No need to update the state trie root after each transaction.
The rollup chain is subdivided into epochs. There is a 1:1 correspondence between L1 block numbers and epoch numbers.
L2 block timestamp can be 10 minutes (sequencer drift parameter) ahead of L1. This parameter is meant to ensure liveness of the L2 chain during temporary loss of connection to L1.
On L2, Optimism adds a new type of transaction in op-geth called deposit transaction.
There are 2 types of deposit transactions:
- System transactions, the first transaction of every L2 block, setting the attributes from L1
- Deposit transactions, transactions sent by users on L1 to L2
Other transactions are sent by users on L2 directly
- Call
OptimismPortal.depositTransaction(to, value, gasLimit, isCreation, data)
on L1. - Emit the
TransactionDeposited(from, to, version, opaqueData)
event in the contract.- If
from
is a contract, address aliasing will be performed. For more information, see the address aliasing documentation.
- If
- The sequencer uses these events to generate deposited transactions on L2 and build the block.
- Deposit transactions are not retryable.
- It's invalid to have an L2 block without including the deposits of its L1 origin. (anti censorship)
The L2 execution gas is paid by the user on L1.
- Call
L2ToL1MessagePasser.initiateWithdrawal(target, gasLimit, data)
on L2. - Set the contract storage for
sentMessages[withdrawalHash]
totrue
. - The proposer should set the L2 state root and storage root of
L2ToL1MessagePasser
on L1 (L2OutputOracle
). - Prove the state on L1 by calling
OptimismPortal.proveWithdrawalTransaction()
.- Prove the
withdrawalHash -> true
key pair exists in the contract’s storage(Merkle Patricia Trie)
- Prove the
- Wait for the finalization period.
- Finalize the withdrawal transaction by calling
OptimismPortal.finalizeWithdrawalTransaction()
. - Withdrawal transactions are not retryable.
They use Safecall.call()
to ignore the return data to avoid the potential return data bomb.
https://www.notonlyowner.com/research/message-traps-in-the-arbitrum-bridge
Optimism constructed a pair of messaging contracts for message passing by utilizing the low-level deposit and withdraw transactions. The contracts used for message passing are L1CrossDomainMessenger.sol
and L2CrossDomainMessenger.sol
.
The messages are retryable, unlike the raw deposits/withdrawals. Developers should use messenger contracts instead of low-level deposits/withdrawals.
- To pass a message to the other chain, users need to call
CrossDomainMessenger.sendMessage(target, message, minGasLimit)
. - To obtain the caller on the other chain, users can use
CrossDomainMessenger.xDomainMsgSender()
.- If it is an L1 → L2 message, remember to undo the address alias.
- user call
L1CrossDomainMessenger.sendMessage()
- gas is charged and computed on L1
- gas usage:
gasLimit = minGasLimit * 1.016 + _message.length * 16 + 200,000
- gas usage:
- call
OptimismPortal.depositTransaction()
portal.depositTransaction{value: msg.value}(
L2CrossDomainMessenger, // The pre-deplyed CrossDomainMessenger on L2
msg.value,
gasLimit,
false,
abi.encodeWithSelector(
this.relayMessage.selector,
messageNonce(),
msg.sender,
_target,
msg.value,
_minGasLimit,
_message
)
)
- emit the
TransactionDeposited()
event in theOptimismPortal
contract on L1
- op-node generate a deposit transaction by reading the
TransactionDeposited
events - the deposited transaction calls the
L2CrossDomainMessager.relayMessage()
on L2 - call the target address in
L2CrossDomainMessager.relayMessage()
and record the result - call the target address with the message
- If the replay transaction failed, user can replay it manually
- user call
L2CrossDomainMessenger.sendMessage()
- call
L2ToL1MessagePasser.intializeWithdraw()
- set
sentMessages[withdrawalHash] = true;
in theL2ToL1MessagePasser
- wait for the proposer submit the state root on L1
L2OutputOracle
- prove the tx hash is already written to
L2ToL1MessagePasser
on L2 - wait for the finalization period
- finalize the withdrawal transaction by calling
OptimismPortal.finalizeWithdrawalTransaction()
- call
L1CrossDomainMessenger.relayMessage()
- call the target address with the message
Optimism built the following bridges for ERC20 and ERC721
L1StandardBridge
&L2StandardBridge
L1ERC721Bridge
&L2ERC721Bridge
Under the hood, Optimism uses the message-passing mechanism provided by CrossDomainMessenger
contracts.
Before bridging from L1 to L2, users need to create a remote ERC20 contract on L2 by using the OptimismMintableERC20Factory
contract.
- Lock the token/ETH and record the amount on L1's
L1StandardBridge
. - Send a message to L2's
L2StandardBridge
.
- Receive the message call in
L2StandardBridge
. - Mint the remote token with the specified amount.
The process is very similar to the above.
L2OutputOracle
is a contract on L1 that saves the L2 state roots for verification. Verifiers can challenge the result through a disputing system.
Currently, only an EOA address operated by Optimism can write to this contract.
The challenger address is set to a multi-sig wallet controlled by Optimism.
L2OutputOracle
also saves the storage root of the L2ToL1MessagePasser
contract on L2 for proving the withdrawal transactions on L1.
op-batcher submits the L2 data as calldata to BatchInbox
address.
- op-node can read these calldata to rebuild the entire L2 chain.
- Verifiers can use these data to verify the state root in
L2OutputOracle
contract.
Batches, ****a batch ****contains all the txs of an L2 block
Channels, Batches are aggregated into channel
Channel Frames, channels are compressed and split into channel frames
Batcher submits those frames in the batcher transactions.
op-proposer submits the L2 state root and the storage trie root hash of the L2ToL1MessagePasser
contract on L2 to L1 L2OutputOracle
.
Proposer queries these data through op-node’s optimism_outputAtBlock
RPC API. Inside optimism_outputAtBlock
:
- fetch the L2 block header through
eth_getBlockByNumber
RPC in op-geth - fetch the storage root of
L2ToL1MessagePasser
contract througheth_getProof
RPC in op-geth - return the state root of the L2 block and the storage root of
L2ToL1MessagePasser
contract- before returning, verify the storage root is contained in the state trie
Test account:
- Address:
0x3bb843cf8e26FF1Fdbdb6B2eC1B0dD5B37082B64
- Private Key:
0x8567689e64d90470cf9e30ed5d59d495e7a27de4fbe964620e45ecbe050018d7