Commit 35aea1ea authored by refcell's avatar refcell Committed by GitHub

feat(op-dispute-mon): pull claim fetching into the extractor (#9569)

parent 5f1b1b21
...@@ -27,15 +27,13 @@ type DetectorMetrics interface { ...@@ -27,15 +27,13 @@ type DetectorMetrics interface {
type detector struct { type detector struct {
logger log.Logger logger log.Logger
metrics DetectorMetrics metrics DetectorMetrics
creator GameCallerCreator
validator OutputValidator validator OutputValidator
} }
func newDetector(logger log.Logger, metrics DetectorMetrics, creator GameCallerCreator, validator OutputValidator) *detector { func newDetector(logger log.Logger, metrics DetectorMetrics, validator OutputValidator) *detector {
return &detector{ return &detector{
logger: logger, logger: logger,
metrics: metrics, metrics: metrics,
creator: creator,
validator: validator, validator: validator,
} }
} }
......
...@@ -5,9 +5,7 @@ import ( ...@@ -5,9 +5,7 @@ import (
"errors" "errors"
"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-dispute-mon/mon/extract"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/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"
...@@ -19,39 +17,29 @@ func TestDetector_Detect(t *testing.T) { ...@@ -19,39 +17,29 @@ func TestDetector_Detect(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("NoGames", func(t *testing.T) { t.Run("NoGames", func(t *testing.T) {
detector, metrics, _, _, _ := setupDetectorTest(t) detector, metrics, _, _ := setupDetectorTest(t)
detector.Detect(context.Background(), []monTypes.EnrichedGameData{}) detector.Detect(context.Background(), []monTypes.EnrichedGameData{})
metrics.Equals(t, 0, 0, 0) metrics.Equals(t, 0, 0, 0)
metrics.Mapped(t, map[string]int{}) metrics.Mapped(t, map[string]int{})
}) })
t.Run("CheckAgreementFails", func(t *testing.T) { t.Run("CheckAgreementFails", func(t *testing.T) {
detector, metrics, creator, rollup, _ := setupDetectorTest(t) detector, metrics, rollup, _ := setupDetectorTest(t)
rollup.err = errors.New("boom") rollup.err = errors.New("boom")
creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
creator.caller.rootClaim = []common.Hash{{}}
detector.Detect(context.Background(), []monTypes.EnrichedGameData{{}}) detector.Detect(context.Background(), []monTypes.EnrichedGameData{{}})
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{})
}) })
t.Run("SingleGame", func(t *testing.T) { t.Run("SingleGame", func(t *testing.T) {
detector, metrics, creator, _, _ := setupDetectorTest(t) detector, metrics, _, _ := setupDetectorTest(t)
creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
creator.caller.rootClaim = []common.Hash{{}}
detector.Detect(context.Background(), []monTypes.EnrichedGameData{{}}) detector.Detect(context.Background(), []monTypes.EnrichedGameData{{}})
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})
}) })
t.Run("MultipleGames", func(t *testing.T) { t.Run("MultipleGames", func(t *testing.T) {
detector, metrics, creator, _, _ := setupDetectorTest(t) detector, metrics, _, _ := setupDetectorTest(t)
creator.caller.status = []types.GameStatus{
types.GameStatusInProgress,
types.GameStatusInProgress,
types.GameStatusInProgress,
}
creator.caller.rootClaim = []common.Hash{{}, {}, {}}
detector.Detect(context.Background(), []monTypes.EnrichedGameData{{}, {}, {}}) detector.Detect(context.Background(), []monTypes.EnrichedGameData{{}, {}, {}})
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})
...@@ -109,7 +97,7 @@ func TestDetector_RecordBatch(t *testing.T) { ...@@ -109,7 +97,7 @@ func TestDetector_RecordBatch(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
monitor, metrics, _, _, _ := setupDetectorTest(t) monitor, metrics, _, _ := setupDetectorTest(t)
monitor.recordBatch(test.batch) monitor.recordBatch(test.batch)
test.expect(t, metrics) test.expect(t, metrics)
}) })
...@@ -117,7 +105,7 @@ func TestDetector_RecordBatch(t *testing.T) { ...@@ -117,7 +105,7 @@ func TestDetector_RecordBatch(t *testing.T) {
} }
func TestDetector_CheckAgreement_Fails(t *testing.T) { func TestDetector_CheckAgreement_Fails(t *testing.T) {
detector, _, _, rollup, _ := setupDetectorTest(t) detector, _, rollup, _ := setupDetectorTest(t)
rollup.err = errors.New("boom") rollup.err = errors.New("boom")
_, err := detector.checkAgreement(context.Background(), common.Address{}, 0, common.Hash{}, types.GameStatusInProgress) _, err := detector.checkAgreement(context.Background(), common.Address{}, 0, common.Hash{}, types.GameStatusInProgress)
require.ErrorIs(t, err, rollup.err) require.ErrorIs(t, err, rollup.err)
...@@ -180,7 +168,7 @@ func TestDetector_CheckAgreement_Succeeds(t *testing.T) { ...@@ -180,7 +168,7 @@ func TestDetector_CheckAgreement_Succeeds(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
detector, _, _, _, logs := setupDetectorTest(t) detector, _, _, logs := setupDetectorTest(t)
batch, err := detector.checkAgreement(context.Background(), common.Address{}, 0, test.rootClaim, test.status) batch, err := detector.checkAgreement(context.Background(), common.Address{}, 0, test.rootClaim, test.status)
require.NoError(t, err) require.NoError(t, err)
test.expectBatch(&batch) test.expectBatch(&batch)
...@@ -201,14 +189,12 @@ func TestDetector_CheckAgreement_Succeeds(t *testing.T) { ...@@ -201,14 +189,12 @@ func TestDetector_CheckAgreement_Succeeds(t *testing.T) {
} }
} }
func setupDetectorTest(t *testing.T) (*detector, *mockDetectorMetricer, *mockGameCallerCreator, *stubOutputValidator, *testlog.CapturingHandler) { func setupDetectorTest(t *testing.T) (*detector, *mockDetectorMetricer, *stubOutputValidator, *testlog.CapturingHandler) {
logger, capturedLogs := testlog.CaptureLogger(t, log.LvlDebug) logger, capturedLogs := testlog.CaptureLogger(t, log.LvlDebug)
metrics := &mockDetectorMetricer{} metrics := &mockDetectorMetricer{}
caller := &mockGameCaller{}
creator := &mockGameCallerCreator{caller: caller}
validator := &stubOutputValidator{} validator := &stubOutputValidator{}
detector := newDetector(logger, metrics, creator, validator) detector := newDetector(logger, metrics, validator)
return detector, metrics, creator, validator, capturedLogs return detector, metrics, validator, capturedLogs
} }
type stubOutputValidator struct { type stubOutputValidator struct {
...@@ -224,48 +210,6 @@ func (s *stubOutputValidator) CheckRootAgreement(ctx context.Context, blockNum u ...@@ -224,48 +210,6 @@ func (s *stubOutputValidator) CheckRootAgreement(ctx context.Context, blockNum u
return rootClaim == mockRootClaim, mockRootClaim, nil return rootClaim == mockRootClaim, mockRootClaim, nil
} }
type mockGameCallerCreator struct {
calls int
err error
caller *mockGameCaller
}
func (m *mockGameCallerCreator) CreateContract(game types.GameMetadata) (extract.GameCaller, error) {
m.calls++
if m.err != nil {
return nil, m.err
}
return m.caller, nil
}
type mockGameCaller struct {
calls int
claimsCalls int
claims [][]faultTypes.Claim
status []types.GameStatus
rootClaim []common.Hash
err error
claimsErr error
}
func (m *mockGameCaller) GetGameMetadata(ctx context.Context) (uint64, common.Hash, types.GameStatus, error) {
idx := m.calls
m.calls++
if m.err != nil {
return 0, m.rootClaim[idx], m.status[idx], m.err
}
return 0, m.rootClaim[idx], m.status[idx], nil
}
func (m *mockGameCaller) GetAllClaims(ctx context.Context) ([]faultTypes.Claim, error) {
idx := m.claimsCalls
m.claimsCalls++
if m.claimsErr != nil {
return nil, m.claimsErr
}
return m.claims[idx], nil
}
type mockDetectorMetricer struct { type mockDetectorMetricer struct {
inProgress int inProgress int
defenderWon int defenderWon int
......
...@@ -33,10 +33,10 @@ func (e *Extractor) Extract(ctx context.Context, blockHash common.Hash, minTimes ...@@ -33,10 +33,10 @@ func (e *Extractor) Extract(ctx context.Context, blockHash common.Hash, minTimes
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load games: %w", err) return nil, fmt.Errorf("failed to load games: %w", err)
} }
return e.enrichGameMetadata(ctx, games), nil return e.enrichGames(ctx, games), nil
} }
func (e *Extractor) enrichGameMetadata(ctx context.Context, games []gameTypes.GameMetadata) []monTypes.EnrichedGameData { func (e *Extractor) enrichGames(ctx context.Context, games []gameTypes.GameMetadata) []monTypes.EnrichedGameData {
var enrichedGames []monTypes.EnrichedGameData var enrichedGames []monTypes.EnrichedGameData
for _, game := range games { for _, game := range games {
caller, err := e.createContract(game) caller, err := e.createContract(game)
...@@ -49,11 +49,17 @@ func (e *Extractor) enrichGameMetadata(ctx context.Context, games []gameTypes.Ga ...@@ -49,11 +49,17 @@ func (e *Extractor) enrichGameMetadata(ctx context.Context, games []gameTypes.Ga
e.logger.Error("failed to fetch game metadata", "err", err) e.logger.Error("failed to fetch game metadata", "err", err)
continue continue
} }
claims, err := caller.GetAllClaims(ctx)
if err != nil {
e.logger.Error("failed to fetch game claims", "err", err)
continue
}
enrichedGames = append(enrichedGames, monTypes.EnrichedGameData{ enrichedGames = append(enrichedGames, monTypes.EnrichedGameData{
GameMetadata: game, GameMetadata: game,
L2BlockNumber: l2BlockNum, L2BlockNumber: l2BlockNum,
RootClaim: rootClaim, RootClaim: rootClaim,
Status: status, Status: status,
Claims: claims,
}) })
} }
return enrichedGames return enrichedGames
......
...@@ -35,20 +35,37 @@ func TestExtractor_Extract(t *testing.T) { ...@@ -35,20 +35,37 @@ func TestExtractor_Extract(t *testing.T) {
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)
verifyLogs(t, logs, 1, 0) require.Equal(t, 0, creator.caller.metadataCalls)
require.Equal(t, 0, creator.caller.claimsCalls)
verifyLogs(t, logs, 1, 0, 0)
}) })
t.Run("MetadataFetchErrorLog", func(t *testing.T) { t.Run("MetadataFetchErrorLog", func(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.err = errors.New("boom") creator.caller.metadataErr = errors.New("boom")
enriched, err := extractor.Extract(context.Background(), common.Hash{}, 0) enriched, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.NoError(t, err) require.NoError(t, err)
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)
require.Equal(t, 1, creator.caller.calls) require.Equal(t, 1, creator.caller.metadataCalls)
verifyLogs(t, logs, 0, 1) require.Equal(t, 0, creator.caller.claimsCalls)
verifyLogs(t, logs, 0, 1, 0)
})
t.Run("ClaimsFetchErrorLog", func(t *testing.T) {
extractor, creator, games, logs := setupExtractorTest(t)
games.games = []gameTypes.GameMetadata{{}}
creator.caller.claimsErr = errors.New("boom")
enriched, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.NoError(t, err)
require.Len(t, enriched, 0)
require.Equal(t, 1, games.calls)
require.Equal(t, 1, creator.calls)
require.Equal(t, 1, creator.caller.metadataCalls)
require.Equal(t, 1, creator.caller.claimsCalls)
verifyLogs(t, logs, 0, 0, 1)
}) })
t.Run("Success", func(t *testing.T) { t.Run("Success", func(t *testing.T) {
...@@ -59,11 +76,12 @@ func TestExtractor_Extract(t *testing.T) { ...@@ -59,11 +76,12 @@ func TestExtractor_Extract(t *testing.T) {
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)
require.Equal(t, 1, creator.caller.calls) require.Equal(t, 1, creator.caller.metadataCalls)
require.Equal(t, 1, creator.caller.claimsCalls)
}) })
} }
func verifyLogs(t *testing.T, logs *testlog.CapturingHandler, createErr int, metadataErr int) { func verifyLogs(t *testing.T, logs *testlog.CapturingHandler, createErr int, metadataErr int, claimsErr int) {
errorLevelFilter := testlog.NewLevelFilter(log.LevelError) errorLevelFilter := testlog.NewLevelFilter(log.LevelError)
createMessageFilter := testlog.NewMessageFilter("failed to create game caller") createMessageFilter := testlog.NewMessageFilter("failed to create game caller")
l := logs.FindLogs(errorLevelFilter, createMessageFilter) l := logs.FindLogs(errorLevelFilter, createMessageFilter)
...@@ -71,6 +89,9 @@ func verifyLogs(t *testing.T, logs *testlog.CapturingHandler, createErr int, met ...@@ -71,6 +89,9 @@ func verifyLogs(t *testing.T, logs *testlog.CapturingHandler, createErr int, met
fetchMessageFilter := testlog.NewMessageFilter("failed to fetch game metadata") fetchMessageFilter := testlog.NewMessageFilter("failed to fetch game metadata")
l = logs.FindLogs(errorLevelFilter, fetchMessageFilter) l = logs.FindLogs(errorLevelFilter, fetchMessageFilter)
require.Len(t, l, metadataErr) require.Len(t, l, metadataErr)
claimsMessageFilter := testlog.NewMessageFilter("failed to fetch game claims")
l = logs.FindLogs(errorLevelFilter, claimsMessageFilter)
require.Len(t, l, claimsErr)
} }
func setupExtractorTest(t *testing.T) (*Extractor, *mockGameCallerCreator, *mockGameFetcher, *testlog.CapturingHandler) { func setupExtractorTest(t *testing.T) (*Extractor, *mockGameCallerCreator, *mockGameFetcher, *testlog.CapturingHandler) {
...@@ -117,23 +138,26 @@ func (m *mockGameCallerCreator) CreateGameCaller(_ gameTypes.GameMetadata) (Game ...@@ -117,23 +138,26 @@ func (m *mockGameCallerCreator) CreateGameCaller(_ gameTypes.GameMetadata) (Game
} }
type mockGameCaller struct { type mockGameCaller struct {
calls int metadataCalls int
err error metadataErr error
rootClaim common.Hash claimsCalls int
claimsErr error
rootClaim common.Hash
claims []faultTypes.Claim
} }
func (m *mockGameCaller) GetGameMetadata(_ context.Context) (uint64, common.Hash, types.GameStatus, error) { func (m *mockGameCaller) GetGameMetadata(_ context.Context) (uint64, common.Hash, types.GameStatus, error) {
m.calls++ m.metadataCalls++
if m.err != nil { if m.metadataErr != nil {
return 0, common.Hash{}, 0, m.err return 0, common.Hash{}, 0, m.metadataErr
} }
return 0, mockRootClaim, 0, nil return 0, mockRootClaim, 0, nil
} }
func (m *mockGameCaller) GetAllClaims(ctx context.Context) ([]faultTypes.Claim, error) { func (m *mockGameCaller) GetAllClaims(ctx context.Context) ([]faultTypes.Claim, error) {
m.calls++ m.claimsCalls++
if m.err != nil { if m.claimsErr != nil {
return nil, m.err return nil, m.claimsErr
} }
return []faultTypes.Claim{{}}, nil return m.claims, nil
} }
...@@ -26,15 +26,13 @@ type ForecastMetrics interface { ...@@ -26,15 +26,13 @@ type ForecastMetrics interface {
type forecast struct { type forecast struct {
logger log.Logger logger log.Logger
metrics ForecastMetrics metrics ForecastMetrics
creator GameCallerCreator
validator OutputValidator validator OutputValidator
} }
func newForecast(logger log.Logger, metrics ForecastMetrics, creator GameCallerCreator, validator OutputValidator) *forecast { func newForecast(logger log.Logger, metrics ForecastMetrics, validator OutputValidator) *forecast {
return &forecast{ return &forecast{
logger: logger, logger: logger,
metrics: metrics, metrics: metrics,
creator: creator,
validator: validator, validator: validator,
} }
} }
...@@ -62,19 +60,8 @@ func (f *forecast) forecastGame(ctx context.Context, game monTypes.EnrichedGameD ...@@ -62,19 +60,8 @@ func (f *forecast) forecastGame(ctx context.Context, game monTypes.EnrichedGameD
return nil return nil
} }
loader, err := f.creator.CreateContract(game.GameMetadata)
if err != nil {
return fmt.Errorf("%w: %w", ErrContractCreation, err)
}
// Load all claims for the game.
claims, err := loader.GetAllClaims(ctx)
if err != nil {
return fmt.Errorf("%w: %w", ErrClaimFetch, err)
}
// Create the bidirectional tree of claims. // Create the bidirectional tree of claims.
tree := transform.CreateBidirectionalTree(claims) tree := transform.CreateBidirectionalTree(game.Claims)
// Compute the resolution status of the game. // Compute the resolution status of the game.
status := Resolve(tree) status := Resolve(tree)
......
This diff is collapsed.
...@@ -111,11 +111,11 @@ func (s *Service) initExtractor() { ...@@ -111,11 +111,11 @@ func (s *Service) initExtractor() {
} }
func (s *Service) initForecast(cfg *config.Config) { func (s *Service) initForecast(cfg *config.Config) {
s.forecast = newForecast(s.logger, s.metrics, s.game, s.validator) s.forecast = newForecast(s.logger, s.metrics, s.validator)
} }
func (s *Service) initDetector() { func (s *Service) initDetector() {
s.detector = newDetector(s.logger, s.metrics, s.game, s.validator) s.detector = newDetector(s.logger, s.metrics, s.validator)
} }
func (s *Service) initOutputRollupClient(ctx context.Context, cfg *config.Config) error { func (s *Service) initOutputRollupClient(ctx context.Context, cfg *config.Config) error {
......
...@@ -11,6 +11,7 @@ type EnrichedGameData struct { ...@@ -11,6 +11,7 @@ type EnrichedGameData struct {
L2BlockNumber uint64 L2BlockNumber uint64
RootClaim common.Hash RootClaim common.Hash
Status types.GameStatus Status types.GameStatus
Claims []faultTypes.Claim
} }
// BidirectionalTree is a tree of claims represented as a flat list of claims. // BidirectionalTree is a tree of claims represented as a flat list of claims.
......
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