Commit 060841d0 authored by Andreas Bigger's avatar Andreas Bigger

Clean up op-chain-ops

parent 01b8b45b
op-migrate: all: check-l2
go build -o ./bin/op-migrate ./cmd/op-migrate/main.go
check-l2:
go build -o ./bin/check-l2 ./cmd/check-l2/main.go
test: test:
go test ./... go test ./...
...@@ -10,4 +12,4 @@ fuzz: ...@@ -10,4 +12,4 @@ fuzz:
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzAliasing ./crossdomain go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzAliasing ./crossdomain
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzVersionedNonce ./crossdomain go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzVersionedNonce ./crossdomain
.PHONY: op-migrate test .PHONY: check-l2 test fuzz
# op-chain-ops # op-chain-ops
This package performs state surgery. It takes the following input: This package contains a number of state utilities.
1. A v0 database ## check-l2
2. A partial `genesis.json`
3. A list of addresses that transacted on the network prior to this past regenesis.
4. A list of addresses that performed approvals on prior versions of the OVM ETH contract.
It creates an initialized Bedrock Geth database as output. It does this by performing the following steps: The `check-l2` binary is used for verifying that an OP Stack L2
has been configured correctly. It iterates over all 2048 predeployed
proxies to make sure they are configured correctly with the correct
proxy admin address. After that, it checks that all [predeploys](../op-bindings/predeploys/addresses.go)
are configured and aliased correctly. Additional contract-specific
checks ensure configuration like ownership, version, and storage
is set correctly for the predeploys.
1. Iterates over the old state. #### Usage
2. For each account in the old state, add that account and its storage to the new state after copying its balance from the OVM_ETH contract.
3. Iterates over the pre-allocated accounts in the genesis file and adds them to the new state.
4. Imports any accounts that have OVM ETH balances but aren't in state.
5. Configures a genesis block in the new state using `genesis.json`.
It performs the following integrity checks: It can be built and run using the [Makefile](./Makefile) `check-l2` target.
Run `make check-l2` to create a binary in [./bin/check-l2](./bin/check-l2)
that can be executed by providing the `--l1-rpc-url` and `--l2-rpc-url` flags.
1. OVM ETH storage slots must be completely accounted for. ```sh
2. The total supply of OVM ETH migrated must match the total supply of the OVM ETH contract. ./bin/check-l2 \
--l2-rpc-url http://localhost:9545 \
--l1-rpc-url http://localhost:8545
```
This process takes about two hours on mainnet. ## eof-crawler
Unlike previous iterations of our state surgery scripts, this one does not write results to a `genesis.json` file. This is for the following reasons: Simple CLI tool to scan all accounts in a geth LevelDB
for contracts that begin with the EOF prefix.
1. **Performance**. It's much faster to write binary to LevelDB than it is to write strings to a JSON file. #### Usage
2. **State Size**. There are nearly 1MM accounts on mainnet, which would create a genesis file several gigabytes in size. This is impossible for Geth to import without a large amount of memory, since the entire JSON gets buffered into memory. Importing the entire state database will be much faster, and can be done with fewer resources.
## Compilation It can be built and run using the [Makefile](./Makefile) `eof-crawler` target.
Run `make eof-crawler` to create a binary in [./bin/eof-crawler](./bin/eof-crawler)
that can be executed by providing the `--db-path` and optional `--out` flags.
Run `make op-migrate`. 1. Pass the directory of the Geth DB into the tool
```sh
./bin/eof-crawler/main.go \
--db-path <db_path> \
--out <out_file>
```
2. Once the indexing has completed, an array of all EOF-prefixed contracts
will be written to designated output file (`eof_contracts.json` by default).
package clients
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
// clients represents a set of initialized RPC clients
type Clients struct {
L1Client *ethclient.Client
L2Client *ethclient.Client
L1RpcClient *rpc.Client
L2RpcClient *rpc.Client
L1GethClient *gethclient.Client
L2GethClient *gethclient.Client
}
// NewClients will create new RPC clients from a CLI context
func NewClients(ctx *cli.Context) (*Clients, error) {
clients := Clients{}
if l1RpcURL := ctx.String("l1-rpc-url"); l1RpcURL != "" {
l1Client, l1RpcClient, l1GethClient, err := newClients(l1RpcURL)
if err != nil {
return nil, err
}
clients.L1Client = l1Client
clients.L1RpcClient = l1RpcClient
clients.L1GethClient = l1GethClient
}
if l2RpcURL := ctx.String("l2-rpc-url"); l2RpcURL != "" {
l2Client, l2RpcClient, l2GethClient, err := newClients(l2RpcURL)
if err != nil {
return nil, err
}
clients.L2Client = l2Client
clients.L2RpcClient = l2RpcClient
clients.L2GethClient = l2GethClient
}
return &clients, nil
}
// newClients will create new clients from a given URL
func newClients(url string) (*ethclient.Client, *rpc.Client, *gethclient.Client, error) {
ethClient, err := ethclient.Dial(url)
if err != nil {
return nil, nil, nil, fmt.Errorf("cannot dial ethclient: %w", err)
}
rpcClient, err := rpc.DialContext(context.Background(), url)
if err != nil {
return nil, nil, nil, fmt.Errorf("cannot dial rpc client: %w", err)
}
return ethClient, rpcClient, gethclient.New(rpcClient), nil
}
...@@ -17,8 +17,8 @@ import ( ...@@ -17,8 +17,8 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/clients"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-chain-ops/util"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -31,59 +31,88 @@ var defaultCrossDomainMessageSender = common.HexToAddress("0x0000000000000000000 ...@@ -31,59 +31,88 @@ var defaultCrossDomainMessageSender = common.HexToAddress("0x0000000000000000000
func main() { func main() {
log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd())))) log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd()))))
flags := []cli.Flag{}
flags = append(flags, util.ClientsFlags...)
flags = append(flags, util.AddressesFlags...)
app := &cli.App{ app := &cli.App{
Name: "check-l2", Name: "check-l2",
Usage: "Check that an OP Stack L2 has been configured correctly", Usage: "Check that an OP Stack L2 has been configured correctly",
Flags: flags, Flags: []cli.Flag{
Action: func(ctx *cli.Context) error { &cli.StringFlag{
clients, err := util.NewClients(ctx) Name: "l1-rpc-url",
if err != nil { Required: true,
return err Usage: "L1 RPC URL",
} EnvVars: []string{"L1_RPC_URL"},
},
&cli.StringFlag{
Name: "l2-rpc-url",
Required: true,
Usage: "L2 RPC URL",
EnvVars: []string{"L2_RPC_URL"},
},
},
Action: entrypoint,
}
log.Info("Checking predeploy proxy config") if err := app.Run(os.Args); err != nil {
g := new(errgroup.Group) log.Crit("error checking l2", "err", err)
}
// Check that all proxies are configured correctly }
// Do this in parallel but not too quickly to allow for
// querying against rate limiting RPC backends // entrypoint is the entrypoint for the check-l2 script
count := uint64(2048) func entrypoint(ctx *cli.Context) error {
for i := uint64(0); i < count; i++ { clients, err := clients.NewClients(ctx)
i := i if err != nil {
if i%4 == 0 { return err
log.Info("Checking proxy", "index", i, "total", count) }
if err := g.Wait(); err != nil {
return err log.Info("Checking predeploy proxy config")
} g := new(errgroup.Group)
}
g.Go(func() error {
return checkPredeploy(clients.L2Client, i)
})
}
// Check that all proxies are configured correctly
// Do this in parallel but not too quickly to allow for
// querying against rate limiting RPC backends
count := uint64(2048)
for i := uint64(0); i < count; i++ {
i := i
if i%4 == 0 {
log.Info("Checking proxy", "index", i, "total", count)
if err := g.Wait(); err != nil { if err := g.Wait(); err != nil {
return err return err
} }
log.Info("All predeploy proxies are set correctly") }
g.Go(func() error {
// Check that all of the defined predeploys are set up correctly return checkPredeploy(clients.L2Client, i)
for name, addr := range predeploys.Predeploys { })
log.Info("Checking predeploy", "name", name, "address", addr.Hex())
if err := checkPredeployConfig(clients.L2Client, name); err != nil {
return err
}
}
return nil
},
} }
if err := app.Run(os.Args); err != nil { if err := g.Wait(); err != nil {
log.Crit("error indexing state", "err", err) return err
}
log.Info("All predeploy proxies are set correctly")
// Check that all of the defined predeploys are set up correctly
for name, addr := range predeploys.Predeploys {
log.Info("Checking predeploy", "name", name, "address", addr.Hex())
if err := checkPredeployConfig(clients.L2Client, name); err != nil {
return err
}
}
return nil
}
// checkPredeploy ensures that the predeploy at index i has the correct proxy admin set
func checkPredeploy(client *ethclient.Client, i uint64) error {
bigAddr := new(big.Int).Or(genesis.BigL2PredeployNamespace, new(big.Int).SetUint64(i))
addr := common.BigToAddress(bigAddr)
if !predeploys.IsProxied(addr) {
return nil
}
admin, err := getEIP1967AdminAddress(client, addr)
if err != nil {
return err
}
if admin != predeploys.ProxyAdminAddr {
return fmt.Errorf("%s does not have correct proxy admin set", addr)
} }
return nil
} }
// checkPredeployConfig checks that the defined predeploys are configured correctly // checkPredeployConfig checks that the defined predeploys are configured correctly
...@@ -760,7 +789,7 @@ func checkEAS(addr common.Address, client *ethclient.Client) error { ...@@ -760,7 +789,7 @@ func checkEAS(addr common.Address, client *ethclient.Client) error {
} }
func getEIP1967AdminAddress(client *ethclient.Client, addr common.Address) (common.Address, error) { func getEIP1967AdminAddress(client *ethclient.Client, addr common.Address) (common.Address, error) {
slot, err := client.StorageAt(context.Background(), addr, util.EIP1967AdminSlot, nil) slot, err := client.StorageAt(context.Background(), addr, genesis.AdminSlot, nil)
if err != nil { if err != nil {
return common.Address{}, err return common.Address{}, err
} }
...@@ -769,27 +798,10 @@ func getEIP1967AdminAddress(client *ethclient.Client, addr common.Address) (comm ...@@ -769,27 +798,10 @@ func getEIP1967AdminAddress(client *ethclient.Client, addr common.Address) (comm
} }
func getEIP1967ImplementationAddress(client *ethclient.Client, addr common.Address) (common.Address, error) { func getEIP1967ImplementationAddress(client *ethclient.Client, addr common.Address) (common.Address, error) {
slot, err := client.StorageAt(context.Background(), addr, util.EIP1967ImplementationSlot, nil) slot, err := client.StorageAt(context.Background(), addr, genesis.ImplementationSlot, nil)
if err != nil { if err != nil {
return common.Address{}, err return common.Address{}, err
} }
impl := common.BytesToAddress(slot) impl := common.BytesToAddress(slot)
return impl, nil return impl, nil
} }
// checkPredeploy ensures that the predeploy at index i has the correct proxy admin set
func checkPredeploy(client *ethclient.Client, i uint64) error {
bigAddr := new(big.Int).Or(genesis.BigL2PredeployNamespace, new(big.Int).SetUint64(i))
addr := common.BigToAddress(bigAddr)
if !predeploys.IsProxied(addr) {
return nil
}
admin, err := getEIP1967AdminAddress(client, addr)
if err != nil {
return err
}
if admin != predeploys.ProxyAdminAddr {
return fmt.Errorf("%s does not have correct proxy admin set", addr)
}
return nil
}
package contracts
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
// parseAddress will parse a [common.Address] from a [cli.Context] and return
// an error if the configured address is not correct.
func parseAddress(ctx *cli.Context, name string) (common.Address, error) {
value := ctx.String(name)
if value == "" {
return common.Address{}, nil
}
if !common.IsHexAddress(value) {
return common.Address{}, fmt.Errorf("invalid address: %s", value)
}
return common.HexToAddress(value), nil
}
package contracts
import (
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
// Addresses represents the address values of various contracts. The values can
// be easily populated via a [cli.Context].
type Addresses struct {
AddressManager common.Address
OptimismPortal common.Address
L1StandardBridge common.Address
L1CrossDomainMessenger common.Address
CanonicalTransactionChain common.Address
StateCommitmentChain common.Address
}
// NewAddresses populates an Addresses struct given a [cli.Context].
// This is useful for writing scripts that interact with smart contracts.
func NewAddresses(ctx *cli.Context) (*Addresses, error) {
var addresses Addresses
var err error
addresses.AddressManager, err = parseAddress(ctx, "address-manager-address")
if err != nil {
return nil, err
}
addresses.OptimismPortal, err = parseAddress(ctx, "optimism-portal-address")
if err != nil {
return nil, err
}
addresses.L1StandardBridge, err = parseAddress(ctx, "l1-standard-bridge-address")
if err != nil {
return nil, err
}
addresses.L1CrossDomainMessenger, err = parseAddress(ctx, "l1-crossdomain-messenger-address")
if err != nil {
return nil, err
}
addresses.CanonicalTransactionChain, err = parseAddress(ctx, "canonical-transaction-chain-address")
if err != nil {
return nil, err
}
addresses.StateCommitmentChain, err = parseAddress(ctx, "state-commitment-chain-address")
if err != nil {
return nil, err
}
return &addresses, nil
}
package db
import (
"path/filepath"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
)
func Open(path string, cache int, handles int) (ethdb.Database, error) {
chaindataPath := filepath.Join(path, "geth", "chaindata")
ancientPath := filepath.Join(chaindataPath, "ancient")
ldb, err := rawdb.Open(rawdb.OpenOptions{
Type: "leveldb",
Directory: chaindataPath,
AncientsDirectory: ancientPath,
Namespace: "",
Cache: cache,
Handles: handles,
ReadOnly: false,
})
if err != nil {
return nil, err
}
return ldb, nil
}
# `eof-crawler`
Simple CLI tool to scan all accounts in a geth LevelDB for contracts that begin with the EOF prefix.
## Usage
1. Pass the directory of the Geth DB into the tool
```sh
go run ./cmd/eof-crawler/main.go --db-path <db_path> [--out <out_file>]
```
2. Once the indexing has completed, an array of all EOF-prefixed contracts will be written to `eof_contracts.json` or the designated output file.
package ether
import (
"encoding/json"
"io"
"os"
"github.com/ethereum/go-ethereum/core"
)
// ReadGenesisFromFile reads a genesis object from a file.
func ReadGenesisFromFile(path string) (*core.Genesis, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return ReadGenesis(f)
}
// ReadGenesis reads a genesis object from an io.Reader.
func ReadGenesis(r io.Reader) (*core.Genesis, error) {
genesis := new(core.Genesis)
if err := json.NewDecoder(r).Decode(genesis); err != nil {
return nil, err
}
return genesis, nil
}
package util
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
var (
// EIP1976ImplementationSlot
EIP1967ImplementationSlot = common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
// EIP1967AdminSlot
EIP1967AdminSlot = common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")
)
// clients represents a set of initialized RPC clients
type Clients struct {
L1Client *ethclient.Client
L2Client *ethclient.Client
L1RpcClient *rpc.Client
L2RpcClient *rpc.Client
L1GethClient *gethclient.Client
L2GethClient *gethclient.Client
}
// NewClients will create new RPC clients from a CLI context
func NewClients(ctx *cli.Context) (*Clients, error) {
clients := Clients{}
l1RpcURL := ctx.String("l1-rpc-url")
if l1RpcURL != "" {
l1Client, l1RpcClient, l1GethClient, err := newClients(l1RpcURL)
if err != nil {
return nil, err
}
clients.L1Client = l1Client
clients.L1RpcClient = l1RpcClient
clients.L1GethClient = l1GethClient
}
l2RpcURL := ctx.String("l2-rpc-url")
if l2RpcURL != "" {
l2Client, l2RpcClient, l2GethClient, err := newClients(l2RpcURL)
if err != nil {
return nil, err
}
clients.L2Client = l2Client
clients.L2RpcClient = l2RpcClient
clients.L2GethClient = l2GethClient
}
return &clients, nil
}
// newClients will create new clients from a given URL
func newClients(url string) (*ethclient.Client, *rpc.Client, *gethclient.Client, error) {
ethClient, err := ethclient.Dial(url)
if err != nil {
return nil, nil, nil, fmt.Errorf("cannot dial ethclient: %w", err)
}
rpcClient, err := rpc.DialContext(context.Background(), url)
if err != nil {
return nil, nil, nil, fmt.Errorf("cannot dial rpc client: %w", err)
}
return ethClient, rpcClient, gethclient.New(rpcClient), nil
}
// ClientsFlags represent the flags associated with creating RPC clients.
var ClientsFlags = []cli.Flag{
&cli.StringFlag{
Name: "l1-rpc-url",
Required: true,
Usage: "L1 RPC URL",
EnvVars: []string{"L1_RPC_URL"},
},
&cli.StringFlag{
Name: "l2-rpc-url",
Required: true,
Usage: "L2 RPC URL",
EnvVars: []string{"L2_RPC_URL"},
},
}
// Addresses represents the address values of various contracts. The values can
// be easily populated via a [cli.Context].
type Addresses struct {
AddressManager common.Address
OptimismPortal common.Address
L1StandardBridge common.Address
L1CrossDomainMessenger common.Address
CanonicalTransactionChain common.Address
StateCommitmentChain common.Address
}
// AddressesFlags represent the flags associated with address parsing.
var AddressesFlags = []cli.Flag{
&cli.StringFlag{
Name: "address-manager-address",
Usage: "AddressManager address",
EnvVars: []string{"ADDRESS_MANAGER_ADDRESS"},
},
&cli.StringFlag{
Name: "optimism-portal-address",
Usage: "OptimismPortal address",
EnvVars: []string{"OPTIMISM_PORTAL_ADDRESS"},
},
&cli.StringFlag{
Name: "l1-standard-bridge-address",
Usage: "L1StandardBridge address",
EnvVars: []string{"L1_STANDARD_BRIDGE_ADDRESS"},
},
&cli.StringFlag{
Name: "l1-crossdomain-messenger-address",
Usage: "L1CrossDomainMessenger address",
EnvVars: []string{"L1_CROSSDOMAIN_MESSENGER_ADDRESS"},
},
&cli.StringFlag{
Name: "canonical-transaction-chain-address",
Usage: "CanonicalTransactionChain address",
EnvVars: []string{"CANONICAL_TRANSACTION_CHAIN_ADDRESS"},
},
&cli.StringFlag{
Name: "state-commitment-chain-address",
Usage: "StateCommitmentChain address",
EnvVars: []string{"STATE_COMMITMENT_CHAIN_ADDRESS"},
},
}
// NewAddresses populates an Addresses struct given a [cli.Context].
// This is useful for writing scripts that interact with smart contracts.
func NewAddresses(ctx *cli.Context) (*Addresses, error) {
var addresses Addresses
var err error
addresses.AddressManager, err = parseAddress(ctx, "address-manager-address")
if err != nil {
return nil, err
}
addresses.OptimismPortal, err = parseAddress(ctx, "optimism-portal-address")
if err != nil {
return nil, err
}
addresses.L1StandardBridge, err = parseAddress(ctx, "l1-standard-bridge-address")
if err != nil {
return nil, err
}
addresses.L1CrossDomainMessenger, err = parseAddress(ctx, "l1-crossdomain-messenger-address")
if err != nil {
return nil, err
}
addresses.CanonicalTransactionChain, err = parseAddress(ctx, "canonical-transaction-chain-address")
if err != nil {
return nil, err
}
addresses.StateCommitmentChain, err = parseAddress(ctx, "state-commitment-chain-address")
if err != nil {
return nil, err
}
return &addresses, nil
}
// parseAddress will parse a [common.Address] from a [cli.Context] and return
// an error if the configured address is not correct.
func parseAddress(ctx *cli.Context, name string) (common.Address, error) {
value := ctx.String(name)
if value == "" {
return common.Address{}, nil
}
if !common.IsHexAddress(value) {
return common.Address{}, fmt.Errorf("invalid address: %s", value)
}
return common.HexToAddress(value), 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