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

Merge pull request #3767 from ethereum-optimism/feat/db-migration

op-chain-ops: implement migration script
parents 8d93fd90 4cf68cbd
package main
import (
"context"
"math/big"
"os"
"path/filepath"
"github.com/ethereum-optimism/optimism/l2geth/core/rawdb"
"github.com/ethereum-optimism/optimism/l2geth/core/state"
"github.com/ethereum-optimism/optimism/l2geth/log"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
op_state "github.com/ethereum-optimism/optimism/op-chain-ops/state"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/mattn/go-isatty"
"github.com/urfave/cli"
)
func main() {
log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd()))))
app := &cli.App{
Name: "migrate",
Usage: "Migrate a legacy database",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "l1-rpc-url",
Value: "http://127.0.0.1:8545",
Usage: "RPC URL for an L1 Node",
},
&cli.Uint64Flag{
Name: "starting-l1-block-number",
Usage: "L1 block number to build the L2 genesis from",
},
&cli.StringFlag{
Name: "ovm-addresses",
Usage: "Path to ovm-addresses.json",
},
&cli.StringFlag{
Name: "evm-addresses",
Usage: "Path to evm-addresses.json",
},
&cli.StringFlag{
Name: "ovm-allowances",
Usage: "Path to ovm-allowances.json",
},
&cli.StringFlag{
Name: "ovm-messages",
Usage: "Path to ovm-messages.json",
},
&cli.StringFlag{
Name: "evm-messages",
Usage: "Path to evm-messages.json",
},
&cli.StringFlag{
Name: "db-path",
Usage: "Path to database",
},
cli.StringFlag{
Name: "deploy-config",
Usage: "Path to hardhat deploy config file",
},
cli.BoolFlag{
Name: "dry-run",
Usage: "Dry run the upgrade by not committing the database",
},
},
Action: func(ctx *cli.Context) error {
deployConfig := ctx.String("deploy-config")
config, err := genesis.NewDeployConfig(deployConfig)
if err != nil {
return err
}
ovmAddresses, err := genesis.NewAddresses(ctx.String("ovm-addresses"))
if err != nil {
return err
}
evmAddresess, err := genesis.NewAddresses(ctx.String("evm-addresses"))
if err != nil {
return err
}
ovmAllowances, err := genesis.NewAllowances(ctx.String("ovm-allowances"))
if err != nil {
return err
}
ovmMessages, err := genesis.NewSentMessage(ctx.String("ovm-messages"))
if err != nil {
return err
}
evmMessages, err := genesis.NewSentMessage(ctx.String("evm-messages"))
if err != nil {
return err
}
migrationData := genesis.MigrationData{
OvmAddresses: ovmAddresses,
EvmAddresses: evmAddresess,
OvmAllowances: ovmAllowances,
OvmMessages: ovmMessages,
EvmMessages: evmMessages,
}
l1RpcURL := ctx.String("l1-rpc-url")
l1Client, err := ethclient.Dial(l1RpcURL)
if err != nil {
return err
}
var blockNumber *big.Int
bnum := ctx.Uint64("starting-l1-block-number")
if bnum != 0 {
blockNumber = new(big.Int).SetUint64(bnum)
}
block, err := l1Client.BlockByNumber(context.Background(), blockNumber)
if err != nil {
return err
}
chaindataPath := filepath.Join(ctx.String("db-path"), "geth", "chaindata")
ldb, err := rawdb.NewLevelDBDatabase(chaindataPath, 1024, 64, "")
if err != nil {
return err
}
hash := rawdb.ReadHeadHeaderHash(ldb)
if err != nil {
return err
}
num := rawdb.ReadHeaderNumber(ldb, hash)
header := rawdb.ReadHeader(ldb, hash, *num)
sdb, err := state.New(header.Root, state.NewDatabase(ldb))
if err != nil {
return err
}
wrappedDB, err := op_state.NewWrappedStateDB(nil, sdb)
if err != nil {
return err
}
l2Addrs := genesis.L2Addresses{
ProxyAdminOwner: config.ProxyAdminOwner,
// TODO: these values are not in the config
L1StandardBridgeProxy: common.Address{},
L1CrossDomainMessengerProxy: common.Address{},
L1ERC721BridgeProxy: common.Address{},
}
if err := genesis.MigrateDB(wrappedDB, config, block, &l2Addrs, &migrationData); err != nil {
return err
}
if ctx.Bool("dry-run") {
log.Info("Dry run complete")
return nil
}
root, err := sdb.Commit(true)
if err != nil {
return err
}
log.Info("Migration complete", "root", root)
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Crit("error in migration", "err", err)
}
}
......@@ -18,7 +18,7 @@ var (
)
// MigrateWithdrawals will migrate a list of pending withdrawals given a StateDB.
func MigrateWithdrawals(withdrawals []*PendingWithdrawal, db vm.StateDB, l1CrossDomainMessenger, l1StandardBridge *common.Address) error {
func MigrateWithdrawals(withdrawals []*LegacyWithdrawal, db vm.StateDB, l1CrossDomainMessenger, l1StandardBridge *common.Address) error {
for _, legacy := range withdrawals {
legacySlot, err := legacy.StorageSlot()
if err != nil {
......@@ -30,7 +30,7 @@ func MigrateWithdrawals(withdrawals []*PendingWithdrawal, db vm.StateDB, l1Cross
return fmt.Errorf("%w: %s", errLegacyStorageSlotNotFound, legacyValue)
}
withdrawal, err := MigrateWithdrawal(&legacy.LegacyWithdrawal, l1CrossDomainMessenger, l1StandardBridge)
withdrawal, err := MigrateWithdrawal(legacy, l1CrossDomainMessenger, l1StandardBridge)
if err != nil {
return err
}
......
package genesis
import (
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
)
// MigrateDB will migrate an old l2geth database to the new bedrock style system
func MigrateDB(db vm.StateDB, config *DeployConfig, l1Block *types.Block, l2Addrs *L2Addresses, migrationData *MigrationData) error {
if err := SetL2Proxies(db); err != nil {
return err
}
storage, err := NewL2StorageConfig(config, l1Block, l2Addrs)
if err != nil {
return err
}
immutable, err := NewL2ImmutableConfig(config, l1Block, l2Addrs)
if err != nil {
return err
}
if err := SetImplementations(db, storage, immutable); err != nil {
return err
}
// Convert all of the messages into legacy withdrawals
messages := make([]*crossdomain.LegacyWithdrawal, 0)
for _, msg := range migrationData.OvmMessages {
wd, err := msg.ToLegacyWithdrawal()
if err != nil {
return err
}
messages = append(messages, wd)
}
for _, msg := range migrationData.EvmMessages {
wd, err := msg.ToLegacyWithdrawal()
if err != nil {
return err
}
messages = append(messages, wd)
}
if err := crossdomain.MigrateWithdrawals(messages, db, &l2Addrs.L1CrossDomainMessengerProxy, &l2Addrs.L1StandardBridgeProxy); err != nil {
return err
}
// TODO: use migration data to double check things
return nil
}
package genesis
import (
"encoding/json"
"fmt"
"os"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// SentMessageJSON represents an entry in the JSON file that is created by
// the `migration-data` package. Each entry represents a call to the
// `LegacyMessagePasser`. The `who` should always be the
// `L2CrossDomainMessenger` and the `msg` should be an abi encoded
// `relayMessage(address,address,bytes,uint256)`
type SentMessage struct {
Who common.Address `json:"who"`
Msg hexutil.Bytes `json:"msg"`
}
// NewSentMessageJSON will read a JSON file from disk given a path to the JSON
// file. The JSON file this function reads from disk is an output from the
// `migration-data` package.
func NewSentMessage(path string) ([]*SentMessage, error) {
file, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("cannot find sent message json at %s: %w", path, err)
}
var j []*SentMessage
if err := json.Unmarshal(file, &j); err != nil {
return nil, err
}
return j, nil
}
// ToLegacyWithdrawal will convert a SentMessageJSON to a LegacyWithdrawal
// struct. This is useful because the LegacyWithdrawal struct has helper
// functions on it that can compute the withdrawal hash and the storage slot.
func (s *SentMessage) ToLegacyWithdrawal() (*crossdomain.LegacyWithdrawal, error) {
data := make([]byte, 0, len(s.Who)+len(s.Msg))
copy(data, s.Msg)
copy(data[len(s.Msg):], s.Who[:])
var w crossdomain.LegacyWithdrawal
if err := w.Decode(data); err != nil {
return nil, err
}
return &w, nil
}
// OVMETHAddresses represents a list of addresses that interacted with
// the ERC20 representation of ether in the pre-bedrock system.
type OVMETHAddresses map[common.Address]bool
// NewAddresses will read an addresses.json file from the filesystem.
func NewAddresses(path string) (OVMETHAddresses, error) {
file, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("cannot find addresses json at %s: %w", path, err)
}
var addresses []common.Address
if err := json.Unmarshal(file, &addresses); err != nil {
return nil, err
}
ovmeth := make(OVMETHAddresses)
for _, addr := range addresses {
ovmeth[addr] = true
}
return ovmeth, nil
}
// Allowance represents the allowances that were set in the
// legacy ERC20 representation of ether
type Allowance struct {
From common.Address `json:"fr"`
To common.Address `json:"to"`
}
// NewAllowances will read the ovm-allowances.json from the file system.
func NewAllowances(path string) ([]*Allowance, error) {
file, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("cannot find allowances json at %s: %w", path, err)
}
var allowances []*Allowance
if err := json.Unmarshal(file, &allowances); err != nil {
return nil, err
}
return allowances, nil
}
// MigrationData represents all of the data required to do a migration
type MigrationData struct {
// OvmAddresses represents the set of addresses that interacted with the
// LegacyERC20ETH contract before the evm equivalence upgrade
OvmAddresses OVMETHAddresses
// EvmAddresses represents the set of addresses that interacted with the
// LegacyERC20ETH contract after the evm equivalence upgrade
EvmAddresses OVMETHAddresses
// OvmAllowances represents the set of allowances in the LegacyERC20ETH from
// before the evm equivalence upgrade
OvmAllowances []*Allowance
// OvmMessages represents the set of withdrawals through the
// L2CrossDomainMessenger from before the evm equivalence upgrade
OvmMessages []*SentMessage
// OvmMessages represents the set of withdrawals through the
// L2CrossDomainMessenger from after the evm equivalence upgrade
EvmMessages []*SentMessage
}
......@@ -9,6 +9,7 @@ require (
github.com/holiman/uint256 v1.2.0
github.com/mattn/go-isatty v0.0.14
github.com/stretchr/testify v1.8.0
github.com/urfave/cli v1.22.1
github.com/urfave/cli/v2 v2.10.2
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
)
......
......@@ -613,6 +613,7 @@ github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:s
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y=
......
package state
import (
"errors"
"math/big"
lcommon "github.com/ethereum-optimism/optimism/l2geth/common"
lstate "github.com/ethereum-optimism/optimism/l2geth/core/state"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
)
// WrappedStateDB wraps both the StateDB types from l2geth and upstream geth.
// This allows for a l2geth StateDB to be passed to functions that expect an
// upstream geth StateDB.
type WrappedStateDB struct {
statedb *state.StateDB
legacyStatedb *lstate.StateDB
}
// NewWrappedStateDB will create a WrappedStateDB. It can wrap either an
// upstream geth database or a legacy l2geth database.
func NewWrappedStateDB(statedb *state.StateDB, legacyStatedb *lstate.StateDB) (*WrappedStateDB, error) {
if statedb == nil && legacyStatedb == nil {
return nil, errors.New("must pass at least 1 database")
}
if statedb != nil && legacyStatedb != nil {
return nil, errors.New("cannot pass both databases")
}
return &WrappedStateDB{
statedb: statedb,
legacyStatedb: legacyStatedb,
}, nil
}
func (w *WrappedStateDB) CreateAccount(addr common.Address) {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
w.legacyStatedb.CreateAccount(address)
} else {
w.statedb.CreateAccount(addr)
}
}
func (w *WrappedStateDB) SubBalance(addr common.Address, value *big.Int) {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
w.legacyStatedb.SubBalance(address, value)
} else {
w.statedb.SubBalance(addr, value)
}
}
func (w *WrappedStateDB) AddBalance(addr common.Address, value *big.Int) {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
w.legacyStatedb.AddBalance(address, value)
} else {
w.statedb.AddBalance(addr, value)
}
}
func (w *WrappedStateDB) GetBalance(addr common.Address) *big.Int {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
return w.legacyStatedb.GetBalance(address)
} else {
return w.statedb.GetBalance(addr)
}
}
func (w *WrappedStateDB) GetNonce(addr common.Address) uint64 {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
return w.legacyStatedb.GetNonce(address)
} else {
return w.statedb.GetNonce(addr)
}
}
func (w *WrappedStateDB) SetNonce(addr common.Address, nonce uint64) {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
w.legacyStatedb.SetNonce(address, nonce)
} else {
w.statedb.SetNonce(addr, nonce)
}
}
func (w *WrappedStateDB) GetCodeHash(addr common.Address) common.Hash {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
hash := w.legacyStatedb.GetCodeHash(address)
return common.BytesToHash(hash.Bytes())
} else {
return w.statedb.GetCodeHash(addr)
}
}
func (w *WrappedStateDB) GetCode(addr common.Address) []byte {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
return w.legacyStatedb.GetCode(address)
} else {
return w.statedb.GetCode(addr)
}
}
func (w *WrappedStateDB) SetCode(addr common.Address, code []byte) {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
w.legacyStatedb.SetCode(address, code)
} else {
w.statedb.SetCode(addr, code)
}
}
func (w *WrappedStateDB) GetCodeSize(addr common.Address) int {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
return w.legacyStatedb.GetCodeSize(address)
} else {
return w.statedb.GetCodeSize(addr)
}
}
func (w *WrappedStateDB) AddRefund(refund uint64) {
if w.legacyStatedb != nil {
w.legacyStatedb.AddRefund(refund)
} else {
w.statedb.AddRefund(refund)
}
}
func (w *WrappedStateDB) SubRefund(refund uint64) {
if w.legacyStatedb != nil {
w.legacyStatedb.SubRefund(refund)
} else {
w.statedb.SubRefund(refund)
}
}
func (w *WrappedStateDB) GetRefund() uint64 {
if w.legacyStatedb != nil {
return w.legacyStatedb.GetRefund()
} else {
return w.statedb.GetRefund()
}
}
func (w *WrappedStateDB) GetCommittedState(addr common.Address, key common.Hash) common.Hash {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
lkey := lcommon.BytesToHash(key.Bytes())
value := w.legacyStatedb.GetCommittedState(address, lkey)
return common.BytesToHash(value.Bytes())
} else {
return w.statedb.GetCommittedState(addr, key)
}
}
func (w *WrappedStateDB) GetState(addr common.Address, key common.Hash) common.Hash {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
lkey := lcommon.BytesToHash(key.Bytes())
value := w.legacyStatedb.GetState(address, lkey)
return common.BytesToHash(value.Bytes())
} else {
return w.statedb.GetState(addr, key)
}
}
func (w *WrappedStateDB) SetState(addr common.Address, key, value common.Hash) {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
lkey := lcommon.BytesToHash(key.Bytes())
lvalue := lcommon.BytesToHash(value.Bytes())
w.legacyStatedb.SetState(address, lkey, lvalue)
} else {
w.statedb.SetState(addr, key, value)
}
}
func (w *WrappedStateDB) Suicide(addr common.Address) bool {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
return w.legacyStatedb.Suicide(address)
} else {
return w.statedb.Suicide(addr)
}
}
func (w *WrappedStateDB) HasSuicided(addr common.Address) bool {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
return w.legacyStatedb.HasSuicided(address)
} else {
return w.statedb.HasSuicided(addr)
}
}
func (w *WrappedStateDB) Exist(addr common.Address) bool {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
return w.legacyStatedb.Exist(address)
} else {
return w.statedb.Exist(addr)
}
}
func (w *WrappedStateDB) Empty(addr common.Address) bool {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
return w.legacyStatedb.Empty(address)
} else {
return w.statedb.Empty(addr)
}
}
func (w *WrappedStateDB) PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) {
if w.legacyStatedb != nil {
panic("PrepareAccessList unimplemented")
} else {
w.statedb.PrepareAccessList(sender, dest, precompiles, txAccesses)
}
}
func (w *WrappedStateDB) AddressInAccessList(addr common.Address) bool {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
return w.legacyStatedb.AddressInAccessList(address)
} else {
return w.statedb.AddressInAccessList(addr)
}
}
func (w *WrappedStateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
lslot := lcommon.BytesToHash(slot.Bytes())
return w.legacyStatedb.SlotInAccessList(address, lslot)
} else {
return w.statedb.SlotInAccessList(addr, slot)
}
}
func (w *WrappedStateDB) AddAddressToAccessList(addr common.Address) {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
w.legacyStatedb.AddAddressToAccessList(address)
} else {
w.statedb.AddAddressToAccessList(addr)
}
}
func (w *WrappedStateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
lslot := lcommon.BytesToHash(slot.Bytes())
w.legacyStatedb.AddSlotToAccessList(address, lslot)
} else {
w.statedb.AddSlotToAccessList(addr, slot)
}
}
func (w *WrappedStateDB) RevertToSnapshot(snapshot int) {
if w.legacyStatedb != nil {
w.legacyStatedb.RevertToSnapshot(snapshot)
} else {
w.statedb.RevertToSnapshot(snapshot)
}
}
func (w *WrappedStateDB) Snapshot() int {
if w.legacyStatedb != nil {
return w.legacyStatedb.Snapshot()
} else {
return w.statedb.Snapshot()
}
}
func (w *WrappedStateDB) AddLog(log *types.Log) {
if w.legacyStatedb != nil {
panic("AddLog unimplemented")
} else {
w.statedb.AddLog(log)
}
}
func (w *WrappedStateDB) AddPreimage(hash common.Hash, preimage []byte) {
if w.legacyStatedb != nil {
lhash := lcommon.BytesToHash(hash.Bytes())
w.legacyStatedb.AddPreimage(lhash, preimage)
} else {
w.statedb.AddPreimage(hash, preimage)
}
}
func (w *WrappedStateDB) ForEachStorage(addr common.Address, cb func(common.Hash, common.Hash) bool) error {
if w.legacyStatedb != nil {
address := lcommon.BytesToAddress(addr.Bytes())
return w.legacyStatedb.ForEachStorage(address, func(lkey, lvalue lcommon.Hash) bool {
key := common.BytesToHash(lkey.Bytes())
value := common.BytesToHash(lvalue.Bytes())
return cb(key, value)
})
} else {
return w.statedb.ForEachStorage(addr, cb)
}
}
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