cmd.go 5.49 KB
Newer Older
1 2 3
package genesis

import (
4
	"context"
5
	"encoding/json"
6
	"errors"
7
	"fmt"
8
	"math/big"
9
	"os"
10 11
	"path/filepath"

12
	"github.com/urfave/cli/v2"
13

Mark Tyneway's avatar
Mark Tyneway committed
14
	"github.com/ethereum/go-ethereum/core/state"
15 16
	"github.com/ethereum/go-ethereum/core/types"
	"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-bindings/hardhat"
21
	"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
22 23 24 25
)

var Subcommands = cli.Commands{
	{
Mark Tyneway's avatar
Mark Tyneway committed
26 27
		Name:  "l1",
		Usage: "Generates a L1 genesis state file",
28
		Flags: []cli.Flag{
29
			&cli.StringFlag{
Mark Tyneway's avatar
Mark Tyneway committed
30 31 32
				Name:     "deploy-config",
				Usage:    "Path to hardhat deploy config file",
				Required: true,
33
			},
34
			&cli.StringFlag{
Mark Tyneway's avatar
Mark Tyneway committed
35 36
				Name:  "l1-allocs",
				Usage: "Path to L1 genesis state dump",
37
			},
38
			&cli.StringFlag{
Mark Tyneway's avatar
Mark Tyneway committed
39 40
				Name:  "l1-deployments",
				Usage: "Path to L1 deployments file",
41
			},
42
			&cli.StringFlag{
Mark Tyneway's avatar
Mark Tyneway committed
43 44
				Name:  "outfile.l1",
				Usage: "Path to L1 genesis output file",
45 46 47
			},
		},
		Action: func(ctx *cli.Context) error {
48
			deployConfig := ctx.String("deploy-config")
49
			config, err := genesis.NewDeployConfig(deployConfig)
50 51 52 53
			if err != nil {
				return err
			}

Mark Tyneway's avatar
Mark Tyneway committed
54 55 56 57 58 59
			var deployments *genesis.L1Deployments
			if l1Deployments := ctx.String("l1-deployments"); l1Deployments != "" {
				deployments, err = genesis.NewL1Deployments(l1Deployments)
				if err != nil {
					return err
				}
60 61
			}

Mark Tyneway's avatar
Mark Tyneway committed
62 63
			if deployments != nil {
				config.SetDeployments(deployments)
64 65
			}

Mark Tyneway's avatar
Mark Tyneway committed
66 67
			if err := config.Check(); err != nil {
				return fmt.Errorf("deploy config at %s invalid: %w", deployConfig, err)
68 69
			}

70 71 72 73 74
			// Check the addresses after setting the deployments
			if err := config.CheckAddresses(); err != nil {
				return fmt.Errorf("deploy config at %s invalid: %w", deployConfig, err)
			}

Mark Tyneway's avatar
Mark Tyneway committed
75 76 77 78 79 80
			var dump *state.Dump
			if l1Allocs := ctx.String("l1-allocs"); l1Allocs != "" {
				dump, err = genesis.NewStateDump(l1Allocs)
				if err != nil {
					return err
				}
81 82
			}

Mark Tyneway's avatar
Mark Tyneway committed
83
			l1Genesis, err := genesis.BuildL1DeveloperGenesis(config, dump, deployments, true)
84 85 86
			if err != nil {
				return err
			}
87

Mark Tyneway's avatar
Mark Tyneway committed
88
			return writeGenesisFile(ctx.String("outfile.l1"), l1Genesis)
89 90
		},
	},
91 92 93 94
	{
		Name:  "l2",
		Usage: "Generates an L2 genesis file and rollup config suitable for a deployed network",
		Flags: []cli.Flag{
95
			&cli.StringFlag{
96 97 98
				Name:  "l1-rpc",
				Usage: "L1 RPC URL",
			},
99
			&cli.StringFlag{
100
				Name:  "deploy-config",
101
				Usage: "Path to deploy config file",
102
			},
103
			&cli.StringFlag{
104
				Name:  "deployment-dir",
105
				Usage: "Path to network deployment directory",
106
			},
107
			&cli.StringFlag{
108 109 110
				Name:  "outfile.l2",
				Usage: "Path to L2 genesis output file",
			},
111
			&cli.StringFlag{
112 113 114 115 116 117
				Name:  "outfile.rollup",
				Usage: "Path to rollup output file",
			},
		},
		Action: func(ctx *cli.Context) error {
			deployConfig := ctx.String("deploy-config")
118
			log.Info("Deploy config", "path", deployConfig)
119 120 121 122 123
			config, err := genesis.NewDeployConfig(deployConfig)
			if err != nil {
				return err
			}

124 125 126 127 128 129 130
			deployDir := ctx.String("deployment-dir")
			if deployDir == "" {
				return errors.New("Must specify --deployment-dir")
			}

			log.Info("Deployment directory", "path", deployDir)
			depPath, network := filepath.Split(deployDir)
131 132 133 134 135 136 137 138
			hh, err := hardhat.New(network, nil, []string{depPath})
			if err != nil {
				return err
			}

			// Read the appropriate deployment addresses from disk
			if err := config.GetDeployedAddresses(hh); err != nil {
				return err
139 140 141 142
			}

			client, err := ethclient.Dial(ctx.String("l1-rpc"))
			if err != nil {
143
				return fmt.Errorf("cannot dial %s: %w", ctx.String("l1-rpc"), err)
144 145 146
			}

			var l1StartBlock *types.Block
147 148
			if config.L1StartingBlockTag == nil {
				l1StartBlock, err = client.BlockByNumber(context.Background(), nil)
149 150
				tag := rpc.BlockNumberOrHashWithHash(l1StartBlock.Hash(), true)
				config.L1StartingBlockTag = (*genesis.MarshalableRPCBlockNumberOrHash)(&tag)
151
			} else if config.L1StartingBlockTag.BlockHash != nil {
152 153 154 155 156
				l1StartBlock, err = client.BlockByHash(context.Background(), *config.L1StartingBlockTag.BlockHash)
			} else if config.L1StartingBlockTag.BlockNumber != nil {
				l1StartBlock, err = client.BlockByNumber(context.Background(), big.NewInt(config.L1StartingBlockTag.BlockNumber.Int64()))
			}
			if err != nil {
157
				return fmt.Errorf("error getting l1 start block: %w", err)
158
			}
159 160 161 162 163 164 165

			// Sanity check the config. Do this after filling in the L1StartingBlockTag
			// if it is not defined.
			if err := config.Check(); err != nil {
				return err
			}

166
			log.Info("Using L1 Start Block", "number", l1StartBlock.Number(), "hash", l1StartBlock.Hash().Hex())
167

Mark Tyneway's avatar
Mark Tyneway committed
168
			// Build the L2 genesis block
169
			l2Genesis, err := genesis.BuildL2Genesis(config, l1StartBlock)
170
			if err != nil {
Mark Tyneway's avatar
Mark Tyneway committed
171
				return fmt.Errorf("error creating l2 genesis: %w", err)
172 173
			}

174 175
			l2GenesisBlock := l2Genesis.ToBlock()
			rollupConfig, err := config.RollupConfig(l1StartBlock, l2GenesisBlock.Hash(), l2GenesisBlock.Number().Uint64())
176 177 178
			if err != nil {
				return err
			}
179 180 181
			if err := rollupConfig.Check(); err != nil {
				return fmt.Errorf("generated rollup config does not pass validation: %w", err)
			}
182 183 184 185 186 187 188 189 190

			if err := writeGenesisFile(ctx.String("outfile.l2"), l2Genesis); err != nil {
				return err
			}
			return writeGenesisFile(ctx.String("outfile.rollup"), rollupConfig)
		},
	},
}

191
func writeGenesisFile(outfile string, input any) error {
192 193 194 195 196 197 198 199 200 201
	f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755)
	if err != nil {
		return err
	}
	defer f.Close()

	enc := json.NewEncoder(f)
	enc.SetIndent("", "  ")
	return enc.Encode(input)
}