Skip to content

Commit

Permalink
feat(op-dispute-mon): pull claim fetching into the extractor (ethereu…
Browse files Browse the repository at this point in the history
  • Loading branch information
refcell authored Feb 15, 2024
1 parent 5f1b1b2 commit 35aea1e
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 202 deletions.
4 changes: 1 addition & 3 deletions op-dispute-mon/mon/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ type DetectorMetrics interface {
type detector struct {
logger log.Logger
metrics DetectorMetrics
creator GameCallerCreator
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{
logger: logger,
metrics: metrics,
creator: creator,
validator: validator,
}
}
Expand Down
76 changes: 10 additions & 66 deletions op-dispute-mon/mon/detector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import (
"errors"
"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-dispute-mon/mon/extract"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
Expand All @@ -19,39 +17,29 @@ func TestDetector_Detect(t *testing.T) {
t.Parallel()

t.Run("NoGames", func(t *testing.T) {
detector, metrics, _, _, _ := setupDetectorTest(t)
detector, metrics, _, _ := setupDetectorTest(t)
detector.Detect(context.Background(), []monTypes.EnrichedGameData{})
metrics.Equals(t, 0, 0, 0)
metrics.Mapped(t, map[string]int{})
})

t.Run("CheckAgreementFails", func(t *testing.T) {
detector, metrics, creator, rollup, _ := setupDetectorTest(t)
detector, metrics, rollup, _ := setupDetectorTest(t)
rollup.err = errors.New("boom")
creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
creator.caller.rootClaim = []common.Hash{{}}
detector.Detect(context.Background(), []monTypes.EnrichedGameData{{}})
metrics.Equals(t, 1, 0, 0) // Status should still be metriced here!
metrics.Mapped(t, map[string]int{})
})

t.Run("SingleGame", func(t *testing.T) {
detector, metrics, creator, _, _ := setupDetectorTest(t)
creator.caller.status = []types.GameStatus{types.GameStatusInProgress}
creator.caller.rootClaim = []common.Hash{{}}
detector, metrics, _, _ := setupDetectorTest(t)
detector.Detect(context.Background(), []monTypes.EnrichedGameData{{}})
metrics.Equals(t, 1, 0, 0)
metrics.Mapped(t, map[string]int{"in_progress": 1})
})

t.Run("MultipleGames", func(t *testing.T) {
detector, metrics, creator, _, _ := setupDetectorTest(t)
creator.caller.status = []types.GameStatus{
types.GameStatusInProgress,
types.GameStatusInProgress,
types.GameStatusInProgress,
}
creator.caller.rootClaim = []common.Hash{{}, {}, {}}
detector, metrics, _, _ := setupDetectorTest(t)
detector.Detect(context.Background(), []monTypes.EnrichedGameData{{}, {}, {}})
metrics.Equals(t, 3, 0, 0)
metrics.Mapped(t, map[string]int{"in_progress": 3})
Expand Down Expand Up @@ -109,15 +97,15 @@ func TestDetector_RecordBatch(t *testing.T) {
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
monitor, metrics, _, _, _ := setupDetectorTest(t)
monitor, metrics, _, _ := setupDetectorTest(t)
monitor.recordBatch(test.batch)
test.expect(t, metrics)
})
}
}

func TestDetector_CheckAgreement_Fails(t *testing.T) {
detector, _, _, rollup, _ := setupDetectorTest(t)
detector, _, rollup, _ := setupDetectorTest(t)
rollup.err = errors.New("boom")
_, err := detector.checkAgreement(context.Background(), common.Address{}, 0, common.Hash{}, types.GameStatusInProgress)
require.ErrorIs(t, err, rollup.err)
Expand Down Expand Up @@ -180,7 +168,7 @@ func TestDetector_CheckAgreement_Succeeds(t *testing.T) {
for _, test := range tests {
test := test
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)
require.NoError(t, err)
test.expectBatch(&batch)
Expand All @@ -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)
metrics := &mockDetectorMetricer{}
caller := &mockGameCaller{}
creator := &mockGameCallerCreator{caller: caller}
validator := &stubOutputValidator{}
detector := newDetector(logger, metrics, creator, validator)
return detector, metrics, creator, validator, capturedLogs
detector := newDetector(logger, metrics, validator)
return detector, metrics, validator, capturedLogs
}

type stubOutputValidator struct {
Expand All @@ -224,48 +210,6 @@ func (s *stubOutputValidator) CheckRootAgreement(ctx context.Context, blockNum u
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 {
inProgress int
defenderWon int
Expand Down
10 changes: 8 additions & 2 deletions op-dispute-mon/mon/extract/extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ func (e *Extractor) Extract(ctx context.Context, blockHash common.Hash, minTimes
if err != nil {
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
for _, game := range games {
caller, err := e.createContract(game)
Expand All @@ -49,11 +49,17 @@ func (e *Extractor) enrichGameMetadata(ctx context.Context, games []gameTypes.Ga
e.logger.Error("failed to fetch game metadata", "err", err)
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{
GameMetadata: game,
L2BlockNumber: l2BlockNum,
RootClaim: rootClaim,
Status: status,
Claims: claims,
})
}
return enrichedGames
Expand Down
56 changes: 40 additions & 16 deletions op-dispute-mon/mon/extract/extractor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,37 @@ func TestExtractor_Extract(t *testing.T) {
require.Len(t, enriched, 0)
require.Equal(t, 1, games.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) {
extractor, creator, games, logs := setupExtractorTest(t)
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)
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.calls)
verifyLogs(t, logs, 0, 1)
require.Equal(t, 1, creator.caller.metadataCalls)
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) {
Expand All @@ -59,18 +76,22 @@ func TestExtractor_Extract(t *testing.T) {
require.Len(t, enriched, 1)
require.Equal(t, 1, games.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)
createMessageFilter := testlog.NewMessageFilter("failed to create game caller")
l := logs.FindLogs(errorLevelFilter, createMessageFilter)
require.Len(t, l, createErr)
fetchMessageFilter := testlog.NewMessageFilter("failed to fetch game metadata")
l = logs.FindLogs(errorLevelFilter, fetchMessageFilter)
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) {
Expand Down Expand Up @@ -117,23 +138,26 @@ func (m *mockGameCallerCreator) CreateGameCaller(_ gameTypes.GameMetadata) (Game
}

type mockGameCaller struct {
calls int
err error
rootClaim common.Hash
metadataCalls int
metadataErr error
claimsCalls int
claimsErr error
rootClaim common.Hash
claims []faultTypes.Claim
}

func (m *mockGameCaller) GetGameMetadata(_ context.Context) (uint64, common.Hash, types.GameStatus, error) {
m.calls++
if m.err != nil {
return 0, common.Hash{}, 0, m.err
m.metadataCalls++
if m.metadataErr != nil {
return 0, common.Hash{}, 0, m.metadataErr
}
return 0, mockRootClaim, 0, nil
}

func (m *mockGameCaller) GetAllClaims(ctx context.Context) ([]faultTypes.Claim, error) {
m.calls++
if m.err != nil {
return nil, m.err
m.claimsCalls++
if m.claimsErr != nil {
return nil, m.claimsErr
}
return []faultTypes.Claim{{}}, nil
return m.claims, nil
}
17 changes: 2 additions & 15 deletions op-dispute-mon/mon/forecast.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@ type ForecastMetrics interface {
type forecast struct {
logger log.Logger
metrics ForecastMetrics
creator GameCallerCreator
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{
logger: logger,
metrics: metrics,
creator: creator,
validator: validator,
}
}
Expand Down Expand Up @@ -62,19 +60,8 @@ func (f *forecast) forecastGame(ctx context.Context, game monTypes.EnrichedGameD
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.
tree := transform.CreateBidirectionalTree(claims)
tree := transform.CreateBidirectionalTree(game.Claims)

// Compute the resolution status of the game.
status := Resolve(tree)
Expand Down
Loading

0 comments on commit 35aea1e

Please sign in to comment.