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

feat(op-dispute-mon): L2BlockNumberChallenged Support (#10451)

* feat(op-dispute-mon): L2BlockNumberChallenged dispute monitor support

feat(op-dispute-mon): query for the l2 block number through the game metadata call

fix(op-dispute-mon): query for the l2 block number through the game metadata call

fix(op-dispute-mon): query for the l2 block number through the game metadata call

* fix(op-dispute-mon): block challenge check

* fix(op-dispute-mon): use agreement and metrice

* op-dispute-mon: Separate l2 challenge metric (#10483)

* op-dispute-mon: Consider l2 block number challenged when forecasting but don't make it a new game status.

Add a separate metric to report the number of successful L2 block number challenges.

* op-challenger: Remove unused blockNumChallenged field from list-games info struct

* op-dispute-mon: Consider l2 block challenger in expected credits. (#10484)

* feat(op-dispute-mon): Update L2 Challenges Metrics (#10492)

* fix: change the l2 challenges to tha gauge vec

* fix: consistent metric labels

---------
Co-authored-by: default avatarrefcell <abigger87@gmail.com>

---------
Co-authored-by: default avatarAdrian Sutton <adrian@oplabs.co>
parent bbc3786d
...@@ -109,14 +109,14 @@ func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contr ...@@ -109,14 +109,14 @@ func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contr
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
_, l2BlockNum, rootClaim, status, _, err := gameContract.GetGameMetadata(ctx, rpcblock.ByHash(block)) metadata, err := gameContract.GetGameMetadata(ctx, rpcblock.ByHash(block))
if err != nil { if err != nil {
info.err = fmt.Errorf("failed to retrieve metadata for game %v: %w", gameProxy, err) info.err = fmt.Errorf("failed to retrieve metadata for game %v: %w", gameProxy, err)
return return
} }
infos[currIndex].status = status infos[currIndex].status = metadata.Status
infos[currIndex].l2BlockNum = l2BlockNum infos[currIndex].l2BlockNum = metadata.L2BlockNum
infos[currIndex].rootClaim = rootClaim infos[currIndex].rootClaim = metadata.RootClaim
claimCount, err := gameContract.GetClaimCount(ctx) claimCount, err := gameContract.GetClaimCount(ctx)
if err != nil { if err != nil {
info.err = fmt.Errorf("failed to retrieve claim count for game %v: %w", gameProxy, err) info.err = fmt.Errorf("failed to retrieve claim count for game %v: %w", gameProxy, err)
......
...@@ -52,6 +52,7 @@ var ( ...@@ -52,6 +52,7 @@ var (
methodCredit = "credit" methodCredit = "credit"
methodWETH = "weth" methodWETH = "weth"
methodL2BlockNumberChallenged = "l2BlockNumberChallenged" methodL2BlockNumberChallenged = "l2BlockNumberChallenged"
methodL2BlockNumberChallenger = "l2BlockNumberChallenger"
methodChallengeRootL2Block = "challengeRootL2Block" methodChallengeRootL2Block = "challengeRootL2Block"
) )
...@@ -162,30 +163,53 @@ func (f *FaultDisputeGameContractLatest) GetBlockRange(ctx context.Context) (pre ...@@ -162,30 +163,53 @@ func (f *FaultDisputeGameContractLatest) GetBlockRange(ctx context.Context) (pre
return return
} }
// GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, and max clock duration. type GameMetadata struct {
func (f *FaultDisputeGameContractLatest) GetGameMetadata(ctx context.Context, block rpcblock.Block) (common.Hash, uint64, common.Hash, gameTypes.GameStatus, uint64, error) { L1Head common.Hash
L2BlockNum uint64
RootClaim common.Hash
Status gameTypes.GameStatus
MaxClockDuration uint64
L2BlockNumberChallenged bool
L2BlockNumberChallenger common.Address
}
// GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, max clock duration, and is l2 block number challenged.
func (f *FaultDisputeGameContractLatest) GetGameMetadata(ctx context.Context, block rpcblock.Block) (GameMetadata, error) {
defer f.metrics.StartContractRequest("GetGameMetadata")() defer f.metrics.StartContractRequest("GetGameMetadata")()
results, err := f.multiCaller.Call(ctx, block, results, err := f.multiCaller.Call(ctx, block,
f.contract.Call(methodL1Head), f.contract.Call(methodL1Head),
f.contract.Call(methodL2BlockNumber), f.contract.Call(methodL2BlockNumber),
f.contract.Call(methodRootClaim), f.contract.Call(methodRootClaim),
f.contract.Call(methodStatus), f.contract.Call(methodStatus),
f.contract.Call(methodMaxClockDuration)) f.contract.Call(methodMaxClockDuration),
f.contract.Call(methodL2BlockNumberChallenged),
f.contract.Call(methodL2BlockNumberChallenger),
)
if err != nil { if err != nil {
return common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("failed to retrieve game metadata: %w", err) return GameMetadata{}, fmt.Errorf("failed to retrieve game metadata: %w", err)
} }
if len(results) != 5 { if len(results) != 7 {
return common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("expected 3 results but got %v", len(results)) return GameMetadata{}, fmt.Errorf("expected 6 results but got %v", len(results))
} }
l1Head := results[0].GetHash(0) l1Head := results[0].GetHash(0)
l2BlockNumber := results[1].GetBigInt(0).Uint64() l2BlockNumber := results[1].GetBigInt(0).Uint64()
rootClaim := results[2].GetHash(0) rootClaim := results[2].GetHash(0)
status, err := gameTypes.GameStatusFromUint8(results[3].GetUint8(0)) status, err := gameTypes.GameStatusFromUint8(results[3].GetUint8(0))
if err != nil { if err != nil {
return common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("failed to convert game status: %w", err) return GameMetadata{}, fmt.Errorf("failed to convert game status: %w", err)
} }
duration := results[4].GetUint64(0) duration := results[4].GetUint64(0)
return l1Head, l2BlockNumber, rootClaim, status, duration, nil blockChallenged := results[5].GetBool(0)
blockChallenger := results[6].GetAddress(0)
return GameMetadata{
L1Head: l1Head,
L2BlockNum: l2BlockNumber,
RootClaim: rootClaim,
Status: status,
MaxClockDuration: duration,
L2BlockNumberChallenged: blockChallenged,
L2BlockNumberChallenger: blockChallenger,
}, nil
} }
func (f *FaultDisputeGameContractLatest) GetStartingRootHash(ctx context.Context) (common.Hash, error) { func (f *FaultDisputeGameContractLatest) GetStartingRootHash(ctx context.Context) (common.Hash, error) {
...@@ -549,7 +573,7 @@ func (f *FaultDisputeGameContractLatest) decodeClaim(result *batching.CallResult ...@@ -549,7 +573,7 @@ func (f *FaultDisputeGameContractLatest) decodeClaim(result *batching.CallResult
type FaultDisputeGameContract interface { type FaultDisputeGameContract interface {
GetBalance(ctx context.Context, block rpcblock.Block) (*big.Int, common.Address, error) GetBalance(ctx context.Context, block rpcblock.Block) (*big.Int, common.Address, error)
GetBlockRange(ctx context.Context) (prestateBlock uint64, poststateBlock uint64, retErr error) GetBlockRange(ctx context.Context) (prestateBlock uint64, poststateBlock uint64, retErr error)
GetGameMetadata(ctx context.Context, block rpcblock.Block) (common.Hash, uint64, common.Hash, gameTypes.GameStatus, uint64, error) GetGameMetadata(ctx context.Context, block rpcblock.Block) (GameMetadata, error)
GetStartingRootHash(ctx context.Context) (common.Hash, error) GetStartingRootHash(ctx context.Context) (common.Hash, error)
GetSplitDepth(ctx context.Context) (types.Depth, error) GetSplitDepth(ctx context.Context) (types.Depth, error)
GetCredit(ctx context.Context, recipient common.Address) (*big.Int, gameTypes.GameStatus, error) GetCredit(ctx context.Context, recipient common.Address) (*big.Int, gameTypes.GameStatus, error)
......
...@@ -3,8 +3,10 @@ package contracts ...@@ -3,8 +3,10 @@ package contracts
import ( import (
"context" "context"
_ "embed" _ "embed"
"fmt"
"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/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
) )
...@@ -16,6 +18,40 @@ type FaultDisputeGameContract0180 struct { ...@@ -16,6 +18,40 @@ type FaultDisputeGameContract0180 struct {
FaultDisputeGameContractLatest FaultDisputeGameContractLatest
} }
// GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, and max clock duration.
func (f *FaultDisputeGameContract0180) GetGameMetadata(ctx context.Context, block rpcblock.Block) (GameMetadata, error) {
defer f.metrics.StartContractRequest("GetGameMetadata")()
results, err := f.multiCaller.Call(ctx, block,
f.contract.Call(methodL1Head),
f.contract.Call(methodL2BlockNumber),
f.contract.Call(methodRootClaim),
f.contract.Call(methodStatus),
f.contract.Call(methodMaxClockDuration),
)
if err != nil {
return GameMetadata{}, fmt.Errorf("failed to retrieve game metadata: %w", err)
}
if len(results) != 5 {
return GameMetadata{}, fmt.Errorf("expected 5 results but got %v", len(results))
}
l1Head := results[0].GetHash(0)
l2BlockNumber := results[1].GetBigInt(0).Uint64()
rootClaim := results[2].GetHash(0)
status, err := gameTypes.GameStatusFromUint8(results[3].GetUint8(0))
if err != nil {
return GameMetadata{}, fmt.Errorf("failed to convert game status: %w", err)
}
duration := results[4].GetUint64(0)
return GameMetadata{
L1Head: l1Head,
L2BlockNum: l2BlockNumber,
RootClaim: rootClaim,
Status: status,
MaxClockDuration: duration,
L2BlockNumberChallenged: false,
}, nil
}
func (f *FaultDisputeGameContract0180) IsL2BlockNumberChallenged(_ context.Context, _ rpcblock.Block) (bool, error) { func (f *FaultDisputeGameContract0180) IsL2BlockNumberChallenged(_ context.Context, _ rpcblock.Block) (bool, error) {
return false, nil return false, nil
} }
......
...@@ -12,7 +12,6 @@ import ( ...@@ -12,7 +12,6 @@ import (
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
) )
//go:embed abis/FaultDisputeGame-0.8.0.json //go:embed abis/FaultDisputeGame-0.8.0.json
...@@ -29,7 +28,7 @@ type FaultDisputeGameContract080 struct { ...@@ -29,7 +28,7 @@ type FaultDisputeGameContract080 struct {
} }
// GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, and max clock duration. // GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, and max clock duration.
func (f *FaultDisputeGameContract080) GetGameMetadata(ctx context.Context, block rpcblock.Block) (common.Hash, uint64, common.Hash, gameTypes.GameStatus, uint64, error) { func (f *FaultDisputeGameContract080) GetGameMetadata(ctx context.Context, block rpcblock.Block) (GameMetadata, error) {
defer f.metrics.StartContractRequest("GetGameMetadata")() defer f.metrics.StartContractRequest("GetGameMetadata")()
results, err := f.multiCaller.Call(ctx, block, results, err := f.multiCaller.Call(ctx, block,
f.contract.Call(methodL1Head), f.contract.Call(methodL1Head),
...@@ -38,20 +37,27 @@ func (f *FaultDisputeGameContract080) GetGameMetadata(ctx context.Context, block ...@@ -38,20 +37,27 @@ func (f *FaultDisputeGameContract080) GetGameMetadata(ctx context.Context, block
f.contract.Call(methodStatus), f.contract.Call(methodStatus),
f.contract.Call(methodGameDuration)) f.contract.Call(methodGameDuration))
if err != nil { if err != nil {
return common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("failed to retrieve game metadata: %w", err) return GameMetadata{}, fmt.Errorf("failed to retrieve game metadata: %w", err)
} }
if len(results) != 5 { if len(results) != 5 {
return common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("expected 3 results but got %v", len(results)) return GameMetadata{}, fmt.Errorf("expected 5 results but got %v", len(results))
} }
l1Head := results[0].GetHash(0) l1Head := results[0].GetHash(0)
l2BlockNumber := results[1].GetBigInt(0).Uint64() l2BlockNumber := results[1].GetBigInt(0).Uint64()
rootClaim := results[2].GetHash(0) rootClaim := results[2].GetHash(0)
status, err := gameTypes.GameStatusFromUint8(results[3].GetUint8(0)) status, err := gameTypes.GameStatusFromUint8(results[3].GetUint8(0))
if err != nil { if err != nil {
return common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("failed to convert game status: %w", err) return GameMetadata{}, fmt.Errorf("failed to convert game status: %w", err)
} }
duration := results[4].GetUint64(0) duration := results[4].GetUint64(0)
return l1Head, l2BlockNumber, rootClaim, status, duration / 2, nil return GameMetadata{
L1Head: l1Head,
L2BlockNum: l2BlockNumber,
RootClaim: rootClaim,
Status: status,
MaxClockDuration: duration / 2,
L2BlockNumberChallenged: false,
}, nil
} }
func (f *FaultDisputeGameContract080) GetMaxClockDuration(ctx context.Context) (time.Duration, error) { func (f *FaultDisputeGameContract080) GetMaxClockDuration(ctx context.Context) (time.Duration, error) {
......
...@@ -475,23 +475,38 @@ func TestGetGameMetadata(t *testing.T) { ...@@ -475,23 +475,38 @@ func TestGetGameMetadata(t *testing.T) {
expectedMaxClockDuration := uint64(456) expectedMaxClockDuration := uint64(456)
expectedRootClaim := common.Hash{0x01, 0x02} expectedRootClaim := common.Hash{0x01, 0x02}
expectedStatus := types.GameStatusChallengerWon expectedStatus := types.GameStatusChallengerWon
expectedL2BlockNumberChallenged := true
expectedL2BlockNumberChallenger := common.Address{0xee}
block := rpcblock.ByNumber(889) block := rpcblock.ByNumber(889)
stubRpc.SetResponse(fdgAddr, methodL1Head, block, nil, []interface{}{expectedL1Head}) stubRpc.SetResponse(fdgAddr, methodL1Head, block, nil, []interface{}{expectedL1Head})
stubRpc.SetResponse(fdgAddr, methodL2BlockNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)}) stubRpc.SetResponse(fdgAddr, methodL2BlockNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)})
stubRpc.SetResponse(fdgAddr, methodRootClaim, block, nil, []interface{}{expectedRootClaim}) stubRpc.SetResponse(fdgAddr, methodRootClaim, block, nil, []interface{}{expectedRootClaim})
stubRpc.SetResponse(fdgAddr, methodStatus, block, nil, []interface{}{expectedStatus}) stubRpc.SetResponse(fdgAddr, methodStatus, block, nil, []interface{}{expectedStatus})
if version.version == vers080 { if version.version == vers080 {
expectedL2BlockNumberChallenged = false
expectedL2BlockNumberChallenger = common.Address{}
stubRpc.SetResponse(fdgAddr, methodGameDuration, block, nil, []interface{}{expectedMaxClockDuration * 2}) stubRpc.SetResponse(fdgAddr, methodGameDuration, block, nil, []interface{}{expectedMaxClockDuration * 2})
} else if version.version == vers0180 {
expectedL2BlockNumberChallenged = false
expectedL2BlockNumberChallenger = common.Address{}
stubRpc.SetResponse(fdgAddr, methodMaxClockDuration, block, nil, []interface{}{expectedMaxClockDuration})
} else { } else {
stubRpc.SetResponse(fdgAddr, methodMaxClockDuration, block, nil, []interface{}{expectedMaxClockDuration}) stubRpc.SetResponse(fdgAddr, methodMaxClockDuration, block, nil, []interface{}{expectedMaxClockDuration})
stubRpc.SetResponse(fdgAddr, methodL2BlockNumberChallenged, block, nil, []interface{}{expectedL2BlockNumberChallenged})
stubRpc.SetResponse(fdgAddr, methodL2BlockNumberChallenger, block, nil, []interface{}{expectedL2BlockNumberChallenger})
}
actual, err := contract.GetGameMetadata(context.Background(), block)
expected := GameMetadata{
L1Head: expectedL1Head,
L2BlockNum: expectedL2BlockNumber,
RootClaim: expectedRootClaim,
Status: expectedStatus,
MaxClockDuration: expectedMaxClockDuration,
L2BlockNumberChallenged: expectedL2BlockNumberChallenged,
L2BlockNumberChallenger: expectedL2BlockNumberChallenger,
} }
l1Head, l2BlockNumber, rootClaim, status, duration, err := contract.GetGameMetadata(context.Background(), block)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expectedL1Head, l1Head) require.Equal(t, expected, actual)
require.Equal(t, expectedL2BlockNumber, l2BlockNumber)
require.Equal(t, expectedRootClaim, rootClaim)
require.Equal(t, expectedStatus, status)
require.Equal(t, expectedMaxClockDuration, duration)
}) })
} }
} }
......
...@@ -132,6 +132,8 @@ type Metricer interface { ...@@ -132,6 +132,8 @@ type Metricer interface {
RecordBondCollateral(addr common.Address, required *big.Int, available *big.Int) RecordBondCollateral(addr common.Address, required *big.Int, available *big.Int)
RecordL2Challenges(agreement bool, count int)
caching.Metrics caching.Metrics
contractMetrics.ContractMetricer contractMetrics.ContractMetricer
} }
...@@ -169,6 +171,7 @@ type Metrics struct { ...@@ -169,6 +171,7 @@ type Metrics struct {
latestInvalidProposal prometheus.Gauge latestInvalidProposal prometheus.Gauge
ignoredGames prometheus.Gauge ignoredGames prometheus.Gauge
failedGames prometheus.Gauge failedGames prometheus.Gauge
l2Challenges prometheus.GaugeVec
requiredCollateral prometheus.GaugeVec requiredCollateral prometheus.GaugeVec
availableCollateral prometheus.GaugeVec availableCollateral prometheus.GaugeVec
...@@ -309,6 +312,15 @@ func NewMetrics() *Metrics { ...@@ -309,6 +312,15 @@ func NewMetrics() *Metrics {
"delayedWETH", "delayedWETH",
"balance", "balance",
}), }),
l2Challenges: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: Namespace,
Name: "l2_block_challenges",
Help: "Number of games where the L2 block number has been successfully challenged",
}, []string{
// Agreement with the root claim, not the actual l2 block number challenge.
// An l2 block number challenge with an agreement means the challenge was invalid.
"root_agreement",
}),
} }
} }
...@@ -467,6 +479,14 @@ func (m *Metrics) RecordBondCollateral(addr common.Address, required *big.Int, a ...@@ -467,6 +479,14 @@ func (m *Metrics) RecordBondCollateral(addr common.Address, required *big.Int, a
m.availableCollateral.WithLabelValues(addr.Hex(), zeroBalanceLabel).Set(0) m.availableCollateral.WithLabelValues(addr.Hex(), zeroBalanceLabel).Set(0)
} }
func (m *Metrics) RecordL2Challenges(agreement bool, count int) {
agree := "disagree"
if agreement {
agree = "agree"
}
m.l2Challenges.WithLabelValues(agree).Set(float64(count))
}
const ( const (
inProgress = true inProgress = true
correct = true correct = true
......
...@@ -17,8 +17,7 @@ var NoopMetrics Metricer = new(NoopMetricsImpl) ...@@ -17,8 +17,7 @@ var NoopMetrics Metricer = new(NoopMetricsImpl)
func (*NoopMetricsImpl) RecordInfo(_ string) {} func (*NoopMetricsImpl) RecordInfo(_ string) {}
func (*NoopMetricsImpl) RecordUp() {} func (*NoopMetricsImpl) RecordUp() {}
func (i *NoopMetricsImpl) RecordMonitorDuration(_ time.Duration) { func (*NoopMetricsImpl) RecordMonitorDuration(_ time.Duration) {}
}
func (*NoopMetricsImpl) CacheAdd(_ string, _ int, _ bool) {} func (*NoopMetricsImpl) CacheAdd(_ string, _ int, _ bool) {}
func (*NoopMetricsImpl) CacheGet(_ string, _ bool) {} func (*NoopMetricsImpl) CacheGet(_ string, _ bool) {}
...@@ -44,3 +43,5 @@ func (*NoopMetricsImpl) RecordIgnoredGames(_ int) {} ...@@ -44,3 +43,5 @@ func (*NoopMetricsImpl) RecordIgnoredGames(_ int) {}
func (*NoopMetricsImpl) RecordFailedGames(_ int) {} func (*NoopMetricsImpl) RecordFailedGames(_ int) {}
func (*NoopMetricsImpl) RecordBondCollateral(_ common.Address, _ *big.Int, _ *big.Int) {} func (*NoopMetricsImpl) RecordBondCollateral(_ common.Address, _ *big.Int, _ *big.Int) {}
func (*NoopMetricsImpl) RecordL2Challenges(_ bool, _ int) {}
...@@ -59,7 +59,10 @@ func (b *Bonds) checkCredits(games []*types.EnrichedGameData) { ...@@ -59,7 +59,10 @@ func (b *Bonds) checkCredits(games []*types.EnrichedGameData) {
} }
// The recipient of a resolved claim is the claimant unless it's been countered. // The recipient of a resolved claim is the claimant unless it's been countered.
recipient := claim.Claimant recipient := claim.Claimant
if claim.CounteredBy != (common.Address{}) { if claim.IsRoot() && game.BlockNumberChallenged {
// The bond for the root claim is paid to the block number challenger if present
recipient = game.BlockNumberChallenger
} else if claim.CounteredBy != (common.Address{}) {
recipient = claim.CounteredBy recipient = claim.CounteredBy
} }
current := expectedCredits[recipient] current := expectedCredits[recipient]
......
...@@ -57,6 +57,7 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -57,6 +57,7 @@ func TestCheckRecipientCredit(t *testing.T) {
addr2 := common.Address{0x2b} addr2 := common.Address{0x2b}
addr3 := common.Address{0x3c} addr3 := common.Address{0x3c}
addr4 := common.Address{0x4d} addr4 := common.Address{0x4d}
notRootPosition := types.NewPositionFromGIndex(big.NewInt(2))
// Game has not reached max duration // Game has not reached max duration
game1 := &monTypes.EnrichedGameData{ game1 := &monTypes.EnrichedGameData{
MaxClockDuration: 50000, MaxClockDuration: 50000,
...@@ -68,7 +69,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -68,7 +69,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // Expect 10 credits for addr1 { // Expect 10 credits for addr1
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(10), Bond: big.NewInt(10),
Position: types.RootPosition,
}, },
Claimant: addr1, Claimant: addr1,
}, },
...@@ -77,7 +79,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -77,7 +79,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // No expected credits as not resolved { // No expected credits as not resolved
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(15), Bond: big.NewInt(15),
Position: notRootPosition,
}, },
Claimant: addr1, Claimant: addr1,
}, },
...@@ -86,7 +89,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -86,7 +89,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // Expect 5 credits for addr1 { // Expect 5 credits for addr1
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(5), Bond: big.NewInt(5),
Position: notRootPosition,
}, },
Claimant: addr1, Claimant: addr1,
}, },
...@@ -95,7 +99,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -95,7 +99,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // Expect 7 credits for addr2 { // Expect 7 credits for addr2
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(7), Bond: big.NewInt(7),
Position: notRootPosition,
}, },
Claimant: addr3, Claimant: addr3,
CounteredBy: addr2, CounteredBy: addr2,
...@@ -105,7 +110,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -105,7 +110,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // Expect 3 credits for addr4 { // Expect 3 credits for addr4
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(3), Bond: big.NewInt(3),
Position: notRootPosition,
}, },
Claimant: addr4, Claimant: addr4,
}, },
...@@ -135,7 +141,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -135,7 +141,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // Expect 11 credits for addr1 { // Expect 11 credits for addr1
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(11), Bond: big.NewInt(11),
Position: types.RootPosition,
}, },
Claimant: addr1, Claimant: addr1,
}, },
...@@ -144,7 +151,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -144,7 +151,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // No expected credits as not resolved { // No expected credits as not resolved
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(15), Bond: big.NewInt(15),
Position: notRootPosition,
}, },
Claimant: addr1, Claimant: addr1,
}, },
...@@ -153,7 +161,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -153,7 +161,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // Expect 6 credits for addr1 { // Expect 6 credits for addr1
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(6), Bond: big.NewInt(6),
Position: notRootPosition,
}, },
Claimant: addr1, Claimant: addr1,
}, },
...@@ -162,7 +171,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -162,7 +171,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // Expect 8 credits for addr2 { // Expect 8 credits for addr2
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(8), Bond: big.NewInt(8),
Position: notRootPosition,
}, },
Claimant: addr3, Claimant: addr3,
CounteredBy: addr2, CounteredBy: addr2,
...@@ -172,7 +182,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -172,7 +182,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // Expect 4 credits for addr4 { // Expect 4 credits for addr4
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(4), Bond: big.NewInt(4),
Position: notRootPosition,
}, },
Claimant: addr4, Claimant: addr4,
}, },
...@@ -204,7 +215,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -204,7 +215,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // Expect 9 credits for addr1 { // Expect 9 credits for addr1
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(9), Bond: big.NewInt(9),
Position: types.RootPosition,
}, },
Claimant: addr1, Claimant: addr1,
}, },
...@@ -213,7 +225,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -213,7 +225,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // Expect 6 credits for addr2 { // Expect 6 credits for addr2
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(6), Bond: big.NewInt(6),
Position: notRootPosition,
}, },
Claimant: addr4, Claimant: addr4,
CounteredBy: addr2, CounteredBy: addr2,
...@@ -223,7 +236,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -223,7 +236,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // Expect 2 credits for addr4 { // Expect 2 credits for addr4
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(2), Bond: big.NewInt(2),
Position: notRootPosition,
}, },
Claimant: addr4, Claimant: addr4,
}, },
...@@ -250,20 +264,25 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -250,20 +264,25 @@ func TestCheckRecipientCredit(t *testing.T) {
Proxy: common.Address{44}, Proxy: common.Address{44},
Timestamp: uint64(frozen.Unix()) - 22, Timestamp: uint64(frozen.Unix()) - 22,
}, },
BlockNumberChallenged: true,
BlockNumberChallenger: addr1,
Claims: []monTypes.EnrichedClaim{ Claims: []monTypes.EnrichedClaim{
{ // Expect 9 credits for addr1 { // Expect 9 credits for addr1 as the block number challenger
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(9), Bond: big.NewInt(9),
Position: types.RootPosition,
}, },
Claimant: addr1, Claimant: addr2,
CounteredBy: addr3,
}, },
Resolved: true, Resolved: true,
}, },
{ // Expect 6 credits for addr2 { // Expect 6 credits for addr2
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(6), Bond: big.NewInt(6),
Position: notRootPosition,
}, },
Claimant: addr4, Claimant: addr4,
CounteredBy: addr2, CounteredBy: addr2,
...@@ -273,7 +292,8 @@ func TestCheckRecipientCredit(t *testing.T) { ...@@ -273,7 +292,8 @@ func TestCheckRecipientCredit(t *testing.T) {
{ // Expect 2 credits for addr4 { // Expect 2 credits for addr4
Claim: types.Claim{ Claim: types.Claim{
ClaimData: types.ClaimData{ ClaimData: types.ClaimData{
Bond: big.NewInt(2), Bond: big.NewInt(2),
Position: notRootPosition,
}, },
Claimant: addr4, Claimant: addr4,
}, },
......
...@@ -27,21 +27,13 @@ func NewBondEnricher() *BondEnricher { ...@@ -27,21 +27,13 @@ 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) recipientAddrs := maps.Keys(game.Recipients)
for _, claim := range game.Claims {
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...) credits, err := caller.GetCredits(ctx, block, recipientAddrs...)
if err != nil { if err != nil {
return err return err
} }
if len(credits) != len(recipients) { if len(credits) != len(recipientAddrs) {
return fmt.Errorf("%w, requested %v values but got %v", ErrIncorrectCreditCount, len(recipients), len(credits)) return fmt.Errorf("%w, requested %v values but got %v", ErrIncorrectCreditCount, len(recipientAddrs), 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 {
......
...@@ -25,7 +25,7 @@ type GameCallerMetrics interface { ...@@ -25,7 +25,7 @@ type GameCallerMetrics interface {
type GameCaller interface { type GameCaller interface {
GetWithdrawals(context.Context, rpcblock.Block, common.Address, ...common.Address) ([]*contracts.WithdrawalRequest, error) GetWithdrawals(context.Context, rpcblock.Block, common.Address, ...common.Address) ([]*contracts.WithdrawalRequest, error)
GetGameMetadata(context.Context, rpcblock.Block) (common.Hash, uint64, common.Hash, gameTypes.GameStatus, uint64, error) GetGameMetadata(context.Context, rpcblock.Block) (contracts.GameMetadata, error)
GetAllClaims(context.Context, rpcblock.Block) ([]faultTypes.Claim, error) GetAllClaims(context.Context, rpcblock.Block) ([]faultTypes.Claim, error)
BondCaller BondCaller
BalanceCaller BalanceCaller
......
...@@ -125,7 +125,7 @@ func (e *Extractor) enrichGame(ctx context.Context, blockHash common.Hash, game ...@@ -125,7 +125,7 @@ func (e *Extractor) enrichGame(ctx context.Context, blockHash common.Hash, game
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create contracts: %w", err) return nil, fmt.Errorf("failed to create contracts: %w", err)
} }
l1Head, l2BlockNum, rootClaim, status, duration, err := caller.GetGameMetadata(ctx, rpcblock.ByHash(blockHash)) meta, err := caller.GetGameMetadata(ctx, rpcblock.ByHash(blockHash))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch game metadata: %w", err) return nil, fmt.Errorf("failed to fetch game metadata: %w", err)
} }
...@@ -138,13 +138,15 @@ func (e *Extractor) enrichGame(ctx context.Context, blockHash common.Hash, game ...@@ -138,13 +138,15 @@ func (e *Extractor) enrichGame(ctx context.Context, blockHash common.Hash, game
enrichedClaims[i] = monTypes.EnrichedClaim{Claim: claim} enrichedClaims[i] = monTypes.EnrichedClaim{Claim: claim}
} }
enrichedGame := &monTypes.EnrichedGameData{ enrichedGame := &monTypes.EnrichedGameData{
GameMetadata: game, GameMetadata: game,
L1Head: l1Head, L1Head: meta.L1Head,
L2BlockNumber: l2BlockNum, L2BlockNumber: meta.L2BlockNum,
RootClaim: rootClaim, RootClaim: meta.RootClaim,
Status: status, Status: meta.Status,
MaxClockDuration: duration, MaxClockDuration: meta.MaxClockDuration,
Claims: enrichedClaims, BlockNumberChallenged: meta.L2BlockNumberChallenged,
BlockNumberChallenger: meta.L2BlockNumberChallenger,
Claims: enrichedClaims,
} }
if err := e.applyEnrichers(ctx, blockHash, caller, enrichedGame); err != nil { if err := e.applyEnrichers(ctx, blockHash, caller, enrichedGame); err != nil {
return nil, fmt.Errorf("failed to enrich game: %w", err) return nil, fmt.Errorf("failed to enrich game: %w", err)
......
...@@ -153,7 +153,7 @@ func TestExtractor_Extract(t *testing.T) { ...@@ -153,7 +153,7 @@ func TestExtractor_Extract(t *testing.T) {
}) })
} }
func verifyLogs(t *testing.T, logs *testlog.CapturingHandler, createErr int, metadataErr int, claimsErr int, durationErr int) { func verifyLogs(t *testing.T, logs *testlog.CapturingHandler, createErr, metadataErr, claimsErr, durationErr int) {
errorLevelFilter := testlog.NewLevelFilter(log.LevelError) errorLevelFilter := testlog.NewLevelFilter(log.LevelError)
createMessageFilter := testlog.NewAttributesContainsFilter("err", "failed to create contracts") createMessageFilter := testlog.NewAttributesContainsFilter("err", "failed to create contracts")
l := logs.FindLogs(errorLevelFilter, createMessageFilter) l := logs.FindLogs(errorLevelFilter, createMessageFilter)
...@@ -254,12 +254,15 @@ func (m *mockGameCaller) GetWithdrawals(_ context.Context, _ rpcblock.Block, _ c ...@@ -254,12 +254,15 @@ func (m *mockGameCaller) GetWithdrawals(_ context.Context, _ rpcblock.Block, _ c
}, nil }, nil
} }
func (m *mockGameCaller) GetGameMetadata(_ context.Context, _ rpcblock.Block) (common.Hash, uint64, common.Hash, gameTypes.GameStatus, uint64, error) { func (m *mockGameCaller) GetGameMetadata(_ context.Context, _ rpcblock.Block) (contracts.GameMetadata, error) {
m.metadataCalls++ m.metadataCalls++
if m.metadataErr != nil { if m.metadataErr != nil {
return common.Hash{}, 0, common.Hash{}, 0, 0, m.metadataErr return contracts.GameMetadata{}, m.metadataErr
} }
return common.Hash{0xaa}, 0, mockRootClaim, 0, 0, nil return contracts.GameMetadata{
L1Head: common.Hash{0xaa},
RootClaim: mockRootClaim,
}, nil
} }
func (m *mockGameCaller) GetAllClaims(_ context.Context, _ rpcblock.Block) ([]faultTypes.Claim, error) { func (m *mockGameCaller) GetAllClaims(_ context.Context, _ rpcblock.Block) ([]faultTypes.Claim, error) {
......
...@@ -25,6 +25,9 @@ func (w *RecipientEnricher) Enrich(_ context.Context, _ rpcblock.Block, _ GameCa ...@@ -25,6 +25,9 @@ func (w *RecipientEnricher) Enrich(_ context.Context, _ rpcblock.Block, _ GameCa
recipients[claim.Claimant] = true recipients[claim.Claimant] = true
} }
} }
if game.BlockNumberChallenger != (common.Address{}) {
recipients[game.BlockNumberChallenger] = true
}
game.Recipients = recipients game.Recipients = recipients
return nil return nil
} }
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
func TestRecipientEnricher(t *testing.T) { func TestRecipientEnricher(t *testing.T) {
game, recipients := makeTestGame() game, recipients := makeTestGame()
game.Recipients = make(map[common.Address]bool) game.Recipients = make(map[common.Address]bool)
game.BlockNumberChallenger = common.Address{0xff, 0xee, 0xdd}
enricher := NewRecipientEnricher() enricher := NewRecipientEnricher()
caller := &mockGameCaller{} caller := &mockGameCaller{}
ctx := context.Background() ctx := context.Background()
...@@ -20,4 +21,5 @@ func TestRecipientEnricher(t *testing.T) { ...@@ -20,4 +21,5 @@ func TestRecipientEnricher(t *testing.T) {
for _, recipient := range recipients { for _, recipient := range recipients {
require.Contains(t, game.Recipients, recipient) require.Contains(t, game.Recipients, recipient)
} }
require.Contains(t, game.Recipients, game.BlockNumberChallenger)
} }
...@@ -111,11 +111,19 @@ func (f *Forecast) forecastGame(game *monTypes.EnrichedGameData, metrics *foreca ...@@ -111,11 +111,19 @@ func (f *Forecast) forecastGame(game *monTypes.EnrichedGameData, metrics *foreca
return nil return nil
} }
// Create the bidirectional tree of claims. var forecastStatus types.GameStatus
tree := transform.CreateBidirectionalTree(game.Claims) // Games that have their block number challenged are won
// by the challenger since the counter is proven on-chain.
// Compute the resolution status of the game. if game.BlockNumberChallenged {
forecastStatus := Resolve(tree) f.logger.Debug("Found game with challenged block number",
"game", game.Proxy, "blockNum", game.L2BlockNumber, "agreement", agreement)
// If the block number is challenged the challenger will always win
forecastStatus = types.GameStatusChallengerWon
} else {
// Otherwise we go through the resolution process to determine who would win based on the current claims
tree := transform.CreateBidirectionalTree(game.Claims)
forecastStatus = Resolve(tree)
}
if agreement { if agreement {
// If we agree with the output root proposal, the Defender should win, defending that claim. // If we agree with the output root proposal, the Defender should win, defending that claim.
......
...@@ -104,6 +104,48 @@ func TestForecast_Forecast_BasicTests(t *testing.T) { ...@@ -104,6 +104,48 @@ func TestForecast_Forecast_BasicTests(t *testing.T) {
func TestForecast_Forecast_EndLogs(t *testing.T) { func TestForecast_Forecast_EndLogs(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("BlockNumberChallenged_AgreeWithChallenge", func(t *testing.T) {
forecast, m, logs := setupForecastTest(t)
expectedGame := monTypes.EnrichedGameData{
Status: types.GameStatusInProgress,
BlockNumberChallenged: true,
L2BlockNumber: 6,
AgreeWithClaim: false,
}
forecast.Forecast([]*monTypes.EnrichedGameData{&expectedGame}, 0, 0)
l := logs.FindLog(testlog.NewLevelFilter(log.LevelDebug), testlog.NewMessageFilter("Found game with challenged block number"))
require.NotNil(t, l)
require.Equal(t, expectedGame.Proxy, l.AttrValue("game"))
require.Equal(t, expectedGame.L2BlockNumber, l.AttrValue("blockNum"))
require.Equal(t, false, l.AttrValue("agreement"))
expectedMetrics := zeroGameAgreement()
// We disagree with the root claim and the challenger is ahead
expectedMetrics[metrics.DisagreeChallengerAhead] = 1
require.Equal(t, expectedMetrics, m.gameAgreement)
})
t.Run("BlockNumberChallenged_DisagreeWithChallenge", func(t *testing.T) {
forecast, m, logs := setupForecastTest(t)
expectedGame := monTypes.EnrichedGameData{
Status: types.GameStatusInProgress,
BlockNumberChallenged: true,
L2BlockNumber: 6,
AgreeWithClaim: true,
}
forecast.Forecast([]*monTypes.EnrichedGameData{&expectedGame}, 0, 0)
l := logs.FindLog(testlog.NewLevelFilter(log.LevelDebug), testlog.NewMessageFilter("Found game with challenged block number"))
require.NotNil(t, l)
require.Equal(t, expectedGame.Proxy, l.AttrValue("game"))
require.Equal(t, expectedGame.L2BlockNumber, l.AttrValue("blockNum"))
require.Equal(t, true, l.AttrValue("agreement"))
expectedMetrics := zeroGameAgreement()
// We agree with the root claim and the challenger is ahead
expectedMetrics[metrics.AgreeChallengerAhead] = 1
require.Equal(t, expectedMetrics, m.gameAgreement)
})
t.Run("AgreeDefenderWins", func(t *testing.T) { t.Run("AgreeDefenderWins", func(t *testing.T) {
forecast, _, logs := setupForecastTest(t) forecast, _, logs := setupForecastTest(t)
games := []*monTypes.EnrichedGameData{{ games := []*monTypes.EnrichedGameData{{
......
package mon
import (
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum/go-ethereum/log"
)
type L2ChallengesMetrics interface {
RecordL2Challenges(agreement bool, count int)
}
type L2ChallengesMonitor struct {
logger log.Logger
metrics L2ChallengesMetrics
}
func NewL2ChallengesMonitor(logger log.Logger, metrics L2ChallengesMetrics) *L2ChallengesMonitor {
return &L2ChallengesMonitor{
logger: logger,
metrics: metrics,
}
}
func (m *L2ChallengesMonitor) CheckL2Challenges(games []*types.EnrichedGameData) {
agreeChallengeCount := 0
disagreeChallengeCount := 0
for _, game := range games {
if game.BlockNumberChallenged {
if game.AgreeWithClaim {
m.logger.Warn("Found game with valid block number challenged",
"game", game.Proxy, "blockNum", game.L2BlockNumber, "agreement", game.AgreeWithClaim, "challenger", game.BlockNumberChallenger)
agreeChallengeCount++
} else {
m.logger.Debug("Found game with invalid block number challenged",
"game", game.Proxy, "blockNum", game.L2BlockNumber, "agreement", game.AgreeWithClaim, "challenger", game.BlockNumberChallenger)
disagreeChallengeCount++
}
}
}
m.metrics.RecordL2Challenges(true, agreeChallengeCount)
m.metrics.RecordL2Challenges(false, disagreeChallengeCount)
}
package mon
import (
"testing"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
func TestMonitorL2Challenges(t *testing.T) {
games := []*types.EnrichedGameData{
{GameMetadata: gameTypes.GameMetadata{Proxy: common.Address{0x44}}, BlockNumberChallenged: true, AgreeWithClaim: true, L2BlockNumber: 44, BlockNumberChallenger: common.Address{0x55}},
{BlockNumberChallenged: false, AgreeWithClaim: true},
{GameMetadata: gameTypes.GameMetadata{Proxy: common.Address{0x22}}, BlockNumberChallenged: true, AgreeWithClaim: false, L2BlockNumber: 22, BlockNumberChallenger: common.Address{0x33}},
{BlockNumberChallenged: false, AgreeWithClaim: false},
{BlockNumberChallenged: false, AgreeWithClaim: false},
{BlockNumberChallenged: false, AgreeWithClaim: true},
}
metrics := &stubL2ChallengeMetrics{}
logger, capturedLogs := testlog.CaptureLogger(t, log.LvlDebug)
monitor := NewL2ChallengesMonitor(logger, metrics)
monitor.CheckL2Challenges(games)
require.Equal(t, 1, metrics.challengeCount[true])
require.Equal(t, 1, metrics.challengeCount[false])
// Warn log for challenged and agreement
levelFilter := testlog.NewLevelFilter(log.LevelWarn)
messageFilter := testlog.NewMessageFilter("Found game with valid block number challenged")
l := capturedLogs.FindLog(levelFilter, messageFilter)
require.NotNil(t, l)
require.Equal(t, common.Address{0x44}, l.AttrValue("game"))
require.Equal(t, uint64(44), l.AttrValue("blockNum"))
require.Equal(t, true, l.AttrValue("agreement"))
require.Equal(t, common.Address{0x55}, l.AttrValue("challenger"))
// Debug log for challenged but disagreement
levelFilter = testlog.NewLevelFilter(log.LevelDebug)
messageFilter = testlog.NewMessageFilter("Found game with invalid block number challenged")
l = capturedLogs.FindLog(levelFilter, messageFilter)
require.NotNil(t, l)
require.Equal(t, common.Address{0x22}, l.AttrValue("game"))
require.Equal(t, uint64(22), l.AttrValue("blockNum"))
require.Equal(t, false, l.AttrValue("agreement"))
require.Equal(t, common.Address{0x33}, l.AttrValue("challenger"))
}
type stubL2ChallengeMetrics struct {
challengeCount map[bool]int
}
func (s *stubL2ChallengeMetrics) RecordL2Challenges(agreement bool, count int) {
if s.challengeCount == nil {
s.challengeCount = make(map[bool]int)
}
s.challengeCount[agreement] = count
}
...@@ -16,8 +16,7 @@ import ( ...@@ -16,8 +16,7 @@ import (
type ForecastResolution func(games []*types.EnrichedGameData, ignoredCount, failedCount int) type ForecastResolution func(games []*types.EnrichedGameData, ignoredCount, failedCount int)
type Bonds func(games []*types.EnrichedGameData) type Bonds func(games []*types.EnrichedGameData)
type Resolutions func(games []*types.EnrichedGameData) type Resolutions func(games []*types.EnrichedGameData)
type MonitorClaims func(games []*types.EnrichedGameData) type Monitor func(games []*types.EnrichedGameData)
type MonitorWithdrawals func(games []*types.EnrichedGameData)
type BlockHashFetcher func(ctx context.Context, number *big.Int) (common.Hash, error) type BlockHashFetcher func(ctx context.Context, number *big.Int) (common.Hash, error)
type BlockNumberFetcher func(ctx context.Context) (uint64, error) type BlockNumberFetcher func(ctx context.Context) (uint64, error)
type Extract func(ctx context.Context, blockHash common.Hash, minTimestamp uint64) ([]*types.EnrichedGameData, int, int, error) type Extract func(ctx context.Context, blockHash common.Hash, minTimestamp uint64) ([]*types.EnrichedGameData, int, int, error)
...@@ -41,8 +40,9 @@ type gameMonitor struct { ...@@ -41,8 +40,9 @@ type gameMonitor struct {
forecast ForecastResolution forecast ForecastResolution
bonds Bonds bonds Bonds
resolutions Resolutions resolutions Resolutions
claims MonitorClaims claims Monitor
withdrawals MonitorWithdrawals withdrawals Monitor
l2Challenges Monitor
extract Extract extract Extract
fetchBlockHash BlockHashFetcher fetchBlockHash BlockHashFetcher
fetchBlockNumber BlockNumberFetcher fetchBlockNumber BlockNumberFetcher
...@@ -58,8 +58,9 @@ func newGameMonitor( ...@@ -58,8 +58,9 @@ func newGameMonitor(
forecast ForecastResolution, forecast ForecastResolution,
bonds Bonds, bonds Bonds,
resolutions Resolutions, resolutions Resolutions,
claims MonitorClaims, claims Monitor,
withdrawals MonitorWithdrawals, withdrawals Monitor,
l2Challenges Monitor,
extract Extract, extract Extract,
fetchBlockNumber BlockNumberFetcher, fetchBlockNumber BlockNumberFetcher,
fetchBlockHash BlockHashFetcher, fetchBlockHash BlockHashFetcher,
...@@ -77,6 +78,7 @@ func newGameMonitor( ...@@ -77,6 +78,7 @@ func newGameMonitor(
resolutions: resolutions, resolutions: resolutions,
claims: claims, claims: claims,
withdrawals: withdrawals, withdrawals: withdrawals,
l2Challenges: l2Challenges,
extract: extract, extract: extract,
fetchBlockNumber: fetchBlockNumber, fetchBlockNumber: fetchBlockNumber,
fetchBlockHash: fetchBlockHash, fetchBlockHash: fetchBlockHash,
...@@ -104,6 +106,7 @@ func (m *gameMonitor) monitorGames() error { ...@@ -104,6 +106,7 @@ func (m *gameMonitor) monitorGames() error {
m.bonds(enrichedGames) m.bonds(enrichedGames)
m.claims(enrichedGames) m.claims(enrichedGames)
m.withdrawals(enrichedGames) m.withdrawals(enrichedGames)
m.l2Challenges(enrichedGames)
timeTaken := m.clock.Since(start) timeTaken := m.clock.Since(start)
m.metrics.RecordMonitorDuration(timeTaken) m.metrics.RecordMonitorDuration(timeTaken)
m.logger.Info("Completed monitoring update", "blockNumber", blockNumber, "blockHash", blockHash, "duration", timeTaken, "games", len(enrichedGames), "ignored", ignored, "failed", failed) m.logger.Info("Completed monitoring update", "blockNumber", blockNumber, "blockHash", blockHash, "duration", timeTaken, "games", len(enrichedGames), "ignored", ignored, "failed", failed)
......
...@@ -25,7 +25,7 @@ func TestMonitor_MonitorGames(t *testing.T) { ...@@ -25,7 +25,7 @@ func TestMonitor_MonitorGames(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("FailedFetchBlocknumber", func(t *testing.T) { t.Run("FailedFetchBlocknumber", func(t *testing.T) {
monitor, _, _, _, _, _, _ := setupMonitorTest(t) monitor, _, _, _, _, _, _, _ := setupMonitorTest(t)
boom := errors.New("boom") boom := errors.New("boom")
monitor.fetchBlockNumber = func(ctx context.Context) (uint64, error) { monitor.fetchBlockNumber = func(ctx context.Context) (uint64, error) {
return 0, boom return 0, boom
...@@ -35,7 +35,7 @@ func TestMonitor_MonitorGames(t *testing.T) { ...@@ -35,7 +35,7 @@ func TestMonitor_MonitorGames(t *testing.T) {
}) })
t.Run("FailedFetchBlockHash", func(t *testing.T) { t.Run("FailedFetchBlockHash", func(t *testing.T) {
monitor, _, _, _, _, _, _ := setupMonitorTest(t) monitor, _, _, _, _, _, _, _ := setupMonitorTest(t)
boom := errors.New("boom") boom := errors.New("boom")
monitor.fetchBlockHash = func(ctx context.Context, number *big.Int) (common.Hash, error) { monitor.fetchBlockHash = func(ctx context.Context, number *big.Int) (common.Hash, error) {
return common.Hash{}, boom return common.Hash{}, boom
...@@ -45,7 +45,7 @@ func TestMonitor_MonitorGames(t *testing.T) { ...@@ -45,7 +45,7 @@ func TestMonitor_MonitorGames(t *testing.T) {
}) })
t.Run("MonitorsWithNoGames", func(t *testing.T) { t.Run("MonitorsWithNoGames", func(t *testing.T) {
monitor, factory, forecast, bonds, withdrawals, resolutions, claims := setupMonitorTest(t) monitor, factory, forecast, bonds, withdrawals, resolutions, claims, l2Challenges := setupMonitorTest(t)
factory.games = []*monTypes.EnrichedGameData{} factory.games = []*monTypes.EnrichedGameData{}
err := monitor.monitorGames() err := monitor.monitorGames()
require.NoError(t, err) require.NoError(t, err)
...@@ -54,10 +54,11 @@ func TestMonitor_MonitorGames(t *testing.T) { ...@@ -54,10 +54,11 @@ func TestMonitor_MonitorGames(t *testing.T) {
require.Equal(t, 1, resolutions.calls) require.Equal(t, 1, resolutions.calls)
require.Equal(t, 1, claims.calls) require.Equal(t, 1, claims.calls)
require.Equal(t, 1, withdrawals.calls) require.Equal(t, 1, withdrawals.calls)
require.Equal(t, 1, l2Challenges.calls)
}) })
t.Run("MonitorsMultipleGames", func(t *testing.T) { t.Run("MonitorsMultipleGames", func(t *testing.T) {
monitor, factory, forecast, bonds, withdrawals, resolutions, claims := setupMonitorTest(t) monitor, factory, forecast, bonds, withdrawals, resolutions, claims, l2Challenges := setupMonitorTest(t)
factory.games = []*monTypes.EnrichedGameData{{}, {}, {}} factory.games = []*monTypes.EnrichedGameData{{}, {}, {}}
err := monitor.monitorGames() err := monitor.monitorGames()
require.NoError(t, err) require.NoError(t, err)
...@@ -66,6 +67,7 @@ func TestMonitor_MonitorGames(t *testing.T) { ...@@ -66,6 +67,7 @@ func TestMonitor_MonitorGames(t *testing.T) {
require.Equal(t, 1, resolutions.calls) require.Equal(t, 1, resolutions.calls)
require.Equal(t, 1, claims.calls) require.Equal(t, 1, claims.calls)
require.Equal(t, 1, withdrawals.calls) require.Equal(t, 1, withdrawals.calls)
require.Equal(t, 1, l2Challenges.calls)
}) })
} }
...@@ -73,7 +75,7 @@ func TestMonitor_StartMonitoring(t *testing.T) { ...@@ -73,7 +75,7 @@ func TestMonitor_StartMonitoring(t *testing.T) {
t.Run("MonitorsGames", func(t *testing.T) { t.Run("MonitorsGames", func(t *testing.T) {
addr1 := common.Address{0xaa} addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb} addr2 := common.Address{0xbb}
monitor, factory, forecaster, _, _, _, _ := setupMonitorTest(t) monitor, factory, forecaster, _, _, _, _, _ := setupMonitorTest(t)
factory.games = []*monTypes.EnrichedGameData{newEnrichedGameData(addr1, 9999), newEnrichedGameData(addr2, 9999)} factory.games = []*monTypes.EnrichedGameData{newEnrichedGameData(addr1, 9999), newEnrichedGameData(addr2, 9999)}
factory.maxSuccess = len(factory.games) // Only allow two successful fetches factory.maxSuccess = len(factory.games) // Only allow two successful fetches
...@@ -86,7 +88,7 @@ func TestMonitor_StartMonitoring(t *testing.T) { ...@@ -86,7 +88,7 @@ func TestMonitor_StartMonitoring(t *testing.T) {
}) })
t.Run("FailsToFetchGames", func(t *testing.T) { t.Run("FailsToFetchGames", func(t *testing.T) {
monitor, factory, forecaster, _, _, _, _ := setupMonitorTest(t) monitor, factory, forecaster, _, _, _, _, _ := setupMonitorTest(t)
factory.fetchErr = errors.New("boom") factory.fetchErr = errors.New("boom")
monitor.StartMonitoring() monitor.StartMonitoring()
...@@ -108,7 +110,7 @@ func newEnrichedGameData(proxy common.Address, timestamp uint64) *monTypes.Enric ...@@ -108,7 +110,7 @@ func newEnrichedGameData(proxy common.Address, timestamp uint64) *monTypes.Enric
} }
} }
func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast, *mockBonds, *mockWithdrawalMonitor, *mockResolutionMonitor, *mockClaimMonitor) { func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast, *mockBonds, *mockMonitor, *mockResolutionMonitor, *mockMonitor, *mockMonitor) {
logger := testlog.Logger(t, log.LvlDebug) logger := testlog.Logger(t, log.LvlDebug)
fetchBlockNum := func(ctx context.Context) (uint64, error) { fetchBlockNum := func(ctx context.Context) (uint64, error) {
return 1, nil return 1, nil
...@@ -123,8 +125,9 @@ func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast ...@@ -123,8 +125,9 @@ func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast
forecast := &mockForecast{} forecast := &mockForecast{}
bonds := &mockBonds{} bonds := &mockBonds{}
resolutions := &mockResolutionMonitor{} resolutions := &mockResolutionMonitor{}
claims := &mockClaimMonitor{} claims := &mockMonitor{}
withdrawals := &mockWithdrawalMonitor{} withdrawals := &mockMonitor{}
l2Challenges := &mockMonitor{}
monitor := newGameMonitor( monitor := newGameMonitor(
context.Background(), context.Background(),
logger, logger,
...@@ -135,13 +138,14 @@ func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast ...@@ -135,13 +138,14 @@ func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast
forecast.Forecast, forecast.Forecast,
bonds.CheckBonds, bonds.CheckBonds,
resolutions.CheckResolutions, resolutions.CheckResolutions,
claims.CheckClaims, claims.Check,
withdrawals.CheckWithdrawals, withdrawals.Check,
l2Challenges.Check,
extractor.Extract, extractor.Extract,
fetchBlockNum, fetchBlockNum,
fetchBlockHash, fetchBlockHash,
) )
return monitor, extractor, forecast, bonds, withdrawals, resolutions, claims return monitor, extractor, forecast, bonds, withdrawals, resolutions, claims, l2Challenges
} }
type mockResolutionMonitor struct { type mockResolutionMonitor struct {
...@@ -152,19 +156,11 @@ func (m *mockResolutionMonitor) CheckResolutions(games []*monTypes.EnrichedGameD ...@@ -152,19 +156,11 @@ func (m *mockResolutionMonitor) CheckResolutions(games []*monTypes.EnrichedGameD
m.calls++ m.calls++
} }
type mockClaimMonitor struct { type mockMonitor struct {
calls int calls int
} }
func (m *mockClaimMonitor) CheckClaims(games []*monTypes.EnrichedGameData) { func (m *mockMonitor) Check(games []*monTypes.EnrichedGameData) {
m.calls++
}
type mockWithdrawalMonitor struct {
calls int
}
func (m *mockWithdrawalMonitor) CheckWithdrawals(games []*monTypes.EnrichedGameData) {
m.calls++ m.calls++
} }
......
...@@ -128,7 +128,7 @@ func (s *Service) initExtractor(cfg *config.Config) { ...@@ -128,7 +128,7 @@ func (s *Service) initExtractor(cfg *config.Config) {
cfg.IgnoredGames, cfg.IgnoredGames,
cfg.MaxConcurrency, cfg.MaxConcurrency,
extract.NewClaimEnricher(), extract.NewClaimEnricher(),
extract.NewRecipientEnricher(), // Must be called before WithdrawalsEnricher extract.NewRecipientEnricher(), // Must be called before WithdrawalsEnricher and BondEnricher
extract.NewWithdrawalsEnricher(), extract.NewWithdrawalsEnricher(),
extract.NewBondEnricher(), extract.NewBondEnricher(),
extract.NewBalanceEnricher(), extract.NewBalanceEnricher(),
...@@ -213,6 +213,7 @@ func (s *Service) initMonitor(ctx context.Context, cfg *config.Config) { ...@@ -213,6 +213,7 @@ func (s *Service) initMonitor(ctx context.Context, cfg *config.Config) {
} }
return block.Hash(), nil return block.Hash(), nil
} }
l2ChallengesMonitor := NewL2ChallengesMonitor(s.logger, s.metrics)
s.monitor = newGameMonitor( s.monitor = newGameMonitor(
ctx, ctx,
s.logger, s.logger,
...@@ -225,6 +226,7 @@ func (s *Service) initMonitor(ctx context.Context, cfg *config.Config) { ...@@ -225,6 +226,7 @@ func (s *Service) initMonitor(ctx context.Context, cfg *config.Config) {
s.resolutions.CheckResolutions, s.resolutions.CheckResolutions,
s.claims.CheckClaims, s.claims.CheckClaims,
s.withdrawals.CheckWithdrawals, s.withdrawals.CheckWithdrawals,
l2ChallengesMonitor.CheckL2Challenges,
s.extractor.Extract, s.extractor.Extract,
s.l1Client.BlockNumber, s.l1Client.BlockNumber,
blockHashFetcher, blockHashFetcher,
......
...@@ -17,13 +17,15 @@ type EnrichedClaim struct { ...@@ -17,13 +17,15 @@ type EnrichedClaim struct {
type EnrichedGameData struct { type EnrichedGameData struct {
types.GameMetadata types.GameMetadata
L1Head common.Hash L1Head common.Hash
L1HeadNum uint64 L1HeadNum uint64
L2BlockNumber uint64 L2BlockNumber uint64
RootClaim common.Hash RootClaim common.Hash
Status types.GameStatus Status types.GameStatus
MaxClockDuration uint64 MaxClockDuration uint64
Claims []EnrichedClaim BlockNumberChallenged bool
BlockNumberChallenger common.Address
Claims []EnrichedClaim
AgreeWithClaim bool AgreeWithClaim bool
ExpectedRootClaim common.Hash ExpectedRootClaim common.Hash
......
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