Commit 06edba14 authored by refcell's avatar refcell Committed by GitHub

fix(op-dispute-mon): forecast metrics and test enhancements (#9468)

parent 0584913a
...@@ -34,7 +34,8 @@ func TestDetector_Detect(t *testing.T) { ...@@ -34,7 +34,8 @@ func TestDetector_Detect(t *testing.T) {
t.Run("CheckAgreementFails", func(t *testing.T) { t.Run("CheckAgreementFails", func(t *testing.T) {
detector, metrics, creator, rollup, _ := setupDetectorTest(t) detector, metrics, creator, rollup, _ := setupDetectorTest(t)
rollup.err = errors.New("boom") rollup.err = errors.New("boom")
creator.caller = &mockGameCaller{status: types.GameStatusInProgress} creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
creator.caller.rootClaim = []common.Hash{{}}
detector.Detect(context.Background(), []types.GameMetadata{{}}) detector.Detect(context.Background(), []types.GameMetadata{{}})
metrics.Equals(t, 1, 0, 0) // Status should still be metriced here! metrics.Equals(t, 1, 0, 0) // Status should still be metriced here!
metrics.Mapped(t, map[string]int{}) metrics.Mapped(t, map[string]int{})
...@@ -42,7 +43,8 @@ func TestDetector_Detect(t *testing.T) { ...@@ -42,7 +43,8 @@ func TestDetector_Detect(t *testing.T) {
t.Run("SingleGame", func(t *testing.T) { t.Run("SingleGame", func(t *testing.T) {
detector, metrics, creator, _, _ := setupDetectorTest(t) detector, metrics, creator, _, _ := setupDetectorTest(t)
creator.caller = &mockGameCaller{status: types.GameStatusInProgress} creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
creator.caller.rootClaim = []common.Hash{{}}
detector.Detect(context.Background(), []types.GameMetadata{{}}) detector.Detect(context.Background(), []types.GameMetadata{{}})
metrics.Equals(t, 1, 0, 0) metrics.Equals(t, 1, 0, 0)
metrics.Mapped(t, map[string]int{"in_progress": 1}) metrics.Mapped(t, map[string]int{"in_progress": 1})
...@@ -50,7 +52,12 @@ func TestDetector_Detect(t *testing.T) { ...@@ -50,7 +52,12 @@ func TestDetector_Detect(t *testing.T) {
t.Run("MultipleGames", func(t *testing.T) { t.Run("MultipleGames", func(t *testing.T) {
detector, metrics, creator, _, _ := setupDetectorTest(t) detector, metrics, creator, _, _ := setupDetectorTest(t)
creator.caller = &mockGameCaller{status: types.GameStatusInProgress} creator.caller.status = []types.GameStatus{
types.GameStatusInProgress,
types.GameStatusInProgress,
types.GameStatusInProgress,
}
creator.caller.rootClaim = []common.Hash{{}, {}, {}}
detector.Detect(context.Background(), []types.GameMetadata{{}, {}, {}}) detector.Detect(context.Background(), []types.GameMetadata{{}, {}, {}})
metrics.Equals(t, 3, 0, 0) metrics.Equals(t, 3, 0, 0)
metrics.Mapped(t, map[string]int{"in_progress": 3}) metrics.Mapped(t, map[string]int{"in_progress": 3})
...@@ -128,13 +135,16 @@ func TestDetector_FetchGameMetadata(t *testing.T) { ...@@ -128,13 +135,16 @@ func TestDetector_FetchGameMetadata(t *testing.T) {
t.Run("GetGameMetadataFails", func(t *testing.T) { t.Run("GetGameMetadataFails", func(t *testing.T) {
detector, _, creator, _, _ := setupDetectorTest(t) detector, _, creator, _, _ := setupDetectorTest(t)
creator.caller = &mockGameCaller{err: errors.New("boom")} creator.caller = &mockGameCaller{err: errors.New("boom")}
creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
creator.caller.rootClaim = []common.Hash{{}}
_, _, _, err := detector.fetchGameMetadata(context.Background(), types.GameMetadata{}) _, _, _, err := detector.fetchGameMetadata(context.Background(), types.GameMetadata{})
require.Error(t, err) require.Error(t, err)
}) })
t.Run("Success", func(t *testing.T) { t.Run("Success", func(t *testing.T) {
detector, _, creator, _, _ := setupDetectorTest(t) detector, _, creator, _, _ := setupDetectorTest(t)
creator.caller = &mockGameCaller{status: types.GameStatusInProgress} creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
creator.caller.rootClaim = []common.Hash{{}}
_, _, status, err := detector.fetchGameMetadata(context.Background(), types.GameMetadata{}) _, _, status, err := detector.fetchGameMetadata(context.Background(), types.GameMetadata{})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, types.GameStatusInProgress, status) require.Equal(t, types.GameStatusInProgress, status)
...@@ -266,27 +276,29 @@ func (m *mockGameCallerCreator) CreateContract(game types.GameMetadata) (GameCal ...@@ -266,27 +276,29 @@ func (m *mockGameCallerCreator) CreateContract(game types.GameMetadata) (GameCal
type mockGameCaller struct { type mockGameCaller struct {
calls int calls int
claimsCalls int claimsCalls int
claims []faultTypes.Claim claims [][]faultTypes.Claim
status types.GameStatus status []types.GameStatus
rootClaim common.Hash rootClaim []common.Hash
err error err error
claimsErr error claimsErr error
} }
func (m *mockGameCaller) GetGameMetadata(ctx context.Context) (uint64, common.Hash, types.GameStatus, error) { func (m *mockGameCaller) GetGameMetadata(ctx context.Context) (uint64, common.Hash, types.GameStatus, error) {
idx := m.calls
m.calls++ m.calls++
if m.err != nil { if m.err != nil {
return 0, m.rootClaim, m.status, m.err return 0, m.rootClaim[idx], m.status[idx], m.err
} }
return 0, m.rootClaim, m.status, nil return 0, m.rootClaim[idx], m.status[idx], nil
} }
func (m *mockGameCaller) GetAllClaims(ctx context.Context) ([]faultTypes.Claim, error) { func (m *mockGameCaller) GetAllClaims(ctx context.Context) ([]faultTypes.Claim, error) {
idx := m.claimsCalls
m.claimsCalls++ m.claimsCalls++
if m.claimsErr != nil { if m.claimsErr != nil {
return nil, m.claimsErr return nil, m.claimsErr
} }
return m.claims, nil return m.claims[idx], nil
} }
type mockDetectorMetricer struct { type mockDetectorMetricer struct {
......
...@@ -18,32 +18,44 @@ var ( ...@@ -18,32 +18,44 @@ var (
ErrRootAgreement = errors.New("failed to check root agreement") ErrRootAgreement = errors.New("failed to check root agreement")
) )
type ForecastMetrics interface {
RecordGameAgreement(status string, count int)
}
type forecast struct { type forecast struct {
logger log.Logger logger log.Logger
// TODO(client-pod#536): Add forecast metrics. metrics ForecastMetrics
// These should only fire if a game is in progress.
// otherwise, the detector should record the game status.
creator GameCallerCreator creator GameCallerCreator
validator OutputValidator validator OutputValidator
} }
func newForecast(logger log.Logger, creator GameCallerCreator, validator OutputValidator) *forecast { func newForecast(logger log.Logger, metrics ForecastMetrics, creator GameCallerCreator, validator OutputValidator) *forecast {
return &forecast{ return &forecast{
logger: logger, logger: logger,
metrics: metrics,
creator: creator, creator: creator,
validator: validator, validator: validator,
} }
} }
func (f *forecast) Forecast(ctx context.Context, games []types.GameMetadata) { func (f *forecast) Forecast(ctx context.Context, games []types.GameMetadata) {
batch := forecastBatch{}
for _, game := range games { for _, game := range games {
if err := f.forecastGame(ctx, game); 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)
}
func (f *forecast) recordBatch(batch forecastBatch) {
f.metrics.RecordGameAgreement("agree_challenger_ahead", batch.AgreeChallengerAhead)
f.metrics.RecordGameAgreement("disagree_challenger_ahead", batch.DisagreeChallengerAhead)
f.metrics.RecordGameAgreement("agree_defender_ahead", batch.AgreeDefenderAhead)
f.metrics.RecordGameAgreement("disagree_defender_ahead", batch.DisagreeDefenderAhead)
} }
func (f *forecast) forecastGame(ctx context.Context, game types.GameMetadata) error { func (f *forecast) forecastGame(ctx context.Context, game types.GameMetadata, metrics *forecastBatch) error {
loader, err := f.creator.CreateContract(game) loader, err := f.creator.CreateContract(game)
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrContractCreation, err) return fmt.Errorf("%w: %w", ErrContractCreation, err)
...@@ -80,15 +92,19 @@ func (f *forecast) forecastGame(ctx context.Context, game types.GameMetadata) er ...@@ -80,15 +92,19 @@ func (f *forecast) forecastGame(ctx context.Context, game types.GameMetadata) er
if agreement { if agreement {
// If we agree with the output root proposal, the Defender should win, defending that claim. // If we agree with the output root proposal, the Defender should win, defending that claim.
if status == types.GameStatusChallengerWon { if status == types.GameStatusChallengerWon {
metrics.AgreeChallengerAhead++
f.logger.Warn("Forecasting unexpected game result", "status", status, "game", game, "rootClaim", rootClaim, "expected", expected) f.logger.Warn("Forecasting unexpected game result", "status", status, "game", game, "rootClaim", rootClaim, "expected", expected)
} else { } else {
metrics.AgreeDefenderAhead++
f.logger.Debug("Forecasting expected game result", "status", status, "game", game, "rootClaim", rootClaim, "expected", expected) f.logger.Debug("Forecasting expected game result", "status", status, "game", game, "rootClaim", rootClaim, "expected", expected)
} }
} else { } else {
// If we disagree with the output root proposal, the Challenger should win, challenging that claim. // If we disagree with the output root proposal, the Challenger should win, challenging that claim.
if status == types.GameStatusDefenderWon { if status == types.GameStatusDefenderWon {
metrics.DisagreeDefenderAhead++
f.logger.Warn("Forecasting unexpected game result", "status", status, "game", game, "rootClaim", rootClaim, "expected", expected) f.logger.Warn("Forecasting unexpected game result", "status", status, "game", game, "rootClaim", rootClaim, "expected", expected)
} else { } else {
metrics.DisagreeChallengerAhead++
f.logger.Debug("Forecasting expected game result", "status", status, "game", game, "rootClaim", rootClaim, "expected", expected) f.logger.Debug("Forecasting expected game result", "status", status, "game", game, "rootClaim", rootClaim, "expected", expected)
} }
} }
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"testing" "testing"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -14,7 +15,7 @@ import ( ...@@ -14,7 +15,7 @@ import (
) )
var ( var (
failedForecaseLog = "Failed to forecast game" failedForecastLog = "Failed to forecast game"
expectedInProgressLog = "Game is not in progress, skipping forecast" expectedInProgressLog = "Game is not in progress, skipping forecast"
unexpectedResultLog = "Forecasting unexpected game result" unexpectedResultLog = "Forecasting unexpected game result"
expectedResultLog = "Forecasting expected game result" expectedResultLog = "Forecasting expected game result"
...@@ -24,19 +25,19 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -24,19 +25,19 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("NoGames", func(t *testing.T) { t.Run("NoGames", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
forecast.Forecast(context.Background(), []types.GameMetadata{}) forecast.Forecast(context.Background(), []types.GameMetadata{})
require.Equal(t, 0, creator.calls) require.Equal(t, 0, creator.calls)
require.Equal(t, 0, creator.caller.calls) require.Equal(t, 0, creator.caller.calls)
require.Equal(t, 0, creator.caller.claimsCalls) require.Equal(t, 0, creator.caller.claimsCalls)
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(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
require.Nil(t, logs.FindLog(levelFilter, messageFilter)) require.Nil(t, logs.FindLog(levelFilter, messageFilter))
}) })
t.Run("ContractCreationFails", func(t *testing.T) { t.Run("ContractCreationFails", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
creator.err = errors.New("boom") creator.err = errors.New("boom")
forecast.Forecast(context.Background(), []types.GameMetadata{{}}) forecast.Forecast(context.Background(), []types.GameMetadata{{}})
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
...@@ -44,7 +45,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -44,7 +45,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
require.Equal(t, 0, creator.caller.claimsCalls) require.Equal(t, 0, creator.caller.claimsCalls)
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(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
l := logs.FindLog(levelFilter, messageFilter) l := logs.FindLog(levelFilter, messageFilter)
require.NotNil(t, l) require.NotNil(t, l)
err := l.AttrValue("err") err := l.AttrValue("err")
...@@ -53,16 +54,17 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -53,16 +54,17 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
}) })
t.Run("MetadataFetchFails", func(t *testing.T) { t.Run("MetadataFetchFails", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
creator.caller = &mockGameCaller{status: types.GameStatusInProgress}
creator.caller.err = errors.New("boom") creator.caller.err = errors.New("boom")
creator.caller.rootClaim = []common.Hash{mockRootClaim}
creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
forecast.Forecast(context.Background(), []types.GameMetadata{{}}) forecast.Forecast(context.Background(), []types.GameMetadata{{}})
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
require.Equal(t, 1, creator.caller.calls) require.Equal(t, 1, creator.caller.calls)
require.Equal(t, 0, creator.caller.claimsCalls) require.Equal(t, 0, creator.caller.claimsCalls)
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(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
l := logs.FindLog(levelFilter, messageFilter) l := logs.FindLog(levelFilter, messageFilter)
require.NotNil(t, l) require.NotNil(t, l)
err := l.AttrValue("err") err := l.AttrValue("err")
...@@ -71,16 +73,17 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -71,16 +73,17 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
}) })
t.Run("ClaimsFetchFails", func(t *testing.T) { t.Run("ClaimsFetchFails", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
creator.caller = &mockGameCaller{status: types.GameStatusInProgress}
creator.caller.claimsErr = errors.New("boom") creator.caller.claimsErr = errors.New("boom")
creator.caller.rootClaim = []common.Hash{mockRootClaim}
creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
forecast.Forecast(context.Background(), []types.GameMetadata{{}}) forecast.Forecast(context.Background(), []types.GameMetadata{{}})
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
require.Equal(t, 1, creator.caller.calls) require.Equal(t, 1, creator.caller.calls)
require.Equal(t, 1, creator.caller.claimsCalls) require.Equal(t, 1, creator.caller.claimsCalls)
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(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
l := logs.FindLog(levelFilter, messageFilter) l := logs.FindLog(levelFilter, messageFilter)
require.NotNil(t, l) require.NotNil(t, l)
err := l.AttrValue("err") err := l.AttrValue("err")
...@@ -89,15 +92,18 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -89,15 +92,18 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
}) })
t.Run("RollupFetchFails", func(t *testing.T) { t.Run("RollupFetchFails", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
rollup.err = errors.New("boom") rollup.err = errors.New("boom")
creator.caller.claims = [][]faultTypes.Claim{createDeepClaimList()[:1]}
creator.caller.rootClaim = []common.Hash{mockRootClaim}
creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
forecast.Forecast(context.Background(), []types.GameMetadata{{}}) forecast.Forecast(context.Background(), []types.GameMetadata{{}})
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
require.Equal(t, 1, creator.caller.calls) require.Equal(t, 1, creator.caller.calls)
require.Equal(t, 1, creator.caller.claimsCalls) require.Equal(t, 1, creator.caller.claimsCalls)
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(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
l := logs.FindLog(levelFilter, messageFilter) l := logs.FindLog(levelFilter, messageFilter)
require.NotNil(t, l) require.NotNil(t, l)
err := l.AttrValue("err") err := l.AttrValue("err")
...@@ -106,9 +112,9 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -106,9 +112,9 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
}) })
t.Run("ChallengerWonGameSkipped", func(t *testing.T) { t.Run("ChallengerWonGameSkipped", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
creator.caller = &mockGameCaller{status: types.GameStatusChallengerWon} creator.caller.status = []types.GameStatus{types.GameStatusChallengerWon}
creator.caller.claims = createDeepClaimList()[:1] creator.caller.claims = [][]faultTypes.Claim{createDeepClaimList()[:1]}
expectedGame := types.GameMetadata{} expectedGame := types.GameMetadata{}
forecast.Forecast(context.Background(), []types.GameMetadata{expectedGame}) forecast.Forecast(context.Background(), []types.GameMetadata{expectedGame})
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
...@@ -116,7 +122,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -116,7 +122,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
require.Equal(t, 0, creator.caller.claimsCalls) require.Equal(t, 0, creator.caller.claimsCalls)
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(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
require.Nil(t, logs.FindLog(levelFilter, messageFilter)) require.Nil(t, logs.FindLog(levelFilter, messageFilter))
levelFilter = testlog.NewLevelFilter(log.LevelDebug) levelFilter = testlog.NewLevelFilter(log.LevelDebug)
messageFilter = testlog.NewMessageFilter(expectedInProgressLog) messageFilter = testlog.NewMessageFilter(expectedInProgressLog)
...@@ -127,9 +133,9 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -127,9 +133,9 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
}) })
t.Run("DefenderWonGameSkipped", func(t *testing.T) { t.Run("DefenderWonGameSkipped", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
creator.caller = &mockGameCaller{status: types.GameStatusDefenderWon} creator.caller.status = []types.GameStatus{types.GameStatusDefenderWon}
creator.caller.claims = createDeepClaimList()[:1] creator.caller.claims = [][]faultTypes.Claim{createDeepClaimList()[:1]}
expectedGame := types.GameMetadata{} expectedGame := types.GameMetadata{}
forecast.Forecast(context.Background(), []types.GameMetadata{expectedGame}) forecast.Forecast(context.Background(), []types.GameMetadata{expectedGame})
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
...@@ -137,7 +143,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -137,7 +143,7 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
require.Equal(t, 0, creator.caller.claimsCalls) require.Equal(t, 0, creator.caller.claimsCalls)
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(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
require.Nil(t, logs.FindLog(levelFilter, messageFilter)) require.Nil(t, logs.FindLog(levelFilter, messageFilter))
levelFilter = testlog.NewLevelFilter(log.LevelDebug) levelFilter = testlog.NewLevelFilter(log.LevelDebug)
messageFilter = testlog.NewMessageFilter(expectedInProgressLog) messageFilter = testlog.NewMessageFilter(expectedInProgressLog)
...@@ -148,16 +154,16 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -148,16 +154,16 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
}) })
t.Run("SingleGame", func(t *testing.T) { t.Run("SingleGame", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
creator.caller = &mockGameCaller{status: types.GameStatusInProgress} creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
creator.caller.claims = createDeepClaimList()[:1] creator.caller.claims = [][]faultTypes.Claim{createDeepClaimList()[:1]}
forecast.Forecast(context.Background(), []types.GameMetadata{{}}) forecast.Forecast(context.Background(), []types.GameMetadata{{}})
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
require.Equal(t, 1, creator.caller.calls) require.Equal(t, 1, creator.caller.calls)
require.Equal(t, 1, creator.caller.claimsCalls) require.Equal(t, 1, creator.caller.claimsCalls)
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(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
require.Nil(t, logs.FindLog(levelFilter, messageFilter)) require.Nil(t, logs.FindLog(levelFilter, messageFilter))
levelFilter = testlog.NewLevelFilter(log.LevelDebug) levelFilter = testlog.NewLevelFilter(log.LevelDebug)
messageFilter = testlog.NewMessageFilter(expectedInProgressLog) messageFilter = testlog.NewMessageFilter(expectedInProgressLog)
...@@ -165,16 +171,17 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -165,16 +171,17 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
}) })
t.Run("MultipleGames", func(t *testing.T) { t.Run("MultipleGames", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
creator.caller.claims = createDeepClaimList()[:1] creator.caller.status = []types.GameStatus{types.GameStatusInProgress, types.GameStatusInProgress, types.GameStatusInProgress}
creator.caller = &mockGameCaller{status: types.GameStatusInProgress} creator.caller.claims = [][]faultTypes.Claim{createDeepClaimList()[:1], createDeepClaimList()[:1], createDeepClaimList()[:1]}
creator.caller.rootClaim = []common.Hash{{}, {}, {}}
forecast.Forecast(context.Background(), []types.GameMetadata{{}, {}, {}}) forecast.Forecast(context.Background(), []types.GameMetadata{{}, {}, {}})
require.Equal(t, 3, creator.calls) require.Equal(t, 3, creator.calls)
require.Equal(t, 3, creator.caller.calls) require.Equal(t, 3, creator.caller.calls)
require.Equal(t, 3, creator.caller.claimsCalls) require.Equal(t, 3, creator.caller.claimsCalls)
require.Equal(t, 3, rollup.calls) require.Equal(t, 3, rollup.calls)
levelFilter := testlog.NewLevelFilter(log.LevelError) levelFilter := testlog.NewLevelFilter(log.LevelError)
messageFilter := testlog.NewMessageFilter(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
require.Nil(t, logs.FindLog(levelFilter, messageFilter)) require.Nil(t, logs.FindLog(levelFilter, messageFilter))
levelFilter = testlog.NewLevelFilter(log.LevelDebug) levelFilter = testlog.NewLevelFilter(log.LevelDebug)
messageFilter = testlog.NewMessageFilter(expectedInProgressLog) messageFilter = testlog.NewMessageFilter(expectedInProgressLog)
...@@ -186,65 +193,66 @@ func TestForecast_Forecast_EndLogs(t *testing.T) { ...@@ -186,65 +193,66 @@ func TestForecast_Forecast_EndLogs(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("AgreeDefenderWins", func(t *testing.T) { t.Run("AgreeDefenderWins", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
creator.caller = &mockGameCaller{status: types.GameStatusInProgress} creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
creator.caller.claims = createDeepClaimList()[:1] creator.caller.claims = [][]faultTypes.Claim{createDeepClaimList()[:1]}
forecast.Forecast(context.Background(), []types.GameMetadata{{}}) forecast.Forecast(context.Background(), []types.GameMetadata{{}})
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
require.Equal(t, 1, creator.caller.calls) require.Equal(t, 1, creator.caller.calls)
require.Equal(t, 1, creator.caller.claimsCalls) require.Equal(t, 1, creator.caller.claimsCalls)
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(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
require.Nil(t, logs.FindLog(levelFilter, messageFilter)) require.Nil(t, logs.FindLog(levelFilter, messageFilter))
levelFilter = testlog.NewLevelFilter(log.LevelDebug) levelFilter = testlog.NewLevelFilter(log.LevelDebug)
messageFilter = testlog.NewMessageFilter(expectedInProgressLog) messageFilter = testlog.NewMessageFilter(expectedInProgressLog)
require.Nil(t, logs.FindLog(levelFilter, messageFilter)) require.Nil(t, logs.FindLog(levelFilter, messageFilter))
levelFilter = testlog.NewLevelFilter(log.LevelWarn) levelFilter = testlog.NewLevelFilter(log.LevelDebug)
messageFilter = testlog.NewMessageFilter(unexpectedResultLog) messageFilter = testlog.NewMessageFilter(expectedResultLog)
l := logs.FindLog(levelFilter, messageFilter) l := logs.FindLog(levelFilter, messageFilter)
require.NotNil(t, l) require.NotNil(t, l)
require.Equal(t, common.Hash{}, l.AttrValue("rootClaim")) require.Equal(t, mockRootClaim, l.AttrValue("rootClaim"))
require.Equal(t, mockRootClaim, l.AttrValue("expected")) require.Equal(t, mockRootClaim, l.AttrValue("expected"))
require.Equal(t, types.GameStatusDefenderWon, l.AttrValue("status")) require.Equal(t, types.GameStatusDefenderWon, l.AttrValue("status"))
}) })
t.Run("AgreeChallengerWins", func(t *testing.T) { t.Run("AgreeChallengerWins", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
creator.caller = &mockGameCaller{status: types.GameStatusInProgress} creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
creator.caller.claims = createDeepClaimList()[:2] creator.caller.claims = [][]faultTypes.Claim{createDeepClaimList()[:2]}
creator.caller.rootClaim = []common.Hash{mockRootClaim}
forecast.Forecast(context.Background(), []types.GameMetadata{{}}) forecast.Forecast(context.Background(), []types.GameMetadata{{}})
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
require.Equal(t, 1, creator.caller.calls) require.Equal(t, 1, creator.caller.calls)
require.Equal(t, 1, creator.caller.claimsCalls) require.Equal(t, 1, creator.caller.claimsCalls)
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(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
require.Nil(t, logs.FindLog(levelFilter, messageFilter)) require.Nil(t, logs.FindLog(levelFilter, messageFilter))
levelFilter = testlog.NewLevelFilter(log.LevelDebug) levelFilter = testlog.NewLevelFilter(log.LevelDebug)
messageFilter = testlog.NewMessageFilter(expectedInProgressLog) messageFilter = testlog.NewMessageFilter(expectedInProgressLog)
require.Nil(t, logs.FindLog(levelFilter, messageFilter)) require.Nil(t, logs.FindLog(levelFilter, messageFilter))
levelFilter = testlog.NewLevelFilter(log.LevelDebug) levelFilter = testlog.NewLevelFilter(log.LevelWarn)
messageFilter = testlog.NewMessageFilter(expectedResultLog) messageFilter = testlog.NewMessageFilter(unexpectedResultLog)
l := logs.FindLog(levelFilter, messageFilter) l := logs.FindLog(levelFilter, messageFilter)
require.NotNil(t, l) require.NotNil(t, l)
require.Equal(t, common.Hash{}, l.AttrValue("rootClaim")) require.Equal(t, mockRootClaim, l.AttrValue("rootClaim"))
require.Equal(t, mockRootClaim, l.AttrValue("expected")) require.Equal(t, mockRootClaim, l.AttrValue("expected"))
require.Equal(t, types.GameStatusChallengerWon, l.AttrValue("status")) require.Equal(t, types.GameStatusChallengerWon, l.AttrValue("status"))
}) })
t.Run("DisagreeChallengerWins", func(t *testing.T) { t.Run("DisagreeChallengerWins", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
creator.caller = &mockGameCaller{status: types.GameStatusInProgress} creator.caller = &mockGameCaller{status: []types.GameStatus{types.GameStatusInProgress}}
creator.caller.rootClaim = common.Hash{} creator.caller.rootClaim = []common.Hash{{}}
creator.caller.claims = createDeepClaimList()[:2] creator.caller.claims = [][]faultTypes.Claim{createDeepClaimList()[:2]}
forecast.Forecast(context.Background(), []types.GameMetadata{{}}) forecast.Forecast(context.Background(), []types.GameMetadata{{}})
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
require.Equal(t, 1, creator.caller.calls) require.Equal(t, 1, creator.caller.calls)
require.Equal(t, 1, creator.caller.claimsCalls) require.Equal(t, 1, creator.caller.claimsCalls)
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(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
require.Nil(t, logs.FindLog(levelFilter, messageFilter)) require.Nil(t, logs.FindLog(levelFilter, messageFilter))
levelFilter = testlog.NewLevelFilter(log.LevelDebug) levelFilter = testlog.NewLevelFilter(log.LevelDebug)
messageFilter = testlog.NewMessageFilter(expectedInProgressLog) messageFilter = testlog.NewMessageFilter(expectedInProgressLog)
...@@ -259,17 +267,17 @@ func TestForecast_Forecast_EndLogs(t *testing.T) { ...@@ -259,17 +267,17 @@ func TestForecast_Forecast_EndLogs(t *testing.T) {
}) })
t.Run("DisagreeDefenderWins", func(t *testing.T) { t.Run("DisagreeDefenderWins", func(t *testing.T) {
forecast, creator, rollup, logs := setupForecastTest(t) forecast, _, creator, rollup, logs := setupForecastTest(t)
creator.caller = &mockGameCaller{status: types.GameStatusInProgress} creator.caller = &mockGameCaller{status: []types.GameStatus{types.GameStatusInProgress}}
creator.caller.rootClaim = common.Hash{} creator.caller.rootClaim = []common.Hash{{}}
creator.caller.claims = createDeepClaimList()[:1] creator.caller.claims = [][]faultTypes.Claim{createDeepClaimList()[:1]}
forecast.Forecast(context.Background(), []types.GameMetadata{{}}) forecast.Forecast(context.Background(), []types.GameMetadata{{}})
require.Equal(t, 1, creator.calls) require.Equal(t, 1, creator.calls)
require.Equal(t, 1, creator.caller.calls) require.Equal(t, 1, creator.caller.calls)
require.Equal(t, 1, creator.caller.claimsCalls) require.Equal(t, 1, creator.caller.claimsCalls)
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(failedForecaseLog) messageFilter := testlog.NewMessageFilter(failedForecastLog)
require.Nil(t, logs.FindLog(levelFilter, messageFilter)) require.Nil(t, logs.FindLog(levelFilter, messageFilter))
levelFilter = testlog.NewLevelFilter(log.LevelDebug) levelFilter = testlog.NewLevelFilter(log.LevelDebug)
messageFilter = testlog.NewMessageFilter(expectedInProgressLog) messageFilter = testlog.NewMessageFilter(expectedInProgressLog)
...@@ -284,10 +292,75 @@ func TestForecast_Forecast_EndLogs(t *testing.T) { ...@@ -284,10 +292,75 @@ func TestForecast_Forecast_EndLogs(t *testing.T) {
}) })
} }
func setupForecastTest(t *testing.T) (*forecast, *mockGameCallerCreator, *stubOutputValidator, *testlog.CapturingHandler) { func TestForecast_Forecast_MultipleGames(t *testing.T) {
forecast, _, creator, rollup, logs := setupForecastTest(t)
creator.caller.status = []types.GameStatus{
types.GameStatusChallengerWon,
types.GameStatusInProgress,
types.GameStatusInProgress,
types.GameStatusDefenderWon,
types.GameStatusInProgress,
types.GameStatusInProgress,
types.GameStatusDefenderWon,
types.GameStatusChallengerWon,
types.GameStatusChallengerWon,
}
creator.caller.claims = [][]faultTypes.Claim{
createDeepClaimList()[:1],
createDeepClaimList()[:2],
createDeepClaimList()[:2],
createDeepClaimList()[:1],
}
creator.caller.rootClaim = []common.Hash{
{},
{},
mockRootClaim,
{},
{},
mockRootClaim,
{},
{},
{},
}
games := make([]types.GameMetadata, 9)
forecast.Forecast(context.Background(), games)
require.Equal(t, 9, creator.calls)
require.Equal(t, 9, creator.caller.calls)
require.Equal(t, 4, creator.caller.claimsCalls)
require.Equal(t, 4, rollup.calls)
levelFilter := testlog.NewLevelFilter(log.LevelError)
messageFilter := testlog.NewMessageFilter(failedForecastLog)
require.Nil(t, logs.FindLog(levelFilter, messageFilter))
levelFilter = testlog.NewLevelFilter(log.LevelDebug)
messageFilter = testlog.NewMessageFilter(expectedInProgressLog)
require.Len(t, logs.FindLogs(levelFilter, messageFilter), 5)
}
func setupForecastTest(t *testing.T) (*forecast, *mockForecastMetrics, *mockGameCallerCreator, *stubOutputValidator, *testlog.CapturingHandler) {
logger, capturedLogs := testlog.CaptureLogger(t, log.LvlDebug) logger, capturedLogs := testlog.CaptureLogger(t, log.LvlDebug)
validator := &stubOutputValidator{} validator := &stubOutputValidator{}
caller := &mockGameCaller{rootClaim: mockRootClaim} caller := &mockGameCaller{rootClaim: []common.Hash{mockRootClaim}}
creator := &mockGameCallerCreator{caller: caller} creator := &mockGameCallerCreator{caller: caller}
return newForecast(logger, creator, validator), creator, validator, capturedLogs metrics := &mockForecastMetrics{}
return newForecast(logger, metrics, creator, validator), metrics, creator, validator, capturedLogs
}
type mockForecastMetrics struct {
agreeDefenderAhead int
disagreeDefenderAhead int
agreeChallengerAhead int
disagreeChallengerAhead int
}
func (m *mockForecastMetrics) RecordGameAgreement(status string, count int) {
switch status {
case "agree_defender_ahead":
m.agreeDefenderAhead = count
case "disagree_defender_ahead":
m.disagreeDefenderAhead = count
case "agree_challenger_ahead":
m.agreeChallengerAhead = count
case "disagree_challenger_ahead":
m.disagreeChallengerAhead = count
}
} }
...@@ -79,11 +79,14 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error ...@@ -79,11 +79,14 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
if err := s.initOutputRollupClient(ctx, cfg); err != nil { if err := s.initOutputRollupClient(ctx, cfg); err != nil {
return fmt.Errorf("failed to init rollup client: %w", err) return fmt.Errorf("failed to init rollup client: %w", err)
} }
s.initOutputValidator()
s.initGameCallerCreator() s.initOutputValidator() // Must be called before initForecast
s.initGameCallerCreator() // Must be called before initForecast
s.initForecast(cfg) s.initForecast(cfg)
s.initDetector() s.initDetector()
s.initMonitor(ctx, cfg)
s.initMonitor(ctx, cfg) // Monitor must be initialized last
s.metrics.RecordInfo(version.SimpleWithMeta) s.metrics.RecordInfo(version.SimpleWithMeta)
s.metrics.RecordUp() s.metrics.RecordUp()
...@@ -100,7 +103,7 @@ func (s *Service) initGameCallerCreator() { ...@@ -100,7 +103,7 @@ func (s *Service) initGameCallerCreator() {
} }
func (s *Service) initForecast(cfg *config.Config) { func (s *Service) initForecast(cfg *config.Config) {
s.forecast = newForecast(s.logger, s.game, s.validator) s.forecast = newForecast(s.logger, s.metrics, s.game, s.validator)
} }
func (s *Service) initDetector() { func (s *Service) initDetector() {
......
...@@ -19,6 +19,13 @@ func (s *statusBatch) Add(status types.GameStatus) { ...@@ -19,6 +19,13 @@ func (s *statusBatch) Add(status types.GameStatus) {
} }
} }
type forecastBatch struct {
AgreeDefenderAhead int
DisagreeDefenderAhead int
AgreeChallengerAhead int
DisagreeChallengerAhead int
}
type detectionBatch struct { type detectionBatch struct {
inProgress int inProgress int
agreeDefenderWins int agreeDefenderWins int
......
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