github.com/ethereum-optimism/optimism@v1.7.2/op-node/cmd/genesis/cmd.go (about)

     1  package genesis
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"math/big"
     9  	"os"
    10  
    11  	"github.com/urfave/cli/v2"
    12  
    13  	"github.com/ethereum/go-ethereum/common"
    14  	"github.com/ethereum/go-ethereum/core/state"
    15  	"github.com/ethereum/go-ethereum/core/types"
    16  	"github.com/ethereum/go-ethereum/ethclient"
    17  	"github.com/ethereum/go-ethereum/log"
    18  	"github.com/ethereum/go-ethereum/rpc"
    19  
    20  	"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
    21  	"github.com/ethereum-optimism/optimism/op-service/jsonutil"
    22  )
    23  
    24  var (
    25  	l1RPCFlag = &cli.StringFlag{
    26  		Name:  "l1-rpc",
    27  		Usage: "RPC URL for an Ethereum L1 node. Cannot be used with --l1-starting-block",
    28  	}
    29  	l1StartingBlockFlag = &cli.PathFlag{
    30  		Name:  "l1-starting-block",
    31  		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",
    32  	}
    33  	deployConfigFlag = &cli.PathFlag{
    34  		Name:     "deploy-config",
    35  		Usage:    "Path to deploy config file",
    36  		Required: true,
    37  	}
    38  	l1DeploymentsFlag = &cli.PathFlag{
    39  		Name:     "l1-deployments",
    40  		Usage:    "Path to L1 deployments JSON file as in superchain-registry",
    41  		Required: true,
    42  	}
    43  	outfileL2Flag = &cli.PathFlag{
    44  		Name:  "outfile.l2",
    45  		Usage: "Path to L2 genesis output file",
    46  	}
    47  	outfileRollupFlag = &cli.PathFlag{
    48  		Name:  "outfile.rollup",
    49  		Usage: "Path to rollup output file",
    50  	}
    51  
    52  	l1AllocsFlag = &cli.StringFlag{
    53  		Name:  "l1-allocs",
    54  		Usage: "Path to L1 genesis state dump",
    55  	}
    56  	outfileL1Flag = &cli.StringFlag{
    57  		Name:  "outfile.l1",
    58  		Usage: "Path to L1 genesis output file",
    59  	}
    60  
    61  	l1Flags = []cli.Flag{
    62  		deployConfigFlag,
    63  		l1AllocsFlag,
    64  		l1DeploymentsFlag,
    65  		outfileL1Flag,
    66  	}
    67  
    68  	l2Flags = []cli.Flag{
    69  		l1RPCFlag,
    70  		l1StartingBlockFlag,
    71  		deployConfigFlag,
    72  		l1DeploymentsFlag,
    73  		outfileL2Flag,
    74  		outfileRollupFlag,
    75  	}
    76  )
    77  
    78  var Subcommands = cli.Commands{
    79  	{
    80  		Name:  "l1",
    81  		Usage: "Generates a L1 genesis state file",
    82  		Flags: l1Flags,
    83  		Action: func(ctx *cli.Context) error {
    84  			deployConfig := ctx.String("deploy-config")
    85  			config, err := genesis.NewDeployConfig(deployConfig)
    86  			if err != nil {
    87  				return err
    88  			}
    89  
    90  			var deployments *genesis.L1Deployments
    91  			if l1Deployments := ctx.String("l1-deployments"); l1Deployments != "" {
    92  				deployments, err = genesis.NewL1Deployments(l1Deployments)
    93  				if err != nil {
    94  					return err
    95  				}
    96  			}
    97  
    98  			if deployments != nil {
    99  				config.SetDeployments(deployments)
   100  			}
   101  
   102  			if err := config.Check(); err != nil {
   103  				return fmt.Errorf("deploy config at %s invalid: %w", deployConfig, err)
   104  			}
   105  
   106  			// Check the addresses after setting the deployments
   107  			if err := config.CheckAddresses(); err != nil {
   108  				return fmt.Errorf("deploy config at %s invalid: %w", deployConfig, err)
   109  			}
   110  
   111  			var dump *state.Dump
   112  			if l1Allocs := ctx.String("l1-allocs"); l1Allocs != "" {
   113  				dump, err = genesis.NewStateDump(l1Allocs)
   114  				if err != nil {
   115  					return err
   116  				}
   117  			}
   118  
   119  			l1Genesis, err := genesis.BuildL1DeveloperGenesis(config, dump, deployments)
   120  			if err != nil {
   121  				return err
   122  			}
   123  
   124  			return jsonutil.WriteJSON(ctx.String("outfile.l1"), l1Genesis, 0o666)
   125  		},
   126  	},
   127  	{
   128  		Name:  "l2",
   129  		Usage: "Generates an L2 genesis file and rollup config suitable for a deployed network",
   130  		Description: "Generating the L2 genesis depends on knowledge of L1 contract addresses for the bridge to be secure. " +
   131  			"A deploy config and either a deployment directory or an L1 deployments file are used to create the L2 genesis. " +
   132  			"The deploy directory and L1 deployments file are generated by the L1 contract deployments. " +
   133  			"An L1 starting block is necessary, it can either be fetched dynamically using config in the deploy config " +
   134  			"or it can be provided as a JSON file.",
   135  		Flags: l2Flags,
   136  		Action: func(ctx *cli.Context) error {
   137  			deployConfig := ctx.Path("deploy-config")
   138  			log.Info("Deploy config", "path", deployConfig)
   139  			config, err := genesis.NewDeployConfig(deployConfig)
   140  			if err != nil {
   141  				return err
   142  			}
   143  
   144  			l1Deployments := ctx.Path("l1-deployments")
   145  			l1StartBlockPath := ctx.Path("l1-starting-block")
   146  			l1RPC := ctx.String("l1-rpc")
   147  
   148  			if l1StartBlockPath == "" && l1RPC == "" {
   149  				return errors.New("must specify either --l1-starting-block or --l1-rpc")
   150  			}
   151  			if l1StartBlockPath != "" && l1RPC != "" {
   152  				return errors.New("cannot specify both --l1-starting-block and --l1-rpc")
   153  			}
   154  
   155  			deployments, err := genesis.NewL1Deployments(l1Deployments)
   156  			if err != nil {
   157  				return fmt.Errorf("cannot read L1 deployments at %s: %w", l1Deployments, err)
   158  			}
   159  			config.SetDeployments(deployments)
   160  
   161  			var l1StartBlock *types.Block
   162  			if l1StartBlockPath != "" {
   163  				if l1StartBlock, err = readBlockJSON(l1StartBlockPath); err != nil {
   164  					return fmt.Errorf("cannot read L1 starting block at %s: %w", l1StartBlockPath, err)
   165  				}
   166  			}
   167  
   168  			if l1RPC != "" {
   169  				client, err := ethclient.Dial(l1RPC)
   170  				if err != nil {
   171  					return fmt.Errorf("cannot dial %s: %w", l1RPC, err)
   172  				}
   173  
   174  				if config.L1StartingBlockTag == nil {
   175  					l1StartBlock, err = client.BlockByNumber(context.Background(), nil)
   176  					if err != nil {
   177  						return fmt.Errorf("cannot fetch latest block: %w", err)
   178  					}
   179  					tag := rpc.BlockNumberOrHashWithHash(l1StartBlock.Hash(), true)
   180  					config.L1StartingBlockTag = (*genesis.MarshalableRPCBlockNumberOrHash)(&tag)
   181  				} else if config.L1StartingBlockTag.BlockHash != nil {
   182  					l1StartBlock, err = client.BlockByHash(context.Background(), *config.L1StartingBlockTag.BlockHash)
   183  					if err != nil {
   184  						return fmt.Errorf("cannot fetch block by hash: %w", err)
   185  					}
   186  				} else if config.L1StartingBlockTag.BlockNumber != nil {
   187  					l1StartBlock, err = client.BlockByNumber(context.Background(), big.NewInt(config.L1StartingBlockTag.BlockNumber.Int64()))
   188  					if err != nil {
   189  						return fmt.Errorf("cannot fetch block by number: %w", err)
   190  					}
   191  				}
   192  			}
   193  
   194  			// Ensure that there is a starting L1 block
   195  			if l1StartBlock == nil {
   196  				return errors.New("no starting L1 block")
   197  			}
   198  
   199  			// Sanity check the config. Do this after filling in the L1StartingBlockTag
   200  			// if it is not defined.
   201  			if err := config.Check(); err != nil {
   202  				return err
   203  			}
   204  
   205  			log.Info("Using L1 Start Block", "number", l1StartBlock.Number(), "hash", l1StartBlock.Hash().Hex())
   206  
   207  			// Build the L2 genesis block
   208  			l2Genesis, err := genesis.BuildL2Genesis(config, l1StartBlock)
   209  			if err != nil {
   210  				return fmt.Errorf("error creating l2 genesis: %w", err)
   211  			}
   212  
   213  			l2GenesisBlock := l2Genesis.ToBlock()
   214  			rollupConfig, err := config.RollupConfig(l1StartBlock, l2GenesisBlock.Hash(), l2GenesisBlock.Number().Uint64())
   215  			if err != nil {
   216  				return err
   217  			}
   218  			if err := rollupConfig.Check(); err != nil {
   219  				return fmt.Errorf("generated rollup config does not pass validation: %w", err)
   220  			}
   221  
   222  			if err := jsonutil.WriteJSON(ctx.String("outfile.l2"), l2Genesis, 0o666); err != nil {
   223  				return err
   224  			}
   225  			return jsonutil.WriteJSON(ctx.String("outfile.rollup"), rollupConfig, 0o666)
   226  		},
   227  	},
   228  }
   229  
   230  // rpcBlock represents the JSON serialization of a block from an Ethereum RPC.
   231  type rpcBlock struct {
   232  	Hash         common.Hash         `json:"hash"`
   233  	Transactions []rpcTransaction    `json:"transactions"`
   234  	UncleHashes  []common.Hash       `json:"uncles"`
   235  	Withdrawals  []*types.Withdrawal `json:"withdrawals,omitempty"`
   236  }
   237  
   238  // rpcTransaction represents the JSON serialization of a transaction from an Ethereum RPC.
   239  type rpcTransaction struct {
   240  	tx *types.Transaction
   241  	txExtraInfo
   242  }
   243  
   244  // txExtraInfo includes extra information about a transaction that is returned from
   245  // and Ethereum RPC endpoint.
   246  type txExtraInfo struct {
   247  	BlockNumber *string         `json:"blockNumber,omitempty"`
   248  	BlockHash   *common.Hash    `json:"blockHash,omitempty"`
   249  	From        *common.Address `json:"from,omitempty"`
   250  }
   251  
   252  // readBlockJSON will read a JSON file from disk containing a serialized block.
   253  // This logic can break if the block format changes but there is no modular way
   254  // to turn a block into JSON in go-ethereum.
   255  func readBlockJSON(path string) (*types.Block, error) {
   256  	raw, err := os.ReadFile(path)
   257  	if err != nil {
   258  		return nil, fmt.Errorf("block file at %s not found: %w", path, err)
   259  	}
   260  
   261  	var header types.Header
   262  	if err := json.Unmarshal(raw, &header); err != nil {
   263  		return nil, fmt.Errorf("cannot unmarshal block: %w", err)
   264  	}
   265  
   266  	var body rpcBlock
   267  	if err := json.Unmarshal(raw, &body); err != nil {
   268  		return nil, err
   269  	}
   270  
   271  	if len(body.UncleHashes) > 0 {
   272  		return nil, fmt.Errorf("cannot unmarshal block with uncles")
   273  	}
   274  
   275  	txs := make([]*types.Transaction, len(body.Transactions))
   276  	for i, tx := range body.Transactions {
   277  		txs[i] = tx.tx
   278  	}
   279  	return types.NewBlockWithHeader(&header).WithBody(txs, nil).WithWithdrawals(body.Withdrawals), nil
   280  }