Commit f638bfa2 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

dispute-mon: Add --network option (#10712)

* challenger: Add --network option

Loads the dispute game factory address from the superchain registry.

* dispute-mon: Add --network option

Loads the dispute game factory address from the superchain registry.
parent 58f74b1d
package main
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-dispute-mon/config"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
"github.com/ethereum-optimism/superchain-registry/superchain"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
var (
l1EthRpc = "http://example.com:8545"
rollupRpc = "http://example.com:8555"
gameFactoryAddressValue = "0xbb00000000000000000000000000000000000000"
)
func TestLogLevel(t *testing.T) {
t.Run("RejectInvalid", func(t *testing.T) {
verifyArgsInvalid(t, "unknown level: foo", addRequiredArgs("--log.level=foo"))
})
for _, lvl := range []string{"trace", "debug", "info", "error", "crit"} {
lvl := lvl
t.Run("AcceptValid_"+lvl, func(t *testing.T) {
logger, _, err := dryRunWithArgs(addRequiredArgs("--log.level", lvl))
require.NoError(t, err)
require.NotNil(t, logger)
})
}
}
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs())
defaultCfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, rollupRpc)
require.Equal(t, defaultCfg, cfg)
}
func TestDefaultConfigIsValid(t *testing.T) {
cfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, rollupRpc)
require.NoError(t, cfg.Check())
}
func TestL1EthRpc(t *testing.T) {
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l1-eth-rpc is required", addRequiredArgsExcept("--l1-eth-rpc"))
})
t.Run("Valid", func(t *testing.T) {
url := "http://example.com:9999"
cfg := configForArgs(t, addRequiredArgsExcept("--l1-eth-rpc", "--l1-eth-rpc", url))
require.Equal(t, url, cfg.L1EthRpc)
})
}
func TestRollupRpc(t *testing.T) {
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag rollup-rpc is required", addRequiredArgsExcept("--rollup-rpc"))
})
t.Run("Valid", func(t *testing.T) {
url := "http://example.com:9999"
cfg := configForArgs(t, addRequiredArgsExcept("--rollup-rpc", "--rollup-rpc", url))
require.Equal(t, url, cfg.RollupRpc)
})
}
func TestGameFactoryAddress(t *testing.T) {
t.Run("RequiredIfNetworkNetSet", func(t *testing.T) {
verifyArgsInvalid(t, "flag game-factory-address or network is required", addRequiredArgsExcept("--game-factory-address"))
})
t.Run("Valid", func(t *testing.T) {
addr := common.Address{0x11, 0x22}
cfg := configForArgs(t, addRequiredArgsExcept("--game-factory-address", "--game-factory-address", addr.Hex()))
require.Equal(t, addr, cfg.GameFactoryAddress)
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t, "invalid address: foo", addRequiredArgsExcept("--game-factory-address", "--game-factory-address", "foo"))
})
}
func TestNetwork(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
opSepoliaChainId := uint64(11155420)
cfg := configForArgs(t, addRequiredArgsExcept("--game-factory-address", "--network=op-sepolia"))
require.EqualValues(t, superchain.Addresses[opSepoliaChainId].DisputeGameFactoryProxy, cfg.GameFactoryAddress)
})
t.Run("UnknownNetwork", func(t *testing.T) {
verifyArgsInvalid(t, "unknown chain: not-a-network", addRequiredArgsExcept("--game-factory-address", "--network=not-a-network"))
})
}
func TestHonestActors(t *testing.T) {
t.Run("NotRequired", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs())
require.Empty(t, cfg.HonestActors)
})
t.Run("SingleValue", func(t *testing.T) {
addr := common.Address{0xbb}
cfg := configForArgs(t, addRequiredArgs("--honest-actors", addr.Hex()))
require.Len(t, cfg.HonestActors, 1)
require.Contains(t, cfg.HonestActors, addr)
})
t.Run("MultiValue", func(t *testing.T) {
addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb}
addr3 := common.Address{0xcc}
cfg := configForArgs(t, addRequiredArgs(
"--honest-actors", addr1.Hex(),
"--honest-actors", addr2.Hex(),
"--honest-actors", addr3.Hex(),
))
require.Len(t, cfg.HonestActors, 3)
require.Contains(t, cfg.HonestActors, addr1)
require.Contains(t, cfg.HonestActors, addr2)
require.Contains(t, cfg.HonestActors, addr3)
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t,
"invalid honest actor address: invalid address: 0xnope",
addRequiredArgs("-honest-actors", "0xnope"))
})
}
func TestMonitorInterval(t *testing.T) {
t.Run("UsesDefault", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs())
require.Equal(t, config.DefaultMonitorInterval, cfg.MonitorInterval)
})
t.Run("Valid", func(t *testing.T) {
expected := 100 * time.Second
cfg := configForArgs(t, addRequiredArgs("--monitor-interval", "100s"))
require.Equal(t, expected, cfg.MonitorInterval)
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(
t,
"invalid value \"abc\" for flag -monitor-interval",
addRequiredArgs("--monitor-interval", "abc"))
})
}
func TestGameWindow(t *testing.T) {
t.Run("UsesDefault", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs())
require.Equal(t, config.DefaultGameWindow, cfg.GameWindow)
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs("--game-window=1m"))
require.Equal(t, time.Minute, cfg.GameWindow)
})
t.Run("ParsesDefault", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs("--game-window=672h"))
require.Equal(t, config.DefaultGameWindow, cfg.GameWindow)
})
}
func TestIgnoredGames(t *testing.T) {
t.Run("NotRequired", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs())
require.Empty(t, cfg.IgnoredGames)
})
t.Run("SingleValue", func(t *testing.T) {
addr := common.Address{0xbb}
cfg := configForArgs(t, addRequiredArgs("--ignored-games", addr.Hex()))
require.Len(t, cfg.IgnoredGames, 1)
require.Contains(t, cfg.IgnoredGames, addr)
})
t.Run("MultiValue", func(t *testing.T) {
addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb}
addr3 := common.Address{0xcc}
cfg := configForArgs(t, addRequiredArgs(
"--ignored-games", addr1.Hex(),
"--ignored-games", addr2.Hex(),
"--ignored-games", addr3.Hex(),
))
require.Len(t, cfg.IgnoredGames, 3)
require.Contains(t, cfg.IgnoredGames, addr1)
require.Contains(t, cfg.IgnoredGames, addr2)
require.Contains(t, cfg.IgnoredGames, addr3)
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t,
"invalid ignored game address: invalid address: 0xnope",
addRequiredArgs("-ignored-games", "0xnope"))
})
}
func TestMaxConcurrency(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
expected := uint(345)
cfg := configForArgs(t, addRequiredArgs("--max-concurrency", "345"))
require.Equal(t, expected, cfg.MaxConcurrency)
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(
t,
"invalid value \"abc\" for flag -max-concurrency",
addRequiredArgs("--max-concurrency", "abc"))
})
t.Run("Zero", func(t *testing.T) {
verifyArgsInvalid(
t,
"max-concurrency must not be 0",
addRequiredArgs("--max-concurrency", "0"))
})
}
func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) {
_, _, err := dryRunWithArgs(cliArgs)
require.ErrorContains(t, err, messageContains)
}
func configForArgs(t *testing.T, cliArgs []string) config.Config {
_, cfg, err := dryRunWithArgs(cliArgs)
require.NoError(t, err)
return cfg
}
func dryRunWithArgs(cliArgs []string) (log.Logger, config.Config, error) {
cfg := new(config.Config)
var logger log.Logger
fullArgs := append([]string{"op-dispute-mon"}, cliArgs...)
testErr := errors.New("dry-run")
err := run(context.Background(), fullArgs, func(ctx context.Context, log log.Logger, config *config.Config) (cliapp.Lifecycle, error) {
logger = log
cfg = config
return nil, testErr
})
if errors.Is(err, testErr) { // expected error
err = nil
}
return logger, *cfg, err
}
func addRequiredArgs(args ...string) []string {
req := requiredArgs()
combined := toArgList(req)
return append(combined, args...)
}
func addRequiredArgsExcept(name string, optionalArgs ...string) []string {
req := requiredArgs()
delete(req, name)
return append(toArgList(req), optionalArgs...)
}
func requiredArgs() map[string]string {
args := map[string]string{
"--l1-eth-rpc": l1EthRpc,
"--rollup-rpc": rollupRpc,
"--game-factory-address": gameFactoryAddressValue,
}
return args
}
func toArgList(req map[string]string) []string {
var combined []string
for name, value := range req {
combined = append(combined, fmt.Sprintf("%s=%s", name, value))
}
return combined
}
...@@ -49,12 +49,12 @@ type Config struct { ...@@ -49,12 +49,12 @@ type Config struct {
PprofConfig oppprof.CLIConfig PprofConfig oppprof.CLIConfig
} }
func NewConfig(gameFactoryAddress common.Address, l1EthRpc string) Config { func NewConfig(gameFactoryAddress common.Address, l1EthRpc string, rollupRpc string) Config {
return Config{ return Config{
L1EthRpc: l1EthRpc, L1EthRpc: l1EthRpc,
RollupRpc: rollupRpc,
GameFactoryAddress: gameFactoryAddress, GameFactoryAddress: gameFactoryAddress,
HonestActors: []common.Address{},
MonitorInterval: DefaultMonitorInterval, MonitorInterval: DefaultMonitorInterval,
GameWindow: DefaultGameWindow, GameWindow: DefaultGameWindow,
MaxConcurrency: DefaultMaxConcurrency, MaxConcurrency: DefaultMaxConcurrency,
......
...@@ -15,9 +15,7 @@ var ( ...@@ -15,9 +15,7 @@ var (
) )
func validConfig() Config { func validConfig() Config {
cfg := NewConfig(validGameFactoryAddress, validL1EthRpc) return NewConfig(validGameFactoryAddress, validL1EthRpc, validRollupRpc)
cfg.RollupRpc = validRollupRpc
return cfg
} }
func TestValidConfigIsValid(t *testing.T) { func TestValidConfigIsValid(t *testing.T) {
......
...@@ -3,6 +3,8 @@ package flags ...@@ -3,6 +3,8 @@ package flags
import ( import (
"fmt" "fmt"
challengerFlags "github.com/ethereum-optimism/optimism/op-challenger/flags"
"github.com/ethereum-optimism/optimism/op-service/flags"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-dispute-mon/config" "github.com/ethereum-optimism/optimism/op-dispute-mon/config"
...@@ -28,17 +30,18 @@ var ( ...@@ -28,17 +30,18 @@ var (
Usage: "HTTP provider URL for L1.", Usage: "HTTP provider URL for L1.",
EnvVars: prefixEnvVars("L1_ETH_RPC"), EnvVars: prefixEnvVars("L1_ETH_RPC"),
} }
GameFactoryAddressFlag = &cli.StringFlag{
Name: "game-factory-address",
Usage: "Address of the fault game factory contract.",
EnvVars: prefixEnvVars("GAME_FACTORY_ADDRESS"),
}
RollupRpcFlag = &cli.StringFlag{ RollupRpcFlag = &cli.StringFlag{
Name: "rollup-rpc", Name: "rollup-rpc",
Usage: "HTTP provider URL for the rollup node", Usage: "HTTP provider URL for the rollup node",
EnvVars: prefixEnvVars("ROLLUP_RPC"), EnvVars: prefixEnvVars("ROLLUP_RPC"),
} }
// Optional Flags // Optional Flags
GameFactoryAddressFlag = &cli.StringFlag{
Name: "game-factory-address",
Usage: "Address of the fault game factory contract.",
EnvVars: prefixEnvVars("GAME_FACTORY_ADDRESS"),
}
NetworkFlag = flags.CLINetworkFlag(envVarPrefix, "")
HonestActorsFlag = &cli.StringSliceFlag{ HonestActorsFlag = &cli.StringSliceFlag{
Name: "honest-actors", Name: "honest-actors",
Usage: "List of honest actors that are monitored for any claims that are resolved against them.", Usage: "List of honest actors that are monitored for any claims that are resolved against them.",
...@@ -73,12 +76,13 @@ var ( ...@@ -73,12 +76,13 @@ var (
// requiredFlags are checked by [CheckRequired] // requiredFlags are checked by [CheckRequired]
var requiredFlags = []cli.Flag{ var requiredFlags = []cli.Flag{
L1EthRpcFlag, L1EthRpcFlag,
GameFactoryAddressFlag,
RollupRpcFlag, RollupRpcFlag,
} }
// optionalFlags is a list of unchecked cli flags // optionalFlags is a list of unchecked cli flags
var optionalFlags = []cli.Flag{ var optionalFlags = []cli.Flag{
GameFactoryAddressFlag,
NetworkFlag,
HonestActorsFlag, HonestActorsFlag,
MonitorIntervalFlag, MonitorIntervalFlag,
GameWindowFlag, GameWindowFlag,
...@@ -111,7 +115,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -111,7 +115,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
if err := CheckRequired(ctx); err != nil { if err := CheckRequired(ctx); err != nil {
return nil, err return nil, err
} }
gameFactoryAddress, err := opservice.ParseAddress(ctx.String(GameFactoryAddressFlag.Name)) gameFactoryAddress, err := challengerFlags.FactoryAddress(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -138,6 +142,11 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -138,6 +142,11 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
} }
} }
maxConcurrency := ctx.Uint(MaxConcurrencyFlag.Name)
if maxConcurrency == 0 {
return nil, fmt.Errorf("%v must not be 0", MaxConcurrencyFlag.Name)
}
metricsConfig := opmetrics.ReadCLIConfig(ctx) metricsConfig := opmetrics.ReadCLIConfig(ctx)
pprofConfig := oppprof.ReadCLIConfig(ctx) pprofConfig := oppprof.ReadCLIConfig(ctx)
...@@ -150,7 +159,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -150,7 +159,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
MonitorInterval: ctx.Duration(MonitorIntervalFlag.Name), MonitorInterval: ctx.Duration(MonitorIntervalFlag.Name),
GameWindow: ctx.Duration(GameWindowFlag.Name), GameWindow: ctx.Duration(GameWindowFlag.Name),
IgnoredGames: ignoredGames, IgnoredGames: ignoredGames,
MaxConcurrency: ctx.Uint(MaxConcurrencyFlag.Name), MaxConcurrency: maxConcurrency,
MetricsConfig: metricsConfig, MetricsConfig: metricsConfig,
PprofConfig: pprofConfig, PprofConfig: pprofConfig,
......
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