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 }