Commit 544c42b3 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-program: Update cli flags to support multiple chains (#13740)

* op-program: Validate that we have L2 chain configs for each rollup

* op-program: Update CLI flags to support specifying multiple L2 chains

* op-program: Allow custom l2 chain ID flag
parent 5a65359f
...@@ -3,11 +3,14 @@ package main ...@@ -3,11 +3,14 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/big"
"os" "os"
"strconv" "strconv"
"strings"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-program/chainconfig" "github.com/ethereum-optimism/optimism/op-program/chainconfig"
"github.com/ethereum-optimism/optimism/op-program/client/boot" "github.com/ethereum-optimism/optimism/op-program/client/boot"
"github.com/ethereum-optimism/optimism/op-program/host/config" "github.com/ethereum-optimism/optimism/op-program/host/config"
...@@ -90,12 +93,10 @@ func TestNetwork(t *testing.T) { ...@@ -90,12 +93,10 @@ func TestNetwork(t *testing.T) {
verifyArgsInvalid(t, "invalid network: \"bar\"", replaceRequiredArg("--network", "bar")) verifyArgsInvalid(t, "invalid network: \"bar\"", replaceRequiredArg("--network", "bar"))
}) })
t.Run("Required", func(t *testing.T) { t.Run("AllowNetworkAndRollupConfig", func(t *testing.T) {
verifyArgsInvalid(t, "flag rollup.config or network is required", addRequiredArgsExcept("--network")) configFile, rollupCfg := writeRollupConfigWithChainID(t, 4297842)
}) cfg := configForArgs(t, addRequiredArgs("--rollup.config", configFile))
require.Equal(t, []*rollup.Config{chaincfg.OPSepolia(), rollupCfg}, cfg.Rollups)
t.Run("DisallowNetworkAndRollupConfig", func(t *testing.T) {
verifyArgsInvalid(t, "cannot specify both rollup.config and network", addRequiredArgs("--rollup.config=foo"))
}) })
t.Run("RollupConfig", func(t *testing.T) { t.Run("RollupConfig", func(t *testing.T) {
...@@ -107,6 +108,15 @@ func TestNetwork(t *testing.T) { ...@@ -107,6 +108,15 @@ func TestNetwork(t *testing.T) {
require.Equal(t, *chaincfg.OPSepolia(), *cfg.Rollups[0]) require.Equal(t, *chaincfg.OPSepolia(), *cfg.Rollups[0])
}) })
t.Run("Multiple", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept("--network", "--network=op-mainnet,op-sepolia"))
require.Len(t, cfg.Rollups, 2)
opMainnetCfg, err := chaincfg.GetRollupConfig("op-mainnet")
require.NoError(t, err)
require.Equal(t, *opMainnetCfg, *cfg.Rollups[0])
require.Equal(t, *chaincfg.OPSepolia(), *cfg.Rollups[1])
})
for _, name := range chaincfg.AvailableNetworks() { for _, name := range chaincfg.AvailableNetworks() {
name := name name := name
expected, err := chaincfg.GetRollupConfig(name) expected, err := chaincfg.GetRollupConfig(name)
...@@ -141,17 +151,20 @@ func TestDataFormat(t *testing.T) { ...@@ -141,17 +151,20 @@ func TestDataFormat(t *testing.T) {
} }
func TestL2(t *testing.T) { func TestL2(t *testing.T) {
t.Run("Single", func(t *testing.T) {
expected := "https://example.com:8545" expected := "https://example.com:8545"
cfg := configForArgs(t, addRequiredArgs("--l2", expected)) cfg := configForArgs(t, addRequiredArgs("--l2", expected))
require.Equal(t, []string{expected}, cfg.L2URLs) require.Equal(t, []string{expected}, cfg.L2URLs)
} })
func TestL2Genesis(t *testing.T) { t.Run("Multiple", func(t *testing.T) {
t.Run("RequiredWithCustomNetwork", func(t *testing.T) { expected := []string{"https://example.com:8545", "https://example.com:9000"}
rollupCfgFile := writeValidRollupConfig(t) cfg := configForArgs(t, addRequiredArgs("--l2", strings.Join(expected, ",")))
verifyArgsInvalid(t, "flag l2.genesis is required", addRequiredArgsExcept("--network", "--rollup.config", rollupCfgFile)) require.Equal(t, expected, cfg.L2URLs)
}) })
}
func TestL2Genesis(t *testing.T) {
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
rollupCfgFile := writeValidRollupConfig(t) rollupCfgFile := writeValidRollupConfig(t)
genesisFile := writeValidGenesis(t) genesisFile := writeValidGenesis(t)
...@@ -165,6 +178,31 @@ func TestL2Genesis(t *testing.T) { ...@@ -165,6 +178,31 @@ func TestL2Genesis(t *testing.T) {
}) })
} }
func TestMultipleNetworkConfigs(t *testing.T) {
t.Run("MultipleCustomChains", func(t *testing.T) {
rollupFile1, rollupCfg1 := writeRollupConfigWithChainID(t, 1)
rollupFile2, rollupCfg2 := writeRollupConfigWithChainID(t, 2)
genesisFile1, chainCfg1 := writeGenesisFileWithChainID(t, 1)
genesisFile2, chainCfg2 := writeGenesisFileWithChainID(t, 2)
cfg := configForArgs(t, addRequiredArgsExcept("--network",
"--rollup.config", rollupFile1+","+rollupFile2,
"--l2.genesis", genesisFile1+","+genesisFile2))
require.Equal(t, []*rollup.Config{rollupCfg1, rollupCfg2}, cfg.Rollups)
require.Equal(t, []*params.ChainConfig{chainCfg1, chainCfg2}, cfg.L2ChainConfigs)
})
t.Run("MixNetworkAndCustomChains", func(t *testing.T) {
rollupFile, rollupCfg := writeRollupConfigWithChainID(t, 1)
genesisFile, chainCfg := writeGenesisFileWithChainID(t, 1)
cfg := configForArgs(t, addRequiredArgsExcept("--network",
"--network", "op-sepolia",
"--rollup.config", rollupFile,
"--l2.genesis", genesisFile))
require.Equal(t, []*rollup.Config{chaincfg.OPSepolia(), rollupCfg}, cfg.Rollups)
require.Equal(t, []*params.ChainConfig{chainconfig.OPSepoliaChainConfig(), chainCfg}, cfg.L2ChainConfigs)
})
}
func TestL2ChainID(t *testing.T) { func TestL2ChainID(t *testing.T) {
t.Run("DefaultToNetworkChainID", func(t *testing.T) { t.Run("DefaultToNetworkChainID", func(t *testing.T) {
cfg := configForArgs(t, replaceRequiredArg("--network", "op-mainnet")) cfg := configForArgs(t, replaceRequiredArg("--network", "op-mainnet"))
...@@ -187,11 +225,24 @@ func TestL2ChainID(t *testing.T) { ...@@ -187,11 +225,24 @@ func TestL2ChainID(t *testing.T) {
"--l2.custom")) "--l2.custom"))
require.Equal(t, boot.CustomChainIDIndicator, cfg.L2ChainID) require.Equal(t, boot.CustomChainIDIndicator, cfg.L2ChainID)
}) })
t.Run("ZeroWhenMultipleL2ChainsSpecified", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept("--network", "--network", "op-sepolia,op-mainnet"))
require.Zero(t, cfg.L2ChainID)
})
} }
func TestL2Head(t *testing.T) { func TestL2Head(t *testing.T) {
t.Run("Required", func(t *testing.T) { t.Run("RequiredWithOutputRoot", func(t *testing.T) {
verifyArgsInvalid(t, "flag l2.head is required", addRequiredArgsExcept("--l2.head")) verifyArgsInvalid(t, "flag l2.head is required when l2.outputroot is specified", addRequiredArgsExcept("--l2.head"))
})
t.Run("NotAllowedWithAgreedPrestate", func(t *testing.T) {
req := requiredArgs()
delete(req, "--l2.head")
delete(req, "--l2.outputroot")
args := append(toArgList(req), "--l2.head", l2HeadValue, "--l2.agreed-prestate", "0x1234")
verifyArgsInvalid(t, "flag l2.head and l2.agreed-prestate must not be specified together", args)
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
...@@ -210,7 +261,7 @@ func TestL2OutputRoot(t *testing.T) { ...@@ -210,7 +261,7 @@ func TestL2OutputRoot(t *testing.T) {
}) })
t.Run("NotRequiredWhenAgreedPrestateProvided", func(t *testing.T) { t.Run("NotRequiredWhenAgreedPrestateProvided", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept("--l2.outputroot", "--l2.agreed-prestate", "0x1234")) configForArgs(t, addRequiredArgsExceptMultiple([]string{"--l2.outputroot", "--l2.head"}, "--l2.agreed-prestate", "0x1234"))
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
...@@ -225,14 +276,14 @@ func TestL2OutputRoot(t *testing.T) { ...@@ -225,14 +276,14 @@ func TestL2OutputRoot(t *testing.T) {
func TestL2AgreedPrestate(t *testing.T) { func TestL2AgreedPrestate(t *testing.T) {
t.Run("NotRequiredWhenL2OutputRootProvided", func(t *testing.T) { t.Run("NotRequiredWhenL2OutputRootProvided", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept("--l2.outputroot", "--l2.outputroot", "0x1234")) configForArgs(t, addRequiredArgsExceptMultiple([]string{"--l2.outputroot", "--l2.head"}, "--l2.agreed-prestate", "0x1234"))
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
prestate := "0x1234" prestate := "0x1234"
prestateBytes := common.FromHex(prestate) prestateBytes := common.FromHex(prestate)
expectedOutputRoot := crypto.Keccak256Hash(prestateBytes) expectedOutputRoot := crypto.Keccak256Hash(prestateBytes)
cfg := configForArgs(t, addRequiredArgsExcept("--l2.outputroot", "--l2.agreed-prestate", prestate)) cfg := configForArgs(t, addRequiredArgsExceptMultiple([]string{"--l2.outputroot", "--l2.head"}, "--l2.agreed-prestate", prestate))
require.Equal(t, expectedOutputRoot, cfg.L2OutputRoot) require.Equal(t, expectedOutputRoot, cfg.L2OutputRoot)
require.Equal(t, prestateBytes, cfg.AgreedPrestate) require.Equal(t, prestateBytes, cfg.AgreedPrestate)
}) })
...@@ -242,11 +293,11 @@ func TestL2AgreedPrestate(t *testing.T) { ...@@ -242,11 +293,11 @@ func TestL2AgreedPrestate(t *testing.T) {
}) })
t.Run("Invalid", func(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t, config.ErrInvalidAgreedPrestate.Error(), addRequiredArgsExcept("--l2.outputroot", "--l2.agreed-prestate", "something")) verifyArgsInvalid(t, config.ErrInvalidAgreedPrestate.Error(), addRequiredArgsExceptMultiple([]string{"--l2.outputroot", "--l2.head"}, "--l2.agreed-prestate", "something"))
}) })
t.Run("ZeroLength", func(t *testing.T) { t.Run("ZeroLength", func(t *testing.T) {
verifyArgsInvalid(t, config.ErrInvalidAgreedPrestate.Error(), addRequiredArgsExcept("--l2.outputroot", "--l2.agreed-prestate", "0x")) verifyArgsInvalid(t, config.ErrInvalidAgreedPrestate.Error(), addRequiredArgsExceptMultiple([]string{"--l2.outputroot", "--l2.head"}, "--l2.agreed-prestate", "0x"))
}) })
} }
...@@ -345,6 +396,12 @@ func TestL2Experimental(t *testing.T) { ...@@ -345,6 +396,12 @@ func TestL2Experimental(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs("--l2.experimental", expected)) cfg := configForArgs(t, addRequiredArgs("--l2.experimental", expected))
require.EqualValues(t, []string{expected}, cfg.L2ExperimentalURLs) require.EqualValues(t, []string{expected}, cfg.L2ExperimentalURLs)
}) })
t.Run("Multiple", func(t *testing.T) {
expected := []string{"https://example.com:8545", "https://example.com:9000"}
cfg := configForArgs(t, addRequiredArgs("--l2.experimental", strings.Join(expected, ",")))
require.EqualValues(t, expected, cfg.L2ExperimentalURLs)
})
} }
func TestL2BlockNumber(t *testing.T) { func TestL2BlockNumber(t *testing.T) {
...@@ -431,6 +488,14 @@ func addRequiredArgsExcept(name string, optionalArgs ...string) []string { ...@@ -431,6 +488,14 @@ func addRequiredArgsExcept(name string, optionalArgs ...string) []string {
return append(toArgList(req), optionalArgs...) return append(toArgList(req), optionalArgs...)
} }
func addRequiredArgsExceptMultiple(remove []string, optionalArgs ...string) []string {
req := requiredArgs()
for _, name := range remove {
delete(req, name)
}
return append(toArgList(req), optionalArgs...)
}
func replaceRequiredArg(name string, value string) []string { func replaceRequiredArg(name string, value string) []string {
req := requiredArgs() req := requiredArgs()
req[name] = value req[name] = value
...@@ -451,8 +516,21 @@ func requiredArgs() map[string]string { ...@@ -451,8 +516,21 @@ func requiredArgs() map[string]string {
} }
func writeValidGenesis(t *testing.T) string { func writeValidGenesis(t *testing.T) string {
genesis := l2Genesis
return writeGenesis(t, genesis)
}
func writeGenesisFileWithChainID(t *testing.T, chainID uint64) (string, *params.ChainConfig) {
genesis := *l2Genesis
chainCfg := *genesis.Config
chainCfg.ChainID = new(big.Int).SetUint64(chainID)
genesis.Config = &chainCfg
return writeGenesis(t, &genesis), &chainCfg
}
func writeGenesis(t *testing.T, genesis *core.Genesis) string {
dir := t.TempDir() dir := t.TempDir()
j, err := json.Marshal(l2Genesis) j, err := json.Marshal(genesis)
require.NoError(t, err) require.NoError(t, err)
genesisFile := dir + "/genesis.json" genesisFile := dir + "/genesis.json"
require.NoError(t, os.WriteFile(genesisFile, j, 0666)) require.NoError(t, os.WriteFile(genesisFile, j, 0666))
...@@ -460,8 +538,18 @@ func writeValidGenesis(t *testing.T) string { ...@@ -460,8 +538,18 @@ func writeValidGenesis(t *testing.T) string {
} }
func writeValidRollupConfig(t *testing.T) string { func writeValidRollupConfig(t *testing.T) string {
return writeRollupConfig(t, chaincfg.OPSepolia())
}
func writeRollupConfigWithChainID(t *testing.T, chainID uint64) (string, *rollup.Config) {
rollupCfg := *chaincfg.OPSepolia()
rollupCfg.L2ChainID = new(big.Int).SetUint64(chainID)
return writeRollupConfig(t, &rollupCfg), &rollupCfg
}
func writeRollupConfig(t *testing.T, rollupCfg *rollup.Config) string {
dir := t.TempDir() dir := t.TempDir()
j, err := json.Marshal(chaincfg.OPSepolia()) j, err := json.Marshal(&rollupCfg)
require.NoError(t, err) require.NoError(t, err)
cfgFile := dir + "/rollup.json" cfgFile := dir + "/rollup.json"
require.NoError(t, os.WriteFile(cfgFile, j, 0666)) require.NoError(t, os.WriteFile(cfgFile, j, 0666))
......
...@@ -25,9 +25,13 @@ import ( ...@@ -25,9 +25,13 @@ import (
) )
var ( var (
ErrNoL2Chains = errors.New("at least one L2 chain must be specified")
ErrMissingL2ChainID = errors.New("missing l2 chain id") ErrMissingL2ChainID = errors.New("missing l2 chain id")
ErrMissingRollupConfig = errors.New("missing rollup config")
ErrMissingL2Genesis = errors.New("missing l2 genesis") ErrMissingL2Genesis = errors.New("missing l2 genesis")
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")
ErrInvalidL1Head = errors.New("invalid l1 head") ErrInvalidL1Head = errors.New("invalid l1 head")
ErrInvalidL2Head = errors.New("invalid l2 head") ErrInvalidL2Head = errors.New("invalid l2 head")
ErrInvalidL2OutputRoot = errors.New("invalid l2 output root") ErrInvalidL2OutputRoot = errors.New("invalid l2 output root")
...@@ -97,7 +101,7 @@ func (c *Config) Check() error { ...@@ -97,7 +101,7 @@ func (c *Config) Check() error {
return ErrMissingL2ChainID return ErrMissingL2ChainID
} }
if len(c.Rollups) == 0 { if len(c.Rollups) == 0 {
return ErrMissingRollupConfig return ErrNoL2Chains
} }
for _, rollupCfg := range c.Rollups { for _, rollupCfg := range c.Rollups {
if err := rollupCfg.Check(); err != nil { if err := rollupCfg.Check(); err != nil {
...@@ -119,6 +123,30 @@ func (c *Config) Check() error { ...@@ -119,6 +123,30 @@ func (c *Config) Check() error {
if len(c.L2ChainConfigs) == 0 { if len(c.L2ChainConfigs) == 0 {
return ErrMissingL2Genesis return ErrMissingL2Genesis
} }
// 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)
}
}
if (c.L1URL != "") != (len(c.L2URLs) > 0) { if (c.L1URL != "") != (len(c.L2URLs) > 0) {
return ErrL1AndL2Inconsistent return ErrL1AndL2Inconsistent
} }
...@@ -201,10 +229,13 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) { ...@@ -201,10 +229,13 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) {
return nil, err return nil, err
} }
l2Head := common.HexToHash(ctx.String(flags.L2Head.Name)) var l2Head common.Hash
if ctx.IsSet(flags.L2Head.Name) {
l2Head = common.HexToHash(ctx.String(flags.L2Head.Name))
if l2Head == (common.Hash{}) { if l2Head == (common.Hash{}) {
return nil, ErrInvalidL2Head return nil, ErrInvalidL2Head
} }
}
var l2OutputRoot common.Hash var l2OutputRoot common.Hash
var agreedPrestate []byte var agreedPrestate []byte
if ctx.IsSet(flags.L2OutputRoot.Name) { if ctx.IsSet(flags.L2OutputRoot.Name) {
...@@ -235,11 +266,11 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) { ...@@ -235,11 +266,11 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) {
} }
var err error var err error
var rollupCfg *rollup.Config var rollupCfgs []*rollup.Config
var l2ChainConfig *params.ChainConfig var l2ChainConfigs []*params.ChainConfig
var l2ChainID uint64 var l2ChainID uint64
networkName := ctx.String(flags.Network.Name) networkNames := ctx.StringSlice(flags.Network.Name)
if networkName != "" { for _, networkName := range networkNames {
var chainID uint64 var chainID uint64
if chainID, err = strconv.ParseUint(networkName, 10, 64); err != nil { if chainID, err = strconv.ParseUint(networkName, 10, 64); err != nil {
ch := chaincfg.ChainByName(networkName) ch := chaincfg.ChainByName(networkName)
...@@ -249,55 +280,58 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) { ...@@ -249,55 +280,58 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) {
chainID = ch.ChainID chainID = ch.ChainID
} }
l2ChainConfig, err = chainconfig.ChainConfigByChainID(chainID) l2ChainConfig, err := chainconfig.ChainConfigByChainID(chainID)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load chain config for chain %d: %w", chainID, err) return nil, fmt.Errorf("failed to load chain config for chain %d: %w", chainID, err)
} }
rollupCfg, err = chainconfig.RollupConfigByChainID(chainID) l2ChainConfigs = append(l2ChainConfigs, l2ChainConfig)
rollupCfg, err := chainconfig.RollupConfigByChainID(chainID)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load rollup config for chain %d: %w", chainID, err) return nil, fmt.Errorf("failed to load rollup config for chain %d: %w", chainID, err)
} }
rollupCfgs = append(rollupCfgs, rollupCfg)
l2ChainID = chainID l2ChainID = chainID
} else { }
l2GenesisPath := ctx.String(flags.L2GenesisPath.Name)
l2ChainConfig, err = loadChainConfigFromGenesis(l2GenesisPath) genesisPaths := ctx.StringSlice(flags.L2GenesisPath.Name)
for _, l2GenesisPath := range genesisPaths {
l2ChainConfig, err := loadChainConfigFromGenesis(l2GenesisPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid genesis: %w", err) return nil, fmt.Errorf("invalid genesis: %w", err)
} }
l2ChainConfigs = append(l2ChainConfigs, l2ChainConfig)
l2ChainID = l2ChainConfig.ChainID.Uint64()
}
rollupConfigPath := ctx.String(flags.RollupConfig.Name) rollupPaths := ctx.StringSlice(flags.RollupConfig.Name)
rollupCfg, err = loadRollupConfig(rollupConfigPath) for _, rollupConfigPath := range rollupPaths {
rollupCfg, err := loadRollupConfig(rollupConfigPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid rollup config: %w", err) return nil, fmt.Errorf("invalid rollup config: %w", err)
} }
rollupCfgs = append(rollupCfgs, rollupCfg)
l2ChainID = l2ChainConfig.ChainID.Uint64() }
if ctx.Bool(flags.L2Custom.Name) { if ctx.Bool(flags.L2Custom.Name) {
log.Warn("Using custom chain configuration via preimage oracle. This is not compatible with on-chain execution.") log.Warn("Using custom chain configuration via preimage oracle. This is not compatible with on-chain execution.")
l2ChainID = boot.CustomChainIDIndicator l2ChainID = boot.CustomChainIDIndicator
} } else if len(rollupCfgs) > 1 {
// L2ChainID is not applicable when multiple L2 sources are used and not using custom configs
l2ChainID = 0
} }
dbFormat := types.DataFormat(ctx.String(flags.DataFormat.Name)) dbFormat := types.DataFormat(ctx.String(flags.DataFormat.Name))
if !slices.Contains(types.SupportedDataFormats, dbFormat) { if !slices.Contains(types.SupportedDataFormats, dbFormat) {
return nil, fmt.Errorf("invalid %w: %v", ErrInvalidDataFormat, dbFormat) return nil, fmt.Errorf("invalid %w: %v", ErrInvalidDataFormat, dbFormat)
} }
var l2URLs []string
if ctx.IsSet(flags.L2NodeAddr.Name) {
l2URLs = append(l2URLs, ctx.String(flags.L2NodeAddr.Name))
}
var l2ExperimentalURLs []string
if ctx.IsSet(flags.L2NodeExperimentalAddr.Name) {
l2ExperimentalURLs = append(l2ExperimentalURLs, ctx.String(flags.L2NodeExperimentalAddr.Name))
}
return &Config{ return &Config{
L2ChainID: l2ChainID, L2ChainID: l2ChainID,
Rollups: []*rollup.Config{rollupCfg}, Rollups: rollupCfgs,
DataDir: ctx.String(flags.DataDir.Name), DataDir: ctx.String(flags.DataDir.Name),
DataFormat: dbFormat, DataFormat: dbFormat,
L2URLs: l2URLs, L2URLs: ctx.StringSlice(flags.L2NodeAddr.Name),
L2ExperimentalURLs: l2ExperimentalURLs, L2ExperimentalURLs: ctx.StringSlice(flags.L2NodeExperimentalAddr.Name),
L2ChainConfigs: []*params.ChainConfig{l2ChainConfig}, L2ChainConfigs: l2ChainConfigs,
L2Head: l2Head, L2Head: l2Head,
L2OutputRoot: l2OutputRoot, L2OutputRoot: l2OutputRoot,
AgreedPrestate: agreedPrestate, AgreedPrestate: agreedPrestate,
......
...@@ -58,7 +58,7 @@ func TestRollupConfig(t *testing.T) { ...@@ -58,7 +58,7 @@ func TestRollupConfig(t *testing.T) {
config := validConfig() config := validConfig()
config.Rollups = nil config.Rollups = nil
err := config.Check() err := config.Check()
require.ErrorIs(t, err, ErrMissingRollupConfig) require.ErrorIs(t, err, ErrNoL2Chains)
}) })
t.Run("Invalid", func(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
...@@ -67,6 +67,12 @@ func TestRollupConfig(t *testing.T) { ...@@ -67,6 +67,12 @@ func TestRollupConfig(t *testing.T) {
err := config.Check() err := config.Check()
require.ErrorIs(t, err, rollup.ErrBlockTimeZero) require.ErrorIs(t, err, rollup.ErrBlockTimeZero)
}) })
t.Run("DisallowDuplicates", func(t *testing.T) {
cfg := validConfig()
cfg.Rollups = append(cfg.Rollups, validRollupConfig)
require.ErrorIs(t, cfg.Check(), ErrDuplicateRollup)
})
} }
func TestL1HeadRequired(t *testing.T) { func TestL1HeadRequired(t *testing.T) {
...@@ -111,6 +117,26 @@ func TestL2GenesisRequired(t *testing.T) { ...@@ -111,6 +117,26 @@ func TestL2GenesisRequired(t *testing.T) {
require.ErrorIs(t, err, ErrMissingL2Genesis) require.ErrorIs(t, err, ErrMissingL2Genesis)
} }
func TestL2Genesis_ExtraGenesisProvided(t *testing.T) {
config := validConfig()
config.L2ChainConfigs = append(config.L2ChainConfigs, &params.ChainConfig{ChainID: big.NewInt(422142)})
require.ErrorIs(t, config.Check(), ErrNoRollupForGenesis)
}
func TestL2Genesis_GenesisMissingForChain(t *testing.T) {
config := validConfig()
secondConfig := *chaincfg.OPSepolia()
secondConfig.L2ChainID = big.NewInt(422142)
config.Rollups = append(config.Rollups, &secondConfig)
require.ErrorIs(t, config.Check(), ErrNoGenesisForRollup)
}
func TestL2Genesis_Duplicate(t *testing.T) {
config := validConfig()
config.L2ChainConfigs = append(config.L2ChainConfigs, validL2Genesis)
require.ErrorIs(t, config.Check(), ErrDuplicateGenesis)
}
func TestFetchingArgConsistency(t *testing.T) { func TestFetchingArgConsistency(t *testing.T) {
t.Run("RequireL2WhenL1Set", func(t *testing.T) { t.Run("RequireL2WhenL1Set", func(t *testing.T) {
cfg := validConfig() cfg := validConfig()
......
...@@ -29,12 +29,12 @@ var ( ...@@ -29,12 +29,12 @@ var (
Value: false, Value: false,
Hidden: true, Hidden: true,
} }
RollupConfig = &cli.StringFlag{ RollupConfig = &cli.StringSliceFlag{
Name: "rollup.config", Name: "rollup.config",
Usage: "Rollup chain parameters", Usage: "Rollup chain parameters",
EnvVars: prefixEnvVars("ROLLUP_CONFIG"), EnvVars: prefixEnvVars("ROLLUP_CONFIG"),
} }
Network = &cli.StringFlag{ Network = &cli.StringSliceFlag{
Name: "network", Name: "network",
Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")), Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")),
EnvVars: prefixEnvVars("NETWORK"), EnvVars: prefixEnvVars("NETWORK"),
...@@ -50,12 +50,12 @@ var ( ...@@ -50,12 +50,12 @@ var (
EnvVars: prefixEnvVars("DATA_FORMAT"), EnvVars: prefixEnvVars("DATA_FORMAT"),
Value: string(types.DataFormatDirectory), Value: string(types.DataFormatDirectory),
} }
L2NodeAddr = &cli.StringFlag{ L2NodeAddr = &cli.StringSliceFlag{
Name: "l2", Name: "l2",
Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)", Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)",
EnvVars: prefixEnvVars("L2_RPC"), EnvVars: prefixEnvVars("L2_RPC"),
} }
L2NodeExperimentalAddr = &cli.StringFlag{ L2NodeExperimentalAddr = &cli.StringSliceFlag{
Name: "l2.experimental", Name: "l2.experimental",
Usage: "Address of L2 JSON-RPC endpoint to use for experimental features (debug_executionWitness)", Usage: "Address of L2 JSON-RPC endpoint to use for experimental features (debug_executionWitness)",
EnvVars: prefixEnvVars("L2_RPC_EXPERIMENTAL_RPC"), EnvVars: prefixEnvVars("L2_RPC_EXPERIMENTAL_RPC"),
...@@ -91,7 +91,7 @@ var ( ...@@ -91,7 +91,7 @@ var (
Usage: "Number of the L2 block that the claim is from", Usage: "Number of the L2 block that the claim is from",
EnvVars: prefixEnvVars("L2_BLOCK_NUM"), EnvVars: prefixEnvVars("L2_BLOCK_NUM"),
} }
L2GenesisPath = &cli.StringFlag{ L2GenesisPath = &cli.StringSliceFlag{
Name: "l2.genesis", Name: "l2.genesis",
Usage: "Path to the op-geth genesis file", Usage: "Path to the op-geth genesis file",
EnvVars: prefixEnvVars("L2_GENESIS"), EnvVars: prefixEnvVars("L2_GENESIS"),
...@@ -138,12 +138,12 @@ var Flags []cli.Flag ...@@ -138,12 +138,12 @@ var Flags []cli.Flag
var requiredFlags = []cli.Flag{ var requiredFlags = []cli.Flag{
L1Head, L1Head,
L2Head,
L2Claim, L2Claim,
L2BlockNumber, L2BlockNumber,
} }
var programFlags = []cli.Flag{ var programFlags = []cli.Flag{
L2Head,
L2OutputRoot, L2OutputRoot,
L2AgreedPrestate, L2AgreedPrestate,
L2Custom, L2Custom,
...@@ -169,21 +169,7 @@ func init() { ...@@ -169,21 +169,7 @@ func init() {
} }
func CheckRequired(ctx *cli.Context) error { func CheckRequired(ctx *cli.Context) error {
rollupConfig := ctx.String(RollupConfig.Name) if ctx.Bool(L2Custom.Name) && ctx.IsSet(Network.Name) {
network := ctx.String(Network.Name)
if rollupConfig == "" && network == "" {
return fmt.Errorf("flag %s or %s is required", RollupConfig.Name, Network.Name)
}
if rollupConfig != "" && network != "" {
return fmt.Errorf("cannot specify both %s and %s", RollupConfig.Name, Network.Name)
}
if network == "" && ctx.String(L2GenesisPath.Name) == "" {
return fmt.Errorf("flag %s is required for custom networks", L2GenesisPath.Name)
}
if ctx.String(L2GenesisPath.Name) != "" && network != "" {
return fmt.Errorf("cannot specify both %s and %s", L2GenesisPath.Name, Network.Name)
}
if ctx.Bool(L2Custom.Name) && rollupConfig == "" {
return fmt.Errorf("flag %s cannot be used with named networks", L2Custom.Name) return fmt.Errorf("flag %s cannot be used with named networks", L2Custom.Name)
} }
for _, flag := range requiredFlags { for _, flag := range requiredFlags {
...@@ -197,5 +183,11 @@ func CheckRequired(ctx *cli.Context) error { ...@@ -197,5 +183,11 @@ func CheckRequired(ctx *cli.Context) error {
if ctx.IsSet(L2OutputRoot.Name) && ctx.IsSet(L2AgreedPrestate.Name) { if ctx.IsSet(L2OutputRoot.Name) && ctx.IsSet(L2AgreedPrestate.Name) {
return fmt.Errorf("flag %s and %s must not be specified together", L2OutputRoot.Name, L2AgreedPrestate.Name) return fmt.Errorf("flag %s and %s must not be specified together", L2OutputRoot.Name, L2AgreedPrestate.Name)
} }
if ctx.IsSet(L2Head.Name) && ctx.IsSet(L2AgreedPrestate.Name) {
return fmt.Errorf("flag %s and %s must not be specified together", L2Head.Name, L2AgreedPrestate.Name)
}
if !ctx.IsSet(L2Head.Name) && ctx.IsSet(L2OutputRoot.Name) {
return fmt.Errorf("flag %s is required when %s is specified", L2Head.Name, L2OutputRoot.Name)
}
return nil return nil
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment