Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions op-dispute-mon/cmd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,13 @@ func TestL1EthRpc(t *testing.T) {
})
}

func TestMustSpecifyEitherRollupRpcOrSupervisorRpc(t *testing.T) {
verifyArgsInvalid(t, "flag rollup-rpc or supervisor-rpc is required", addRequiredArgsExcept("--rollup-rpc"))
}

func TestRollupRpc(t *testing.T) {
t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag rollup-rpc is required", addRequiredArgsExcept("--rollup-rpc"))
t.Run("NotRequiredIfSupervisorRpcSupplied", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept("--rollup-rpc", "--supervisor-rpc", "http://localhost/supervisor"))
})

t.Run("Valid", func(t *testing.T) {
Expand All @@ -71,6 +75,19 @@ func TestRollupRpc(t *testing.T) {
})
}

func TestSupervisorRpc(t *testing.T) {
t.Run("NotRequiredIfRollupRpcSupplied", func(t *testing.T) {
// rollup-rpc is in the default args.
configForArgs(t, addRequiredArgsExcept("--supervisor-rpc"))
})

t.Run("Valid", func(t *testing.T) {
url := "http://example.com:9999"
cfg := configForArgs(t, addRequiredArgsExcept("--rollup-rpc", "--supervisor-rpc", url))
require.Equal(t, url, cfg.SupervisorRpc)
})
}

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"))
Expand Down
22 changes: 16 additions & 6 deletions op-dispute-mon/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import (
)

var (
ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url")
ErrMissingGameFactoryAddress = errors.New("missing game factory address")
ErrMissingRollupRpc = errors.New("missing rollup rpc url")
ErrMissingMaxConcurrency = errors.New("missing max concurrency")
ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url")
ErrMissingGameFactoryAddress = errors.New("missing game factory address")
ErrMissingRollupAndSupervisorRpc = errors.New("must specify rollup rpc or supervisor rpc")
ErrMissingMaxConcurrency = errors.New("missing max concurrency")
)

const (
Expand All @@ -40,6 +40,7 @@ type Config struct {

HonestActors []common.Address // List of honest actors to monitor claims for.
RollupRpc string // The rollup node RPC URL.
SupervisorRpc string // The supervisor RPC URL.
MonitorInterval time.Duration // Frequency to check for new games to monitor.
GameWindow time.Duration // Maximum window to look for games to monitor.
IgnoredGames []common.Address // Games to exclude from monitoring
Expand All @@ -49,10 +50,19 @@ type Config struct {
PprofConfig oppprof.CLIConfig
}

func NewInteropConfig(gameFactoryAddress common.Address, l1EthRpc string, supervisorRpc string) Config {
return NewCombinedConfig(gameFactoryAddress, l1EthRpc, "", supervisorRpc)
}

func NewConfig(gameFactoryAddress common.Address, l1EthRpc string, rollupRpc string) Config {
return NewCombinedConfig(gameFactoryAddress, l1EthRpc, rollupRpc, "")
}

func NewCombinedConfig(gameFactoryAddress common.Address, l1EthRpc string, rollupRpc string, supervisorRpc string) Config {
return Config{
L1EthRpc: l1EthRpc,
RollupRpc: rollupRpc,
SupervisorRpc: supervisorRpc,
GameFactoryAddress: gameFactoryAddress,

MonitorInterval: DefaultMonitorInterval,
Expand All @@ -68,8 +78,8 @@ func (c Config) Check() error {
if c.L1EthRpc == "" {
return ErrMissingL1EthRPC
}
if c.RollupRpc == "" {
return ErrMissingRollupRpc
if c.RollupRpc == "" && c.SupervisorRpc == "" {
return ErrMissingRollupAndSupervisorRpc
}
if c.GameFactoryAddress == (common.Address{}) {
return ErrMissingGameFactoryAddress
Expand Down
20 changes: 18 additions & 2 deletions op-dispute-mon/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var (
validL1EthRpc = "http://localhost:8545"
validGameFactoryAddress = common.Address{0x23}
validRollupRpc = "http://localhost:8555"
validSupervisorRpc = "http://localhost:8999"
)

func validConfig() Config {
Expand All @@ -34,10 +35,25 @@ func TestGameFactoryAddressRequired(t *testing.T) {
require.ErrorIs(t, config.Check(), ErrMissingGameFactoryAddress)
}

func TestRollupRpcRequired(t *testing.T) {
func TestRollupRpcOrSupervisorRpcRequired(t *testing.T) {
config := validConfig()
config.RollupRpc = ""
require.ErrorIs(t, config.Check(), ErrMissingRollupRpc)
config.SupervisorRpc = ""
require.ErrorIs(t, config.Check(), ErrMissingRollupAndSupervisorRpc)
}

func TestRollupRpcNotRequiredWhenSupervisorRpcSet(t *testing.T) {
config := validConfig()
config.RollupRpc = ""
config.SupervisorRpc = validSupervisorRpc
require.NoError(t, config.Check())
}

func TestSupervisorRpcNotRequiredWhenRollupRpcSet(t *testing.T) {
config := validConfig()
config.RollupRpc = validRollupRpc
config.SupervisorRpc = ""
require.NoError(t, config.Check())
}

func TestMaxConcurrencyRequired(t *testing.T) {
Expand Down
14 changes: 12 additions & 2 deletions op-dispute-mon/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,17 @@ var (
Usage: "HTTP provider URL for L1.",
EnvVars: prefixEnvVars("L1_ETH_RPC"),
}
// Optional Flags
RollupRpcFlag = &cli.StringFlag{
Name: "rollup-rpc",
Usage: "HTTP provider URL for the rollup node",
EnvVars: prefixEnvVars("ROLLUP_RPC"),
}
// Optional Flags
SupervisorRpcFlag = &cli.StringFlag{
Name: "supervisor-rpc",
Usage: "HTTP provider URL for the supervisor node",
EnvVars: prefixEnvVars("SUPERVISOR_RPC"),
}
GameFactoryAddressFlag = &cli.StringFlag{
Name: "game-factory-address",
Usage: "Address of the fault game factory contract.",
Expand Down Expand Up @@ -78,11 +83,12 @@ var (
// requiredFlags are checked by [CheckRequired]
var requiredFlags = []cli.Flag{
L1EthRpcFlag,
RollupRpcFlag,
}

// optionalFlags is a list of unchecked cli flags
var optionalFlags = []cli.Flag{
RollupRpcFlag,
SupervisorRpcFlag,
GameFactoryAddressFlag,
NetworkFlag,
HonestActorsFlag,
Expand All @@ -109,6 +115,9 @@ func CheckRequired(ctx *cli.Context) error {
return fmt.Errorf("flag %s is required", f.Names()[0])
}
}
if !ctx.IsSet(RollupRpcFlag.Name) && !ctx.IsSet(SupervisorRpcFlag.Name) {
return fmt.Errorf("flag %s or %s is required", RollupRpcFlag.Name, SupervisorRpcFlag.Name)
}
return nil
}

Expand Down Expand Up @@ -156,6 +165,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
L1EthRpc: ctx.String(L1EthRpcFlag.Name),
GameFactoryAddress: gameFactoryAddress,
RollupRpc: ctx.String(RollupRpcFlag.Name),
SupervisorRpc: ctx.String(SupervisorRpcFlag.Name),

HonestActors: actors,
MonitorInterval: ctx.Duration(MonitorIntervalFlag.Name),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ package extract

import (
"context"
"errors"
"fmt"
"strings"
"time"

monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)

"github.com/ethereum-optimism/optimism/op-service/eth"
var (
ErrRollupRpcRequired = errors.New("rollup rpc required")
)

type OutputRollupClient interface {
Expand All @@ -23,22 +27,30 @@ type OutputMetrics interface {
RecordOutputFetchTime(float64)
}

type AgreementEnricher struct {
type OutputAgreementEnricher struct {
log log.Logger
metrics OutputMetrics
client OutputRollupClient
clock clock.Clock
}

func NewAgreementEnricher(logger log.Logger, metrics OutputMetrics, client OutputRollupClient) *AgreementEnricher {
return &AgreementEnricher{
func NewOutputAgreementEnricher(logger log.Logger, metrics OutputMetrics, client OutputRollupClient, cl clock.Clock) *OutputAgreementEnricher {
return &OutputAgreementEnricher{
log: logger,
metrics: metrics,
client: client,
clock: cl,
}
}

// Enrich validates the specified root claim against the output at the given block number.
func (o *AgreementEnricher) Enrich(ctx context.Context, block rpcblock.Block, caller GameCaller, game *monTypes.EnrichedGameData) error {
func (o *OutputAgreementEnricher) Enrich(ctx context.Context, block rpcblock.Block, caller GameCaller, game *monTypes.EnrichedGameData) error {
if !game.UsesOutputRoots() {
return nil
}
if o.client == nil {
return fmt.Errorf("%w but required for game type %v", ErrRollupRpcRequired, game.GameType)
}
output, err := o.client.OutputAtBlock(ctx, game.L2BlockNumber)
if err != nil {
// string match as the error comes from the remote server so we can't use Errors.Is sadly.
Expand All @@ -49,7 +61,7 @@ func (o *AgreementEnricher) Enrich(ctx context.Context, block rpcblock.Block, ca
}
return fmt.Errorf("failed to get output at block: %w", err)
}
o.metrics.RecordOutputFetchTime(float64(time.Now().Unix()))
o.metrics.RecordOutputFetchTime(float64(o.clock.Now().Unix()))
game.ExpectedRootClaim = common.Hash(output.OutputRoot)
rootMatches := game.RootClaim == game.ExpectedRootClaim
if !rootMatches {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package extract
import (
"context"
"errors"
"fmt"
"testing"
"time"

challengerTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum-optimism/optimism/op-service/testlog"
Expand All @@ -14,9 +18,67 @@ import (
"github.com/stretchr/testify/require"
)

func TestDetector_CheckRootAgreement(t *testing.T) {
func TestDetector_CheckOutputRootAgreement(t *testing.T) {
t.Parallel()

t.Run("ErrorWhenNoRollupClient", func(t *testing.T) {
validator, _, _ := setupOutputValidatorTest(t)
validator.client = nil
game := &types.EnrichedGameData{
GameMetadata: challengerTypes.GameMetadata{
GameType: 0,
},
L1HeadNum: 200,
L2BlockNumber: 0,
RootClaim: mockRootClaim,
}
err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game)
require.ErrorIs(t, err, ErrRollupRpcRequired)
})

t.Run("SkipNonOutputRootGameTypes", func(t *testing.T) {
gameTypes := []uint32{4, 5, 7, 8, 10, 49812}
for _, gameType := range gameTypes {
gameType := gameType
t.Run(fmt.Sprintf("GameType_%d", gameType), func(t *testing.T) {
validator, _, metrics := setupOutputValidatorTest(t)
validator.client = nil // Should not error even though there's no rollup client
game := &types.EnrichedGameData{
GameMetadata: challengerTypes.GameMetadata{
GameType: gameType,
},
L1HeadNum: 200,
L2BlockNumber: 0,
RootClaim: mockRootClaim,
}
err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game)
require.NoError(t, err)
require.Zero(t, metrics.fetchTime)
})
}
})

t.Run("FetchAllOutputRootGameTypes", func(t *testing.T) {
gameTypes := []uint32{0, 1, 2, 3, 6, 254, 255, 1337}
for _, gameType := range gameTypes {
gameType := gameType
t.Run(fmt.Sprintf("GameType_%d", gameType), func(t *testing.T) {
validator, _, metrics := setupOutputValidatorTest(t)
game := &types.EnrichedGameData{
GameMetadata: challengerTypes.GameMetadata{
GameType: gameType,
},
L1HeadNum: 200,
L2BlockNumber: 0,
RootClaim: mockRootClaim,
}
err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game)
require.NoError(t, err)
require.NotZero(t, metrics.fetchTime, "should have fetched output root")
})
}
})

t.Run("OutputFetchFails", func(t *testing.T) {
validator, rollup, metrics := setupOutputValidatorTest(t)
rollup.outputErr = errors.New("boom")
Expand Down Expand Up @@ -136,11 +198,11 @@ func TestDetector_CheckRootAgreement(t *testing.T) {
})
}

func setupOutputValidatorTest(t *testing.T) (*AgreementEnricher, *stubRollupClient, *stubOutputMetrics) {
func setupOutputValidatorTest(t *testing.T) (*OutputAgreementEnricher, *stubRollupClient, *stubOutputMetrics) {
logger := testlog.Logger(t, log.LvlInfo)
client := &stubRollupClient{safeHeadNum: 99999999999}
metrics := &stubOutputMetrics{}
validator := NewAgreementEnricher(logger, metrics, client)
validator := NewOutputAgreementEnricher(logger, metrics, client, clock.NewDeterministicClock(time.Unix(9824924, 499)))
return validator, client, metrics
}

Expand Down
Loading