Skip to content

Commit

Permalink
feat(op-dispute-mon): extractor component (#9548)
Browse files Browse the repository at this point in the history
  • Loading branch information
refcell authored Feb 15, 2024
1 parent 0b4fb00 commit 3e44b61
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 0 deletions.
60 changes: 60 additions & 0 deletions op-dispute-mon/mon/extract/extractor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package extract

import (
"context"
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"

gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
)

type CreateGameCaller func(game gameTypes.GameMetadata) (GameCaller, error)
type FactoryGameFetcher func(ctx context.Context, blockHash common.Hash, earliestTimestamp uint64) ([]gameTypes.GameMetadata, error)

type Extractor struct {
logger log.Logger
createContract CreateGameCaller
fetchGames FactoryGameFetcher
}

func NewExtractor(logger log.Logger, creator CreateGameCaller, fetchGames FactoryGameFetcher) *Extractor {
return &Extractor{
logger: logger,
createContract: creator,
fetchGames: fetchGames,
}
}

func (e *Extractor) Extract(ctx context.Context, blockHash common.Hash, minTimestamp uint64) ([]monTypes.EnrichedGameData, error) {
games, err := e.fetchGames(ctx, blockHash, minTimestamp)
if err != nil {
return nil, fmt.Errorf("failed to load games: %w", err)
}
return e.enrichGameMetadata(ctx, games), nil
}

func (e *Extractor) enrichGameMetadata(ctx context.Context, games []gameTypes.GameMetadata) []monTypes.EnrichedGameData {
var enrichedGames []monTypes.EnrichedGameData
for _, game := range games {
caller, err := e.createContract(game)
if err != nil {
e.logger.Error("failed to create game caller", "err", err)
continue
}
l2BlockNum, rootClaim, status, err := caller.GetGameMetadata(ctx)
if err != nil {
e.logger.Error("failed to fetch game metadata", "err", err)
continue
}
enrichedGames = append(enrichedGames, monTypes.EnrichedGameData{
GameMetadata: game,
L2BlockNumber: l2BlockNum,
RootClaim: rootClaim,
Status: status,
})
}
return enrichedGames
}
139 changes: 139 additions & 0 deletions op-dispute-mon/mon/extract/extractor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package extract

import (
"context"
"errors"
"testing"

"github.com/stretchr/testify/require"

faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)

var mockRootClaim = common.HexToHash("0x1234")

func TestExtractor_Extract(t *testing.T) {
t.Run("FetchGamesError", func(t *testing.T) {
extractor, _, games, _ := setupExtractorTest(t)
games.err = errors.New("boom")
_, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.ErrorIs(t, err, games.err)
require.Equal(t, 1, games.calls)
})

t.Run("CreateGameErrorLog", func(t *testing.T) {
extractor, creator, games, logs := setupExtractorTest(t)
games.games = []gameTypes.GameMetadata{{}}
creator.err = 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)
verifyLogs(t, logs, 1, 0)
})

t.Run("MetadataFetchErrorLog", func(t *testing.T) {
extractor, creator, games, logs := setupExtractorTest(t)
games.games = []gameTypes.GameMetadata{{}}
creator.caller.err = 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)
})

t.Run("Success", func(t *testing.T) {
extractor, creator, games, _ := setupExtractorTest(t)
games.games = []gameTypes.GameMetadata{{}}
enriched, err := extractor.Extract(context.Background(), common.Hash{}, 0)
require.NoError(t, err)
require.Len(t, enriched, 1)
require.Equal(t, 1, games.calls)
require.Equal(t, 1, creator.calls)
require.Equal(t, 1, creator.caller.calls)
})
}

func verifyLogs(t *testing.T, logs *testlog.CapturingHandler, createErr int, metadataErr 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)
}

func setupExtractorTest(t *testing.T) (*Extractor, *mockGameCallerCreator, *mockGameFetcher, *testlog.CapturingHandler) {
logger, capturedLogs := testlog.CaptureLogger(t, log.LvlDebug)
games := &mockGameFetcher{}
caller := &mockGameCaller{rootClaim: mockRootClaim}
creator := &mockGameCallerCreator{caller: caller}
return NewExtractor(
logger,
creator.CreateGameCaller,
games.FetchGames,
),
creator,
games,
capturedLogs
}

type mockGameFetcher struct {
calls int
err error
games []gameTypes.GameMetadata
}

func (m *mockGameFetcher) FetchGames(_ context.Context, _ common.Hash, _ uint64) ([]gameTypes.GameMetadata, error) {
m.calls++
if m.err != nil {
return nil, m.err
}
return m.games, nil
}

type mockGameCallerCreator struct {
calls int
err error
caller *mockGameCaller
}

func (m *mockGameCallerCreator) CreateGameCaller(_ gameTypes.GameMetadata) (GameCaller, error) {
m.calls++
if m.err != nil {
return nil, m.err
}
return m.caller, nil
}

type mockGameCaller struct {
calls int
err error
rootClaim common.Hash
}

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
}
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
}
return []faultTypes.Claim{{}}, nil
}
8 changes: 8 additions & 0 deletions op-dispute-mon/mon/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ package types

import (
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/common"
)

type EnrichedGameData struct {
types.GameMetadata
L2BlockNumber uint64
RootClaim common.Hash
Status types.GameStatus
}

type StatusBatch struct {
InProgress int
DefenderWon int
Expand Down

0 comments on commit 3e44b61

Please sign in to comment.