Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(op-dispute-mon): Introduce the Extractor Component #9548

Merged
merged 1 commit into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading