config.go 13 KB
Newer Older
1 2
package config

3
import (
4
	"encoding/json"
5
	"errors"
6 7
	"fmt"
	"os"
8
	"slices"
9

10
	"github.com/ethereum-optimism/optimism/op-node/chaincfg"
11
	"github.com/ethereum-optimism/optimism/op-program/chainconfig"
12
	"github.com/ethereum-optimism/optimism/op-program/client/boot"
13
	"github.com/ethereum-optimism/optimism/op-program/host/types"
14
	"github.com/ethereum-optimism/optimism/op-service/eth"
15
	"github.com/ethereum/go-ethereum/crypto"
16

17
	"github.com/ethereum-optimism/optimism/op-node/rollup"
18
	"github.com/ethereum-optimism/optimism/op-program/host/flags"
Sabnock01's avatar
Sabnock01 committed
19
	"github.com/ethereum-optimism/optimism/op-service/sources"
20
	"github.com/ethereum/go-ethereum/common"
21
	"github.com/ethereum/go-ethereum/core"
22
	"github.com/ethereum/go-ethereum/log"
23
	"github.com/ethereum/go-ethereum/params"
24
	"github.com/urfave/cli/v2"
25 26 27
)

var (
28
	ErrNoL2Chains            = errors.New("at least one L2 chain must be specified")
29
	ErrMissingL2ChainID      = errors.New("missing l2 chain id")
30
	ErrMissingL2Genesis      = errors.New("missing l2 genesis")
31 32 33 34
	ErrNoRollupForGenesis    = errors.New("no rollup config matching l2 genesis")
	ErrNoGenesisForRollup    = errors.New("no l2 genesis for rollup")
	ErrDuplicateRollup       = errors.New("duplicate rollup")
	ErrDuplicateGenesis      = errors.New("duplicate l2 genesis")
35 36 37 38 39 40 41 42 43 44 45
	ErrInvalidL1Head         = errors.New("invalid l1 head")
	ErrInvalidL2Head         = errors.New("invalid l2 head")
	ErrInvalidL2OutputRoot   = errors.New("invalid l2 output root")
	ErrInvalidAgreedPrestate = errors.New("invalid l2 agreed prestate")
	ErrL1AndL2Inconsistent   = errors.New("l1 and l2 options must be specified together or both omitted")
	ErrInvalidL2Claim        = errors.New("invalid l2 claim")
	ErrInvalidL2ClaimBlock   = errors.New("invalid l2 claim block number")
	ErrDataDirRequired       = errors.New("datadir must be specified when in non-fetching mode")
	ErrNoExecInServerMode    = errors.New("exec command must not be set when in server mode")
	ErrInvalidDataFormat     = errors.New("invalid data format")
	ErrMissingAgreedPrestate = errors.New("missing agreed prestate")
46
)
47 48

type Config struct {
49
	L2ChainID eth.ChainID // TODO: Forbid for interop
50
	Rollups   []*rollup.Config
51
	// DataDir is the directory to read/write pre-image data from/to.
52
	// If not set, an in-memory key-value store is used and fetching data must be enabled
53 54
	DataDir string

55 56 57
	// DataFormat specifies the format to use for on-disk storage. Only applies when DataDir is set.
	DataFormat types.DataFormat

58
	// L1Head is the block hash of the L1 chain head block
59 60 61 62 63
	L1Head      common.Hash
	L1URL       string
	L1BeaconURL string
	L1TrustRPC  bool
	L1RPCKind   sources.RPCProviderKind
64

65 66
	// L2Head is the l2 block hash contained in the L2 Output referenced by the L2OutputRoot for pre-interop mode
	L2Head common.Hash // TODO: Forbid for interop
67 68
	// L2OutputRoot is the agreed L2 output root to start derivation from
	L2OutputRoot common.Hash
69 70 71 72 73 74 75
	// L2URLs are the URLs of the L2 nodes to fetch L2 data from, these are the canonical URL for L2 data
	// These URLs are used as a fallback for L2ExperimentalURL if the experimental URL fails or cannot retrieve the desired data
	// Must have one L2URL for each chain in Rollups
	L2URLs []string
	// L2ExperimentalURLs are the URLs of the L2 nodes (non hash db archival node, for example, reth archival node) to fetch L2 data from
	// Must have one url for each chain in Rollups
	L2ExperimentalURLs []string
76 77
	// L2Claim is the claimed L2 output root to verify
	L2Claim common.Hash
78 79
	// L2ClaimBlockNumber is the block number the claimed L2 output root is from
	// Must be above 0 and to be a valid claim needs to be above the L2Head block.
80
	// For interop this is the superchain root timestamp
81
	L2ClaimBlockNumber uint64
82 83 84
	// L2ChainConfigs are the op-geth chain config for the L2 execution engines
	// Must have one chain config for each rollup config
	L2ChainConfigs []*params.ChainConfig
85 86 87
	// ExecCmd specifies the client program to execute in a separate process.
	// If unset, the fault proof client is run in the same process.
	ExecCmd string
88 89 90 91

	// ServerMode indicates that the program should run in pre-image server mode and wait for requests.
	// No client program is run.
	ServerMode bool
92 93 94

	// InteropEnabled enables interop fault proof rules when running the client in-process
	InteropEnabled bool
95 96
	// AgreedPrestate is the preimage of the agreed prestate claim. Required for interop.
	AgreedPrestate []byte
97 98
}

99
func (c *Config) Check() error {
100
	if !c.InteropEnabled && c.L2ChainID == (eth.ChainID{}) {
101 102
		return ErrMissingL2ChainID
	}
103
	if len(c.Rollups) == 0 {
104
		return ErrNoL2Chains
105
	}
106 107 108 109
	for _, rollupCfg := range c.Rollups {
		if err := rollupCfg.Check(); err != nil {
			return fmt.Errorf("invalid rollup config for chain %v: %w", rollupCfg.L2ChainID, err)
		}
110
	}
111 112
	if c.L1Head == (common.Hash{}) {
		return ErrInvalidL1Head
113
	}
inphi's avatar
inphi committed
114 115 116
	if c.L2Head == (common.Hash{}) {
		return ErrInvalidL2Head
	}
117 118 119
	if c.L2OutputRoot == (common.Hash{}) {
		return ErrInvalidL2OutputRoot
	}
120 121 122
	if c.L2ClaimBlockNumber == 0 {
		return ErrInvalidL2ClaimBlock
	}
123
	if len(c.L2ChainConfigs) == 0 {
124 125
		return ErrMissingL2Genesis
	}
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
	// Make of known rollup chain IDs to whether we have the L2 chain config for it
	chainIDToHasChainConfig := make(map[uint64]bool, len(c.Rollups))
	for _, config := range c.Rollups {
		chainID := config.L2ChainID.Uint64()
		if _, ok := chainIDToHasChainConfig[chainID]; ok {
			return fmt.Errorf("%w for chain ID %v", ErrDuplicateRollup, chainID)
		}
		chainIDToHasChainConfig[chainID] = false
	}
	for _, config := range c.L2ChainConfigs {
		chainID := config.ChainID.Uint64()
		if _, ok := chainIDToHasChainConfig[chainID]; !ok {
			return fmt.Errorf("%w for chain ID %v", ErrNoRollupForGenesis, config.ChainID)
		}
		if chainIDToHasChainConfig[chainID] {
			return fmt.Errorf("%w for chain ID %v", ErrDuplicateGenesis, config.ChainID)
		}
		chainIDToHasChainConfig[chainID] = true
	}
	for chainID, hasChainConfig := range chainIDToHasChainConfig {
		if !hasChainConfig {
			return fmt.Errorf("%w for chain ID %v", ErrNoGenesisForRollup, chainID)
		}
	}
150
	if (c.L1URL != "") != (len(c.L2URLs) > 0) {
151 152
		return ErrL1AndL2Inconsistent
	}
153 154 155
	if !c.FetchingEnabled() && c.DataDir == "" {
		return ErrDataDirRequired
	}
156 157 158
	if c.ServerMode && c.ExecCmd != "" {
		return ErrNoExecInServerMode
	}
159 160 161
	if c.DataDir != "" && !slices.Contains(types.SupportedDataFormats, c.DataFormat) {
		return ErrInvalidDataFormat
	}
162 163 164 165 166 167 168 169
	if c.InteropEnabled {
		if len(c.AgreedPrestate) == 0 {
			return ErrMissingAgreedPrestate
		}
		if crypto.Keccak256Hash(c.AgreedPrestate) != c.L2OutputRoot {
			return fmt.Errorf("%w: must be preimage of L2 output root", ErrInvalidAgreedPrestate)
		}
	}
170 171 172
	return nil
}

173
func (c *Config) FetchingEnabled() bool {
174
	return c.L1URL != "" && len(c.L2URLs) > 0 && c.L1BeaconURL != ""
175 176
}

177
func NewSingleChainConfig(
178
	rollupCfg *rollup.Config,
179
	l2ChainConfig *params.ChainConfig,
180 181 182 183 184 185
	l1Head common.Hash,
	l2Head common.Hash,
	l2OutputRoot common.Hash,
	l2Claim common.Hash,
	l2ClaimBlockNum uint64,
) *Config {
186 187
	l2ChainID := eth.ChainIDFromBig(l2ChainConfig.ChainID)
	_, err := params.LoadOPStackChainConfig(eth.EvilChainIDToUInt64(l2ChainID))
188 189
	if err != nil {
		// Unknown chain ID so assume it is custom
190
		l2ChainID = boot.CustomChainIDIndicator
191
	}
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
	cfg := NewConfig(
		[]*rollup.Config{rollupCfg},
		[]*params.ChainConfig{l2ChainConfig},
		l1Head,
		l2Head,
		l2OutputRoot,
		l2Claim,
		l2ClaimBlockNum)
	cfg.L2ChainID = l2ChainID
	return cfg
}

// NewConfig creates a Config with all optional values set to the CLI default value
func NewConfig(
	rollupCfgs []*rollup.Config,
	l2ChainConfigs []*params.ChainConfig,
	l1Head common.Hash,
	l2Head common.Hash,
	l2OutputRoot common.Hash,
	l2Claim common.Hash,
	l2ClaimBlockNum uint64,
) *Config {
214
	return &Config{
215 216
		Rollups:            rollupCfgs,
		L2ChainConfigs:     l2ChainConfigs,
217 218 219 220 221 222 223
		L1Head:             l1Head,
		L2Head:             l2Head,
		L2OutputRoot:       l2OutputRoot,
		L2Claim:            l2Claim,
		L2ClaimBlockNumber: l2ClaimBlockNum,
		L1RPCKind:          sources.RPCKindStandard,
		DataFormat:         types.DataFormatDirectory,
224
	}
225 226
}

227
func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) {
228 229 230
	if err := flags.CheckRequired(ctx); err != nil {
		return nil, err
	}
231

232 233 234 235 236 237
	var l2Head common.Hash
	if ctx.IsSet(flags.L2Head.Name) {
		l2Head = common.HexToHash(ctx.String(flags.L2Head.Name))
		if l2Head == (common.Hash{}) {
			return nil, ErrInvalidL2Head
		}
238
	}
239 240 241 242 243 244 245 246 247 248 249 250
	var l2OutputRoot common.Hash
	var agreedPrestate []byte
	if ctx.IsSet(flags.L2OutputRoot.Name) {
		l2OutputRoot = common.HexToHash(ctx.String(flags.L2OutputRoot.Name))
	} else if ctx.IsSet(flags.L2AgreedPrestate.Name) {
		prestateStr := ctx.String(flags.L2AgreedPrestate.Name)
		agreedPrestate = common.FromHex(prestateStr)
		if len(agreedPrestate) == 0 {
			return nil, ErrInvalidAgreedPrestate
		}
		l2OutputRoot = crypto.Keccak256Hash(agreedPrestate)
	}
251 252 253
	if l2OutputRoot == (common.Hash{}) {
		return nil, ErrInvalidL2OutputRoot
	}
254 255 256 257 258 259 260
	strClaim := ctx.String(flags.L2Claim.Name)
	l2Claim := common.HexToHash(strClaim)
	// Require a valid hash, with the zero hash explicitly allowed.
	if l2Claim == (common.Hash{}) &&
		strClaim != "0x0000000000000000000000000000000000000000000000000000000000000000" &&
		strClaim != "0000000000000000000000000000000000000000000000000000000000000000" {
		return nil, fmt.Errorf("%w: %v", ErrInvalidL2Claim, strClaim)
261
	}
262 263
	l2ClaimBlockNum := ctx.Uint64(flags.L2BlockNumber.Name)
	l1Head := common.HexToHash(ctx.String(flags.L1Head.Name))
264 265 266
	if l1Head == (common.Hash{}) {
		return nil, ErrInvalidL1Head
	}
267 268

	var err error
269 270
	var rollupCfgs []*rollup.Config
	var l2ChainConfigs []*params.ChainConfig
271
	var l2ChainID eth.ChainID
272 273
	networkNames := ctx.StringSlice(flags.Network.Name)
	for _, networkName := range networkNames {
274 275
		var chainID eth.ChainID
		if chainID, err = eth.ParseDecimalChainID(networkName); err != nil {
276 277 278 279
			ch := chaincfg.ChainByName(networkName)
			if ch == nil {
				return nil, fmt.Errorf("invalid network: %q", networkName)
			}
280
			chainID = eth.ChainIDFromUInt64(ch.ChainID)
281
		}
282

283
		l2ChainConfig, err := chainconfig.ChainConfigByChainID(chainID)
284
		if err != nil {
285 286
			return nil, fmt.Errorf("failed to load chain config for chain %d: %w", chainID, err)
		}
287 288
		l2ChainConfigs = append(l2ChainConfigs, l2ChainConfig)
		rollupCfg, err := chainconfig.RollupConfigByChainID(chainID)
289 290
		if err != nil {
			return nil, fmt.Errorf("failed to load rollup config for chain %d: %w", chainID, err)
291
		}
292
		rollupCfgs = append(rollupCfgs, rollupCfg)
293
		l2ChainID = chainID
294 295 296 297 298
	}

	genesisPaths := ctx.StringSlice(flags.L2GenesisPath.Name)
	for _, l2GenesisPath := range genesisPaths {
		l2ChainConfig, err := loadChainConfigFromGenesis(l2GenesisPath)
299 300 301
		if err != nil {
			return nil, fmt.Errorf("invalid genesis: %w", err)
		}
302
		l2ChainConfigs = append(l2ChainConfigs, l2ChainConfig)
303
		l2ChainID = eth.ChainIDFromBig(l2ChainConfig.ChainID)
304
	}
305

306 307 308
	rollupPaths := ctx.StringSlice(flags.RollupConfig.Name)
	for _, rollupConfigPath := range rollupPaths {
		rollupCfg, err := loadRollupConfig(rollupConfigPath)
309 310 311
		if err != nil {
			return nil, fmt.Errorf("invalid rollup config: %w", err)
		}
312
		rollupCfgs = append(rollupCfgs, rollupCfg)
313

314 315 316 317 318 319
	}
	if ctx.Bool(flags.L2Custom.Name) {
		log.Warn("Using custom chain configuration via preimage oracle. This is not compatible with on-chain execution.")
		l2ChainID = boot.CustomChainIDIndicator
	} else if len(rollupCfgs) > 1 {
		// L2ChainID is not applicable when multiple L2 sources are used and not using custom configs
320
		l2ChainID = eth.ChainID{}
321
	}
322

323 324 325 326
	dbFormat := types.DataFormat(ctx.String(flags.DataFormat.Name))
	if !slices.Contains(types.SupportedDataFormats, dbFormat) {
		return nil, fmt.Errorf("invalid %w: %v", ErrInvalidDataFormat, dbFormat)
	}
327
	return &Config{
328
		L2ChainID:          l2ChainID,
329
		Rollups:            rollupCfgs,
330 331
		DataDir:            ctx.String(flags.DataDir.Name),
		DataFormat:         dbFormat,
332 333 334
		L2URLs:             ctx.StringSlice(flags.L2NodeAddr.Name),
		L2ExperimentalURLs: ctx.StringSlice(flags.L2NodeExperimentalAddr.Name),
		L2ChainConfigs:     l2ChainConfigs,
335 336
		L2Head:             l2Head,
		L2OutputRoot:       l2OutputRoot,
337
		AgreedPrestate:     agreedPrestate,
338 339 340 341 342 343 344 345 346
		L2Claim:            l2Claim,
		L2ClaimBlockNumber: l2ClaimBlockNum,
		L1Head:             l1Head,
		L1URL:              ctx.String(flags.L1NodeAddr.Name),
		L1BeaconURL:        ctx.String(flags.L1BeaconAddr.Name),
		L1TrustRPC:         ctx.Bool(flags.L1TrustRPC.Name),
		L1RPCKind:          sources.RPCProviderKind(ctx.String(flags.L1RPCProviderKind.Name)),
		ExecCmd:            ctx.String(flags.Exec.Name),
		ServerMode:         ctx.Bool(flags.Server.Name),
347
	}, nil
348
}
349 350 351 352 353 354 355 356 357 358 359 360 361

func loadChainConfigFromGenesis(path string) (*params.ChainConfig, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read l2 genesis file: %w", err)
	}
	var genesis core.Genesis
	err = json.Unmarshal(data, &genesis)
	if err != nil {
		return nil, fmt.Errorf("parse l2 genesis file: %w", err)
	}
	return genesis.Config, nil
}
362 363 364 365 366 367 368 369 370 371 372

func loadRollupConfig(rollupConfigPath string) (*rollup.Config, error) {
	file, err := os.Open(rollupConfigPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read rollup config: %w", err)
	}
	defer file.Close()

	var rollupConfig rollup.Config
	return &rollupConfig, rollupConfig.ParseRollupConfig(file)
}