Commit 5b749451 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Delay claiming credit until games are complete (#9720)

Avoids paying additional transaction costs while a game's claims are being resolved and works better with bond locking.
parent 39881a87
...@@ -17,7 +17,7 @@ type BondClaimMetrics interface { ...@@ -17,7 +17,7 @@ type BondClaimMetrics interface {
} }
type BondContract interface { type BondContract interface {
GetCredit(ctx context.Context, receipient common.Address) (*big.Int, error) GetCredit(ctx context.Context, receipient common.Address) (*big.Int, types.GameStatus, error)
ClaimCredit(receipient common.Address) (txmgr.TxCandidate, error) ClaimCredit(receipient common.Address) (txmgr.TxCandidate, error)
} }
...@@ -55,11 +55,15 @@ func (c *Claimer) claimBond(ctx context.Context, game types.GameMetadata) error ...@@ -55,11 +55,15 @@ func (c *Claimer) claimBond(ctx context.Context, game types.GameMetadata) error
if err != nil { if err != nil {
return fmt.Errorf("failed to create bond contract bindings: %w", err) return fmt.Errorf("failed to create bond contract bindings: %w", err)
} }
credit, err := contract.GetCredit(ctx, c.txSender.From()) credit, status, err := contract.GetCredit(ctx, c.txSender.From())
if err != nil { if err != nil {
return fmt.Errorf("failed to get credit: %w", err) return fmt.Errorf("failed to get credit: %w", err)
} }
if status == types.GameStatusInProgress {
c.logger.Debug("Not claiming credit from in progress game", "game", game.Proxy, "status", status)
return nil
}
if credit.Cmp(big.NewInt(0)) == 0 { if credit.Cmp(big.NewInt(0)) == 0 {
c.logger.Debug("No credit to claim", "game", game.Proxy) c.logger.Debug("No credit to claim", "game", game.Proxy)
return nil return nil
......
...@@ -40,6 +40,17 @@ func TestClaimer_ClaimBonds(t *testing.T) { ...@@ -40,6 +40,17 @@ func TestClaimer_ClaimBonds(t *testing.T) {
require.Equal(t, 1, m.RecordBondClaimedCalls) require.Equal(t, 1, m.RecordBondClaimedCalls)
}) })
t.Run("BondClaimSkippedForInProgressGame", func(t *testing.T) {
gameAddr := common.HexToAddress("0x1234")
c, m, contract, txSender := newTestClaimer(t, gameAddr)
contract.credit = 1
contract.status = types.GameStatusInProgress
err := c.ClaimBonds(context.Background(), []types.GameMetadata{{Proxy: gameAddr}})
require.NoError(t, err)
require.Equal(t, 0, txSender.sends)
require.Equal(t, 0, m.RecordBondClaimedCalls)
})
t.Run("BondClaimFails", func(t *testing.T) { t.Run("BondClaimFails", func(t *testing.T) {
gameAddr := common.HexToAddress("0x1234") gameAddr := common.HexToAddress("0x1234")
c, m, contract, txSender := newTestClaimer(t, gameAddr) c, m, contract, txSender := newTestClaimer(t, gameAddr)
...@@ -77,7 +88,7 @@ func newTestClaimer(t *testing.T, gameAddr common.Address) (*Claimer, *mockClaim ...@@ -77,7 +88,7 @@ func newTestClaimer(t *testing.T, gameAddr common.Address) (*Claimer, *mockClaim
logger := testlog.Logger(t, log.LvlDebug) logger := testlog.Logger(t, log.LvlDebug)
m := &mockClaimMetrics{} m := &mockClaimMetrics{}
txSender := &mockTxSender{} txSender := &mockTxSender{}
bondContract := &stubBondContract{} bondContract := &stubBondContract{status: types.GameStatusChallengerWon}
contractCreator := func(game types.GameMetadata) (BondContract, error) { contractCreator := func(game types.GameMetadata) (BondContract, error) {
return bondContract, nil return bondContract, nil
} }
...@@ -116,10 +127,11 @@ func (s *mockTxSender) SendAndWait(_ string, _ ...txmgr.TxCandidate) ([]*ethtype ...@@ -116,10 +127,11 @@ func (s *mockTxSender) SendAndWait(_ string, _ ...txmgr.TxCandidate) ([]*ethtype
type stubBondContract struct { type stubBondContract struct {
credit int64 credit int64
status types.GameStatus
} }
func (s *stubBondContract) GetCredit(_ context.Context, _ common.Address) (*big.Int, error) { func (s *stubBondContract) GetCredit(_ context.Context, _ common.Address) (*big.Int, types.GameStatus, error) {
return big.NewInt(s.credit), nil return big.NewInt(s.credit), s.status, nil
} }
func (s *stubBondContract) ClaimCredit(_ common.Address) (txmgr.TxCandidate, error) { func (s *stubBondContract) ClaimCredit(_ common.Address) (txmgr.TxCandidate, error) {
......
...@@ -119,12 +119,22 @@ func (c *FaultDisputeGameContract) GetSplitDepth(ctx context.Context) (types.Dep ...@@ -119,12 +119,22 @@ func (c *FaultDisputeGameContract) GetSplitDepth(ctx context.Context) (types.Dep
return types.Depth(splitDepth.GetBigInt(0).Uint64()), nil return types.Depth(splitDepth.GetBigInt(0).Uint64()), nil
} }
func (c *FaultDisputeGameContract) GetCredit(ctx context.Context, recipient common.Address) (*big.Int, error) { func (c *FaultDisputeGameContract) GetCredit(ctx context.Context, recipient common.Address) (*big.Int, gameTypes.GameStatus, error) {
if credits, err := c.GetCredits(ctx, batching.BlockLatest, recipient); err != nil { results, err := c.multiCaller.Call(ctx, batching.BlockLatest,
return nil, err c.contract.Call(methodCredit, recipient),
} else { c.contract.Call(methodStatus))
return credits[0], nil if err != nil {
return nil, gameTypes.GameStatusInProgress, err
}
if len(results) != 2 {
return nil, gameTypes.GameStatusInProgress, fmt.Errorf("expected 2 results but got %v", len(results))
}
credit := results[0].GetBigInt(0)
status, err := gameTypes.GameStatusFromUint8(results[1].GetUint8(0))
if err != nil {
return nil, gameTypes.GameStatusInProgress, fmt.Errorf("invalid game status %v: %w", status, err)
} }
return credit, status, nil
} }
func (c *FaultDisputeGameContract) GetCredits(ctx context.Context, block batching.Block, recipients ...common.Address) ([]*big.Int, error) { func (c *FaultDisputeGameContract) GetCredits(ctx context.Context, block batching.Block, recipients ...common.Address) ([]*big.Int, error) {
......
...@@ -377,12 +377,15 @@ func TestFaultDisputeGame_UpdateOracleTx(t *testing.T) { ...@@ -377,12 +377,15 @@ func TestFaultDisputeGame_UpdateOracleTx(t *testing.T) {
func TestFaultDisputeGame_GetCredit(t *testing.T) { func TestFaultDisputeGame_GetCredit(t *testing.T) {
stubRpc, game := setupFaultDisputeGameTest(t) stubRpc, game := setupFaultDisputeGameTest(t)
addr := common.Address{0x01} addr := common.Address{0x01}
expected := big.NewInt(4284) expectedCredit := big.NewInt(4284)
stubRpc.SetResponse(fdgAddr, methodCredit, batching.BlockLatest, []interface{}{addr}, []interface{}{expected}) expectedStatus := types.GameStatusChallengerWon
stubRpc.SetResponse(fdgAddr, methodCredit, batching.BlockLatest, []interface{}{addr}, []interface{}{expectedCredit})
stubRpc.SetResponse(fdgAddr, methodStatus, batching.BlockLatest, nil, []interface{}{expectedStatus})
actual, err := game.GetCredit(context.Background(), addr) actualCredit, actualStatus, err := game.GetCredit(context.Background(), addr)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected, actual) require.Equal(t, expectedCredit, actualCredit)
require.Equal(t, expectedStatus, actualStatus)
} }
func TestFaultDisputeGame_GetCredits(t *testing.T) { func TestFaultDisputeGame_GetCredits(t *testing.T) {
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/preimages" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/preimages"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/responder" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/responder"
...@@ -44,6 +45,7 @@ type GamePlayer struct { ...@@ -44,6 +45,7 @@ type GamePlayer struct {
type GameContract interface { type GameContract interface {
preimages.PreimageGameContract preimages.PreimageGameContract
responder.GameContract responder.GameContract
claims.BondContract
GameInfo GameInfo
ClaimLoader ClaimLoader
GetStatus(ctx context.Context) (gameTypes.GameStatus, error) GetStatus(ctx context.Context) (gameTypes.GameStatus, error)
......
...@@ -24,8 +24,6 @@ type GameContract interface { ...@@ -24,8 +24,6 @@ type GameContract interface {
DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error)
GetRequiredBond(ctx context.Context, position types.Position) (*big.Int, error) GetRequiredBond(ctx context.Context, position types.Position) (*big.Int, error)
GetCredit(ctx context.Context, receipient common.Address) (*big.Int, error)
ClaimCredit(receipient common.Address) (txmgr.TxCandidate, error)
} }
type Oracle interface { type Oracle interface {
......
...@@ -123,7 +123,7 @@ func (s stubPreimageOracle) GetActivePreimages(_ context.Context, _ common.Hash) ...@@ -123,7 +123,7 @@ func (s stubPreimageOracle) GetActivePreimages(_ context.Context, _ common.Hash)
type stubBondContract struct{} type stubBondContract struct{}
func (s *stubBondContract) GetCredit(ctx context.Context, receipient common.Address) (*big.Int, error) { func (s *stubBondContract) GetCredit(ctx context.Context, receipient common.Address) (*big.Int, types.GameStatus, error) {
panic("not supported") panic("not supported")
} }
......
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