Commit 43a70df7 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-dispute-mon: Report valid and invalid claims and bonds by honest actors (#10316)

* op-dispute-mon: Report valid and invalid claims by honest actors

* op-dispute-mon: Report won, lost and pending bonds for honest actors

* op-dispute-mon: Exclude refunded bonds from winnings
parent e1562379
...@@ -65,11 +65,20 @@ const ( ...@@ -65,11 +65,20 @@ const (
SecondHalfNotExpiredUnresolved SecondHalfNotExpiredUnresolved
) )
type HonestActorData struct {
PendingClaimCount int
ValidClaimCount int
InvalidClaimCount int
PendingBonds *big.Int
LostBonds *big.Int
WonBonds *big.Int
}
type Metricer interface { type Metricer interface {
RecordInfo(version string) RecordInfo(version string)
RecordUp() RecordUp()
RecordUnexpectedClaimResolution(address common.Address, count int) RecordHonestActorClaims(address common.Address, stats *HonestActorData)
RecordGameResolutionStatus(complete bool, maxDurationReached bool, count int) RecordGameResolutionStatus(complete bool, maxDurationReached bool, count int)
...@@ -108,7 +117,8 @@ type Metrics struct { ...@@ -108,7 +117,8 @@ type Metrics struct {
claims prometheus.GaugeVec claims prometheus.GaugeVec
unexpectedClaimResolutions prometheus.GaugeVec honestActorClaims prometheus.GaugeVec
honestActorBonds prometheus.GaugeVec
withdrawalRequests prometheus.GaugeVec withdrawalRequests prometheus.GaugeVec
...@@ -168,12 +178,21 @@ func NewMetrics() *Metrics { ...@@ -168,12 +178,21 @@ func NewMetrics() *Metrics {
Name: "claim_resolution_delay_max", Name: "claim_resolution_delay_max",
Help: "Maximum claim resolution delay in seconds", Help: "Maximum claim resolution delay in seconds",
}), }),
unexpectedClaimResolutions: *factory.NewGaugeVec(prometheus.GaugeOpts{ honestActorClaims: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: Namespace, Namespace: Namespace,
Name: "unexpected_claim_resolutions", Name: "honest_actor_claims",
Help: "Total number of unexpected claim resolutions against an honest actor", Help: "Total number of claims from an honest actor",
}, []string{ }, []string{
"honest_actor_address", "honest_actor_address",
"state",
}),
honestActorBonds: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: Namespace,
Name: "honest_actor_bonds",
Help: "Sum of bonds posted, won and lost by an honest actor",
}, []string{
"honest_actor_address",
"state",
}), }),
resolutionStatus: *factory.NewGaugeVec(prometheus.GaugeOpts{ resolutionStatus: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: Namespace, Namespace: Namespace,
...@@ -270,8 +289,14 @@ func (m *Metrics) RecordUp() { ...@@ -270,8 +289,14 @@ func (m *Metrics) RecordUp() {
m.up.Set(1) m.up.Set(1)
} }
func (m *Metrics) RecordUnexpectedClaimResolution(address common.Address, count int) { func (m *Metrics) RecordHonestActorClaims(address common.Address, stats *HonestActorData) {
m.unexpectedClaimResolutions.WithLabelValues(address.Hex()).Set(float64(count)) m.honestActorClaims.WithLabelValues(address.Hex(), "pending").Set(float64(stats.PendingClaimCount))
m.honestActorClaims.WithLabelValues(address.Hex(), "invalid").Set(float64(stats.InvalidClaimCount))
m.honestActorClaims.WithLabelValues(address.Hex(), "valid").Set(float64(stats.ValidClaimCount))
m.honestActorBonds.WithLabelValues(address.Hex(), "pending").Set(weiToEther(stats.PendingBonds))
m.honestActorBonds.WithLabelValues(address.Hex(), "lost").Set(weiToEther(stats.LostBonds))
m.honestActorBonds.WithLabelValues(address.Hex(), "won").Set(weiToEther(stats.WonBonds))
} }
func (m *Metrics) RecordGameResolutionStatus(complete bool, maxDurationReached bool, count int) { func (m *Metrics) RecordGameResolutionStatus(complete bool, maxDurationReached bool, count int) {
......
...@@ -19,7 +19,7 @@ func (*NoopMetricsImpl) RecordUp() {} ...@@ -19,7 +19,7 @@ func (*NoopMetricsImpl) RecordUp() {}
func (*NoopMetricsImpl) CacheAdd(_ string, _ int, _ bool) {} func (*NoopMetricsImpl) CacheAdd(_ string, _ int, _ bool) {}
func (*NoopMetricsImpl) CacheGet(_ string, _ bool) {} func (*NoopMetricsImpl) CacheGet(_ string, _ bool) {}
func (*NoopMetricsImpl) RecordUnexpectedClaimResolution(_ common.Address, _ int) {} func (*NoopMetricsImpl) RecordHonestActorClaims(_ common.Address, _ *HonestActorData) {}
func (*NoopMetricsImpl) RecordGameResolutionStatus(_ bool, _ bool, _ int) {} func (*NoopMetricsImpl) RecordGameResolutionStatus(_ bool, _ bool, _ int) {}
......
package mon package mon
import ( import (
"math/big"
"time" "time"
"github.com/ethereum-optimism/optimism/op-dispute-mon/metrics" "github.com/ethereum-optimism/optimism/op-dispute-mon/metrics"
...@@ -15,48 +16,73 @@ type RClock interface { ...@@ -15,48 +16,73 @@ type RClock interface {
type ClaimMetrics interface { type ClaimMetrics interface {
RecordClaims(status metrics.ClaimStatus, count int) RecordClaims(status metrics.ClaimStatus, count int)
RecordUnexpectedClaimResolution(address common.Address, count int) RecordHonestActorClaims(address common.Address, data *metrics.HonestActorData)
} }
type ClaimMonitor struct { type ClaimMonitor struct {
logger log.Logger logger log.Logger
clock RClock clock RClock
honestActors []common.Address honestActors map[common.Address]bool // Map for efficient lookup
metrics ClaimMetrics metrics ClaimMetrics
} }
func NewClaimMonitor(logger log.Logger, clock RClock, honestActors []common.Address, metrics ClaimMetrics) *ClaimMonitor { func NewClaimMonitor(logger log.Logger, clock RClock, honestActors []common.Address, metrics ClaimMetrics) *ClaimMonitor {
return &ClaimMonitor{logger, clock, honestActors, metrics} actors := make(map[common.Address]bool)
for _, actor := range honestActors {
actors[actor] = true
}
return &ClaimMonitor{logger, clock, actors, metrics}
} }
func (c *ClaimMonitor) CheckClaims(games []*types.EnrichedGameData) { func (c *ClaimMonitor) CheckClaims(games []*types.EnrichedGameData) {
claimStatus := make(map[metrics.ClaimStatus]int) claimStatus := make(map[metrics.ClaimStatus]int)
unexpected := make(map[common.Address]int) honest := make(map[common.Address]*metrics.HonestActorData)
for actor := range c.honestActors {
honest[actor] = &metrics.HonestActorData{
PendingBonds: big.NewInt(0),
LostBonds: big.NewInt(0),
WonBonds: big.NewInt(0),
}
}
for _, game := range games { for _, game := range games {
c.checkGameClaims(game, claimStatus, unexpected) c.checkGameClaims(game, claimStatus, honest)
} }
for status, count := range claimStatus { for status, count := range claimStatus {
c.metrics.RecordClaims(status, count) c.metrics.RecordClaims(status, count)
} }
for address, count := range unexpected { for actor := range c.honestActors {
c.metrics.RecordUnexpectedClaimResolution(address, count) c.metrics.RecordHonestActorClaims(actor, honest[actor])
} }
} }
func (c *ClaimMonitor) checkResolvedAgainstHonestActor(proxy common.Address, claim *types.EnrichedClaim, unexpected map[common.Address]int) { func (c *ClaimMonitor) checkUpdateHonestActorStats(proxy common.Address, claim *types.EnrichedClaim, honest map[common.Address]*metrics.HonestActorData) {
for _, actor := range c.honestActors { if !claim.Resolved {
if claim.Claimant == actor && claim.CounteredBy != (common.Address{}) { if c.honestActors[claim.Claimant] {
unexpected[actor]++ honest[claim.Claimant].PendingClaimCount++
honest[claim.Claimant].PendingBonds = new(big.Int).Add(honest[claim.Claimant].PendingBonds, claim.Bond)
}
return
}
if c.honestActors[claim.Claimant] {
actor := claim.Claimant
if claim.CounteredBy != (common.Address{}) {
honest[actor].InvalidClaimCount++
honest[actor].LostBonds = new(big.Int).Add(honest[actor].LostBonds, claim.Bond)
c.logger.Error("Claim resolved against honest actor", "game", proxy, "honest_actor", actor, "countered_by", claim.CounteredBy, "claim_contract_index", claim.ContractIndex) c.logger.Error("Claim resolved against honest actor", "game", proxy, "honest_actor", actor, "countered_by", claim.CounteredBy, "claim_contract_index", claim.ContractIndex)
break } else {
honest[actor].ValidClaimCount++
// Note that we don't count refunded bonds as won
} }
} }
if c.honestActors[claim.CounteredBy] {
honest[claim.CounteredBy].WonBonds = new(big.Int).Add(honest[claim.CounteredBy].WonBonds, claim.Bond)
}
} }
func (c *ClaimMonitor) checkGameClaims( func (c *ClaimMonitor) checkGameClaims(
game *types.EnrichedGameData, game *types.EnrichedGameData,
claimStatus map[metrics.ClaimStatus]int, claimStatus map[metrics.ClaimStatus]int,
unexpected map[common.Address]int, honest map[common.Address]*metrics.HonestActorData,
) { ) {
// Check if the game is in the first half // Check if the game is in the first half
duration := uint64(c.clock.Now().Unix()) - game.Timestamp duration := uint64(c.clock.Now().Unix()) - game.Timestamp
...@@ -64,10 +90,7 @@ func (c *ClaimMonitor) checkGameClaims( ...@@ -64,10 +90,7 @@ func (c *ClaimMonitor) checkGameClaims(
// Iterate over the game's claims // Iterate over the game's claims
for _, claim := range game.Claims { for _, claim := range game.Claims {
// Check if the claim has resolved against an honest actor c.checkUpdateHonestActorStats(game.Proxy, &claim, honest)
if claim.Resolved {
c.checkResolvedAgainstHonestActor(game.Proxy, &claim, unexpected)
}
// Check if the clock has expired // Check if the clock has expired
if firstHalf && claim.Resolved { if firstHalf && claim.Resolved {
......
package mon package mon
import ( import (
"math/big"
"testing" "testing"
"time" "time"
...@@ -38,13 +39,28 @@ func TestClaimMonitor_CheckClaims(t *testing.T) { ...@@ -38,13 +39,28 @@ func TestClaimMonitor_CheckClaims(t *testing.T) {
games := makeMultipleTestGames(uint64(cl.Now().Unix())) games := makeMultipleTestGames(uint64(cl.Now().Unix()))
monitor.CheckClaims(games) monitor.CheckClaims(games)
// Should only have entries for honest actors
require.Contains(t, cMetrics.honest, common.Address{0x01})
require.Contains(t, cMetrics.honest, common.Address{0x02})
require.NotContains(t, cMetrics.honest, common.Address{0x03})
require.NotContains(t, cMetrics.honest, common.Address{0x04})
actor1 := cMetrics.honest[common.Address{0x01}]
actor2 := cMetrics.honest[common.Address{0x02}]
// Our honest actors 0x01 has claims resolved against them (1 per game) // Our honest actors 0x01 has claims resolved against them (1 per game)
require.Equal(t, 2, cMetrics.unexpected[common.Address{0x01}]) require.Equal(t, 2, actor1.InvalidClaimCount)
require.Equal(t, 0, cMetrics.unexpected[common.Address{0x02}]) require.Equal(t, 0, actor1.ValidClaimCount)
require.Equal(t, 2, actor1.PendingClaimCount)
require.EqualValues(t, 4, actor1.LostBonds.Int64())
require.EqualValues(t, 0, actor1.WonBonds.Int64())
require.EqualValues(t, 10, actor1.PendingBonds.Int64())
// The other actors should not be metriced require.Equal(t, 0, actor2.InvalidClaimCount)
require.Equal(t, 0, cMetrics.unexpected[common.Address{0x03}]) require.Equal(t, 2, actor2.ValidClaimCount)
require.Equal(t, 0, cMetrics.unexpected[common.Address{0x04}]) require.Equal(t, 0, actor2.PendingClaimCount)
require.EqualValues(t, 0, actor2.LostBonds.Int64())
require.EqualValues(t, 6, actor2.WonBonds.Int64())
require.EqualValues(t, 0, actor2.PendingBonds.Int64())
}) })
} }
...@@ -60,8 +76,8 @@ func newTestClaimMonitor(t *testing.T) (*ClaimMonitor, *clock.DeterministicClock ...@@ -60,8 +76,8 @@ func newTestClaimMonitor(t *testing.T) (*ClaimMonitor, *clock.DeterministicClock
} }
type stubClaimMetrics struct { type stubClaimMetrics struct {
calls map[metrics.ClaimStatus]int calls map[metrics.ClaimStatus]int
unexpected map[common.Address]int honest map[common.Address]metrics.HonestActorData
} }
func (s *stubClaimMetrics) RecordClaims(status metrics.ClaimStatus, count int) { func (s *stubClaimMetrics) RecordClaims(status metrics.ClaimStatus, count int) {
...@@ -71,11 +87,11 @@ func (s *stubClaimMetrics) RecordClaims(status metrics.ClaimStatus, count int) { ...@@ -71,11 +87,11 @@ func (s *stubClaimMetrics) RecordClaims(status metrics.ClaimStatus, count int) {
s.calls[status] += count s.calls[status] += count
} }
func (s *stubClaimMetrics) RecordUnexpectedClaimResolution(address common.Address, count int) { func (s *stubClaimMetrics) RecordHonestActorClaims(address common.Address, data *metrics.HonestActorData) {
if s.unexpected == nil { if s.honest == nil {
s.unexpected = make(map[common.Address]int) s.honest = make(map[common.Address]metrics.HonestActorData)
} }
s.unexpected[address] += count s.honest[address] = *data
} }
func makeMultipleTestGames(duration uint64) []*types.EnrichedGameData { func makeMultipleTestGames(duration uint64) []*types.EnrichedGameData {
...@@ -98,6 +114,9 @@ func makeTestGame(duration uint64) *types.EnrichedGameData { ...@@ -98,6 +114,9 @@ func makeTestGame(duration uint64) *types.EnrichedGameData {
Claim: faultTypes.Claim{ Claim: faultTypes.Claim{
Clock: faultTypes.NewClock(time.Duration(0), frozen), Clock: faultTypes.NewClock(time.Duration(0), frozen),
Claimant: common.Address{0x02}, Claimant: common.Address{0x02},
ClaimData: faultTypes.ClaimData{
Bond: big.NewInt(1),
},
}, },
Resolved: true, Resolved: true,
}, },
...@@ -105,6 +124,9 @@ func makeTestGame(duration uint64) *types.EnrichedGameData { ...@@ -105,6 +124,9 @@ func makeTestGame(duration uint64) *types.EnrichedGameData {
Claim: faultTypes.Claim{ Claim: faultTypes.Claim{
Claimant: common.Address{0x01}, Claimant: common.Address{0x01},
CounteredBy: common.Address{0x03}, CounteredBy: common.Address{0x03},
ClaimData: faultTypes.ClaimData{
Bond: big.NewInt(2),
},
}, },
Resolved: true, Resolved: true,
}, },
...@@ -112,6 +134,9 @@ func makeTestGame(duration uint64) *types.EnrichedGameData { ...@@ -112,6 +134,9 @@ func makeTestGame(duration uint64) *types.EnrichedGameData {
Claim: faultTypes.Claim{ Claim: faultTypes.Claim{
Claimant: common.Address{0x04}, Claimant: common.Address{0x04},
CounteredBy: common.Address{0x02}, CounteredBy: common.Address{0x02},
ClaimData: faultTypes.ClaimData{
Bond: big.NewInt(3),
},
}, },
Resolved: true, Resolved: true,
}, },
...@@ -120,11 +145,17 @@ func makeTestGame(duration uint64) *types.EnrichedGameData { ...@@ -120,11 +145,17 @@ func makeTestGame(duration uint64) *types.EnrichedGameData {
Claimant: common.Address{0x04}, Claimant: common.Address{0x04},
CounteredBy: common.Address{0x02}, CounteredBy: common.Address{0x02},
Clock: faultTypes.NewClock(time.Duration(0), frozen), Clock: faultTypes.NewClock(time.Duration(0), frozen),
ClaimData: faultTypes.ClaimData{
Bond: big.NewInt(4),
},
}, },
}, },
{ {
Claim: faultTypes.Claim{ Claim: faultTypes.Claim{
Claimant: common.Address{0x01}, Claimant: common.Address{0x01},
ClaimData: faultTypes.ClaimData{
Bond: big.NewInt(5),
},
}, },
}, },
}, },
......
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