Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 6 commits into from
Jul 24, 2024
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 24 additions & 105 deletions op-node/cmd/genesis/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -24,11 +21,7 @@ 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",
bitwiseguy marked this conversation as resolved.
Show resolved Hide resolved
}
deployConfigFlag = &cli.PathFlag{
Name: "deploy-config",
Expand Down Expand Up @@ -71,7 +64,6 @@ var (

l2Flags = []cli.Flag{
l1RPCFlag,
l1StartingBlockFlag,
deployConfigFlag,
l2AllocsFlag,
l1DeploymentsFlag,
Expand Down Expand Up @@ -147,14 +139,9 @@ var Subcommands = cli.Commands{
}

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")
if l1RPC == "" {
return errors.New("must specify --l1-rpc")
}

deployments, err := genesis.NewL1Deployments(l1Deployments)
Expand All @@ -163,13 +150,6 @@ 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 != "" {
l2Allocs, err = foundry.LoadForgeAllocs(l2AllocsPath)
Expand All @@ -180,39 +160,30 @@ var Subcommands = cli.Commands{
return errors.New("missing l2-allocs")
}

if l1RPC != "" {
client, err := ethclient.Dial(l1RPC)
if err != nil {
return fmt.Errorf("cannot dial %s: %w", l1RPC, err)
}
// 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)
}
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)
if err != nil {
return fmt.Errorf("cannot fetch block by hash: %w", err)
}
} else if config.L1StartingBlockTag.BlockNumber != nil {
l1StartBlock, err = client.BlockByNumber(context.Background(), big.NewInt(config.L1StartingBlockTag.BlockNumber.Int64()))
if err != nil {
return fmt.Errorf("cannot fetch block by number: %w", err)
}
}
funcDef := "startBlock()"
funcHash := crypto.Keccak256Hash([]byte(funcDef))
msg := ethereum.CallMsg{
To: &config.SystemConfigProxy,
Data: funcHash[:4], // hardcode the 4byte sig
}
result, err := client.CallContract(context.Background(), msg, nil)
if err != nil {
return fmt.Errorf("failed to fetch startBlock from SystemConfig contract: %w", err)
}

// Ensure that there is a starting L1 block
if l1StartBlock == nil {
return errors.New("no starting L1 block")
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)
}

// 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
}
Expand Down Expand Up @@ -241,55 +212,3 @@ var Subcommands = cli.Commands{
},
},
}

// 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
}