Commit c1e7f122 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge pull request #4078 from ethereum-optimism/feat/receipts

op-chain-ops: add bedrock transition receipts
parents 1366092e 7d2fa734
......@@ -6,6 +6,7 @@ import (
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
......@@ -126,3 +127,46 @@ func (w *LegacyWithdrawal) StorageSlot() (common.Hash, error) {
slot := crypto.Keccak256(preimage)
return common.BytesToHash(slot), nil
}
// Value returns the ETH value associated with the withdrawal. Since
// ETH was represented as an ERC20 token before the Bedrock upgrade,
// the sender and calldata must be observed and the value must be parsed
// out if "finalizeETHWithdrawal" is the method.
func (w *LegacyWithdrawal) Value() (*big.Int, error) {
abi, err := bindings.L1StandardBridgeMetaData.GetAbi()
if err != nil {
return nil, err
}
value := new(big.Int)
// Parse the 4byte selector
method, err := abi.MethodById(w.Data)
// If it is an unknown selector, there is no value
if err != nil {
return value, nil
}
if w.Sender == nil {
return nil, errors.New("sender is nil")
}
isFromL2StandardBridge := *w.Sender == predeploys.L2StandardBridgeAddr
if isFromL2StandardBridge && method.Name == "finalizeETHWithdrawal" {
data, err := method.Inputs.Unpack(w.Data[4:])
if err != nil {
return nil, err
}
// bounds check
if len(data) < 3 {
return nil, errors.New("not enough data")
}
var ok bool
value, ok = data[2].(*big.Int)
if !ok {
return nil, errors.New("not big.Int")
}
}
return value, nil
}
package crossdomain
import (
"errors"
"fmt"
"math/big"
......@@ -19,7 +18,7 @@ var (
)
// MigrateWithdrawals will migrate a list of pending withdrawals given a StateDB.
func MigrateWithdrawals(withdrawals []*LegacyWithdrawal, db vm.StateDB, l1CrossDomainMessenger, l1StandardBridge *common.Address) error {
func MigrateWithdrawals(withdrawals []*LegacyWithdrawal, db vm.StateDB, l1CrossDomainMessenger *common.Address) error {
for i, legacy := range withdrawals {
legacySlot, err := legacy.StorageSlot()
if err != nil {
......@@ -36,7 +35,7 @@ func MigrateWithdrawals(withdrawals []*LegacyWithdrawal, db vm.StateDB, l1CrossD
continue
}
withdrawal, err := MigrateWithdrawal(legacy, l1CrossDomainMessenger, l1StandardBridge)
withdrawal, err := MigrateWithdrawal(legacy, l1CrossDomainMessenger)
if err != nil {
return err
}
......@@ -54,42 +53,11 @@ func MigrateWithdrawals(withdrawals []*LegacyWithdrawal, db vm.StateDB, l1CrossD
// MigrateWithdrawal will turn a LegacyWithdrawal into a bedrock
// style Withdrawal.
func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger, l1StandardBridge *common.Address) (*Withdrawal, error) {
value := new(big.Int)
isFromL2StandardBridge := *withdrawal.Sender == predeploys.L2StandardBridgeAddr
if withdrawal.Target == nil {
return nil, errors.New("withdrawal target cannot be nil")
}
isToL1StandardBridge := *withdrawal.Target == *l1StandardBridge
if isFromL2StandardBridge && isToL1StandardBridge {
abi, err := bindings.L1StandardBridgeMetaData.GetAbi()
if err != nil {
return nil, err
}
method, err := abi.MethodById(withdrawal.Data)
if err != nil {
return nil, err
}
if method.Name == "finalizeETHWithdrawal" {
data, err := method.Inputs.Unpack(withdrawal.Data[4:])
if err != nil {
return nil, err
}
// bounds check
if len(data) < 3 {
return nil, errors.New("not enough data")
}
var ok bool
value, ok = data[2].(*big.Int)
if !ok {
return nil, errors.New("not big.Int")
}
}
func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *common.Address) (*Withdrawal, error) {
// Attempt to parse the value
value, err := withdrawal.Value()
if err != nil {
return nil, fmt.Errorf("cannot migrate withdrawal: %w", err)
}
abi, err := bindings.L1CrossDomainMessengerMetaData.GetAbi()
......
......@@ -25,11 +25,9 @@ func TestMigrateWithdrawal(t *testing.T) {
}
l1CrossDomainMessenger := common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1")
l1StandardBridge := common.HexToAddress("0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1")
for i, legacy := range withdrawals {
t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) {
withdrawal, err := crossdomain.MigrateWithdrawal(legacy, &l1CrossDomainMessenger, &l1StandardBridge)
withdrawal, err := crossdomain.MigrateWithdrawal(legacy, &l1CrossDomainMessenger)
require.Nil(t, err)
require.NotNil(t, withdrawal)
......
......@@ -10,6 +10,7 @@ var (
Uint256Type, _ = abi.NewType("uint256", "", nil)
BytesType, _ = abi.NewType("bytes", "", nil)
AddressType, _ = abi.NewType("address", "", nil)
Bytes32Type, _ = abi.NewType("bytes32", "", nil)
)
// WithdrawalMessage represents a Withdrawal. The Withdrawal
......
......@@ -4,11 +4,22 @@ import (
"errors"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)
var (
SentMessageEventABI = "SentMessage(address,address,bytes,uint256)"
SentMessageEventABIHash = crypto.Keccak256Hash([]byte(SentMessageEventABI))
SentMessageExtension1EventABI = "SentMessage(address,uint256)"
SentMessageExtension1EventABIHash = crypto.Keccak256Hash([]byte(SentMessageExtension1EventABI))
MessagePassedEventABI = "MessagePassed(uint256,address,address,uint256,uint256,bytes,bytes32)"
MessagePassedEventABIHash = crypto.Keccak256Hash([]byte(MessagePassedEventABI))
)
var _ WithdrawalMessage = (*Withdrawal)(nil)
// Withdrawal represents a withdrawal transaction on L2
......@@ -130,3 +141,107 @@ func (w *Withdrawal) StorageSlot() (common.Hash, error) {
slot := crypto.Keccak256(preimage)
return common.BytesToHash(slot), nil
}
// Compute the receipt corresponding to the withdrawal. This receipt
// is in the bedrock transition block. It contains 3 logs.
// SentMessage, SentMessageExtension1 and MessagePassed.
// These logs are enough for the standard withdrawal flow to happen
// which is driven by events being emitted.
func (w *Withdrawal) Receipt(hdr *types.Header, txIndex uint) (*types.Receipt, error) {
// Create a new receipt with the state root, successful execution and no gas
// used
receipt := types.NewReceipt(hdr.Root.Bytes(), false, 0)
if receipt.Logs == nil {
receipt.Logs = make([]*types.Log, 0)
}
// Use a counter to track the log index. Each receipt has 3 events and there
// is 1 receipt per transaction. Increment the logIndex after appending the
// log to the receipt.
logIndex := txIndex * 3
// Create the SentMessage log.
args := abi.Arguments{
{Name: "target", Type: AddressType},
{Name: "sender", Type: AddressType},
{Name: "data", Type: BytesType},
{Name: "nonce", Type: Uint256Type},
}
data, err := args.Pack(w.Target, w.Sender, w.Data, w.Nonce)
if err != nil {
return nil, err
}
// The L2CrossDomainMessenger emits this event. The target is
// indexed.
sm := &types.Log{
Address: predeploys.L2CrossDomainMessengerAddr,
Topics: []common.Hash{
SentMessageEventABIHash,
w.Target.Hash(),
},
Data: data,
BlockNumber: hdr.Number.Uint64(),
TxHash: common.Hash{},
TxIndex: txIndex,
BlockHash: hdr.Hash(),
Index: logIndex,
Removed: false,
}
receipt.Logs = append(receipt.Logs, sm)
logIndex++
// Create the SentMessageExtension1 log. The L2CrossDomainMessenger
// emits this event. The sender is indexed.
sm1 := &types.Log{
Address: predeploys.L2CrossDomainMessengerAddr,
Topics: []common.Hash{
SentMessageExtension1EventABIHash,
w.Sender.Hash(),
},
Data: common.LeftPadBytes(w.Value.Bytes(), 32),
BlockNumber: hdr.Number.Uint64(),
TxHash: common.Hash{},
TxIndex: txIndex,
BlockHash: hdr.Hash(),
Index: logIndex,
Removed: false,
}
receipt.Logs = append(receipt.Logs, sm1)
logIndex++
// Create the MessagePassed log.
mpargs := abi.Arguments{
{Name: "value", Type: Uint256Type},
{Name: "gasLimit", Type: Uint256Type},
{Name: "data", Type: BytesType},
{Name: "withdrawalHash", Type: Bytes32Type},
}
hash, err := w.Hash()
if err != nil {
return nil, err
}
mpdata, err := mpargs.Pack(w.Value, w.GasLimit, w.Data, hash)
if err != nil {
return nil, err
}
// The L2ToL1MessagePasser emits this event.
mp := &types.Log{
Address: predeploys.L2ToL1MessagePasserAddr,
Topics: []common.Hash{
MessagePassedEventABIHash,
common.BytesToHash(common.LeftPadBytes(w.Nonce.Bytes(), 32)),
w.Sender.Hash(),
w.Target.Hash(),
},
Data: mpdata,
BlockNumber: hdr.Number.Uint64(),
TxHash: common.Hash{},
TxIndex: txIndex,
BlockHash: hdr.Hash(),
Index: logIndex,
Removed: false,
}
receipt.Logs = append(receipt.Logs, mp)
return receipt, nil
}
......@@ -80,7 +80,7 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m
}
log.Info("Starting to migrate withdrawals")
err = crossdomain.MigrateWithdrawals(withdrawals, db, &config.L1CrossDomainMessengerProxy, &config.L1StandardBridgeProxy)
err = crossdomain.MigrateWithdrawals(withdrawals, db, &config.L1CrossDomainMessengerProxy)
if err != nil {
return nil, fmt.Errorf("cannot migrate withdrawals: %w", err)
}
......@@ -115,7 +115,12 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m
BaseFee: (*big.Int)(config.L2GenesisBlockBaseFeePerGas),
}
bedrockBlock := types.NewBlock(bedrockHeader, nil, nil, nil, trie.NewStackTrie(nil))
receipts, err := CreateReceipts(bedrockHeader, withdrawals, &config.L1CrossDomainMessengerProxy)
if err != nil {
return nil, err
}
bedrockBlock := types.NewBlock(bedrockHeader, nil, nil, receipts, trie.NewStackTrie(nil))
res := &MigrationResult{
TransitionHeight: bedrockBlock.NumberU64(),
......@@ -130,7 +135,7 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m
rawdb.WriteTd(ldb, bedrockBlock.Hash(), bedrockBlock.NumberU64(), bedrockBlock.Difficulty())
rawdb.WriteBlock(ldb, bedrockBlock)
rawdb.WriteReceipts(ldb, bedrockBlock.Hash(), bedrockBlock.NumberU64(), nil)
rawdb.WriteReceipts(ldb, bedrockBlock.Hash(), bedrockBlock.NumberU64(), receipts)
rawdb.WriteCanonicalHash(ldb, bedrockBlock.Hash(), bedrockBlock.NumberU64())
rawdb.WriteHeadBlockHash(ldb, bedrockBlock.Hash())
rawdb.WriteHeadFastBlockHash(ldb, bedrockBlock.Hash())
......
package genesis
import (
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// CreateReceipts will create the set of bedrock genesis receipts given
// a list of legacy withdrawals.
func CreateReceipts(hdr *types.Header, withdrawals []*crossdomain.LegacyWithdrawal, l1CrossDomainMessenger *common.Address) ([]*types.Receipt, error) {
receipts := make([]*types.Receipt, 0)
for i, withdrawal := range withdrawals {
wd, err := crossdomain.MigrateWithdrawal(withdrawal, l1CrossDomainMessenger)
if err != nil {
return nil, err
}
receipt, err := wd.Receipt(hdr, uint(i))
if err != nil {
return nil, err
}
receipts = append(receipts, receipt)
}
return receipts, nil
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment