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
import (
"encoding/json"
"fmt"
"math/big"
"os"
"strconv"
"strings"
"testing"
"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/client/boot"
"github.com/ethereum-optimism/optimism/op-program/host/config"
......@@ -90,12 +93,10 @@ func TestNetwork(t *testing.T) {
verifyArgsInvalid(t, "invalid network: \"bar\"", replaceRequiredArg("--network", "bar"))
})
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag rollup.config or network is required", addRequiredArgsExcept("--network"))
})
t.Run("DisallowNetworkAndRollupConfig", func(t *testing.T) {
verifyArgsInvalid(t, "cannot specify both rollup.config and network", addRequiredArgs("--rollup.config=foo"))
t.Run("AllowNetworkAndRollupConfig", func(t *testing.T) {
configFile, rollupCfg := writeRollupConfigWithChainID(t, 4297842)
cfg := configForArgs(t, addRequiredArgs("--rollup.config", configFile))
require.Equal(t, []*rollup.Config{chaincfg.OPSepolia(), rollupCfg}, cfg.Rollups)
})
t.Run("RollupConfig", func(t *testing.T) {
......@@ -107,6 +108,15 @@ func TestNetwork(t *testing.T) {
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() {
name := name
expected, err := chaincfg.GetRollupConfig(name)
......@@ -141,17 +151,20 @@ func TestDataFormat(t *testing.T) {
}
func TestL2(t *testing.T) {
expected := "https://example.com:8545"
cfg := configForArgs(t, addRequiredArgs("--l2", expected))
require.Equal(t, []string{expected}, cfg.L2URLs)
}
t.Run("Single", func(t *testing.T) {
expected := "https://example.com:8545"
cfg := configForArgs(t, addRequiredArgs("--l2", expected))
require.Equal(t, []string{expected}, cfg.L2URLs)
})
func TestL2Genesis(t *testing.T) {
t.Run("RequiredWithCustomNetwork", func(t *testing.T) {
rollupCfgFile := writeValidRollupConfig(t)
verifyArgsInvalid(t, "flag l2.genesis is required", addRequiredArgsExcept("--network", "--rollup.config", rollupCfgFile))
t.Run("Multiple", func(t *testing.T) {
expected := []string{"https://example.com:8545", "https://example.com:9000"}
cfg := configForArgs(t, addRequiredArgs("--l2", strings.Join(expected, ",")))
require.Equal(t, expected, cfg.L2URLs)
})
}
func TestL2Genesis(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
rollupCfgFile := writeValidRollupConfig(t)
genesisFile := writeValidGenesis(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) {
t.Run("DefaultToNetworkChainID", func(t *testing.T) {
cfg := configForArgs(t, replaceRequiredArg("--network", "op-mainnet"))
......@@ -187,11 +225,24 @@ func TestL2ChainID(t *testing.T) {
"--l2.custom"))
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) {
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l2.head is required", addRequiredArgsExcept("--l2.head"))
t.Run("RequiredWithOutputRoot", func(t *testing.T) {
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) {
......@@ -210,7 +261,7 @@ func TestL2OutputRoot(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) {
......@@ -225,14 +276,14 @@ func TestL2OutputRoot(t *testing.T) {
func TestL2AgreedPrestate(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) {
prestate := "0x1234"
prestateBytes := common.FromHex(prestate)
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, prestateBytes, cfg.AgreedPrestate)
})
......@@ -242,11 +293,11 @@ func TestL2AgreedPrestate(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) {
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) {
cfg := configForArgs(t, addRequiredArgs("--l2.experimental", expected))
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) {
......@@ -431,6 +488,14 @@ func addRequiredArgsExcept(name string, optionalArgs ...string) []string {
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 {
req := requiredArgs()
req[name] = value
......@@ -451,8 +516,21 @@ func requiredArgs() map[string]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()
j, err := json.Marshal(l2Genesis)
j, err := json.Marshal(genesis)
require.NoError(t, err)
genesisFile := dir + "/genesis.json"
require.NoError(t, os.WriteFile(genesisFile, j, 0666))
......@@ -460,8 +538,18 @@ func writeValidGenesis(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()
j, err := json.Marshal(chaincfg.OPSepolia())
j, err := json.Marshal(&rollupCfg)
require.NoError(t, err)
cfgFile := dir + "/rollup.json"
require.NoError(t, os.WriteFile(cfgFile, j, 0666))
......
......@@ -25,9 +25,13 @@ import (
)
var (
ErrNoL2Chains = errors.New("at least one L2 chain must be specified")
ErrMissingL2ChainID = errors.New("missing l2 chain id")
ErrMissingRollupConfig = errors.New("missing rollup config")
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")
ErrInvalidL2Head = errors.New("invalid l2 head")
ErrInvalidL2OutputRoot = errors.New("invalid l2 output root")
......@@ -97,7 +101,7 @@ func (c *Config) Check() error {
return ErrMissingL2ChainID
}
if len(c.Rollups) == 0 {
return ErrMissingRollupConfig
return ErrNoL2Chains
}
for _, rollupCfg := range c.Rollups {
if err := rollupCfg.Check(); err != nil {
......@@ -119,6 +123,30 @@ func (c *Config) Check() error {
if len(c.L2ChainConfigs) == 0 {
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) {
return ErrL1AndL2Inconsistent
}
......@@ -201,9 +229,12 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) {
return nil, err
}
l2Head := common.HexToHash(ctx.String(flags.L2Head.Name))
if l2Head == (common.Hash{}) {
return nil, ErrInvalidL2Head
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
}
}
var l2OutputRoot common.Hash
var agreedPrestate []byte
......@@ -235,11 +266,11 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) {
}
var err error
var rollupCfg *rollup.Config
var l2ChainConfig *params.ChainConfig
var rollupCfgs []*rollup.Config
var l2ChainConfigs []*params.ChainConfig
var l2ChainID uint64
networkName := ctx.String(flags.Network.Name)
if networkName != "" {
networkNames := ctx.StringSlice(flags.Network.Name)
for _, networkName := range networkNames {
var chainID uint64
if chainID, err = strconv.ParseUint(networkName, 10, 64); err != nil {
ch := chaincfg.ChainByName(networkName)
......@@ -249,55 +280,58 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) {
chainID = ch.ChainID
}
l2ChainConfig, err = chainconfig.ChainConfigByChainID(chainID)
l2ChainConfig, err := chainconfig.ChainConfigByChainID(chainID)
if err != nil {
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 {
return nil, fmt.Errorf("failed to load rollup config for chain %d: %w", chainID, err)
}
rollupCfgs = append(rollupCfgs, rollupCfg)
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 {
return nil, fmt.Errorf("invalid genesis: %w", err)
}
l2ChainConfigs = append(l2ChainConfigs, l2ChainConfig)
l2ChainID = l2ChainConfig.ChainID.Uint64()
}
rollupConfigPath := ctx.String(flags.RollupConfig.Name)
rollupCfg, err = loadRollupConfig(rollupConfigPath)
rollupPaths := ctx.StringSlice(flags.RollupConfig.Name)
for _, rollupConfigPath := range rollupPaths {
rollupCfg, err := loadRollupConfig(rollupConfigPath)
if err != nil {
return nil, fmt.Errorf("invalid rollup config: %w", err)
}
rollupCfgs = append(rollupCfgs, rollupCfg)
l2ChainID = l2ChainConfig.ChainID.Uint64()
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
}
}
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
l2ChainID = 0
}
dbFormat := types.DataFormat(ctx.String(flags.DataFormat.Name))
if !slices.Contains(types.SupportedDataFormats, 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{
L2ChainID: l2ChainID,
Rollups: []*rollup.Config{rollupCfg},
Rollups: rollupCfgs,
DataDir: ctx.String(flags.DataDir.Name),
DataFormat: dbFormat,
L2URLs: l2URLs,
L2ExperimentalURLs: l2ExperimentalURLs,
L2ChainConfigs: []*params.ChainConfig{l2ChainConfig},
L2URLs: ctx.StringSlice(flags.L2NodeAddr.Name),
L2ExperimentalURLs: ctx.StringSlice(flags.L2NodeExperimentalAddr.Name),
L2ChainConfigs: l2ChainConfigs,
L2Head: l2Head,
L2OutputRoot: l2OutputRoot,
AgreedPrestate: agreedPrestate,
......
......@@ -58,7 +58,7 @@ func TestRollupConfig(t *testing.T) {
config := validConfig()
config.Rollups = nil
err := config.Check()
require.ErrorIs(t, err, ErrMissingRollupConfig)
require.ErrorIs(t, err, ErrNoL2Chains)
})
t.Run("Invalid", func(t *testing.T) {
......@@ -67,6 +67,12 @@ func TestRollupConfig(t *testing.T) {
err := config.Check()
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) {
......@@ -111,6 +117,26 @@ func TestL2GenesisRequired(t *testing.T) {
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) {
t.Run("RequireL2WhenL1Set", func(t *testing.T) {
cfg := validConfig()
......
......@@ -29,12 +29,12 @@ var (
Value: false,
Hidden: true,
}
RollupConfig = &cli.StringFlag{
RollupConfig = &cli.StringSliceFlag{
Name: "rollup.config",
Usage: "Rollup chain parameters",
EnvVars: prefixEnvVars("ROLLUP_CONFIG"),
}
Network = &cli.StringFlag{
Network = &cli.StringSliceFlag{
Name: "network",
Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")),
EnvVars: prefixEnvVars("NETWORK"),
......@@ -50,12 +50,12 @@ var (
EnvVars: prefixEnvVars("DATA_FORMAT"),
Value: string(types.DataFormatDirectory),
}
L2NodeAddr = &cli.StringFlag{
L2NodeAddr = &cli.StringSliceFlag{
Name: "l2",
Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)",
EnvVars: prefixEnvVars("L2_RPC"),
}
L2NodeExperimentalAddr = &cli.StringFlag{
L2NodeExperimentalAddr = &cli.StringSliceFlag{
Name: "l2.experimental",
Usage: "Address of L2 JSON-RPC endpoint to use for experimental features (debug_executionWitness)",
EnvVars: prefixEnvVars("L2_RPC_EXPERIMENTAL_RPC"),
......@@ -91,7 +91,7 @@ var (
Usage: "Number of the L2 block that the claim is from",
EnvVars: prefixEnvVars("L2_BLOCK_NUM"),
}
L2GenesisPath = &cli.StringFlag{
L2GenesisPath = &cli.StringSliceFlag{
Name: "l2.genesis",
Usage: "Path to the op-geth genesis file",
EnvVars: prefixEnvVars("L2_GENESIS"),
......@@ -138,12 +138,12 @@ var Flags []cli.Flag
var requiredFlags = []cli.Flag{
L1Head,
L2Head,
L2Claim,
L2BlockNumber,
}
var programFlags = []cli.Flag{
L2Head,
L2OutputRoot,
L2AgreedPrestate,
L2Custom,
......@@ -169,21 +169,7 @@ func init() {
}
func CheckRequired(ctx *cli.Context) error {
rollupConfig := ctx.String(RollupConfig.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 == "" {
if ctx.Bool(L2Custom.Name) && ctx.IsSet(Network.Name) {
return fmt.Errorf("flag %s cannot be used with named networks", L2Custom.Name)
}
for _, flag := range requiredFlags {
......@@ -197,5 +183,11 @@ func CheckRequired(ctx *cli.Context) error {
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)
}
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
}
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