Commit abcb627f authored by refcell's avatar refcell Committed by GitHub

feat(op-dispute-mon): WithdrawalRequest and Recipients Enricher (#9940)

* feat(op-challenger): Delayed Weth Withdrawal Request Caller

* fix(op-challenger): withdrawal request field ordering

* fix(op-challenger): encapsulate delayed weth behind the fault dispute game contract binding

* feat(op-dispute-mon): get withdrawals game caller method

* fix(op-dispute-mon): revert service name changes

* fix(op-dispute-mon): revert extractor test name

* fix(op-dispute-mon): revert extractor changes

* fix(op-dispute-mon): revert newline add

* fix(op-dispute-mon): revert caller field reordering

* feat(op-dispute-mon): weth caller creation and pass it through sub components

* chore(op-dispute-mon): weth caller creation unit test

* fix(op-dispute-mon): extractor test for weth caller creation error

* feat(op-dispute-mon): wire up the withdrawals extractor

* fix(op-dispute-mon): rebases

* fix(op-dispute-mon): revert caller test change

* fix(op-dispute-mon): recipient enricher
parent e61cd76e
...@@ -27,26 +27,17 @@ func NewBondEnricher() *BondEnricher { ...@@ -27,26 +27,17 @@ func NewBondEnricher() *BondEnricher {
} }
func (b *BondEnricher) Enrich(ctx context.Context, block rpcblock.Block, caller GameCaller, game *monTypes.EnrichedGameData) error { func (b *BondEnricher) Enrich(ctx context.Context, block rpcblock.Block, caller GameCaller, game *monTypes.EnrichedGameData) error {
recipients := make(map[common.Address]bool) recipients := maps.Keys(game.Recipients)
for _, claim := range game.Claims { credits, err := caller.GetCredits(ctx, block, recipients...)
if claim.CounteredBy != (common.Address{}) {
recipients[claim.CounteredBy] = true
} else {
recipients[claim.Claimant] = true
}
}
recipientAddrs := maps.Keys(recipients)
credits, err := caller.GetCredits(ctx, block, recipientAddrs...)
if err != nil { if err != nil {
return err return err
} }
if len(credits) != len(recipientAddrs) { if len(credits) != len(recipients) {
return fmt.Errorf("%w, requested %v values but got %v", ErrIncorrectCreditCount, len(recipientAddrs), len(credits)) return fmt.Errorf("%w, requested %v values but got %v", ErrIncorrectCreditCount, len(recipients), len(credits))
} }
game.Credits = make(map[common.Address]*big.Int) game.Credits = make(map[common.Address]*big.Int)
for i, credit := range credits { for i, credit := range credits {
game.Credits[recipientAddrs[i]] = credit game.Credits[recipients[i]] = credit
} }
return nil return nil
} }
...@@ -13,43 +13,55 @@ import ( ...@@ -13,43 +13,55 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestBondEnricher(t *testing.T) { // makeTestGame returns an enriched game with 3 claims and a list of expected recipients.
makeGame := func() *monTypes.EnrichedGameData { func makeTestGame() (*monTypes.EnrichedGameData, []common.Address) {
return &monTypes.EnrichedGameData{ game := &monTypes.EnrichedGameData{
Claims: []monTypes.EnrichedClaim{ Recipients: map[common.Address]bool{
{ common.Address{0x02}: true,
Claim: faultTypes.Claim{ common.Address{0x03}: true,
Claimant: common.Address{0x01}, common.Address{0x04}: true,
CounteredBy: common.Address{0x02}, },
}, Claims: []monTypes.EnrichedClaim{
Resolved: true, {
Claim: faultTypes.Claim{
Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
}, },
{ Resolved: true,
Claim: faultTypes.Claim{ },
ClaimData: faultTypes.ClaimData{ {
Bond: big.NewInt(5), Claim: faultTypes.Claim{
}, ClaimData: faultTypes.ClaimData{
Claimant: common.Address{0x03}, Bond: big.NewInt(5),
CounteredBy: common.Address{},
}, },
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
{ },
Claim: faultTypes.Claim{ {
ClaimData: faultTypes.ClaimData{ Claim: faultTypes.Claim{
Bond: big.NewInt(7), ClaimData: faultTypes.ClaimData{
}, Bond: big.NewInt(7),
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
Claimant: common.Address{0x03},
CounteredBy: common.Address{0x04},
}, },
}, },
} },
} }
recipients := []common.Address{
game.Claims[0].CounteredBy,
game.Claims[1].Claimant,
game.Claims[2].CounteredBy,
}
return game, recipients
}
func TestBondEnricher(t *testing.T) {
t.Run("GetCreditsFails", func(t *testing.T) { t.Run("GetCreditsFails", func(t *testing.T) {
enricher := NewBondEnricher() enricher := NewBondEnricher()
caller := &mockGameCaller{creditsErr: errors.New("nope")} caller := &mockGameCaller{creditsErr: errors.New("nope")}
game := makeGame() game, _ := makeTestGame()
err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game) err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game)
require.ErrorIs(t, err, caller.creditsErr) require.ErrorIs(t, err, caller.creditsErr)
}) })
...@@ -57,31 +69,25 @@ func TestBondEnricher(t *testing.T) { ...@@ -57,31 +69,25 @@ func TestBondEnricher(t *testing.T) {
t.Run("GetCreditsWrongNumberOfResults", func(t *testing.T) { t.Run("GetCreditsWrongNumberOfResults", func(t *testing.T) {
enricher := NewBondEnricher() enricher := NewBondEnricher()
caller := &mockGameCaller{extraCredit: []*big.Int{big.NewInt(4)}} caller := &mockGameCaller{extraCredit: []*big.Int{big.NewInt(4)}}
game := makeGame() game, _ := makeTestGame()
err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game) err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game)
require.ErrorIs(t, err, ErrIncorrectCreditCount) require.ErrorIs(t, err, ErrIncorrectCreditCount)
}) })
t.Run("GetCreditsSuccess", func(t *testing.T) { t.Run("GetCreditsSuccess", func(t *testing.T) {
game := makeGame() game, recipients := makeTestGame()
expectedRecipients := []common.Address{
game.Claims[0].CounteredBy,
game.Claims[1].Claimant,
// Claim 1 CounteredBy is unset
// Claim 2 Claimant is same as claim 1 Claimant
// Claim 2 CounteredBy is unset
}
enricher := NewBondEnricher() enricher := NewBondEnricher()
expectedCredits := map[common.Address]*big.Int{ expectedCredits := map[common.Address]*big.Int{
expectedRecipients[0]: big.NewInt(20), recipients[0]: big.NewInt(20),
expectedRecipients[1]: big.NewInt(30), recipients[1]: big.NewInt(30),
recipients[2]: big.NewInt(40),
} }
caller := &mockGameCaller{credits: expectedCredits} caller := &mockGameCaller{credits: expectedCredits}
err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game) err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(expectedRecipients), len(caller.requestedCredits)) require.Equal(t, len(recipients), len(caller.requestedCredits))
for _, recipient := range expectedRecipients { for _, recipient := range recipients {
require.Contains(t, caller.requestedCredits, recipient) require.Contains(t, caller.requestedCredits, recipient)
} }
require.Equal(t, expectedCredits, game.Credits) require.Equal(t, expectedCredits, game.Credits)
......
...@@ -192,6 +192,7 @@ type mockGameCaller struct { ...@@ -192,6 +192,7 @@ type mockGameCaller struct {
balanceAddr common.Address balanceAddr common.Address
withdrawalsCalls int withdrawalsCalls int
withdrawalsErr error withdrawalsErr error
withdrawals []*contracts.WithdrawalRequest
} }
func (m *mockGameCaller) GetWithdrawals(_ context.Context, _ rpcblock.Block, _ common.Address, _ ...common.Address) ([]*contracts.WithdrawalRequest, error) { func (m *mockGameCaller) GetWithdrawals(_ context.Context, _ rpcblock.Block, _ common.Address, _ ...common.Address) ([]*contracts.WithdrawalRequest, error) {
...@@ -199,11 +200,18 @@ func (m *mockGameCaller) GetWithdrawals(_ context.Context, _ rpcblock.Block, _ c ...@@ -199,11 +200,18 @@ func (m *mockGameCaller) GetWithdrawals(_ context.Context, _ rpcblock.Block, _ c
if m.withdrawalsErr != nil { if m.withdrawalsErr != nil {
return nil, m.withdrawalsErr return nil, m.withdrawalsErr
} }
if m.withdrawals != nil {
return m.withdrawals, nil
}
return []*contracts.WithdrawalRequest{ return []*contracts.WithdrawalRequest{
{ {
Timestamp: big.NewInt(1), Timestamp: big.NewInt(1),
Amount: big.NewInt(2), Amount: big.NewInt(2),
}, },
{
Timestamp: big.NewInt(3),
Amount: big.NewInt(4),
},
}, nil }, nil
} }
......
package extract
import (
"context"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/common"
)
var _ Enricher = (*RecipientEnricher)(nil)
type RecipientEnricher struct{}
func NewRecipientEnricher() *RecipientEnricher {
return &RecipientEnricher{}
}
func (w *RecipientEnricher) Enrich(_ context.Context, _ rpcblock.Block, _ GameCaller, game *monTypes.EnrichedGameData) error {
recipients := make(map[common.Address]bool)
for _, claim := range game.Claims {
if claim.CounteredBy != (common.Address{}) {
recipients[claim.CounteredBy] = true
} else {
recipients[claim.Claimant] = true
}
}
game.Recipients = recipients
return nil
}
package extract
import (
"context"
"testing"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestRecipientEnricher(t *testing.T) {
game, recipients := makeTestGame()
game.Recipients = make(map[common.Address]bool)
enricher := NewRecipientEnricher()
caller := &mockGameCaller{}
ctx := context.Background()
err := enricher.Enrich(ctx, rpcblock.Latest, caller, game)
require.NoError(t, err)
for _, recipient := range recipients {
require.Contains(t, game.Recipients, recipient)
}
}
package extract
import (
"context"
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/common"
"golang.org/x/exp/maps"
)
var ErrIncorrectWithdrawalsCount = errors.New("incorrect withdrawals count")
var _ Enricher = (*WithdrawalsEnricher)(nil)
type WithdrawalsEnricher struct{}
func NewWithdrawalsEnricher() *WithdrawalsEnricher {
return &WithdrawalsEnricher{}
}
func (w *WithdrawalsEnricher) Enrich(ctx context.Context, block rpcblock.Block, caller GameCaller, game *monTypes.EnrichedGameData) error {
recipients := maps.Keys(game.Recipients)
withdrawals, err := caller.GetWithdrawals(ctx, block, game.Proxy, recipients...)
if err != nil {
return fmt.Errorf("failed to fetch withdrawals: %w", err)
}
if len(withdrawals) != len(recipients) {
return fmt.Errorf("%w, requested %v values but got %v", ErrIncorrectWithdrawalsCount, len(recipients), len(withdrawals))
}
if game.WithdrawalRequests == nil {
game.WithdrawalRequests = make(map[common.Address]*contracts.WithdrawalRequest)
}
for i, recipient := range recipients {
game.WithdrawalRequests[recipient] = withdrawals[i]
}
return nil
}
package extract
import (
"context"
"errors"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestWithdrawalsEnricher(t *testing.T) {
makeGame := func() *monTypes.EnrichedGameData {
return &monTypes.EnrichedGameData{
Recipients: map[common.Address]bool{
common.Address{0x02}: true,
common.Address{0x03}: true,
},
Claims: []monTypes.EnrichedClaim{
{
Claim: faultTypes.Claim{
ClaimData: faultTypes.ClaimData{
Bond: big.NewInt(10),
},
Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
},
Resolved: true,
},
{
Claim: faultTypes.Claim{
ClaimData: faultTypes.ClaimData{
Bond: big.NewInt(5),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
},
{
Claim: faultTypes.Claim{
ClaimData: faultTypes.ClaimData{
Bond: big.NewInt(7),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
},
},
}
}
t.Run("GetWithdrawalsFails", func(t *testing.T) {
enricher := NewWithdrawalsEnricher()
caller := &mockGameCaller{withdrawalsErr: errors.New("nope")}
game := makeGame()
err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game)
require.ErrorIs(t, err, caller.withdrawalsErr)
})
t.Run("GetWithdrawalsWrongNumberOfResults", func(t *testing.T) {
enricher := NewWithdrawalsEnricher()
caller := &mockGameCaller{withdrawals: []*contracts.WithdrawalRequest{{}}}
game := makeGame()
err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game)
require.ErrorIs(t, err, ErrIncorrectWithdrawalsCount)
})
t.Run("GetWithdrawalsSuccess", func(t *testing.T) {
game := makeGame()
enricher := NewWithdrawalsEnricher()
caller := &mockGameCaller{}
err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game)
require.NoError(t, err)
require.Equal(t, 2, len(game.WithdrawalRequests))
})
}
...@@ -115,10 +115,13 @@ func (s *Service) initDelayCalculator() { ...@@ -115,10 +115,13 @@ func (s *Service) initDelayCalculator() {
} }
func (s *Service) initExtractor() { func (s *Service) initExtractor() {
s.extractor = extract.NewExtractor(s.logger, s.game.CreateContract, s.factoryContract.GetGamesAtOrAfter, s.extractor = extract.NewExtractor(
// Note: Claim enricher should precede other enrichers to ensure the claim Resolved field s.logger,
// is set by checking if the claim's bond amount is equal to the configured flag. s.game.CreateContract,
s.factoryContract.GetGamesAtOrAfter,
extract.NewClaimEnricher(), extract.NewClaimEnricher(),
extract.NewRecipientEnricher(), // Must be called before WithdrawalsEnricher
extract.NewWithdrawalsEnricher(),
extract.NewBondEnricher(), extract.NewBondEnricher(),
extract.NewBalanceEnricher(), extract.NewBalanceEnricher(),
extract.NewL1HeadBlockNumEnricher(s.l1Client), extract.NewL1HeadBlockNumEnricher(s.l1Client),
......
...@@ -25,6 +25,9 @@ type EnrichedGameData struct { ...@@ -25,6 +25,9 @@ type EnrichedGameData struct {
Duration uint64 Duration uint64
Claims []EnrichedClaim Claims []EnrichedClaim
// Recipients maps addresses to true if they are a bond recipient in the game.
Recipients map[common.Address]bool
// Credits records the paid out bonds for the game, keyed by recipient. // Credits records the paid out bonds for the game, keyed by recipient.
Credits map[common.Address]*big.Int Credits map[common.Address]*big.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