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

op-dispute-mon: Add option to ignore games (#10334)

When a game is ignored because of this option it logs a warning and reports a count of ignored games via a new metric.
parent 5a829ce7
...@@ -37,6 +37,7 @@ type Config struct { ...@@ -37,6 +37,7 @@ type Config struct {
RollupRpc string // The rollup node RPC URL. RollupRpc string // The rollup node RPC URL.
MonitorInterval time.Duration // Frequency to check for new games to monitor. MonitorInterval time.Duration // Frequency to check for new games to monitor.
GameWindow time.Duration // Maximum window to look for games to monitor. GameWindow time.Duration // Maximum window to look for games to monitor.
IgnoredGames []common.Address // Games to exclude from monitoring
MetricsConfig opmetrics.CLIConfig MetricsConfig opmetrics.CLIConfig
PprofConfig oppprof.CLIConfig PprofConfig oppprof.CLIConfig
......
...@@ -57,6 +57,11 @@ var ( ...@@ -57,6 +57,11 @@ var (
EnvVars: prefixEnvVars("GAME_WINDOW"), EnvVars: prefixEnvVars("GAME_WINDOW"),
Value: config.DefaultGameWindow, Value: config.DefaultGameWindow,
} }
IgnoredGamesFlag = &cli.StringSliceFlag{
Name: "ignored-games",
Usage: "List of game addresses to exclude from monitoring.",
EnvVars: prefixEnvVars("IGNORED_GAMES"),
}
) )
// requiredFlags are checked by [CheckRequired] // requiredFlags are checked by [CheckRequired]
...@@ -71,6 +76,7 @@ var optionalFlags = []cli.Flag{ ...@@ -71,6 +76,7 @@ var optionalFlags = []cli.Flag{
HonestActorsFlag, HonestActorsFlag,
MonitorIntervalFlag, MonitorIntervalFlag,
GameWindowFlag, GameWindowFlag,
IgnoredGamesFlag,
} }
func init() { func init() {
...@@ -114,6 +120,17 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -114,6 +120,17 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
} }
} }
var ignoredGames []common.Address
if ctx.IsSet(IgnoredGamesFlag.Name) {
for _, addrStr := range ctx.StringSlice(IgnoredGamesFlag.Name) {
game, err := opservice.ParseAddress(addrStr)
if err != nil {
return nil, fmt.Errorf("invalid ignored game address: %w", err)
}
ignoredGames = append(ignoredGames, game)
}
}
metricsConfig := opmetrics.ReadCLIConfig(ctx) metricsConfig := opmetrics.ReadCLIConfig(ctx)
pprofConfig := oppprof.ReadCLIConfig(ctx) pprofConfig := oppprof.ReadCLIConfig(ctx)
...@@ -125,6 +142,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -125,6 +142,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
RollupRpc: ctx.String(RollupRpcFlag.Name), RollupRpc: ctx.String(RollupRpcFlag.Name),
MonitorInterval: ctx.Duration(MonitorIntervalFlag.Name), MonitorInterval: ctx.Duration(MonitorIntervalFlag.Name),
GameWindow: ctx.Duration(GameWindowFlag.Name), GameWindow: ctx.Duration(GameWindowFlag.Name),
IgnoredGames: ignoredGames,
MetricsConfig: metricsConfig, MetricsConfig: metricsConfig,
PprofConfig: pprofConfig, PprofConfig: pprofConfig,
......
...@@ -85,6 +85,8 @@ type Metricer interface { ...@@ -85,6 +85,8 @@ type Metricer interface {
RecordGameAgreement(status GameAgreementStatus, count int) RecordGameAgreement(status GameAgreementStatus, count int)
RecordIgnoredGames(count int)
RecordBondCollateral(addr common.Address, required *big.Int, available *big.Int) RecordBondCollateral(addr common.Address, required *big.Int, available *big.Int)
caching.Metrics caching.Metrics
...@@ -120,6 +122,7 @@ type Metrics struct { ...@@ -120,6 +122,7 @@ type Metrics struct {
claimResolutionDelayMax prometheus.Gauge claimResolutionDelayMax prometheus.Gauge
gamesAgreement prometheus.GaugeVec gamesAgreement prometheus.GaugeVec
ignoredGames prometheus.Gauge
requiredCollateral prometheus.GaugeVec requiredCollateral prometheus.GaugeVec
availableCollateral prometheus.GaugeVec availableCollateral prometheus.GaugeVec
...@@ -215,6 +218,11 @@ func NewMetrics() *Metrics { ...@@ -215,6 +218,11 @@ func NewMetrics() *Metrics {
"result_correctness", "result_correctness",
"root_agreement", "root_agreement",
}), }),
ignoredGames: factory.NewGauge(prometheus.GaugeOpts{
Namespace: Namespace,
Name: "ignored_games",
Help: "Number of games present in the game window but ignored via config",
}),
requiredCollateral: *factory.NewGaugeVec(prometheus.GaugeOpts{ requiredCollateral: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: Namespace, Namespace: Namespace,
Name: "bond_collateral_required", Name: "bond_collateral_required",
...@@ -350,6 +358,10 @@ func (m *Metrics) RecordGameAgreement(status GameAgreementStatus, count int) { ...@@ -350,6 +358,10 @@ func (m *Metrics) RecordGameAgreement(status GameAgreementStatus, count int) {
m.gamesAgreement.WithLabelValues(labelValuesFor(status)...).Set(float64(count)) m.gamesAgreement.WithLabelValues(labelValuesFor(status)...).Set(float64(count))
} }
func (m *Metrics) RecordIgnoredGames(count int) {
m.ignoredGames.Set(float64(count))
}
func (m *Metrics) RecordBondCollateral(addr common.Address, required *big.Int, available *big.Int) { func (m *Metrics) RecordBondCollateral(addr common.Address, required *big.Int, available *big.Int) {
balance := "sufficient" balance := "sufficient"
if required.Cmp(available) > 0 { if required.Cmp(available) > 0 {
...@@ -400,6 +412,7 @@ func labelValuesFor(status GameAgreementStatus) []string { ...@@ -400,6 +412,7 @@ func labelValuesFor(status GameAgreementStatus) []string {
return asStrings("agree_challenger_wins", !inProgress, !correct, agree) return asStrings("agree_challenger_wins", !inProgress, !correct, agree)
case DisagreeChallengerWins: case DisagreeChallengerWins:
return asStrings("disagree_challenger_wins", !inProgress, correct, !agree) return asStrings("disagree_challenger_wins", !inProgress, correct, !agree)
default: default:
panic(fmt.Errorf("unknown game agreement status: %v", status)) panic(fmt.Errorf("unknown game agreement status: %v", status))
} }
......
...@@ -35,4 +35,6 @@ func (*NoopMetricsImpl) RecordOutputFetchTime(_ float64) {} ...@@ -35,4 +35,6 @@ func (*NoopMetricsImpl) RecordOutputFetchTime(_ float64) {}
func (*NoopMetricsImpl) RecordGameAgreement(_ GameAgreementStatus, _ int) {} func (*NoopMetricsImpl) RecordGameAgreement(_ GameAgreementStatus, _ int) {}
func (*NoopMetricsImpl) RecordIgnoredGames(_ int) {}
func (i *NoopMetricsImpl) RecordBondCollateral(_ common.Address, _ *big.Int, _ *big.Int) {} func (i *NoopMetricsImpl) RecordBondCollateral(_ common.Address, _ *big.Int, _ *big.Int) {}
...@@ -26,28 +26,41 @@ type Extractor struct { ...@@ -26,28 +26,41 @@ type Extractor struct {
createContract CreateGameCaller createContract CreateGameCaller
fetchGames FactoryGameFetcher fetchGames FactoryGameFetcher
enrichers []Enricher enrichers []Enricher
ignoredGames map[common.Address]bool
} }
func NewExtractor(logger log.Logger, creator CreateGameCaller, fetchGames FactoryGameFetcher, enrichers ...Enricher) *Extractor { func NewExtractor(logger log.Logger, creator CreateGameCaller, fetchGames FactoryGameFetcher, ignoredGames []common.Address, enrichers ...Enricher) *Extractor {
ignored := make(map[common.Address]bool)
for _, game := range ignoredGames {
ignored[game] = true
}
return &Extractor{ return &Extractor{
logger: logger, logger: logger,
createContract: creator, createContract: creator,
fetchGames: fetchGames, fetchGames: fetchGames,
enrichers: enrichers, enrichers: enrichers,
ignoredGames: ignored,
} }
} }
func (e *Extractor) Extract(ctx context.Context, blockHash common.Hash, minTimestamp uint64) ([]*monTypes.EnrichedGameData, error) { func (e *Extractor) Extract(ctx context.Context, blockHash common.Hash, minTimestamp uint64) ([]*monTypes.EnrichedGameData, int, error) {
games, err := e.fetchGames(ctx, blockHash, minTimestamp) games, err := e.fetchGames(ctx, blockHash, minTimestamp)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load games: %w", err) return nil, 0, fmt.Errorf("failed to load games: %w", err)
} }
return e.enrichGames(ctx, blockHash, games), nil enriched, ignored := e.enrichGames(ctx, blockHash, games)
return enriched, ignored, nil
} }
func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, games []gameTypes.GameMetadata) []*monTypes.EnrichedGameData { func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, games []gameTypes.GameMetadata) ([]*monTypes.EnrichedGameData, int) {
var enrichedGames []*monTypes.EnrichedGameData var enrichedGames []*monTypes.EnrichedGameData
ignored := 0
for _, game := range games { for _, game := range games {
if e.ignoredGames[game.Proxy] {
ignored++
e.logger.Warn("Ignoring game", "game", game.Proxy)
continue
}
caller, err := e.createContract(ctx, game) caller, err := e.createContract(ctx, game)
if err != nil { if err != nil {
e.logger.Error("Failed to create game caller", "err", err) e.logger.Error("Failed to create game caller", "err", err)
...@@ -82,7 +95,7 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game ...@@ -82,7 +95,7 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game
} }
enrichedGames = append(enrichedGames, enrichedGame) enrichedGames = append(enrichedGames, enrichedGame)
} }
return enrichedGames return enrichedGames, ignored
} }
func (e *Extractor) applyEnrichers(ctx context.Context, blockHash common.Hash, caller GameCaller, game *monTypes.EnrichedGameData) error { func (e *Extractor) applyEnrichers(ctx context.Context, blockHash common.Hash, caller GameCaller, game *monTypes.EnrichedGameData) error {
......
...@@ -18,13 +18,16 @@ import ( ...@@ -18,13 +18,16 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
var mockRootClaim = common.HexToHash("0x1234") var (
mockRootClaim = common.HexToHash("0x1234")
ignoredGames = []common.Address{common.HexToAddress("0xdeadbeef")}
)
func TestExtractor_Extract(t *testing.T) { func TestExtractor_Extract(t *testing.T) {
t.Run("FetchGamesError", func(t *testing.T) { t.Run("FetchGamesError", func(t *testing.T) {
extractor, _, games, _ := setupExtractorTest(t) extractor, _, games, _ := setupExtractorTest(t)
games.err = errors.New("boom") games.err = errors.New("boom")
_, err := extractor.Extract(context.Background(), common.Hash{}, 0) _, _, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.ErrorIs(t, err, games.err) require.ErrorIs(t, err, games.err)
require.Equal(t, 1, games.calls) require.Equal(t, 1, games.calls)
}) })
...@@ -33,8 +36,9 @@ func TestExtractor_Extract(t *testing.T) { ...@@ -33,8 +36,9 @@ func TestExtractor_Extract(t *testing.T) {
extractor, creator, games, logs := setupExtractorTest(t) extractor, creator, games, logs := setupExtractorTest(t)
games.games = []gameTypes.GameMetadata{{}} games.games = []gameTypes.GameMetadata{{}}
creator.err = errors.New("boom") creator.err = errors.New("boom")
enriched, err := extractor.Extract(context.Background(), common.Hash{}, 0) enriched, ignored, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, ignored)
require.Len(t, enriched, 0) require.Len(t, enriched, 0)
require.Equal(t, 1, games.calls) require.Equal(t, 1, games.calls)
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
...@@ -47,8 +51,9 @@ func TestExtractor_Extract(t *testing.T) { ...@@ -47,8 +51,9 @@ func TestExtractor_Extract(t *testing.T) {
extractor, creator, games, logs := setupExtractorTest(t) extractor, creator, games, logs := setupExtractorTest(t)
games.games = []gameTypes.GameMetadata{{}} games.games = []gameTypes.GameMetadata{{}}
creator.caller.metadataErr = errors.New("boom") creator.caller.metadataErr = errors.New("boom")
enriched, err := extractor.Extract(context.Background(), common.Hash{}, 0) enriched, ignored, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, ignored)
require.Len(t, enriched, 0) require.Len(t, enriched, 0)
require.Equal(t, 1, games.calls) require.Equal(t, 1, games.calls)
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
...@@ -61,8 +66,9 @@ func TestExtractor_Extract(t *testing.T) { ...@@ -61,8 +66,9 @@ func TestExtractor_Extract(t *testing.T) {
extractor, creator, games, logs := setupExtractorTest(t) extractor, creator, games, logs := setupExtractorTest(t)
games.games = []gameTypes.GameMetadata{{}} games.games = []gameTypes.GameMetadata{{}}
creator.caller.claimsErr = errors.New("boom") creator.caller.claimsErr = errors.New("boom")
enriched, err := extractor.Extract(context.Background(), common.Hash{}, 0) enriched, ignored, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, ignored)
require.Len(t, enriched, 0) require.Len(t, enriched, 0)
require.Equal(t, 1, games.calls) require.Equal(t, 1, games.calls)
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
...@@ -74,8 +80,9 @@ func TestExtractor_Extract(t *testing.T) { ...@@ -74,8 +80,9 @@ func TestExtractor_Extract(t *testing.T) {
t.Run("Success", func(t *testing.T) { t.Run("Success", func(t *testing.T) {
extractor, creator, games, _ := setupExtractorTest(t) extractor, creator, games, _ := setupExtractorTest(t)
games.games = []gameTypes.GameMetadata{{}} games.games = []gameTypes.GameMetadata{{}}
enriched, err := extractor.Extract(context.Background(), common.Hash{}, 0) enriched, ignored, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, ignored)
require.Len(t, enriched, 1) require.Len(t, enriched, 1)
require.Equal(t, 1, games.calls) require.Equal(t, 1, games.calls)
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
...@@ -87,8 +94,9 @@ func TestExtractor_Extract(t *testing.T) { ...@@ -87,8 +94,9 @@ func TestExtractor_Extract(t *testing.T) {
enricher := &mockEnricher{err: errors.New("whoops")} enricher := &mockEnricher{err: errors.New("whoops")}
extractor, _, games, logs := setupExtractorTest(t, enricher) extractor, _, games, logs := setupExtractorTest(t, enricher)
games.games = []gameTypes.GameMetadata{{}} games.games = []gameTypes.GameMetadata{{}}
enriched, err := extractor.Extract(context.Background(), common.Hash{}, 0) enriched, ignored, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, ignored)
l := logs.FindLogs(testlog.NewMessageFilter("Failed to enrich game")) l := logs.FindLogs(testlog.NewMessageFilter("Failed to enrich game"))
require.Len(t, l, 1, "Should have logged error") require.Len(t, l, 1, "Should have logged error")
require.Len(t, enriched, 0, "Should not return games that failed to enrich") require.Len(t, enriched, 0, "Should not return games that failed to enrich")
...@@ -98,8 +106,9 @@ func TestExtractor_Extract(t *testing.T) { ...@@ -98,8 +106,9 @@ func TestExtractor_Extract(t *testing.T) {
enricher := &mockEnricher{} enricher := &mockEnricher{}
extractor, _, games, _ := setupExtractorTest(t, enricher) extractor, _, games, _ := setupExtractorTest(t, enricher)
games.games = []gameTypes.GameMetadata{{}} games.games = []gameTypes.GameMetadata{{}}
enriched, err := extractor.Extract(context.Background(), common.Hash{}, 0) enriched, ignored, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, ignored)
require.Len(t, enriched, 1) require.Len(t, enriched, 1)
require.Equal(t, 1, enricher.calls) require.Equal(t, 1, enricher.calls)
}) })
...@@ -109,12 +118,31 @@ func TestExtractor_Extract(t *testing.T) { ...@@ -109,12 +118,31 @@ func TestExtractor_Extract(t *testing.T) {
enricher2 := &mockEnricher{} enricher2 := &mockEnricher{}
extractor, _, games, _ := setupExtractorTest(t, enricher1, enricher2) extractor, _, games, _ := setupExtractorTest(t, enricher1, enricher2)
games.games = []gameTypes.GameMetadata{{}, {}} games.games = []gameTypes.GameMetadata{{}, {}}
enriched, err := extractor.Extract(context.Background(), common.Hash{}, 0) enriched, ignored, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, ignored)
require.Len(t, enriched, 2) require.Len(t, enriched, 2)
require.Equal(t, 2, enricher1.calls) require.Equal(t, 2, enricher1.calls)
require.Equal(t, 2, enricher2.calls) require.Equal(t, 2, enricher2.calls)
}) })
t.Run("IgnoreGames", func(t *testing.T) {
enricher1 := &mockEnricher{}
extractor, _, games, logs := setupExtractorTest(t, enricher1)
// Two games, one of which is ignored
games.games = []gameTypes.GameMetadata{{Proxy: ignoredGames[0]}, {Proxy: common.Address{0xaa}}}
enriched, ignored, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.NoError(t, err)
// Should ignore one and enrich the other
require.Equal(t, 1, ignored)
require.Len(t, enriched, 1)
require.Equal(t, 1, enricher1.calls)
require.Equal(t, enriched[0].Proxy, common.Address{0xaa})
require.NotNil(t, logs.FindLog(
testlog.NewLevelFilter(log.LevelWarn),
testlog.NewMessageFilter("Ignoring game"),
testlog.NewAttributesFilter("game", ignoredGames[0].Hex())))
})
} }
func verifyLogs(t *testing.T, logs *testlog.CapturingHandler, createErr int, metadataErr int, claimsErr int, durationErr int) { func verifyLogs(t *testing.T, logs *testlog.CapturingHandler, createErr int, metadataErr int, claimsErr int, durationErr int) {
...@@ -142,6 +170,7 @@ func setupExtractorTest(t *testing.T, enrichers ...Enricher) (*Extractor, *mockG ...@@ -142,6 +170,7 @@ func setupExtractorTest(t *testing.T, enrichers ...Enricher) (*Extractor, *mockG
logger, logger,
creator.CreateGameCaller, creator.CreateGameCaller,
games.FetchGames, games.FetchGames,
ignoredGames,
enrichers..., enrichers...,
) )
return extractor, creator, games, capturedLogs return extractor, creator, games, capturedLogs
......
...@@ -26,6 +26,7 @@ type OutputValidator interface { ...@@ -26,6 +26,7 @@ type OutputValidator interface {
type ForecastMetrics interface { type ForecastMetrics interface {
RecordClaimResolutionDelayMax(delay float64) RecordClaimResolutionDelayMax(delay float64)
RecordGameAgreement(status metrics.GameAgreementStatus, count int) RecordGameAgreement(status metrics.GameAgreementStatus, count int)
RecordIgnoredGames(count int)
} }
type forecast struct { type forecast struct {
...@@ -42,17 +43,17 @@ func newForecast(logger log.Logger, metrics ForecastMetrics, validator OutputVal ...@@ -42,17 +43,17 @@ func newForecast(logger log.Logger, metrics ForecastMetrics, validator OutputVal
} }
} }
func (f *forecast) Forecast(ctx context.Context, games []*monTypes.EnrichedGameData) { func (f *forecast) Forecast(ctx context.Context, games []*monTypes.EnrichedGameData, ignoredCount int) {
batch := monTypes.ForecastBatch{} batch := monTypes.ForecastBatch{}
for _, game := range games { for _, game := range games {
if err := f.forecastGame(ctx, game, &batch); err != nil { if err := f.forecastGame(ctx, game, &batch); err != nil {
f.logger.Error("Failed to forecast game", "err", err) f.logger.Error("Failed to forecast game", "err", err)
} }
} }
f.recordBatch(batch) f.recordBatch(batch, ignoredCount)
} }
func (f *forecast) recordBatch(batch monTypes.ForecastBatch) { func (f *forecast) recordBatch(batch monTypes.ForecastBatch, ignoredCount int) {
f.metrics.RecordGameAgreement(metrics.AgreeDefenderWins, batch.AgreeDefenderWins) f.metrics.RecordGameAgreement(metrics.AgreeDefenderWins, batch.AgreeDefenderWins)
f.metrics.RecordGameAgreement(metrics.DisagreeDefenderWins, batch.DisagreeDefenderWins) f.metrics.RecordGameAgreement(metrics.DisagreeDefenderWins, batch.DisagreeDefenderWins)
f.metrics.RecordGameAgreement(metrics.AgreeChallengerWins, batch.AgreeChallengerWins) f.metrics.RecordGameAgreement(metrics.AgreeChallengerWins, batch.AgreeChallengerWins)
...@@ -62,6 +63,8 @@ func (f *forecast) recordBatch(batch monTypes.ForecastBatch) { ...@@ -62,6 +63,8 @@ func (f *forecast) recordBatch(batch monTypes.ForecastBatch) {
f.metrics.RecordGameAgreement(metrics.DisagreeChallengerAhead, batch.DisagreeChallengerAhead) f.metrics.RecordGameAgreement(metrics.DisagreeChallengerAhead, batch.DisagreeChallengerAhead)
f.metrics.RecordGameAgreement(metrics.AgreeDefenderAhead, batch.AgreeDefenderAhead) f.metrics.RecordGameAgreement(metrics.AgreeDefenderAhead, batch.AgreeDefenderAhead)
f.metrics.RecordGameAgreement(metrics.DisagreeDefenderAhead, batch.DisagreeDefenderAhead) f.metrics.RecordGameAgreement(metrics.DisagreeDefenderAhead, batch.DisagreeDefenderAhead)
f.metrics.RecordIgnoredGames(ignoredCount)
} }
func (f *forecast) forecastGame(ctx context.Context, game *monTypes.EnrichedGameData, metrics *monTypes.ForecastBatch) error { func (f *forecast) forecastGame(ctx context.Context, game *monTypes.EnrichedGameData, metrics *monTypes.ForecastBatch) error {
......
...@@ -30,7 +30,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -30,7 +30,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
t.Run("NoGames", func(t *testing.T) { t.Run("NoGames", func(t *testing.T) {
forecast, _, rollup, logs := setupForecastTest(t) forecast, _, rollup, logs := setupForecastTest(t)
forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{}) forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{}, 0)
require.Equal(t, 0, rollup.calls) require.Equal(t, 0, rollup.calls)
levelFilter := testlog.NewLevelFilter(log.LevelError) levelFilter := testlog.NewLevelFilter(log.LevelError)
messageFilter := testlog.NewMessageFilter(failedForecastLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
...@@ -40,7 +40,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -40,7 +40,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
t.Run("RollupFetchFails", func(t *testing.T) { t.Run("RollupFetchFails", func(t *testing.T) {
forecast, _, rollup, logs := setupForecastTest(t) forecast, _, rollup, logs := setupForecastTest(t)
rollup.err = errors.New("boom") rollup.err = errors.New("boom")
forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{{}}) forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{{}}, 0)
require.Equal(t, 1, rollup.calls) require.Equal(t, 1, rollup.calls)
levelFilter := testlog.NewLevelFilter(log.LevelError) levelFilter := testlog.NewLevelFilter(log.LevelError)
messageFilter := testlog.NewMessageFilter(failedForecastLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
...@@ -54,7 +54,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -54,7 +54,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
t.Run("ChallengerWonGame_Agree", func(t *testing.T) { t.Run("ChallengerWonGame_Agree", func(t *testing.T) {
forecast, m, _, logs := setupForecastTest(t) forecast, m, _, logs := setupForecastTest(t)
expectedGame := monTypes.EnrichedGameData{Status: types.GameStatusChallengerWon, RootClaim: mockRootClaim} expectedGame := monTypes.EnrichedGameData{Status: types.GameStatusChallengerWon, RootClaim: mockRootClaim}
forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{&expectedGame}) forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{&expectedGame}, 0)
l := logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(lostGameLog)) l := logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(lostGameLog))
require.NotNil(t, l) require.NotNil(t, l)
require.Equal(t, expectedGame.Proxy, l.AttrValue("game")) require.Equal(t, expectedGame.Proxy, l.AttrValue("game"))
...@@ -69,7 +69,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -69,7 +69,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
t.Run("ChallengerWonGame_Disagree", func(t *testing.T) { t.Run("ChallengerWonGame_Disagree", func(t *testing.T) {
forecast, m, _, logs := setupForecastTest(t) forecast, m, _, logs := setupForecastTest(t)
expectedGame := monTypes.EnrichedGameData{Status: types.GameStatusChallengerWon, RootClaim: common.Hash{0xbb}} expectedGame := monTypes.EnrichedGameData{Status: types.GameStatusChallengerWon, RootClaim: common.Hash{0xbb}}
forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{&expectedGame}) forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{&expectedGame}, 0)
l := logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(lostGameLog)) l := logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(lostGameLog))
require.Nil(t, l) require.Nil(t, l)
...@@ -81,7 +81,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -81,7 +81,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
t.Run("DefenderWonGame_Agree", func(t *testing.T) { t.Run("DefenderWonGame_Agree", func(t *testing.T) {
forecast, m, _, logs := setupForecastTest(t) forecast, m, _, logs := setupForecastTest(t)
expectedGame := monTypes.EnrichedGameData{Status: types.GameStatusDefenderWon, RootClaim: mockRootClaim} expectedGame := monTypes.EnrichedGameData{Status: types.GameStatusDefenderWon, RootClaim: mockRootClaim}
forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{&expectedGame}) forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{&expectedGame}, 0)
l := logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(lostGameLog)) l := logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(lostGameLog))
require.Nil(t, l) require.Nil(t, l)
...@@ -93,7 +93,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -93,7 +93,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
t.Run("DefenderWonGame_Disagree", func(t *testing.T) { t.Run("DefenderWonGame_Disagree", func(t *testing.T) {
forecast, m, _, logs := setupForecastTest(t) forecast, m, _, logs := setupForecastTest(t)
expectedGame := monTypes.EnrichedGameData{Status: types.GameStatusDefenderWon, RootClaim: common.Hash{0xbb}} expectedGame := monTypes.EnrichedGameData{Status: types.GameStatusDefenderWon, RootClaim: common.Hash{0xbb}}
forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{&expectedGame}) forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{&expectedGame}, 0)
l := logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(lostGameLog)) l := logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(lostGameLog))
require.NotNil(t, l) require.NotNil(t, l)
require.Equal(t, expectedGame.Proxy, l.AttrValue("game")) require.Equal(t, expectedGame.Proxy, l.AttrValue("game"))
...@@ -107,14 +107,14 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -107,14 +107,14 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
t.Run("SingleGame", func(t *testing.T) { t.Run("SingleGame", func(t *testing.T) {
forecast, _, rollup, logs := setupForecastTest(t) forecast, _, rollup, logs := setupForecastTest(t)
forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{{}}) forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{{}}, 0)
require.Equal(t, 1, rollup.calls) require.Equal(t, 1, rollup.calls)
require.Nil(t, logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(failedForecastLog))) require.Nil(t, logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(failedForecastLog)))
}) })
t.Run("MultipleGames", func(t *testing.T) { t.Run("MultipleGames", func(t *testing.T) {
forecast, _, rollup, logs := setupForecastTest(t) forecast, _, rollup, logs := setupForecastTest(t)
forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{{}, {}, {}}) forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{{}, {}, {}}, 0)
require.Equal(t, 3, rollup.calls) require.Equal(t, 3, rollup.calls)
require.Nil(t, logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(failedForecastLog))) require.Nil(t, logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(failedForecastLog)))
}) })
...@@ -130,7 +130,7 @@ func TestForecast_Forecast_EndLogs(t *testing.T) { ...@@ -130,7 +130,7 @@ func TestForecast_Forecast_EndLogs(t *testing.T) {
RootClaim: mockRootClaim, RootClaim: mockRootClaim,
Claims: createDeepClaimList()[:1], Claims: createDeepClaimList()[:1],
}} }}
forecast.Forecast(context.Background(), games) forecast.Forecast(context.Background(), games, 0)
require.Equal(t, 1, rollup.calls) require.Equal(t, 1, rollup.calls)
levelFilter := testlog.NewLevelFilter(log.LevelError) levelFilter := testlog.NewLevelFilter(log.LevelError)
messageFilter := testlog.NewMessageFilter(failedForecastLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
...@@ -151,7 +151,7 @@ func TestForecast_Forecast_EndLogs(t *testing.T) { ...@@ -151,7 +151,7 @@ func TestForecast_Forecast_EndLogs(t *testing.T) {
RootClaim: mockRootClaim, RootClaim: mockRootClaim,
Claims: createDeepClaimList()[:2], Claims: createDeepClaimList()[:2],
}} }}
forecast.Forecast(context.Background(), games) forecast.Forecast(context.Background(), games, 0)
require.Equal(t, 1, rollup.calls) require.Equal(t, 1, rollup.calls)
levelFilter := testlog.NewLevelFilter(log.LevelError) levelFilter := testlog.NewLevelFilter(log.LevelError)
messageFilter := testlog.NewMessageFilter(failedForecastLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
...@@ -170,7 +170,7 @@ func TestForecast_Forecast_EndLogs(t *testing.T) { ...@@ -170,7 +170,7 @@ func TestForecast_Forecast_EndLogs(t *testing.T) {
forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{{ forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{{
Status: types.GameStatusInProgress, Status: types.GameStatusInProgress,
Claims: createDeepClaimList()[:2], Claims: createDeepClaimList()[:2],
}}) }}, 0)
require.Equal(t, 1, rollup.calls) require.Equal(t, 1, rollup.calls)
levelFilter := testlog.NewLevelFilter(log.LevelError) levelFilter := testlog.NewLevelFilter(log.LevelError)
messageFilter := testlog.NewMessageFilter(failedForecastLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
...@@ -189,7 +189,7 @@ func TestForecast_Forecast_EndLogs(t *testing.T) { ...@@ -189,7 +189,7 @@ func TestForecast_Forecast_EndLogs(t *testing.T) {
forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{{ forecast.Forecast(context.Background(), []*monTypes.EnrichedGameData{{
Status: types.GameStatusInProgress, Status: types.GameStatusInProgress,
Claims: createDeepClaimList()[:1], Claims: createDeepClaimList()[:1],
}}) }}, 0)
require.Equal(t, 1, rollup.calls) require.Equal(t, 1, rollup.calls)
levelFilter := testlog.NewLevelFilter(log.LevelError) levelFilter := testlog.NewLevelFilter(log.LevelError)
messageFilter := testlog.NewMessageFilter(failedForecastLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
...@@ -205,7 +205,7 @@ func TestForecast_Forecast_EndLogs(t *testing.T) { ...@@ -205,7 +205,7 @@ func TestForecast_Forecast_EndLogs(t *testing.T) {
} }
func TestForecast_Forecast_MultipleGames(t *testing.T) { func TestForecast_Forecast_MultipleGames(t *testing.T) {
forecast, _, rollup, logs := setupForecastTest(t) forecast, m, rollup, logs := setupForecastTest(t)
gameStatus := []types.GameStatus{ gameStatus := []types.GameStatus{
types.GameStatusChallengerWon, types.GameStatusChallengerWon,
types.GameStatusInProgress, types.GameStatusInProgress,
...@@ -247,20 +247,27 @@ func TestForecast_Forecast_MultipleGames(t *testing.T) { ...@@ -247,20 +247,27 @@ func TestForecast_Forecast_MultipleGames(t *testing.T) {
RootClaim: rootClaims[i], RootClaim: rootClaims[i],
} }
} }
forecast.Forecast(context.Background(), games) forecast.Forecast(context.Background(), games, 3)
require.Equal(t, len(games), rollup.calls) require.Equal(t, len(games), rollup.calls)
levelFilter := testlog.NewLevelFilter(log.LevelError) require.Nil(t, logs.FindLog(testlog.NewLevelFilter(log.LevelError), testlog.NewMessageFilter(failedForecastLog)))
messageFilter := testlog.NewMessageFilter(failedForecastLog) expectedMetrics := zeroGameAgreement()
require.Nil(t, logs.FindLog(levelFilter, messageFilter)) expectedMetrics[metrics.AgreeChallengerAhead] = 1
expectedMetrics[metrics.DisagreeChallengerAhead] = 1
expectedMetrics[metrics.AgreeDefenderAhead] = 1
expectedMetrics[metrics.DisagreeDefenderAhead] = 1
expectedMetrics[metrics.DisagreeDefenderWins] = 2
expectedMetrics[metrics.DisagreeChallengerWins] = 3
require.Equal(t, expectedMetrics, m.gameAgreement)
require.Equal(t, 3, m.ignoredGames)
} }
func setupForecastTest(t *testing.T) (*forecast, *mockForecastMetrics, *stubOutputValidator, *testlog.CapturingHandler) { func setupForecastTest(t *testing.T) (*forecast, *mockForecastMetrics, *stubOutputValidator, *testlog.CapturingHandler) {
logger, capturedLogs := testlog.CaptureLogger(t, log.LvlDebug) logger, capturedLogs := testlog.CaptureLogger(t, log.LvlDebug)
validator := &stubOutputValidator{} validator := &stubOutputValidator{}
metrics := &mockForecastMetrics{ m := &mockForecastMetrics{
gameAgreement: zeroGameAgreement(), gameAgreement: zeroGameAgreement(),
} }
return newForecast(logger, metrics, validator), metrics, validator, capturedLogs return newForecast(logger, m, validator), m, validator, capturedLogs
} }
func zeroGameAgreement() map[metrics.GameAgreementStatus]int { func zeroGameAgreement() map[metrics.GameAgreementStatus]int {
...@@ -278,6 +285,7 @@ func zeroGameAgreement() map[metrics.GameAgreementStatus]int { ...@@ -278,6 +285,7 @@ func zeroGameAgreement() map[metrics.GameAgreementStatus]int {
type mockForecastMetrics struct { type mockForecastMetrics struct {
gameAgreement map[metrics.GameAgreementStatus]int gameAgreement map[metrics.GameAgreementStatus]int
ignoredGames int
claimResolutionDelayMax float64 claimResolutionDelayMax float64
} }
...@@ -285,6 +293,10 @@ func (m *mockForecastMetrics) RecordGameAgreement(status metrics.GameAgreementSt ...@@ -285,6 +293,10 @@ func (m *mockForecastMetrics) RecordGameAgreement(status metrics.GameAgreementSt
m.gameAgreement[status] = count m.gameAgreement[status] = count
} }
func (m *mockForecastMetrics) RecordIgnoredGames(count int) {
m.ignoredGames = count
}
func (m *mockForecastMetrics) RecordClaimResolutionDelayMax(delay float64) { func (m *mockForecastMetrics) RecordClaimResolutionDelayMax(delay float64) {
m.claimResolutionDelayMax = delay m.claimResolutionDelayMax = delay
} }
......
...@@ -13,14 +13,14 @@ import ( ...@@ -13,14 +13,14 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
type Forecast func(ctx context.Context, games []*types.EnrichedGameData) type Forecast func(ctx context.Context, games []*types.EnrichedGameData, ignoredCount int)
type Bonds func(games []*types.EnrichedGameData) type Bonds func(games []*types.EnrichedGameData)
type Resolutions func(games []*types.EnrichedGameData) type Resolutions func(games []*types.EnrichedGameData)
type MonitorClaims func(games []*types.EnrichedGameData) type MonitorClaims func(games []*types.EnrichedGameData)
type MonitorWithdrawals func(games []*types.EnrichedGameData) type MonitorWithdrawals func(games []*types.EnrichedGameData)
type BlockHashFetcher func(ctx context.Context, number *big.Int) (common.Hash, error) type BlockHashFetcher func(ctx context.Context, number *big.Int) (common.Hash, error)
type BlockNumberFetcher func(ctx context.Context) (uint64, error) type BlockNumberFetcher func(ctx context.Context) (uint64, error)
type Extract func(ctx context.Context, blockHash common.Hash, minTimestamp uint64) ([]*types.EnrichedGameData, error) type Extract func(ctx context.Context, blockHash common.Hash, minTimestamp uint64) ([]*types.EnrichedGameData, int, error)
type RecordClaimResolutionDelayMax func([]*types.EnrichedGameData) type RecordClaimResolutionDelayMax func([]*types.EnrichedGameData)
type gameMonitor struct { type gameMonitor struct {
...@@ -91,13 +91,13 @@ func (m *gameMonitor) monitorGames() error { ...@@ -91,13 +91,13 @@ func (m *gameMonitor) monitorGames() error {
return fmt.Errorf("failed to fetch block hash: %w", err) return fmt.Errorf("failed to fetch block hash: %w", err)
} }
minGameTimestamp := clock.MinCheckedTimestamp(m.clock, m.gameWindow) minGameTimestamp := clock.MinCheckedTimestamp(m.clock, m.gameWindow)
enrichedGames, err := m.extract(m.ctx, blockHash, minGameTimestamp) enrichedGames, ignored, err := m.extract(m.ctx, blockHash, minGameTimestamp)
if err != nil { if err != nil {
return fmt.Errorf("failed to load games: %w", err) return fmt.Errorf("failed to load games: %w", err)
} }
m.resolutions(enrichedGames) m.resolutions(enrichedGames)
m.delays(enrichedGames) m.delays(enrichedGames)
m.forecast(m.ctx, enrichedGames) m.forecast(m.ctx, enrichedGames, ignored)
m.bonds(enrichedGames) m.bonds(enrichedGames)
m.claims(enrichedGames) m.claims(enrichedGames)
m.withdrawals(enrichedGames) m.withdrawals(enrichedGames)
......
...@@ -182,7 +182,7 @@ type mockForecast struct { ...@@ -182,7 +182,7 @@ type mockForecast struct {
calls int calls int
} }
func (m *mockForecast) Forecast(ctx context.Context, games []*monTypes.EnrichedGameData) { func (m *mockForecast) Forecast(_ context.Context, _ []*monTypes.EnrichedGameData, _ int) {
m.calls++ m.calls++
} }
...@@ -199,19 +199,20 @@ type mockExtractor struct { ...@@ -199,19 +199,20 @@ type mockExtractor struct {
calls int calls int
maxSuccess int maxSuccess int
games []*monTypes.EnrichedGameData games []*monTypes.EnrichedGameData
ignoredCount int
} }
func (m *mockExtractor) Extract( func (m *mockExtractor) Extract(
_ context.Context, _ context.Context,
_ common.Hash, _ common.Hash,
_ uint64, _ uint64,
) ([]*monTypes.EnrichedGameData, error) { ) ([]*monTypes.EnrichedGameData, int, error) {
m.calls++ m.calls++
if m.fetchErr != nil { if m.fetchErr != nil {
return nil, m.fetchErr return nil, 0, m.fetchErr
} }
if m.calls > m.maxSuccess && m.maxSuccess != 0 { if m.calls > m.maxSuccess && m.maxSuccess != 0 {
return nil, mockErr return nil, 0, mockErr
} }
return m.games, nil return m.games, m.ignoredCount, nil
} }
...@@ -96,7 +96,7 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error ...@@ -96,7 +96,7 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
s.initGameCallerCreator() // Must be called before initForecast s.initGameCallerCreator() // Must be called before initForecast
s.initDelayCalculator() s.initDelayCalculator()
s.initExtractor() s.initExtractor(cfg)
s.initForecast(cfg) s.initForecast(cfg)
s.initBonds() s.initBonds()
...@@ -133,11 +133,12 @@ func (s *Service) initDelayCalculator() { ...@@ -133,11 +133,12 @@ func (s *Service) initDelayCalculator() {
s.delays = resolution.NewDelayCalculator(s.metrics, s.cl) s.delays = resolution.NewDelayCalculator(s.metrics, s.cl)
} }
func (s *Service) initExtractor() { func (s *Service) initExtractor(cfg *config.Config) {
s.extractor = extract.NewExtractor( s.extractor = extract.NewExtractor(
s.logger, s.logger,
s.game.CreateContract, s.game.CreateContract,
s.factoryContract.GetGamesAtOrAfter, s.factoryContract.GetGamesAtOrAfter,
cfg.IgnoredGames,
extract.NewClaimEnricher(), extract.NewClaimEnricher(),
extract.NewRecipientEnricher(), // Must be called before WithdrawalsEnricher extract.NewRecipientEnricher(), // Must be called before WithdrawalsEnricher
extract.NewWithdrawalsEnricher(), extract.NewWithdrawalsEnricher(),
......
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