Commit 3c0e8a98 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Improve logging (#6394)

Fix handling of transaction failures when resolving the game

Avoid duplicate logging of errors
Reduce expected situations to debug level
Log an error if the game is lost
Consistently start log messages with a capital letter
parent 7eb478fa
...@@ -31,7 +31,7 @@ func Main(ctx context.Context, logger log.Logger, cfg *config.Config) error { ...@@ -31,7 +31,7 @@ func Main(ctx context.Context, logger log.Logger, cfg *config.Config) error {
return fmt.Errorf("failed to bind the fault dispute game contract: %w", err) return fmt.Errorf("failed to bind the fault dispute game contract: %w", err)
} }
loader := fault.NewLoader(logger, contract) loader := fault.NewLoader(contract)
responder, err := fault.NewFaultResponder(logger, txMgr, cfg.GameAddress) responder, err := fault.NewFaultResponder(logger, txMgr, cfg.GameAddress)
if err != nil { if err != nil {
return fmt.Errorf("failed to create the responder: %w", err) return fmt.Errorf("failed to create the responder: %w", err)
...@@ -45,14 +45,27 @@ func Main(ctx context.Context, logger log.Logger, cfg *config.Config) error { ...@@ -45,14 +45,27 @@ func Main(ctx context.Context, logger log.Logger, cfg *config.Config) error {
return fmt.Errorf("failed to bind the fault contract: %w", err) return fmt.Errorf("failed to bind the fault contract: %w", err)
} }
logger.Info("Fault game started") logger.Info("Monitoring fault dispute game", "game", cfg.GameAddress, "agreeWithOutput", cfg.AgreeWithProposedOutput)
for { for {
logger.Info("Performing action") logger.Trace("Checking if actions are required", "game", cfg.GameAddress)
_ = agent.Act(ctx) if err = agent.Act(ctx); err != nil {
status, _ := caller.GetGameStatus(ctx) logger.Error("Error when acting on game", "err", err)
if status != 0 { }
caller.LogGameStatus(ctx) if status, err := caller.GetGameStatus(ctx); err != nil {
logger.Warn("Unable to retrieve game status", "err", err)
} else if status != 0 {
var expectedStatus fault.GameStatus
if cfg.AgreeWithProposedOutput {
expectedStatus = fault.GameStatusChallengerWon
} else {
expectedStatus = fault.GameStatusDefenderWon
}
if expectedStatus == status {
logger.Info("Game won", "status", fault.GameStatusString(status))
} else {
logger.Error("Game lost", "status", fault.GameStatusString(status))
}
return nil return nil
} else { } else {
caller.LogGameInfo(ctx) caller.LogGameInfo(ctx)
......
...@@ -35,12 +35,11 @@ func (a *Agent) Act(ctx context.Context) error { ...@@ -35,12 +35,11 @@ func (a *Agent) Act(ctx context.Context) error {
} }
game, err := a.newGameFromContracts(ctx) game, err := a.newGameFromContracts(ctx)
if err != nil { if err != nil {
a.log.Error("Failed to create new game", "err", err) return fmt.Errorf("create game from contracts: %w", err)
return err
} }
// Create counter claims // Create counter claims
for _, claim := range game.Claims() { for _, claim := range game.Claims() {
if err := a.move(ctx, claim, game); err != nil { if err := a.move(ctx, claim, game); err != nil && !errors.Is(err, ErrGameDepthReached) {
log.Error("Failed to move", "err", err) log.Error("Failed to move", "err", err)
} }
} }
...@@ -57,11 +56,13 @@ func (a *Agent) Act(ctx context.Context) error { ...@@ -57,11 +56,13 @@ func (a *Agent) Act(ctx context.Context) error {
// and returns true if the game resolves successfully. // and returns true if the game resolves successfully.
func (a *Agent) tryResolve(ctx context.Context) bool { func (a *Agent) tryResolve(ctx context.Context) bool {
if a.responder.CanResolve(ctx) { if a.responder.CanResolve(ctx) {
a.log.Info("Resolving game")
err := a.responder.Resolve(ctx) err := a.responder.Resolve(ctx)
if err != nil { if err != nil {
return true a.log.Error("Failed to resolve the game", "err", err)
return false
} }
a.log.Error("failed to resolve the game", "err", err) return true
} }
return false return false
} }
...@@ -86,8 +87,7 @@ func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) { ...@@ -86,8 +87,7 @@ func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) {
func (a *Agent) move(ctx context.Context, claim Claim, game Game) error { func (a *Agent) move(ctx context.Context, claim Claim, game Game) error {
nextMove, err := a.solver.NextMove(claim, game.AgreeWithClaimLevel(claim)) nextMove, err := a.solver.NextMove(claim, game.AgreeWithClaimLevel(claim))
if err != nil { if err != nil {
a.log.Warn("Failed to execute the next move", "err", err) return fmt.Errorf("execute next move: %w", err)
return err
} }
if nextMove == nil { if nextMove == nil {
a.log.Debug("No next move") a.log.Debug("No next move")
...@@ -98,7 +98,7 @@ func (a *Agent) move(ctx context.Context, claim Claim, game Game) error { ...@@ -98,7 +98,7 @@ func (a *Agent) move(ctx context.Context, claim Claim, game Game) error {
"value", move.Value, "trace_index", move.TraceIndex(a.maxDepth), "value", move.Value, "trace_index", move.TraceIndex(a.maxDepth),
"parent_value", claim.Value, "parent_trace_index", claim.TraceIndex(a.maxDepth)) "parent_value", claim.Value, "parent_trace_index", claim.TraceIndex(a.maxDepth))
if game.IsDuplicate(move) { if game.IsDuplicate(move) {
log.Debug("Duplicate move") log.Debug("Skipping duplicate move")
return nil return nil
} }
log.Info("Performing move") log.Info("Performing move")
...@@ -113,20 +113,19 @@ func (a *Agent) step(ctx context.Context, claim Claim, game Game) error { ...@@ -113,20 +113,19 @@ func (a *Agent) step(ctx context.Context, claim Claim, game Game) error {
agreeWithClaimLevel := game.AgreeWithClaimLevel(claim) agreeWithClaimLevel := game.AgreeWithClaimLevel(claim)
if agreeWithClaimLevel { if agreeWithClaimLevel {
a.log.Warn("Agree with leaf claim, skipping step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth) a.log.Debug("Agree with leaf claim, skipping step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
return nil return nil
} }
if claim.Countered { if claim.Countered {
a.log.Info("Claim already stepped on", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth) a.log.Debug("Step already executed against claim", "depth", claim.Depth(), "index_at_depth", claim.IndexAtDepth(), "value", claim.Value)
return nil return nil
} }
a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth) a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
step, err := a.solver.AttemptStep(claim, agreeWithClaimLevel) step, err := a.solver.AttemptStep(claim, agreeWithClaimLevel)
if err != nil { if err != nil {
a.log.Warn("Failed to get a step", "err", err) return fmt.Errorf("attempt step: %w", err)
return err
} }
a.log.Info("Performing step", "is_attack", step.IsAttack, a.log.Info("Performing step", "is_attack", step.IsAttack,
......
...@@ -62,17 +62,9 @@ func (fc *FaultCaller) LogGameInfo(ctx context.Context) { ...@@ -62,17 +62,9 @@ func (fc *FaultCaller) LogGameInfo(ctx context.Context) {
// 0: In Progress // 0: In Progress
// 1: Challenger Won // 1: Challenger Won
// 2: Defender Won // 2: Defender Won
func (fc *FaultCaller) GetGameStatus(ctx context.Context) (uint8, error) { func (fc *FaultCaller) GetGameStatus(ctx context.Context) (GameStatus, error) {
return fc.Status(&bind.CallOpts{Context: ctx}) status, err := fc.Status(&bind.CallOpts{Context: ctx})
} return GameStatus(status), err
func (fc *FaultCaller) LogGameStatus(ctx context.Context) {
status, err := fc.GetGameStatus(ctx)
if err != nil {
fc.log.Error("failed to get game status", "err", err)
return
}
fc.log.Info("Game status", "status", GameStatusString(status))
} }
// GetClaimDataLength returns the number of claims in the game. // GetClaimDataLength returns the number of claims in the game.
...@@ -90,13 +82,13 @@ func (fc *FaultCaller) LogClaimDataLength(ctx context.Context) { ...@@ -90,13 +82,13 @@ func (fc *FaultCaller) LogClaimDataLength(ctx context.Context) {
} }
// GameStatusString returns the current game status as a string. // GameStatusString returns the current game status as a string.
func GameStatusString(status uint8) string { func GameStatusString(status GameStatus) string {
switch status { switch status {
case 0: case GameStatusInProgress:
return "In Progress" return "In Progress"
case 1: case GameStatusChallengerWon:
return "Challenger Won" return "Challenger Won"
case 2: case GameStatusDefenderWon:
return "Defender Won" return "Defender Won"
default: default:
return "Unknown" return "Unknown"
......
...@@ -42,7 +42,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) { ...@@ -42,7 +42,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
caller FaultDisputeGameCaller caller FaultDisputeGameCaller
expectedStatus uint8 expectedStatus GameStatus
expectedErr error expectedErr error
}{ }{
{ {
...@@ -50,7 +50,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) { ...@@ -50,7 +50,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) {
caller: &mockFaultDisputeGameCaller{ caller: &mockFaultDisputeGameCaller{
status: 1, status: 1,
}, },
expectedStatus: 1, expectedStatus: GameStatusChallengerWon,
expectedErr: nil, expectedErr: nil,
}, },
{ {
......
...@@ -5,7 +5,6 @@ import ( ...@@ -5,7 +5,6 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/log"
) )
// ClaimFetcher is a minimal interface around [bindings.FaultDisputeGameCaller]. // ClaimFetcher is a minimal interface around [bindings.FaultDisputeGameCaller].
...@@ -28,14 +27,12 @@ type Loader interface { ...@@ -28,14 +27,12 @@ type Loader interface {
// loader pulls in fault dispute game claim data periodically and over subscriptions. // loader pulls in fault dispute game claim data periodically and over subscriptions.
type loader struct { type loader struct {
log log.Logger
claimFetcher ClaimFetcher claimFetcher ClaimFetcher
} }
// NewLoader creates a new [loader]. // NewLoader creates a new [loader].
func NewLoader(log log.Logger, claimFetcher ClaimFetcher) *loader { func NewLoader(claimFetcher ClaimFetcher) *loader {
return &loader{ return &loader{
log: log,
claimFetcher: claimFetcher, claimFetcher: claimFetcher,
} }
} }
......
...@@ -6,9 +6,7 @@ import ( ...@@ -6,9 +6,7 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -91,10 +89,9 @@ func (m *mockClaimFetcher) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) { ...@@ -91,10 +89,9 @@ func (m *mockClaimFetcher) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) {
// TestLoader_FetchClaims_Succeeds tests [loader.FetchClaims]. // TestLoader_FetchClaims_Succeeds tests [loader.FetchClaims].
func TestLoader_FetchClaims_Succeeds(t *testing.T) { func TestLoader_FetchClaims_Succeeds(t *testing.T) {
log := testlog.Logger(t, log.LvlError)
mockClaimFetcher := newMockClaimFetcher() mockClaimFetcher := newMockClaimFetcher()
expectedClaims := mockClaimFetcher.returnClaims expectedClaims := mockClaimFetcher.returnClaims
loader := NewLoader(log, mockClaimFetcher) loader := NewLoader(mockClaimFetcher)
claims, err := loader.FetchClaims(context.Background()) claims, err := loader.FetchClaims(context.Background())
require.NoError(t, err) require.NoError(t, err)
require.ElementsMatch(t, []Claim{ require.ElementsMatch(t, []Claim{
...@@ -143,10 +140,9 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) { ...@@ -143,10 +140,9 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) {
// TestLoader_FetchClaims_ClaimDataErrors tests [loader.FetchClaims] // TestLoader_FetchClaims_ClaimDataErrors tests [loader.FetchClaims]
// when the claim fetcher [ClaimData] function call errors. // when the claim fetcher [ClaimData] function call errors.
func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) { func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) {
log := testlog.Logger(t, log.LvlError)
mockClaimFetcher := newMockClaimFetcher() mockClaimFetcher := newMockClaimFetcher()
mockClaimFetcher.claimDataError = true mockClaimFetcher.claimDataError = true
loader := NewLoader(log, mockClaimFetcher) loader := NewLoader(mockClaimFetcher)
claims, err := loader.FetchClaims(context.Background()) claims, err := loader.FetchClaims(context.Background())
require.ErrorIs(t, err, mockClaimDataError) require.ErrorIs(t, err, mockClaimDataError)
require.Empty(t, claims) require.Empty(t, claims)
...@@ -155,10 +151,9 @@ func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) { ...@@ -155,10 +151,9 @@ func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) {
// TestLoader_FetchClaims_ClaimLenErrors tests [loader.FetchClaims] // TestLoader_FetchClaims_ClaimLenErrors tests [loader.FetchClaims]
// when the claim fetcher [ClaimDataLen] function call errors. // when the claim fetcher [ClaimDataLen] function call errors.
func TestLoader_FetchClaims_ClaimLenErrors(t *testing.T) { func TestLoader_FetchClaims_ClaimLenErrors(t *testing.T) {
log := testlog.Logger(t, log.LvlError)
mockClaimFetcher := newMockClaimFetcher() mockClaimFetcher := newMockClaimFetcher()
mockClaimFetcher.claimLenError = true mockClaimFetcher.claimLenError = true
loader := NewLoader(log, mockClaimFetcher) loader := NewLoader(mockClaimFetcher)
claims, err := loader.FetchClaims(context.Background()) claims, err := loader.FetchClaims(context.Background())
require.ErrorIs(t, err, mockClaimLenError) require.ErrorIs(t, err, mockClaimLenError)
require.Empty(t, claims) require.Empty(t, claims)
......
...@@ -123,9 +123,9 @@ func (r *faultResponder) sendTxAndWait(ctx context.Context, txData []byte) error ...@@ -123,9 +123,9 @@ func (r *faultResponder) sendTxAndWait(ctx context.Context, txData []byte) error
return err return err
} }
if receipt.Status == types.ReceiptStatusFailed { if receipt.Status == types.ReceiptStatusFailed {
r.log.Error("responder tx successfully published but reverted", "tx_hash", receipt.TxHash) r.log.Error("Responder tx successfully published but reverted", "tx_hash", receipt.TxHash)
} else { } else {
r.log.Info("responder tx successfully published", "tx_hash", receipt.TxHash) r.log.Debug("Responder tx successfully published", "tx_hash", receipt.TxHash)
} }
return nil return nil
} }
......
...@@ -2,10 +2,15 @@ package fault ...@@ -2,10 +2,15 @@ package fault
import ( import (
"errors" "errors"
"fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
var (
ErrGameDepthReached = errors.New("game depth reached")
)
// Solver uses a [TraceProvider] to determine the moves to make in a dispute game. // Solver uses a [TraceProvider] to determine the moves to make in a dispute game.
type Solver struct { type Solver struct {
TraceProvider TraceProvider
...@@ -54,7 +59,7 @@ func (s *Solver) handleMiddle(claim Claim) (*Claim, error) { ...@@ -54,7 +59,7 @@ func (s *Solver) handleMiddle(claim Claim) (*Claim, error) {
return nil, err return nil, err
} }
if claim.Depth() == s.gameDepth { if claim.Depth() == s.gameDepth {
return nil, errors.New("game depth reached") return nil, ErrGameDepthReached
} }
if claimCorrect { if claimCorrect {
return s.defend(claim) return s.defend(claim)
...@@ -113,7 +118,7 @@ func (s *Solver) attack(claim Claim) (*Claim, error) { ...@@ -113,7 +118,7 @@ func (s *Solver) attack(claim Claim) (*Claim, error) {
position := claim.Attack() position := claim.Attack()
value, err := s.traceAtPosition(position) value, err := s.traceAtPosition(position)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("attack claim: %w", err)
} }
return &Claim{ return &Claim{
ClaimData: ClaimData{Value: value, Position: position}, ClaimData: ClaimData{Value: value, Position: position},
...@@ -127,7 +132,7 @@ func (s *Solver) defend(claim Claim) (*Claim, error) { ...@@ -127,7 +132,7 @@ func (s *Solver) defend(claim Claim) (*Claim, error) {
position := claim.Defend() position := claim.Defend()
value, err := s.traceAtPosition(position) value, err := s.traceAtPosition(position)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("defend claim: %w", err)
} }
return &Claim{ return &Claim{
ClaimData: ClaimData{Value: value, Position: position}, ClaimData: ClaimData{Value: value, Position: position},
......
...@@ -11,6 +11,14 @@ var ( ...@@ -11,6 +11,14 @@ var (
ErrIndexTooLarge = errors.New("index is larger than the maximum index") ErrIndexTooLarge = errors.New("index is larger than the maximum index")
) )
type GameStatus uint8
const (
GameStatusInProgress GameStatus = iota
GameStatusChallengerWon
GameStatusDefenderWon
)
// StepCallData encapsulates the data needed to perform a step. // StepCallData encapsulates the data needed to perform a step.
type StepCallData struct { type StepCallData struct {
ClaimIndex uint64 ClaimIndex uint64
......
...@@ -209,7 +209,7 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (* ...@@ -209,7 +209,7 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (*
Data: candidate.TxData, Data: candidate.TxData,
} }
m.l.Info("creating tx", "to", rawTx.To, "from", m.cfg.From) m.l.Info("Creating tx", "to", rawTx.To, "from", m.cfg.From)
// If the gas limit is set, we can use that as the gas // If the gas limit is set, we can use that as the gas
if candidate.GasLimit != 0 { if candidate.GasLimit != 0 {
...@@ -333,7 +333,7 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t ...@@ -333,7 +333,7 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t
// for the transaction. // for the transaction.
func (m *SimpleTxManager) publishAndWaitForTx(ctx context.Context, tx *types.Transaction, sendState *SendState, receiptChan chan *types.Receipt) { func (m *SimpleTxManager) publishAndWaitForTx(ctx context.Context, tx *types.Transaction, sendState *SendState, receiptChan chan *types.Receipt) {
log := m.l.New("hash", tx.Hash(), "nonce", tx.Nonce(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) log := m.l.New("hash", tx.Hash(), "nonce", tx.Nonce(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
log.Info("publishing transaction") log.Info("Publishing transaction")
cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout) cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout)
defer cancel() defer cancel()
......
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