Commit 23d0635b authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #7080 from ethereum-optimism/refcell/game-gauge

feat(op-challenger): Game GaugeVec
parents 1d861eb5 07cf16db
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/solver" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/solver"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -14,7 +15,7 @@ import ( ...@@ -14,7 +15,7 @@ import (
// Responder takes a response action & executes. // Responder takes a response action & executes.
// For full op-challenger this means executing the transaction on chain. // For full op-challenger this means executing the transaction on chain.
type Responder interface { type Responder interface {
CallResolve(ctx context.Context) (types.GameStatus, error) CallResolve(ctx context.Context) (gameTypes.GameStatus, error)
Resolve(ctx context.Context) error Resolve(ctx context.Context) error
Respond(ctx context.Context, response types.Claim) error Respond(ctx context.Context, response types.Claim) error
Step(ctx context.Context, stepData types.StepCallData) error Step(ctx context.Context, stepData types.StepCallData) error
...@@ -74,10 +75,10 @@ func (a *Agent) Act(ctx context.Context) error { ...@@ -74,10 +75,10 @@ func (a *Agent) Act(ctx context.Context) error {
// shouldResolve returns true if the agent should resolve the game. // shouldResolve returns true if the agent should resolve the game.
// This method will return false if the game is still in progress. // This method will return false if the game is still in progress.
func (a *Agent) shouldResolve(status types.GameStatus) bool { func (a *Agent) shouldResolve(status gameTypes.GameStatus) bool {
expected := types.GameStatusDefenderWon expected := gameTypes.GameStatusDefenderWon
if a.agreeWithProposedOutput { if a.agreeWithProposedOutput {
expected = types.GameStatusChallengerWon expected = gameTypes.GameStatusChallengerWon
} }
if expected != status { if expected != status {
a.log.Warn("Game will be lost", "expected", expected, "actual", status) a.log.Warn("Game will be lost", "expected", expected, "actual", status)
...@@ -89,7 +90,7 @@ func (a *Agent) shouldResolve(status types.GameStatus) bool { ...@@ -89,7 +90,7 @@ func (a *Agent) shouldResolve(status types.GameStatus) bool {
// Returns true if the game is resolvable (regardless of whether it was actually resolved) // Returns true if the game is resolvable (regardless of whether it was actually resolved)
func (a *Agent) tryResolve(ctx context.Context) bool { func (a *Agent) tryResolve(ctx context.Context) bool {
status, err := a.responder.CallResolve(ctx) status, err := a.responder.CallResolve(ctx)
if err != nil || status == types.GameStatusInProgress { if err != nil || status == gameTypes.GameStatusInProgress {
return false return false
} }
if !a.shouldResolve(status) { if !a.shouldResolve(status) {
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/test" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -19,16 +20,16 @@ import ( ...@@ -19,16 +20,16 @@ import (
func TestShouldResolve(t *testing.T) { func TestShouldResolve(t *testing.T) {
t.Run("AgreeWithProposedOutput", func(t *testing.T) { t.Run("AgreeWithProposedOutput", func(t *testing.T) {
agent, _, _ := setupTestAgent(t, true) agent, _, _ := setupTestAgent(t, true)
require.False(t, agent.shouldResolve(types.GameStatusDefenderWon)) require.False(t, agent.shouldResolve(gameTypes.GameStatusDefenderWon))
require.True(t, agent.shouldResolve(types.GameStatusChallengerWon)) require.True(t, agent.shouldResolve(gameTypes.GameStatusChallengerWon))
require.False(t, agent.shouldResolve(types.GameStatusInProgress)) require.False(t, agent.shouldResolve(gameTypes.GameStatusInProgress))
}) })
t.Run("DisagreeWithProposedOutput", func(t *testing.T) { t.Run("DisagreeWithProposedOutput", func(t *testing.T) {
agent, _, _ := setupTestAgent(t, false) agent, _, _ := setupTestAgent(t, false)
require.True(t, agent.shouldResolve(types.GameStatusDefenderWon)) require.True(t, agent.shouldResolve(gameTypes.GameStatusDefenderWon))
require.False(t, agent.shouldResolve(types.GameStatusChallengerWon)) require.False(t, agent.shouldResolve(gameTypes.GameStatusChallengerWon))
require.False(t, agent.shouldResolve(types.GameStatusInProgress)) require.False(t, agent.shouldResolve(gameTypes.GameStatusInProgress))
}) })
} }
...@@ -38,31 +39,31 @@ func TestDoNotMakeMovesWhenGameIsResolvable(t *testing.T) { ...@@ -38,31 +39,31 @@ func TestDoNotMakeMovesWhenGameIsResolvable(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
agreeWithProposedOutput bool agreeWithProposedOutput bool
callResolveStatus types.GameStatus callResolveStatus gameTypes.GameStatus
shouldResolve bool shouldResolve bool
}{ }{
{ {
name: "Agree_Losing", name: "Agree_Losing",
agreeWithProposedOutput: true, agreeWithProposedOutput: true,
callResolveStatus: types.GameStatusDefenderWon, callResolveStatus: gameTypes.GameStatusDefenderWon,
shouldResolve: false, shouldResolve: false,
}, },
{ {
name: "Agree_Winning", name: "Agree_Winning",
agreeWithProposedOutput: true, agreeWithProposedOutput: true,
callResolveStatus: types.GameStatusChallengerWon, callResolveStatus: gameTypes.GameStatusChallengerWon,
shouldResolve: true, shouldResolve: true,
}, },
{ {
name: "Disagree_Losing", name: "Disagree_Losing",
agreeWithProposedOutput: false, agreeWithProposedOutput: false,
callResolveStatus: types.GameStatusChallengerWon, callResolveStatus: gameTypes.GameStatusChallengerWon,
shouldResolve: false, shouldResolve: false,
}, },
{ {
name: "Disagree_Winning", name: "Disagree_Winning",
agreeWithProposedOutput: false, agreeWithProposedOutput: false,
callResolveStatus: types.GameStatusDefenderWon, callResolveStatus: gameTypes.GameStatusDefenderWon,
shouldResolve: true, shouldResolve: true,
}, },
} }
...@@ -126,14 +127,14 @@ func (s *stubClaimLoader) FetchClaims(ctx context.Context) ([]types.Claim, error ...@@ -126,14 +127,14 @@ func (s *stubClaimLoader) FetchClaims(ctx context.Context) ([]types.Claim, error
type stubResponder struct { type stubResponder struct {
callResolveCount int callResolveCount int
callResolveStatus types.GameStatus callResolveStatus gameTypes.GameStatus
callResolveErr error callResolveErr error
resolveCount int resolveCount int
resolveErr error resolveErr error
} }
func (s *stubResponder) CallResolve(ctx context.Context) (types.GameStatus, error) { func (s *stubResponder) CallResolve(ctx context.Context) (gameTypes.GameStatus, error) {
s.callResolveCount++ s.callResolveCount++
return s.callResolveStatus, s.callResolveErr return s.callResolveStatus, s.callResolveErr
} }
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -49,9 +50,9 @@ func NewLoaderFromBindings(fdgAddr common.Address, client bind.ContractCaller) ( ...@@ -49,9 +50,9 @@ func NewLoaderFromBindings(fdgAddr common.Address, client bind.ContractCaller) (
} }
// GetGameStatus returns the current game status. // GetGameStatus returns the current game status.
func (l *loader) GetGameStatus(ctx context.Context) (types.GameStatus, error) { func (l *loader) GetGameStatus(ctx context.Context) (gameTypes.GameStatus, error) {
status, err := l.caller.Status(&bind.CallOpts{Context: ctx}) status, err := l.caller.Status(&bind.CallOpts{Context: ctx})
return types.GameStatus(status), err return gameTypes.GameStatus(status), err
} }
// GetClaimCount returns the number of claims in the game. // GetClaimCount returns the number of claims in the game.
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -30,15 +31,15 @@ func TestLoader_GetGameStatus(t *testing.T) { ...@@ -30,15 +31,15 @@ func TestLoader_GetGameStatus(t *testing.T) {
}{ }{
{ {
name: "challenger won status", name: "challenger won status",
status: uint8(types.GameStatusChallengerWon), status: uint8(gameTypes.GameStatusChallengerWon),
}, },
{ {
name: "defender won status", name: "defender won status",
status: uint8(types.GameStatusDefenderWon), status: uint8(gameTypes.GameStatusDefenderWon),
}, },
{ {
name: "in progress status", name: "in progress status",
status: uint8(types.GameStatusInProgress), status: uint8(gameTypes.GameStatusInProgress),
}, },
{ {
name: "error bubbled up", name: "error bubbled up",
...@@ -57,7 +58,7 @@ func TestLoader_GetGameStatus(t *testing.T) { ...@@ -57,7 +58,7 @@ func TestLoader_GetGameStatus(t *testing.T) {
require.ErrorIs(t, err, mockStatusError) require.ErrorIs(t, err, mockStatusError)
} else { } else {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, types.GameStatus(test.status), status) require.Equal(t, gameTypes.GameStatus(test.status), status)
} }
}) })
} }
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
...@@ -22,7 +23,7 @@ import ( ...@@ -22,7 +23,7 @@ import (
type actor func(ctx context.Context) error type actor func(ctx context.Context) error
type GameInfo interface { type GameInfo interface {
GetGameStatus(context.Context) (types.GameStatus, error) GetGameStatus(context.Context) (gameTypes.GameStatus, error)
GetClaimCount(context.Context) (uint64, error) GetClaimCount(context.Context) (uint64, error)
} }
...@@ -31,8 +32,7 @@ type GamePlayer struct { ...@@ -31,8 +32,7 @@ type GamePlayer struct {
agreeWithProposedOutput bool agreeWithProposedOutput bool
loader GameInfo loader GameInfo
logger log.Logger logger log.Logger
status gameTypes.GameStatus
completed bool
} }
func NewGamePlayer( func NewGamePlayer(
...@@ -57,14 +57,14 @@ func NewGamePlayer( ...@@ -57,14 +57,14 @@ func NewGamePlayer(
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch game status: %w", err) return nil, fmt.Errorf("failed to fetch game status: %w", err)
} }
if status != types.GameStatusInProgress { if status != gameTypes.GameStatusInProgress {
logger.Info("Game already resolved", "status", status) logger.Info("Game already resolved", "status", status)
// Game is already complete so skip creating the trace provider, loading game inputs etc. // Game is already complete so skip creating the trace provider, loading game inputs etc.
return &GamePlayer{ return &GamePlayer{
logger: logger, logger: logger,
loader: loader, loader: loader,
agreeWithProposedOutput: cfg.AgreeWithProposedOutput, agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
completed: true, status: status,
// Act function does nothing because the game is already complete // Act function does nothing because the game is already complete
act: func(ctx context.Context) error { act: func(ctx context.Context) error {
return nil return nil
...@@ -111,32 +111,32 @@ func NewGamePlayer( ...@@ -111,32 +111,32 @@ func NewGamePlayer(
agreeWithProposedOutput: cfg.AgreeWithProposedOutput, agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
loader: loader, loader: loader,
logger: logger, logger: logger,
completed: status != types.GameStatusInProgress, status: status,
}, nil }, nil
} }
func (g *GamePlayer) ProgressGame(ctx context.Context) bool { func (g *GamePlayer) ProgressGame(ctx context.Context) gameTypes.GameStatus {
if g.completed { if g.status != gameTypes.GameStatusInProgress {
// Game is already complete so don't try to perform further actions. // Game is already complete so don't try to perform further actions.
g.logger.Trace("Skipping completed game") g.logger.Trace("Skipping completed game")
return true return g.status
} }
g.logger.Trace("Checking if actions are required") g.logger.Trace("Checking if actions are required")
if err := g.act(ctx); err != nil { if err := g.act(ctx); err != nil {
g.logger.Error("Error when acting on game", "err", err) g.logger.Error("Error when acting on game", "err", err)
} }
if status, err := g.loader.GetGameStatus(ctx); err != nil { status, err := g.loader.GetGameStatus(ctx)
if err != nil {
g.logger.Warn("Unable to retrieve game status", "err", err) g.logger.Warn("Unable to retrieve game status", "err", err)
} else { return gameTypes.GameStatusInProgress
g.logGameStatus(ctx, status)
g.completed = status != types.GameStatusInProgress
return g.completed
} }
return false g.logGameStatus(ctx, status)
g.status = status
return status
} }
func (g *GamePlayer) logGameStatus(ctx context.Context, status types.GameStatus) { func (g *GamePlayer) logGameStatus(ctx context.Context, status gameTypes.GameStatus) {
if status == types.GameStatusInProgress { if status == gameTypes.GameStatusInProgress {
claimCount, err := g.loader.GetClaimCount(ctx) claimCount, err := g.loader.GetClaimCount(ctx)
if err != nil { if err != nil {
g.logger.Error("Failed to get claim count for in progress game", "err", err) g.logger.Error("Failed to get claim count for in progress game", "err", err)
...@@ -145,11 +145,11 @@ func (g *GamePlayer) logGameStatus(ctx context.Context, status types.GameStatus) ...@@ -145,11 +145,11 @@ func (g *GamePlayer) logGameStatus(ctx context.Context, status types.GameStatus)
g.logger.Info("Game info", "claims", claimCount, "status", status) g.logger.Info("Game info", "claims", claimCount, "status", status)
return return
} }
var expectedStatus types.GameStatus var expectedStatus gameTypes.GameStatus
if g.agreeWithProposedOutput { if g.agreeWithProposedOutput {
expectedStatus = types.GameStatusChallengerWon expectedStatus = gameTypes.GameStatusChallengerWon
} else { } else {
expectedStatus = types.GameStatusDefenderWon expectedStatus = gameTypes.GameStatusDefenderWon
} }
if expectedStatus == status { if expectedStatus == status {
g.logger.Info("Game won", "status", status) g.logger.Info("Game won", "status", status)
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
...@@ -22,8 +23,8 @@ var ( ...@@ -22,8 +23,8 @@ var (
func TestProgressGame_LogErrorFromAct(t *testing.T) { func TestProgressGame_LogErrorFromAct(t *testing.T) {
handler, game, actor := setupProgressGameTest(t, true) handler, game, actor := setupProgressGameTest(t, true)
actor.actErr = errors.New("boom") actor.actErr = errors.New("boom")
done := game.ProgressGame(context.Background()) status := game.ProgressGame(context.Background())
require.False(t, done, "should not be done") require.Equal(t, gameTypes.GameStatusInProgress, status)
require.Equal(t, 1, actor.callCount, "should perform next actions") require.Equal(t, 1, actor.callCount, "should perform next actions")
errLog := handler.FindLog(log.LvlError, "Error when acting on game") errLog := handler.FindLog(log.LvlError, "Error when acting on game")
require.NotNil(t, errLog, "should log error") require.NotNil(t, errLog, "should log error")
...@@ -38,42 +39,42 @@ func TestProgressGame_LogErrorFromAct(t *testing.T) { ...@@ -38,42 +39,42 @@ func TestProgressGame_LogErrorFromAct(t *testing.T) {
func TestProgressGame_LogGameStatus(t *testing.T) { func TestProgressGame_LogGameStatus(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
status types.GameStatus status gameTypes.GameStatus
agreeWithOutput bool agreeWithOutput bool
logLevel log.Lvl logLevel log.Lvl
logMsg string logMsg string
}{ }{
{ {
name: "GameLostAsDefender", name: "GameLostAsDefender",
status: types.GameStatusChallengerWon, status: gameTypes.GameStatusChallengerWon,
agreeWithOutput: false, agreeWithOutput: false,
logLevel: log.LvlError, logLevel: log.LvlError,
logMsg: "Game lost", logMsg: "Game lost",
}, },
{ {
name: "GameLostAsChallenger", name: "GameLostAsChallenger",
status: types.GameStatusDefenderWon, status: gameTypes.GameStatusDefenderWon,
agreeWithOutput: true, agreeWithOutput: true,
logLevel: log.LvlError, logLevel: log.LvlError,
logMsg: "Game lost", logMsg: "Game lost",
}, },
{ {
name: "GameWonAsDefender", name: "GameWonAsDefender",
status: types.GameStatusDefenderWon, status: gameTypes.GameStatusDefenderWon,
agreeWithOutput: false, agreeWithOutput: false,
logLevel: log.LvlInfo, logLevel: log.LvlInfo,
logMsg: "Game won", logMsg: "Game won",
}, },
{ {
name: "GameWonAsChallenger", name: "GameWonAsChallenger",
status: types.GameStatusChallengerWon, status: gameTypes.GameStatusChallengerWon,
agreeWithOutput: true, agreeWithOutput: true,
logLevel: log.LvlInfo, logLevel: log.LvlInfo,
logMsg: "Game won", logMsg: "Game won",
}, },
{ {
name: "GameInProgress", name: "GameInProgress",
status: types.GameStatusInProgress, status: gameTypes.GameStatusInProgress,
agreeWithOutput: true, agreeWithOutput: true,
logLevel: log.LvlInfo, logLevel: log.LvlInfo,
logMsg: "Game info", logMsg: "Game info",
...@@ -85,9 +86,9 @@ func TestProgressGame_LogGameStatus(t *testing.T) { ...@@ -85,9 +86,9 @@ func TestProgressGame_LogGameStatus(t *testing.T) {
handler, game, gameState := setupProgressGameTest(t, test.agreeWithOutput) handler, game, gameState := setupProgressGameTest(t, test.agreeWithOutput)
gameState.status = test.status gameState.status = test.status
done := game.ProgressGame(context.Background()) status := game.ProgressGame(context.Background())
require.Equal(t, 1, gameState.callCount, "should perform next actions") require.Equal(t, 1, gameState.callCount, "should perform next actions")
require.Equal(t, test.status != types.GameStatusInProgress, done, "should be done when not in progress") require.Equal(t, test.status, status)
errLog := handler.FindLog(test.logLevel, test.logMsg) errLog := handler.FindLog(test.logLevel, test.logMsg)
require.NotNil(t, errLog, "should log game result") require.NotNil(t, errLog, "should log game result")
require.Equal(t, test.status, errLog.GetContextValue("status")) require.Equal(t, test.status, errLog.GetContextValue("status"))
...@@ -96,19 +97,19 @@ func TestProgressGame_LogGameStatus(t *testing.T) { ...@@ -96,19 +97,19 @@ func TestProgressGame_LogGameStatus(t *testing.T) {
} }
func TestDoNotActOnCompleteGame(t *testing.T) { func TestDoNotActOnCompleteGame(t *testing.T) {
for _, status := range []types.GameStatus{types.GameStatusChallengerWon, types.GameStatusDefenderWon} { for _, status := range []gameTypes.GameStatus{gameTypes.GameStatusChallengerWon, gameTypes.GameStatusDefenderWon} {
t.Run(status.String(), func(t *testing.T) { t.Run(status.String(), func(t *testing.T) {
_, game, gameState := setupProgressGameTest(t, true) _, game, gameState := setupProgressGameTest(t, true)
gameState.status = status gameState.status = status
done := game.ProgressGame(context.Background()) fetched := game.ProgressGame(context.Background())
require.Equal(t, 1, gameState.callCount, "acts the first time") require.Equal(t, 1, gameState.callCount, "acts the first time")
require.True(t, done, "should be done") require.Equal(t, status, fetched)
// Should not act when it knows the game is already complete // Should not act when it knows the game is already complete
done = game.ProgressGame(context.Background()) fetched = game.ProgressGame(context.Background())
require.Equal(t, 1, gameState.callCount, "does not act after game is complete") require.Equal(t, 1, gameState.callCount, "does not act after game is complete")
require.True(t, done, "should still be done") require.Equal(t, status, fetched)
}) })
} }
} }
...@@ -166,7 +167,7 @@ func setupProgressGameTest(t *testing.T, agreeWithProposedRoot bool) (*testlog.C ...@@ -166,7 +167,7 @@ func setupProgressGameTest(t *testing.T, agreeWithProposedRoot bool) (*testlog.C
} }
type stubGameState struct { type stubGameState struct {
status types.GameStatus status gameTypes.GameStatus
claimCount uint64 claimCount uint64
callCount int callCount int
actErr error actErr error
...@@ -178,7 +179,7 @@ func (s *stubGameState) Act(ctx context.Context) error { ...@@ -178,7 +179,7 @@ func (s *stubGameState) Act(ctx context.Context) error {
return s.actErr return s.actErr
} }
func (s *stubGameState) GetGameStatus(ctx context.Context) (types.GameStatus, error) { func (s *stubGameState) GetGameStatus(ctx context.Context) (gameTypes.GameStatus, error) {
return s.status, nil return s.status, nil
} }
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
...@@ -81,23 +82,23 @@ func (r *faultResponder) BuildTx(ctx context.Context, response types.Claim) ([]b ...@@ -81,23 +82,23 @@ func (r *faultResponder) BuildTx(ctx context.Context, response types.Claim) ([]b
// CallResolve determines if the resolve function on the fault dispute game contract // CallResolve determines if the resolve function on the fault dispute game contract
// would succeed. Returns the game status if the call would succeed, errors otherwise. // would succeed. Returns the game status if the call would succeed, errors otherwise.
func (r *faultResponder) CallResolve(ctx context.Context) (types.GameStatus, error) { func (r *faultResponder) CallResolve(ctx context.Context) (gameTypes.GameStatus, error) {
txData, err := r.buildResolveData() txData, err := r.buildResolveData()
if err != nil { if err != nil {
return types.GameStatusInProgress, err return gameTypes.GameStatusInProgress, err
} }
res, err := r.txMgr.Call(ctx, ethereum.CallMsg{ res, err := r.txMgr.Call(ctx, ethereum.CallMsg{
To: &r.fdgAddr, To: &r.fdgAddr,
Data: txData, Data: txData,
}, nil) }, nil)
if err != nil { if err != nil {
return types.GameStatusInProgress, err return gameTypes.GameStatusInProgress, err
} }
var status uint8 var status uint8
if err = r.fdgAbi.UnpackIntoInterface(&status, "resolve", res); err != nil { if err = r.fdgAbi.UnpackIntoInterface(&status, "resolve", res); err != nil {
return types.GameStatusInProgress, err return gameTypes.GameStatusInProgress, err
} }
return types.GameStatusFromUint8(status) return gameTypes.GameStatusFromUint8(status)
} }
// Resolve executes a resolve transaction to resolve a fault dispute game. // Resolve executes a resolve transaction to resolve a fault dispute game.
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
...@@ -32,7 +33,7 @@ func TestCallResolve(t *testing.T) { ...@@ -32,7 +33,7 @@ func TestCallResolve(t *testing.T) {
mockTxMgr.callFails = true mockTxMgr.callFails = true
status, err := responder.CallResolve(context.Background()) status, err := responder.CallResolve(context.Background())
require.ErrorIs(t, err, mockCallError) require.ErrorIs(t, err, mockCallError)
require.Equal(t, types.GameStatusInProgress, status) require.Equal(t, gameTypes.GameStatusInProgress, status)
require.Equal(t, 0, mockTxMgr.calls) require.Equal(t, 0, mockTxMgr.calls)
}) })
...@@ -41,7 +42,7 @@ func TestCallResolve(t *testing.T) { ...@@ -41,7 +42,7 @@ func TestCallResolve(t *testing.T) {
mockTxMgr.callBytes = []byte{0x00, 0x01} mockTxMgr.callBytes = []byte{0x00, 0x01}
status, err := responder.CallResolve(context.Background()) status, err := responder.CallResolve(context.Background())
require.Error(t, err) require.Error(t, err)
require.Equal(t, types.GameStatusInProgress, status) require.Equal(t, gameTypes.GameStatusInProgress, status)
require.Equal(t, 1, mockTxMgr.calls) require.Equal(t, 1, mockTxMgr.calls)
}) })
...@@ -49,7 +50,7 @@ func TestCallResolve(t *testing.T) { ...@@ -49,7 +50,7 @@ func TestCallResolve(t *testing.T) {
responder, mockTxMgr := newTestFaultResponder(t) responder, mockTxMgr := newTestFaultResponder(t)
status, err := responder.CallResolve(context.Background()) status, err := responder.CallResolve(context.Background())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, types.GameStatusInProgress, status) require.Equal(t, gameTypes.GameStatusInProgress, status)
require.Equal(t, 1, mockTxMgr.calls) require.Equal(t, 1, mockTxMgr.calls)
}) })
} }
......
...@@ -3,7 +3,6 @@ package types ...@@ -3,7 +3,6 @@ package types
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -13,36 +12,6 @@ var ( ...@@ -13,36 +12,6 @@ var (
ErrGameDepthReached = errors.New("game depth reached") ErrGameDepthReached = errors.New("game depth reached")
) )
type GameStatus uint8
const (
GameStatusInProgress GameStatus = iota
GameStatusChallengerWon
GameStatusDefenderWon
)
// String returns the string representation of the game status.
func (s GameStatus) String() string {
switch s {
case GameStatusInProgress:
return "In Progress"
case GameStatusChallengerWon:
return "Challenger Won"
case GameStatusDefenderWon:
return "Defender Won"
default:
return "Unknown"
}
}
// GameStatusFromUint8 returns a game status from the uint8 representation.
func GameStatusFromUint8(i uint8) (GameStatus, error) {
if i > 2 {
return GameStatus(i), fmt.Errorf("invalid game status: %d", i)
}
return GameStatus(i), nil
}
// PreimageOracleData encapsulates the preimage oracle data // PreimageOracleData encapsulates the preimage oracle data
// to load into the onchain oracle. // to load into the onchain oracle.
type PreimageOracleData struct { type PreimageOracleData struct {
......
package types package types
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var validGameStatuses = []GameStatus{
GameStatusInProgress,
GameStatusChallengerWon,
GameStatusDefenderWon,
}
func TestGameStatusFromUint8(t *testing.T) {
for _, status := range validGameStatuses {
t.Run(fmt.Sprintf("Valid Game Status %v", status), func(t *testing.T) {
parsed, err := GameStatusFromUint8(uint8(status))
require.NoError(t, err)
require.Equal(t, status, parsed)
})
}
t.Run("Invalid", func(t *testing.T) {
status, err := GameStatusFromUint8(3)
require.Error(t, err)
require.Equal(t, GameStatus(3), status)
})
}
func TestNewPreimageOracleData(t *testing.T) { func TestNewPreimageOracleData(t *testing.T) {
t.Run("LocalData", func(t *testing.T) { t.Run("LocalData", func(t *testing.T) {
data := NewPreimageOracleData([]byte{1, 2, 3}, []byte{4, 5, 6}, 7) data := NewPreimageOracleData([]byte{1, 2, 3}, []byte{4, 5, 6}, 7)
......
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"time" "time"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler" "github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -27,7 +26,6 @@ type gameScheduler interface { ...@@ -27,7 +26,6 @@ type gameScheduler interface {
type gameMonitor struct { type gameMonitor struct {
logger log.Logger logger log.Logger
metrics metrics.Metricer
clock clock.Clock clock clock.Clock
source gameSource source gameSource
scheduler gameScheduler scheduler gameScheduler
...@@ -38,7 +36,6 @@ type gameMonitor struct { ...@@ -38,7 +36,6 @@ type gameMonitor struct {
func newGameMonitor( func newGameMonitor(
logger log.Logger, logger log.Logger,
m metrics.Metricer,
cl clock.Clock, cl clock.Clock,
source gameSource, source gameSource,
scheduler gameScheduler, scheduler gameScheduler,
...@@ -48,7 +45,6 @@ func newGameMonitor( ...@@ -48,7 +45,6 @@ func newGameMonitor(
) *gameMonitor { ) *gameMonitor {
return &gameMonitor{ return &gameMonitor{
logger: logger, logger: logger,
metrics: m,
clock: cl, clock: cl,
scheduler: scheduler, scheduler: scheduler,
source: source, source: source,
......
...@@ -6,7 +6,6 @@ import ( ...@@ -6,7 +6,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -101,7 +100,7 @@ func setupMonitorTest(t *testing.T, allowedGames []common.Address) (*gameMonitor ...@@ -101,7 +100,7 @@ func setupMonitorTest(t *testing.T, allowedGames []common.Address) (*gameMonitor
return i, nil return i, nil
} }
sched := &stubScheduler{} sched := &stubScheduler{}
monitor := newGameMonitor(logger, metrics.NoopMetrics, clock.SystemClock, source, sched, time.Duration(0), fetchBlockNum, allowedGames) monitor := newGameMonitor(logger, clock.SystemClock, source, sched, time.Duration(0), fetchBlockNum, allowedGames)
return monitor, source, sched return monitor, source, sched
} }
......
...@@ -5,6 +5,8 @@ import ( ...@@ -5,6 +5,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
...@@ -17,7 +19,7 @@ type PlayerCreator func(address common.Address, dir string) (GamePlayer, error) ...@@ -17,7 +19,7 @@ type PlayerCreator func(address common.Address, dir string) (GamePlayer, error)
type gameState struct { type gameState struct {
player GamePlayer player GamePlayer
inflight bool inflight bool
resolved bool status types.GameStatus
} }
// coordinator manages the set of current games, queues games to be played (on separate worker threads) and // coordinator manages the set of current games, queues games to be played (on separate worker threads) and
...@@ -31,6 +33,7 @@ type coordinator struct { ...@@ -31,6 +33,7 @@ type coordinator struct {
resultQueue <-chan job resultQueue <-chan job
logger log.Logger logger log.Logger
m SchedulerMetricer
createPlayer PlayerCreator createPlayer PlayerCreator
states map[common.Address]*gameState states map[common.Address]*gameState
disk DiskManager disk DiskManager
...@@ -49,18 +52,35 @@ func (c *coordinator) schedule(ctx context.Context, games []common.Address) erro ...@@ -49,18 +52,35 @@ func (c *coordinator) schedule(ctx context.Context, games []common.Address) erro
} }
} }
var gamesInProgress int
var gamesChallengerWon int
var gamesDefenderWon int
var errs []error var errs []error
var jobs []job
// Next collect all the jobs to schedule and ensure all games are recorded in the states map. // Next collect all the jobs to schedule and ensure all games are recorded in the states map.
// Otherwise, results may start being processed before all games are recorded, resulting in existing // Otherwise, results may start being processed before all games are recorded, resulting in existing
// data directories potentially being deleted for games that are required. // data directories potentially being deleted for games that are required.
var jobs []job
for _, addr := range games { for _, addr := range games {
if j, err := c.createJob(addr); err != nil { if j, err := c.createJob(addr); err != nil {
errs = append(errs, err) errs = append(errs, err)
} else if j != nil { } else if j != nil {
jobs = append(jobs, *j) jobs = append(jobs, *j)
} }
state, ok := c.states[addr]
if ok {
switch state.status {
case types.GameStatusInProgress:
gamesInProgress++
case types.GameStatusDefenderWon:
gamesDefenderWon++
case types.GameStatusChallengerWon:
gamesChallengerWon++
}
} else {
c.logger.Warn("Game not found in states map", "game", addr)
}
} }
c.m.RecordGamesStatus(gamesInProgress, gamesChallengerWon, gamesDefenderWon)
// Finally, enqueue the jobs // Finally, enqueue the jobs
for _, j := range jobs { for _, j := range jobs {
...@@ -114,7 +134,7 @@ func (c *coordinator) processResult(j job) error { ...@@ -114,7 +134,7 @@ func (c *coordinator) processResult(j job) error {
return fmt.Errorf("game %v received unexpected result: %w", j.addr, errUnknownGame) return fmt.Errorf("game %v received unexpected result: %w", j.addr, errUnknownGame)
} }
state.inflight = false state.inflight = false
state.resolved = j.resolved state.status = j.status
c.deleteResolvedGameFiles() c.deleteResolvedGameFiles()
return nil return nil
} }
...@@ -122,7 +142,7 @@ func (c *coordinator) processResult(j job) error { ...@@ -122,7 +142,7 @@ func (c *coordinator) processResult(j job) error {
func (c *coordinator) deleteResolvedGameFiles() { func (c *coordinator) deleteResolvedGameFiles() {
var keepGames []common.Address var keepGames []common.Address
for addr, state := range c.states { for addr, state := range c.states {
if !state.resolved || state.inflight { if state.status == types.GameStatusInProgress || state.inflight {
keepGames = append(keepGames, addr) keepGames = append(keepGames, addr)
} }
} }
...@@ -131,9 +151,10 @@ func (c *coordinator) deleteResolvedGameFiles() { ...@@ -131,9 +151,10 @@ func (c *coordinator) deleteResolvedGameFiles() {
} }
} }
func newCoordinator(logger log.Logger, jobQueue chan<- job, resultQueue <-chan job, createPlayer PlayerCreator, disk DiskManager) *coordinator { func newCoordinator(logger log.Logger, m SchedulerMetricer, jobQueue chan<- job, resultQueue <-chan job, createPlayer PlayerCreator, disk DiskManager) *coordinator {
return &coordinator{ return &coordinator{
logger: logger, logger: logger,
m: m,
jobQueue: jobQueue, jobQueue: jobQueue,
resultQueue: resultQueue, resultQueue: resultQueue,
createPlayer: createPlayer, createPlayer: createPlayer,
......
...@@ -5,6 +5,8 @@ import ( ...@@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -140,7 +142,7 @@ func TestDeleteDataForResolvedGames(t *testing.T) { ...@@ -140,7 +142,7 @@ func TestDeleteDataForResolvedGames(t *testing.T) {
require.NoError(t, c.schedule(ctx, []common.Address{gameAddr3})) require.NoError(t, c.schedule(ctx, []common.Address{gameAddr3}))
require.Len(t, workQueue, 1) require.Len(t, workQueue, 1)
j := <-workQueue j := <-workQueue
j.resolved = true j.status = types.GameStatusDefenderWon
require.NoError(t, c.processResult(j)) require.NoError(t, c.processResult(j))
// But ensure its data directory is marked as existing // But ensure its data directory is marked as existing
disk.DirForGame(gameAddr3) disk.DirForGame(gameAddr3)
...@@ -155,7 +157,9 @@ func TestDeleteDataForResolvedGames(t *testing.T) { ...@@ -155,7 +157,9 @@ func TestDeleteDataForResolvedGames(t *testing.T) {
// Game 3 hasn't yet progressed (update is still in flight) // Game 3 hasn't yet progressed (update is still in flight)
for i := 0; i < len(gameAddrs)-1; i++ { for i := 0; i < len(gameAddrs)-1; i++ {
j := <-workQueue j := <-workQueue
j.resolved = j.addr == gameAddr2 if j.addr == gameAddr2 {
j.status = types.GameStatusDefenderWon
}
require.NoError(t, c.processResult(j)) require.NoError(t, c.processResult(j))
} }
...@@ -229,20 +233,20 @@ func setupCoordinatorTest(t *testing.T, bufferSize int) (*coordinator, <-chan jo ...@@ -229,20 +233,20 @@ func setupCoordinatorTest(t *testing.T, bufferSize int) (*coordinator, <-chan jo
created: make(map[common.Address]*stubGame), created: make(map[common.Address]*stubGame),
} }
disk := &stubDiskManager{gameDirExists: make(map[common.Address]bool)} disk := &stubDiskManager{gameDirExists: make(map[common.Address]bool)}
c := newCoordinator(logger, workQueue, resultQueue, games.CreateGame, disk) c := newCoordinator(logger, metrics.NoopMetrics, workQueue, resultQueue, games.CreateGame, disk)
return c, workQueue, resultQueue, games, disk return c, workQueue, resultQueue, games, disk
} }
type stubGame struct { type stubGame struct {
addr common.Address addr common.Address
progressCount int progressCount int
done bool status types.GameStatus
dir string dir string
} }
func (g *stubGame) ProgressGame(_ context.Context) bool { func (g *stubGame) ProgressGame(_ context.Context) types.GameStatus {
g.progressCount++ g.progressCount++
return g.done return g.status
} }
type createdGames struct { type createdGames struct {
...@@ -259,10 +263,14 @@ func (c *createdGames) CreateGame(addr common.Address, dir string) (GamePlayer, ...@@ -259,10 +263,14 @@ func (c *createdGames) CreateGame(addr common.Address, dir string) (GamePlayer,
if _, exists := c.created[addr]; exists { if _, exists := c.created[addr]; exists {
c.t.Fatalf("game %v already exists", addr) c.t.Fatalf("game %v already exists", addr)
} }
status := types.GameStatusInProgress
if addr == c.createCompleted {
status = types.GameStatusDefenderWon
}
game := &stubGame{ game := &stubGame{
addr: addr, addr: addr,
done: addr == c.createCompleted, status: status,
dir: dir, dir: dir,
} }
c.created[addr] = game c.created[addr] = game
return game, nil return game, nil
......
...@@ -11,6 +11,10 @@ import ( ...@@ -11,6 +11,10 @@ import (
var ErrBusy = errors.New("busy scheduling previous update") var ErrBusy = errors.New("busy scheduling previous update")
type SchedulerMetricer interface {
RecordGamesStatus(inProgress, defenderWon, challengerWon int)
}
type Scheduler struct { type Scheduler struct {
logger log.Logger logger log.Logger
coordinator *coordinator coordinator *coordinator
...@@ -22,7 +26,7 @@ type Scheduler struct { ...@@ -22,7 +26,7 @@ type Scheduler struct {
cancel func() cancel func()
} }
func NewScheduler(logger log.Logger, disk DiskManager, maxConcurrency uint, createPlayer PlayerCreator) *Scheduler { func NewScheduler(logger log.Logger, m SchedulerMetricer, disk DiskManager, maxConcurrency uint, createPlayer PlayerCreator) *Scheduler {
// Size job and results queues to be fairly small so backpressure is applied early // Size job and results queues to be fairly small so backpressure is applied early
// but with enough capacity to keep the workers busy // but with enough capacity to keep the workers busy
jobQueue := make(chan job, maxConcurrency*2) jobQueue := make(chan job, maxConcurrency*2)
...@@ -34,7 +38,7 @@ func NewScheduler(logger log.Logger, disk DiskManager, maxConcurrency uint, crea ...@@ -34,7 +38,7 @@ func NewScheduler(logger log.Logger, disk DiskManager, maxConcurrency uint, crea
return &Scheduler{ return &Scheduler{
logger: logger, logger: logger,
coordinator: newCoordinator(logger, jobQueue, resultQueue, createPlayer, disk), coordinator: newCoordinator(logger, m, jobQueue, resultQueue, createPlayer, disk),
maxConcurrency: maxConcurrency, maxConcurrency: maxConcurrency,
scheduleQueue: scheduleQueue, scheduleQueue: scheduleQueue,
jobQueue: jobQueue, jobQueue: jobQueue,
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -18,7 +19,7 @@ func TestSchedulerProcessesGames(t *testing.T) { ...@@ -18,7 +19,7 @@ func TestSchedulerProcessesGames(t *testing.T) {
} }
removeExceptCalls := make(chan []common.Address) removeExceptCalls := make(chan []common.Address)
disk := &trackingDiskManager{removeExceptCalls: removeExceptCalls} disk := &trackingDiskManager{removeExceptCalls: removeExceptCalls}
s := NewScheduler(logger, disk, 2, createPlayer) s := NewScheduler(logger, metrics.NoopMetrics, disk, 2, createPlayer)
s.Start(ctx) s.Start(ctx)
gameAddr1 := common.Address{0xaa} gameAddr1 := common.Address{0xaa}
...@@ -46,7 +47,7 @@ func TestReturnBusyWhenScheduleQueueFull(t *testing.T) { ...@@ -46,7 +47,7 @@ func TestReturnBusyWhenScheduleQueueFull(t *testing.T) {
} }
removeExceptCalls := make(chan []common.Address) removeExceptCalls := make(chan []common.Address)
disk := &trackingDiskManager{removeExceptCalls: removeExceptCalls} disk := &trackingDiskManager{removeExceptCalls: removeExceptCalls}
s := NewScheduler(logger, disk, 2, createPlayer) s := NewScheduler(logger, metrics.NoopMetrics, disk, 2, createPlayer)
// Scheduler not started - first call fills the queue // Scheduler not started - first call fills the queue
require.NoError(t, s.Schedule([]common.Address{{0xaa}})) require.NoError(t, s.Schedule([]common.Address{{0xaa}}))
......
...@@ -4,10 +4,12 @@ import ( ...@@ -4,10 +4,12 @@ import (
"context" "context"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
) )
type GamePlayer interface { type GamePlayer interface {
ProgressGame(ctx context.Context) bool ProgressGame(ctx context.Context) types.GameStatus
} }
type DiskManager interface { type DiskManager interface {
...@@ -16,7 +18,7 @@ type DiskManager interface { ...@@ -16,7 +18,7 @@ type DiskManager interface {
} }
type job struct { type job struct {
addr common.Address addr common.Address
player GamePlayer player GamePlayer
resolved bool status types.GameStatus
} }
...@@ -15,7 +15,7 @@ func progressGames(ctx context.Context, in <-chan job, out chan<- job, wg *sync. ...@@ -15,7 +15,7 @@ func progressGames(ctx context.Context, in <-chan job, out chan<- job, wg *sync.
case <-ctx.Done(): case <-ctx.Done():
return return
case j := <-in: case j := <-in:
j.resolved = j.player.ProgressGame(ctx) j.status = j.player.ProgressGame(ctx)
out <- j out <- j
} }
} }
......
...@@ -6,6 +6,8 @@ import ( ...@@ -6,6 +6,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -20,17 +22,17 @@ func TestWorkerShouldProcessJobsUntilContextDone(t *testing.T) { ...@@ -20,17 +22,17 @@ func TestWorkerShouldProcessJobsUntilContextDone(t *testing.T) {
go progressGames(ctx, in, out, &wg) go progressGames(ctx, in, out, &wg)
in <- job{ in <- job{
player: &stubPlayer{done: false}, player: &stubPlayer{status: types.GameStatusInProgress},
} }
in <- job{ in <- job{
player: &stubPlayer{done: true}, player: &stubPlayer{status: types.GameStatusDefenderWon},
} }
result1 := readWithTimeout(t, out) result1 := readWithTimeout(t, out)
result2 := readWithTimeout(t, out) result2 := readWithTimeout(t, out)
require.Equal(t, result1.resolved, false) require.Equal(t, result1.status, types.GameStatusInProgress)
require.Equal(t, result2.resolved, true) require.Equal(t, result2.status, types.GameStatusDefenderWon)
// Cancel the context which should exit the worker // Cancel the context which should exit the worker
cancel() cancel()
...@@ -38,11 +40,11 @@ func TestWorkerShouldProcessJobsUntilContextDone(t *testing.T) { ...@@ -38,11 +40,11 @@ func TestWorkerShouldProcessJobsUntilContextDone(t *testing.T) {
} }
type stubPlayer struct { type stubPlayer struct {
done bool status types.GameStatus
} }
func (s *stubPlayer) ProgressGame(ctx context.Context) bool { func (s *stubPlayer) ProgressGame(ctx context.Context) types.GameStatus {
return s.done return s.status
} }
func readWithTimeout[T any](t *testing.T, ch <-chan T) T { func readWithTimeout[T any](t *testing.T, ch <-chan T) T {
......
...@@ -69,13 +69,14 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Se ...@@ -69,13 +69,14 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Se
disk := newDiskManager(cfg.Datadir) disk := newDiskManager(cfg.Datadir)
sched := scheduler.NewScheduler( sched := scheduler.NewScheduler(
logger, logger,
m,
disk, disk,
cfg.MaxConcurrency, cfg.MaxConcurrency,
func(addr common.Address, dir string) (scheduler.GamePlayer, error) { func(addr common.Address, dir string) (scheduler.GamePlayer, error) {
return fault.NewGamePlayer(ctx, logger, m, cfg, dir, addr, txMgr, client) return fault.NewGamePlayer(ctx, logger, m, cfg, dir, addr, txMgr, client)
}) })
monitor := newGameMonitor(logger, m, cl, loader, sched, cfg.GameWindow, client.BlockNumber, cfg.GameAllowlist) monitor := newGameMonitor(logger, cl, loader, sched, cfg.GameWindow, client.BlockNumber, cfg.GameAllowlist)
m.RecordInfo(version.SimpleWithMeta) m.RecordInfo(version.SimpleWithMeta)
m.RecordUp() m.RecordUp()
......
package types
import (
"fmt"
)
type GameStatus uint8
const (
GameStatusInProgress GameStatus = iota
GameStatusChallengerWon
GameStatusDefenderWon
)
// String returns the string representation of the game status.
func (s GameStatus) String() string {
switch s {
case GameStatusInProgress:
return "In Progress"
case GameStatusChallengerWon:
return "Challenger Won"
case GameStatusDefenderWon:
return "Defender Won"
default:
return "Unknown"
}
}
// GameStatusFromUint8 returns a game status from the uint8 representation.
func GameStatusFromUint8(i uint8) (GameStatus, error) {
if i > 2 {
return GameStatus(i), fmt.Errorf("invalid game status: %d", i)
}
return GameStatus(i), nil
}
package types
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
var validGameStatuses = []GameStatus{
GameStatusInProgress,
GameStatusChallengerWon,
GameStatusDefenderWon,
}
func TestGameStatusFromUint8(t *testing.T) {
for _, status := range validGameStatuses {
t.Run(fmt.Sprintf("Valid Game Status %v", status), func(t *testing.T) {
parsed, err := GameStatusFromUint8(uint8(status))
require.NoError(t, err)
require.Equal(t, status, parsed)
})
}
t.Run("Invalid", func(t *testing.T) {
status, err := GameStatusFromUint8(3)
require.Error(t, err)
require.Equal(t, GameStatus(3), status)
})
}
...@@ -24,6 +24,8 @@ type Metricer interface { ...@@ -24,6 +24,8 @@ type Metricer interface {
RecordGameStep() RecordGameStep()
RecordGameMove() RecordGameMove()
RecordCannonExecutionTime(t float64) RecordCannonExecutionTime(t float64)
RecordGamesStatus(inProgress, defenderWon, challengerWon int)
} }
type Metrics struct { type Metrics struct {
...@@ -36,9 +38,12 @@ type Metrics struct { ...@@ -36,9 +38,12 @@ type Metrics struct {
info prometheus.GaugeVec info prometheus.GaugeVec
up prometheus.Gauge up prometheus.Gauge
moves prometheus.Counter moves prometheus.Counter
steps prometheus.Counter steps prometheus.Counter
cannonExecutionTime prometheus.Histogram cannonExecutionTime prometheus.Histogram
trackedGames prometheus.GaugeVec
} }
var _ Metricer = (*Metrics)(nil) var _ Metricer = (*Metrics)(nil)
...@@ -82,6 +87,13 @@ func NewMetrics() *Metrics { ...@@ -82,6 +87,13 @@ func NewMetrics() *Metrics {
Help: "Time (in seconds) to execute cannon", Help: "Time (in seconds) to execute cannon",
Buckets: append([]float64{1.0, 10.0}, prometheus.ExponentialBuckets(30.0, 2.0, 14)...), Buckets: append([]float64{1.0, 10.0}, prometheus.ExponentialBuckets(30.0, 2.0, 14)...),
}), }),
trackedGames: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: Namespace,
Name: "tracked_games",
Help: "Number of games being tracked by the challenger",
}, []string{
"status",
}),
} }
} }
...@@ -120,3 +132,9 @@ func (m *Metrics) RecordGameStep() { ...@@ -120,3 +132,9 @@ func (m *Metrics) RecordGameStep() {
func (m *Metrics) RecordCannonExecutionTime(t float64) { func (m *Metrics) RecordCannonExecutionTime(t float64) {
m.cannonExecutionTime.Observe(t) m.cannonExecutionTime.Observe(t)
} }
func (m *Metrics) RecordGamesStatus(inProgress, defenderWon, challengerWon int) {
m.trackedGames.WithLabelValues("in_progress").Set(float64(inProgress))
m.trackedGames.WithLabelValues("defender_won").Set(float64(defenderWon))
m.trackedGames.WithLabelValues("challenger_won").Set(float64(challengerWon))
}
...@@ -10,8 +10,12 @@ type noopMetrics struct { ...@@ -10,8 +10,12 @@ type noopMetrics struct {
var NoopMetrics Metricer = new(noopMetrics) var NoopMetrics Metricer = new(noopMetrics)
func (*noopMetrics) RecordInfo(version string) {} func (*noopMetrics) RecordInfo(version string) {}
func (*noopMetrics) RecordUp() {} func (*noopMetrics) RecordUp() {}
func (*noopMetrics) RecordGameMove() {}
func (*noopMetrics) RecordGameStep() {} func (*noopMetrics) RecordGameMove() {}
func (*noopMetrics) RecordGameStep() {}
func (*noopMetrics) RecordCannonExecutionTime(t float64) {} func (*noopMetrics) RecordCannonExecutionTime(t float64) {}
func (*noopMetrics) RecordGamesStatus(inProgress, defenderWon, challengerWon 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