Commit 7b857820 authored by Sam Stokes's avatar Sam Stokes Committed by GitHub

op-node: set genesis l1 start block to value from SystemConfig contract (#11181)

* set genesis l1 start block to value from SystemConfig

* always retrieve genesis l1StartBlock from SystemConfig.startBlock()

* use contract-bedrock abi instead of large bindings file

* fix startBlock contract call

* use single func call instead of importing full SystemConfig abi

* use vars instead of hardcoded flag strings
parent 421f44fc
......@@ -2,19 +2,16 @@ package genesis
import (
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
"github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
......@@ -24,11 +21,8 @@ import (
var (
l1RPCFlag = &cli.StringFlag{
Name: "l1-rpc",
Usage: "RPC URL for an Ethereum L1 node. Cannot be used with --l1-starting-block",
}
l1StartingBlockFlag = &cli.PathFlag{
Name: "l1-starting-block",
Usage: "Path to a JSON file containing the L1 starting block. Overrides the need for using an L1 RPC to fetch the block. Cannot be used with --l1-rpc",
Usage: "RPC URL for an Ethereum L1 node",
Required: true,
}
deployConfigFlag = &cli.PathFlag{
Name: "deploy-config",
......@@ -71,7 +65,6 @@ var (
l2Flags = []cli.Flag{
l1RPCFlag,
l1StartingBlockFlag,
deployConfigFlag,
l2AllocsFlag,
l1DeploymentsFlag,
......@@ -86,14 +79,15 @@ var Subcommands = cli.Commands{
Usage: "Generates a L1 genesis state file",
Flags: l1Flags,
Action: func(ctx *cli.Context) error {
deployConfig := ctx.String("deploy-config")
deployConfig := ctx.String(deployConfigFlag.Name)
config, err := genesis.NewDeployConfig(deployConfig)
if err != nil {
return err
}
var deployments *genesis.L1Deployments
if l1Deployments := ctx.String("l1-deployments"); l1Deployments != "" {
if l1Deployments := ctx.String(l1DeploymentsFlag.Name); l1Deployments != "" {
deployments, err = genesis.NewL1Deployments(l1Deployments)
if err != nil {
return err
......@@ -114,7 +108,7 @@ var Subcommands = cli.Commands{
}
var dump *foundry.ForgeAllocs
if l1Allocs := ctx.String("l1-allocs"); l1Allocs != "" {
if l1Allocs := ctx.String(l1AllocsFlag.Name); l1Allocs != "" {
dump, err = foundry.LoadForgeAllocs(l1Allocs)
if err != nil {
return err
......@@ -126,7 +120,7 @@ var Subcommands = cli.Commands{
return err
}
return jsonutil.WriteJSON(ctx.String("outfile.l1"), l1Genesis, 0o666)
return jsonutil.WriteJSON(ctx.String(outfileL1Flag.Name), l1Genesis, 0o666)
},
},
{
......@@ -139,23 +133,15 @@ var Subcommands = cli.Commands{
"or it can be provided as a JSON file.",
Flags: l2Flags,
Action: func(ctx *cli.Context) error {
deployConfig := ctx.Path("deploy-config")
deployConfig := ctx.Path(deployConfigFlag.Name)
log.Info("Deploy config", "path", deployConfig)
config, err := genesis.NewDeployConfig(deployConfig)
if err != nil {
return err
}
l1Deployments := ctx.Path("l1-deployments")
l1StartBlockPath := ctx.Path("l1-starting-block")
l1RPC := ctx.String("l1-rpc")
if l1StartBlockPath == "" && l1RPC == "" {
return errors.New("must specify either --l1-starting-block or --l1-rpc")
}
if l1StartBlockPath != "" && l1RPC != "" {
return errors.New("cannot specify both --l1-starting-block and --l1-rpc")
}
l1Deployments := ctx.Path(l1DeploymentsFlag.Name)
l1RPC := ctx.String(l1RPCFlag.Name)
deployments, err := genesis.NewL1Deployments(l1Deployments)
if err != nil {
......@@ -163,15 +149,8 @@ var Subcommands = cli.Commands{
}
config.SetDeployments(deployments)
var l1StartBlock *types.Block
if l1StartBlockPath != "" {
if l1StartBlock, err = readBlockJSON(l1StartBlockPath); err != nil {
return fmt.Errorf("cannot read L1 starting block at %s: %w", l1StartBlockPath, err)
}
}
var l2Allocs *foundry.ForgeAllocs
if l2AllocsPath := ctx.String("l2-allocs"); l2AllocsPath != "" {
if l2AllocsPath := ctx.String(l2AllocsFlag.Name); l2AllocsPath != "" {
l2Allocs, err = foundry.LoadForgeAllocs(l2AllocsPath)
if err != nil {
return err
......@@ -180,39 +159,30 @@ var Subcommands = cli.Commands{
return errors.New("missing l2-allocs")
}
if l1RPC != "" {
// Retrieve SystemConfig.startBlock()
client, err := ethclient.Dial(l1RPC)
if err != nil {
return fmt.Errorf("cannot dial %s: %w", l1RPC, err)
}
if config.L1StartingBlockTag == nil {
l1StartBlock, err = client.BlockByNumber(context.Background(), nil)
if err != nil {
return fmt.Errorf("cannot fetch latest block: %w", err)
funcDef := "startBlock()"
funcHash := crypto.Keccak256Hash([]byte(funcDef))
msg := ethereum.CallMsg{
To: &config.SystemConfigProxy,
Data: funcHash[:4], // hardcode the 4byte sig
}
tag := rpc.BlockNumberOrHashWithHash(l1StartBlock.Hash(), true)
config.L1StartingBlockTag = (*genesis.MarshalableRPCBlockNumberOrHash)(&tag)
} else if config.L1StartingBlockTag.BlockHash != nil {
l1StartBlock, err = client.BlockByHash(context.Background(), *config.L1StartingBlockTag.BlockHash)
result, err := client.CallContract(context.Background(), msg, nil)
if err != nil {
return fmt.Errorf("cannot fetch block by hash: %w", err)
return fmt.Errorf("failed to fetch startBlock from SystemConfig contract: %w", err)
}
} else if config.L1StartingBlockTag.BlockNumber != nil {
l1StartBlock, err = client.BlockByNumber(context.Background(), big.NewInt(config.L1StartingBlockTag.BlockNumber.Int64()))
l1StartBlockNum := new(big.Int).SetBytes(result)
l1StartBlock, err := client.BlockByNumber(context.Background(), l1StartBlockNum)
if err != nil {
return fmt.Errorf("cannot fetch block by number: %w", err)
}
}
}
// Ensure that there is a starting L1 block
if l1StartBlock == nil {
return errors.New("no starting L1 block")
}
// Sanity check the config. Do this after filling in the L1StartingBlockTag
// if it is not defined.
// Sanity check the config
if err := config.Check(); err != nil {
return err
}
......@@ -234,62 +204,10 @@ var Subcommands = cli.Commands{
return fmt.Errorf("generated rollup config does not pass validation: %w", err)
}
if err := jsonutil.WriteJSON(ctx.String("outfile.l2"), l2Genesis, 0o666); err != nil {
if err := jsonutil.WriteJSON(ctx.String(outfileL2Flag.Name), l2Genesis, 0o666); err != nil {
return err
}
return jsonutil.WriteJSON(ctx.String("outfile.rollup"), rollupConfig, 0o666)
return jsonutil.WriteJSON(ctx.String(outfileRollupFlag.Name), rollupConfig, 0o666)
},
},
}
// rpcBlock represents the JSON serialization of a block from an Ethereum RPC.
type rpcBlock struct {
Hash common.Hash `json:"hash"`
Transactions []rpcTransaction `json:"transactions"`
UncleHashes []common.Hash `json:"uncles"`
Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
}
// rpcTransaction represents the JSON serialization of a transaction from an Ethereum RPC.
type rpcTransaction struct {
tx *types.Transaction
txExtraInfo
}
// txExtraInfo includes extra information about a transaction that is returned from
// and Ethereum RPC endpoint.
type txExtraInfo struct {
BlockNumber *string `json:"blockNumber,omitempty"`
BlockHash *common.Hash `json:"blockHash,omitempty"`
From *common.Address `json:"from,omitempty"`
}
// readBlockJSON will read a JSON file from disk containing a serialized block.
// This logic can break if the block format changes but there is no modular way
// to turn a block into JSON in go-ethereum.
func readBlockJSON(path string) (*types.Block, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("block file at %s not found: %w", path, err)
}
var header types.Header
if err := json.Unmarshal(raw, &header); err != nil {
return nil, fmt.Errorf("cannot unmarshal block: %w", err)
}
var body rpcBlock
if err := json.Unmarshal(raw, &body); err != nil {
return nil, err
}
if len(body.UncleHashes) > 0 {
return nil, fmt.Errorf("cannot unmarshal block with uncles")
}
txs := make([]*types.Transaction, len(body.Transactions))
for i, tx := range body.Transactions {
txs[i] = tx.tx
}
return types.NewBlockWithHeader(&header).WithBody(txs, nil).WithWithdrawals(body.Withdrawals), 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