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

feat(op-dispute-mon): Enrich Claims with Resolved Status (#9937)

* feat(op-dispute-mon): enrich claims with resolution status

* fix(op-dispute-mon): add the claim enricher to the extractor instantiation

* fix(op-dispute-mon): localize required bond amount

* fix(op-challenger): import cycle
parent 006002aa
...@@ -80,8 +80,16 @@ func verifyChallengerNeverCountersAClaimTwice(t *testing.T, tree *disputeTypes.B ...@@ -80,8 +80,16 @@ func verifyChallengerNeverCountersAClaimTwice(t *testing.T, tree *disputeTypes.B
} }
} }
func enrichClaims(claims []types.Claim) []disputeTypes.EnrichedClaim {
enriched := make([]disputeTypes.EnrichedClaim, len(claims))
for i, claim := range claims {
enriched[i] = disputeTypes.EnrichedClaim{Claim: claim}
}
return enriched
}
func gameResult(game types.Game) (gameTypes.GameStatus, *disputeTypes.BidirectionalTree, types.Game) { func gameResult(game types.Game) (gameTypes.GameStatus, *disputeTypes.BidirectionalTree, types.Game) {
tree := transform.CreateBidirectionalTree(game.Claims()) tree := transform.CreateBidirectionalTree(enrichClaims(game.Claims()))
result := resolution.Resolve(tree) result := resolution.Resolve(tree)
resolvedClaims := make([]types.Claim, 0, len(tree.Claims)) resolvedClaims := make([]types.Claim, 0, len(tree.Claims))
for _, claim := range tree.Claims { for _, claim := range tree.Claims {
......
...@@ -38,7 +38,7 @@ func CalculateRequiredCollateral(games []*monTypes.EnrichedGameData) map[common. ...@@ -38,7 +38,7 @@ func CalculateRequiredCollateral(games []*monTypes.EnrichedGameData) map[common.
func requiredCollateralForGame(game *monTypes.EnrichedGameData) *big.Int { func requiredCollateralForGame(game *monTypes.EnrichedGameData) *big.Int {
required := big.NewInt(0) required := big.NewInt(0)
for _, claim := range game.Claims { for _, claim := range game.Claims {
if monTypes.ResolvedBondAmount.Cmp(claim.Bond) != 0 { if claim.Resolved {
required = new(big.Int).Add(required, claim.Bond) required = new(big.Int).Add(required, claim.Bond)
} }
} }
......
...@@ -16,27 +16,34 @@ func TestCalculateRequiredCollateral(t *testing.T) { ...@@ -16,27 +16,34 @@ func TestCalculateRequiredCollateral(t *testing.T) {
weth2 := common.Address{0x2b} weth2 := common.Address{0x2b}
weth2Balance := big.NewInt(6000) weth2Balance := big.NewInt(6000)
game1 := &monTypes.EnrichedGameData{ game1 := &monTypes.EnrichedGameData{
Claims: []types.Claim{ Claims: []monTypes.EnrichedClaim{
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Bond: monTypes.ResolvedBondAmount, ClaimData: types.ClaimData{
Bond: big.NewInt(17),
},
Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
}, },
Claimant: common.Address{0x01}, Resolved: true,
CounteredBy: common.Address{0x02},
}, },
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Bond: big.NewInt(5), ClaimData: types.ClaimData{
Bond: big.NewInt(5),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Bond: big.NewInt(7), ClaimData: types.ClaimData{
Bond: big.NewInt(7),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
}, },
Credits: map[common.Address]*big.Int{ Credits: map[common.Address]*big.Int{
...@@ -47,27 +54,34 @@ func TestCalculateRequiredCollateral(t *testing.T) { ...@@ -47,27 +54,34 @@ func TestCalculateRequiredCollateral(t *testing.T) {
ETHCollateral: weth1Balance, ETHCollateral: weth1Balance,
} }
game2 := &monTypes.EnrichedGameData{ game2 := &monTypes.EnrichedGameData{
Claims: []types.Claim{ Claims: []monTypes.EnrichedClaim{
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Bond: monTypes.ResolvedBondAmount, ClaimData: types.ClaimData{
Bond: big.NewInt(10),
},
Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
}, },
Claimant: common.Address{0x01}, Resolved: true,
CounteredBy: common.Address{0x02},
}, },
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Bond: big.NewInt(6), ClaimData: types.ClaimData{
Bond: big.NewInt(6),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Bond: big.NewInt(9), ClaimData: types.ClaimData{
Bond: big.NewInt(9),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
}, },
Credits: map[common.Address]*big.Int{ Credits: map[common.Address]*big.Int{
...@@ -78,13 +92,16 @@ func TestCalculateRequiredCollateral(t *testing.T) { ...@@ -78,13 +92,16 @@ func TestCalculateRequiredCollateral(t *testing.T) {
ETHCollateral: weth1Balance, ETHCollateral: weth1Balance,
} }
game3 := &monTypes.EnrichedGameData{ game3 := &monTypes.EnrichedGameData{
Claims: []types.Claim{ Claims: []monTypes.EnrichedClaim{
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Bond: big.NewInt(23), ClaimData: types.ClaimData{
Bond: big.NewInt(23),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
Claimant: common.Address{0x03}, Resolved: true,
CounteredBy: common.Address{},
}, },
}, },
Credits: map[common.Address]*big.Int{ Credits: map[common.Address]*big.Int{
......
...@@ -16,27 +16,31 @@ import ( ...@@ -16,27 +16,31 @@ import (
func TestBondEnricher(t *testing.T) { func TestBondEnricher(t *testing.T) {
makeGame := func() *monTypes.EnrichedGameData { makeGame := func() *monTypes.EnrichedGameData {
return &monTypes.EnrichedGameData{ return &monTypes.EnrichedGameData{
Claims: []faultTypes.Claim{ Claims: []monTypes.EnrichedClaim{
{ {
ClaimData: faultTypes.ClaimData{ Claim: faultTypes.Claim{
Bond: monTypes.ResolvedBondAmount, Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
}, },
Claimant: common.Address{0x01}, Resolved: true,
CounteredBy: common.Address{0x02},
}, },
{ {
ClaimData: faultTypes.ClaimData{ Claim: faultTypes.Claim{
Bond: big.NewInt(5), ClaimData: faultTypes.ClaimData{
Bond: big.NewInt(5),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
}, },
{ {
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{},
}, },
}, },
} }
......
...@@ -9,10 +9,11 @@ import ( ...@@ -9,10 +9,11 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"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/caching" "github.com/ethereum-optimism/optimism/op-service/sources/caching"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
) )
const metricsLabel = "game_caller_creator" const metricsLabel = "game_caller_creator"
...@@ -22,7 +23,7 @@ type GameCallerMetrics interface { ...@@ -22,7 +23,7 @@ type GameCallerMetrics interface {
contractMetrics.ContractMetricer contractMetrics.ContractMetricer
} }
type GameCaller interface { type GameCaller interface {
GetGameMetadata(context.Context, rpcblock.Block) (common.Hash, uint64, common.Hash, types.GameStatus, uint64, error) GetGameMetadata(context.Context, rpcblock.Block) (common.Hash, uint64, common.Hash, gameTypes.GameStatus, uint64, error)
GetAllClaims(context.Context, rpcblock.Block) ([]faultTypes.Claim, error) GetAllClaims(context.Context, rpcblock.Block) ([]faultTypes.Claim, error)
BondCaller BondCaller
BalanceCaller BalanceCaller
...@@ -42,7 +43,7 @@ func NewGameCallerCreator(m GameCallerMetrics, caller *batching.MultiCaller) *Ga ...@@ -42,7 +43,7 @@ func NewGameCallerCreator(m GameCallerMetrics, caller *batching.MultiCaller) *Ga
} }
} }
func (g *GameCallerCreator) CreateContract(game types.GameMetadata) (GameCaller, error) { func (g *GameCallerCreator) CreateContract(game gameTypes.GameMetadata) (GameCaller, error) {
if fdg, ok := g.cache.Get(game.Proxy); ok { if fdg, ok := g.cache.Get(game.Proxy); ok {
return fdg, nil return fdg, nil
} }
......
package extract
import (
"context"
"math/big"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
)
var _ Enricher = (*ClaimEnricher)(nil)
type ClaimEnricher struct{}
func NewClaimEnricher() *ClaimEnricher {
return &ClaimEnricher{}
}
var resolvedBondAmount = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 128), big.NewInt(1))
func (e *ClaimEnricher) Enrich(_ context.Context, _ rpcblock.Block, _ GameCaller, game *types.EnrichedGameData) error {
for i, claim := range game.Claims {
if claim.Bond.Cmp(resolvedBondAmount) == 0 {
game.Claims[i].Resolved = true
}
}
return nil
}
package extract
import (
"context"
"math/big"
"testing"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/stretchr/testify/require"
)
func TestMaxValue(t *testing.T) {
require.Equal(t, resolvedBondAmount.String(), "340282366920938463463374607431768211455")
}
func TestClaimEnricher(t *testing.T) {
enricher := NewClaimEnricher()
game := &types.EnrichedGameData{
Claims: []types.EnrichedClaim{
newClaimWithBond(resolvedBondAmount),
newClaimWithBond(big.NewInt(0)),
newClaimWithBond(big.NewInt(100)),
newClaimWithBond(new(big.Int).Sub(resolvedBondAmount, big.NewInt(1))),
newClaimWithBond(new(big.Int).Add(resolvedBondAmount, big.NewInt(1))),
},
}
caller := &mockGameCaller{}
err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game)
require.NoError(t, err)
expected := []bool{true, false, false, false, false}
for i, claim := range game.Claims {
require.Equal(t, expected[i], claim.Resolved)
}
}
func newClaimWithBond(bond *big.Int) types.EnrichedClaim {
return types.EnrichedClaim{Claim: faultTypes.Claim{ClaimData: faultTypes.ClaimData{Bond: bond}}}
}
...@@ -61,6 +61,10 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game ...@@ -61,6 +61,10 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game
e.logger.Error("Failed to fetch game claims", "err", err) e.logger.Error("Failed to fetch game claims", "err", err)
continue continue
} }
enrichedClaims := make([]monTypes.EnrichedClaim, len(claims))
for i, claim := range claims {
enrichedClaims[i] = monTypes.EnrichedClaim{Claim: claim}
}
enrichedGame := &monTypes.EnrichedGameData{ enrichedGame := &monTypes.EnrichedGameData{
GameMetadata: game, GameMetadata: game,
L1Head: l1Head, L1Head: l1Head,
...@@ -68,7 +72,7 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game ...@@ -68,7 +72,7 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game
RootClaim: rootClaim, RootClaim: rootClaim,
Status: status, Status: status,
Duration: duration, Duration: duration,
Claims: claims, Claims: enrichedClaims,
} }
if err := e.applyEnrichers(ctx, blockHash, caller, enrichedGame); err != nil { if err := e.applyEnrichers(ctx, blockHash, caller, enrichedGame); err != nil {
e.logger.Error("Failed to enrich game", "err", err) e.logger.Error("Failed to enrich game", "err", err)
......
...@@ -217,7 +217,7 @@ func TestForecast_Forecast_MultipleGames(t *testing.T) { ...@@ -217,7 +217,7 @@ func TestForecast_Forecast_MultipleGames(t *testing.T) {
types.GameStatusChallengerWon, types.GameStatusChallengerWon,
types.GameStatusChallengerWon, types.GameStatusChallengerWon,
} }
claims := [][]faultTypes.Claim{ claims := [][]monTypes.EnrichedClaim{
createDeepClaimList()[:1], createDeepClaimList()[:1],
createDeepClaimList()[:2], createDeepClaimList()[:2],
createDeepClaimList()[:2], createDeepClaimList()[:2],
...@@ -289,31 +289,37 @@ func (m *mockForecastMetrics) RecordClaimResolutionDelayMax(delay float64) { ...@@ -289,31 +289,37 @@ func (m *mockForecastMetrics) RecordClaimResolutionDelayMax(delay float64) {
m.claimResolutionDelayMax = delay m.claimResolutionDelayMax = delay
} }
func createDeepClaimList() []faultTypes.Claim { func createDeepClaimList() []monTypes.EnrichedClaim {
return []faultTypes.Claim{ return []monTypes.EnrichedClaim{
{ {
ClaimData: faultTypes.ClaimData{ Claim: faultTypes.Claim{
Position: faultTypes.NewPosition(0, big.NewInt(0)), ClaimData: faultTypes.ClaimData{
Position: faultTypes.NewPosition(0, big.NewInt(0)),
},
ContractIndex: 0,
ParentContractIndex: math.MaxInt64,
Claimant: common.HexToAddress("0x111111"),
}, },
ContractIndex: 0,
ParentContractIndex: math.MaxInt64,
Claimant: common.HexToAddress("0x111111"),
}, },
{ {
ClaimData: faultTypes.ClaimData{ Claim: faultTypes.Claim{
Position: faultTypes.NewPosition(1, big.NewInt(0)), ClaimData: faultTypes.ClaimData{
Position: faultTypes.NewPosition(1, big.NewInt(0)),
},
ContractIndex: 1,
ParentContractIndex: 0,
Claimant: common.HexToAddress("0x222222"),
}, },
ContractIndex: 1,
ParentContractIndex: 0,
Claimant: common.HexToAddress("0x222222"),
}, },
{ {
ClaimData: faultTypes.ClaimData{ Claim: faultTypes.Claim{
Position: faultTypes.NewPosition(2, big.NewInt(0)), ClaimData: faultTypes.ClaimData{
Position: faultTypes.NewPosition(2, big.NewInt(0)),
},
ContractIndex: 2,
ParentContractIndex: 1,
Claimant: common.HexToAddress("0x111111"),
}, },
ContractIndex: 2,
ParentContractIndex: 1,
Claimant: common.HexToAddress("0x111111"),
}, },
} }
} }
......
package resolution package resolution
import ( import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/clock"
) )
...@@ -22,7 +21,7 @@ func NewDelayCalculator(metrics DelayMetrics, clock clock.Clock) *DelayCalculato ...@@ -22,7 +21,7 @@ func NewDelayCalculator(metrics DelayMetrics, clock clock.Clock) *DelayCalculato
} }
} }
func (d *DelayCalculator) RecordClaimResolutionDelayMax(games []*monTypes.EnrichedGameData) { func (d *DelayCalculator) RecordClaimResolutionDelayMax(games []*types.EnrichedGameData) {
var maxDelay uint64 = 0 var maxDelay uint64 = 0
for _, game := range games { for _, game := range games {
maxDelay = max(d.getMaxResolutionDelay(game), maxDelay) maxDelay = max(d.getMaxResolutionDelay(game), maxDelay)
...@@ -30,7 +29,7 @@ func (d *DelayCalculator) RecordClaimResolutionDelayMax(games []*monTypes.Enrich ...@@ -30,7 +29,7 @@ func (d *DelayCalculator) RecordClaimResolutionDelayMax(games []*monTypes.Enrich
d.metrics.RecordClaimResolutionDelayMax(float64(maxDelay)) d.metrics.RecordClaimResolutionDelayMax(float64(maxDelay))
} }
func (d *DelayCalculator) getMaxResolutionDelay(game *monTypes.EnrichedGameData) uint64 { func (d *DelayCalculator) getMaxResolutionDelay(game *types.EnrichedGameData) uint64 {
var maxDelay uint64 = 0 var maxDelay uint64 = 0
for _, claim := range game.Claims { for _, claim := range game.Claims {
maxDelay = max(d.getOverflowTime(game.Duration, &claim), maxDelay) maxDelay = max(d.getOverflowTime(game.Duration, &claim), maxDelay)
...@@ -38,9 +37,8 @@ func (d *DelayCalculator) getMaxResolutionDelay(game *monTypes.EnrichedGameData) ...@@ -38,9 +37,8 @@ func (d *DelayCalculator) getMaxResolutionDelay(game *monTypes.EnrichedGameData)
return maxDelay return maxDelay
} }
func (d *DelayCalculator) getOverflowTime(maxGameDuration uint64, claim *types.Claim) uint64 { func (d *DelayCalculator) getOverflowTime(maxGameDuration uint64, claim *types.EnrichedClaim) uint64 {
// If the bond amount is the max uint128 value, the claim is resolved. if claim.Resolved {
if monTypes.ResolvedBondAmount.Cmp(claim.ClaimData.Bond) == 0 {
return 0 return 0
} }
maxChessTime := maxGameDuration / 2 maxChessTime := maxGameDuration / 2
......
...@@ -19,10 +19,8 @@ var ( ...@@ -19,10 +19,8 @@ var (
func TestDelayCalculator_getOverflowTime(t *testing.T) { func TestDelayCalculator_getOverflowTime(t *testing.T) {
t.Run("NoClock", func(t *testing.T) { t.Run("NoClock", func(t *testing.T) {
d, metrics, _ := setupDelayCalculatorTest(t) d, metrics, _ := setupDelayCalculatorTest(t)
claim := &types.Claim{ claim := &monTypes.EnrichedClaim{
ClaimData: types.ClaimData{ Resolved: true,
Bond: monTypes.ResolvedBondAmount,
},
} }
delay := d.getOverflowTime(maxGameDuration, claim) delay := d.getOverflowTime(maxGameDuration, claim)
require.Equal(t, uint64(0), delay) require.Equal(t, uint64(0), delay)
...@@ -33,11 +31,13 @@ func TestDelayCalculator_getOverflowTime(t *testing.T) { ...@@ -33,11 +31,13 @@ func TestDelayCalculator_getOverflowTime(t *testing.T) {
d, metrics, cl := setupDelayCalculatorTest(t) d, metrics, cl := setupDelayCalculatorTest(t)
duration := uint64(3 * 60) duration := uint64(3 * 60)
timestamp := uint64(cl.Now().Add(-time.Minute).Unix()) timestamp := uint64(cl.Now().Add(-time.Minute).Unix())
claim := &types.Claim{ claim := &monTypes.EnrichedClaim{
ClaimData: types.ClaimData{ Claim: types.Claim{
Bond: big.NewInt(5), ClaimData: types.ClaimData{
Bond: big.NewInt(5),
},
Clock: types.NewClock(duration, timestamp),
}, },
Clock: types.NewClock(duration, timestamp),
} }
delay := d.getOverflowTime(maxGameDuration, claim) delay := d.getOverflowTime(maxGameDuration, claim)
require.Equal(t, uint64(0), delay) require.Equal(t, uint64(0), delay)
...@@ -48,11 +48,13 @@ func TestDelayCalculator_getOverflowTime(t *testing.T) { ...@@ -48,11 +48,13 @@ func TestDelayCalculator_getOverflowTime(t *testing.T) {
d, metrics, cl := setupDelayCalculatorTest(t) d, metrics, cl := setupDelayCalculatorTest(t)
duration := maxGameDuration / 2 duration := maxGameDuration / 2
timestamp := uint64(cl.Now().Add(4 * -time.Minute).Unix()) timestamp := uint64(cl.Now().Add(4 * -time.Minute).Unix())
claim := &types.Claim{ claim := &monTypes.EnrichedClaim{
ClaimData: types.ClaimData{ Claim: types.Claim{
Bond: big.NewInt(5), ClaimData: types.ClaimData{
Bond: big.NewInt(5),
},
Clock: types.NewClock(duration, timestamp),
}, },
Clock: types.NewClock(duration, timestamp),
} }
delay := d.getOverflowTime(maxGameDuration, claim) delay := d.getOverflowTime(maxGameDuration, claim)
require.Equal(t, uint64(240), delay) require.Equal(t, uint64(240), delay)
...@@ -63,10 +65,10 @@ func TestDelayCalculator_getOverflowTime(t *testing.T) { ...@@ -63,10 +65,10 @@ func TestDelayCalculator_getOverflowTime(t *testing.T) {
func TestDelayCalculator_getMaxResolutionDelay(t *testing.T) { func TestDelayCalculator_getMaxResolutionDelay(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
claims []types.Claim claims []monTypes.EnrichedClaim
want uint64 want uint64
}{ }{
{"NoClaims", []types.Claim{}, 0}, {"NoClaims", []monTypes.EnrichedClaim{}, 0},
{"SingleClaim", createClaimList()[:1], 180}, {"SingleClaim", createClaimList()[:1], 180},
{"MultipleClaims", createClaimList()[:2], 300}, {"MultipleClaims", createClaimList()[:2], 300},
{"ClaimsWithMaxUint128", createClaimList(), 300}, {"ClaimsWithMaxUint128", createClaimList(), 300},
...@@ -133,37 +135,42 @@ func createGameWithClaimsList() []*monTypes.EnrichedGameData { ...@@ -133,37 +135,42 @@ func createGameWithClaimsList() []*monTypes.EnrichedGameData {
} }
} }
func createClaimList() []types.Claim { func createClaimList() []monTypes.EnrichedClaim {
newClock := func(multiplier int) *types.Clock { newClock := func(multiplier int) *types.Clock {
duration := maxGameDuration / 2 duration := maxGameDuration / 2
timestamp := uint64(frozen.Add(-time.Minute * time.Duration(multiplier)).Unix()) timestamp := uint64(frozen.Add(-time.Minute * time.Duration(multiplier)).Unix())
return types.NewClock(duration, timestamp) return types.NewClock(duration, timestamp)
} }
return []types.Claim{ return []monTypes.EnrichedClaim{
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Bond: big.NewInt(5), ClaimData: types.ClaimData{
Bond: big.NewInt(5),
},
Clock: newClock(3),
}, },
Clock: newClock(3),
}, },
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Bond: big.NewInt(10), ClaimData: types.ClaimData{
Bond: big.NewInt(10),
},
Clock: newClock(5),
}, },
Clock: newClock(5),
}, },
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Bond: big.NewInt(100), ClaimData: types.ClaimData{
Bond: big.NewInt(100),
},
Clock: newClock(2),
}, },
Clock: newClock(2),
}, },
{ {
// This claim should be skipped because it's resolved. Claim: types.Claim{
ClaimData: types.ClaimData{ Clock: newClock(10),
Bond: monTypes.ResolvedBondAmount,
}, },
Clock: newClock(10), Resolved: true,
}, },
} }
} }
......
...@@ -11,18 +11,19 @@ import ( ...@@ -11,18 +11,19 @@ import (
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
) )
func TestResolver_Resolve(t *testing.T) { func TestResolver_Resolve(t *testing.T) {
t.Run("NoClaims", func(t *testing.T) { t.Run("NoClaims", func(t *testing.T) {
tree := transform.CreateBidirectionalTree([]faultTypes.Claim{}) tree := transform.CreateBidirectionalTree([]monTypes.EnrichedClaim{})
status := Resolve(tree) status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusDefenderWon, status) require.Equal(t, gameTypes.GameStatusDefenderWon, status)
}) })
t.Run("SingleRootClaim", func(t *testing.T) { t.Run("SingleRootClaim", func(t *testing.T) {
builder := test.NewAlphabetClaimBuilder(t, big.NewInt(10), 4).GameBuilder() builder := test.NewAlphabetClaimBuilder(t, big.NewInt(10), 4).GameBuilder()
tree := transform.CreateBidirectionalTree(builder.Game.Claims()) tree := transform.CreateBidirectionalTree(enrichClaims(builder.Game.Claims()))
tree.Claims[0].Claim.CounteredBy = common.Address{} tree.Claims[0].Claim.CounteredBy = common.Address{}
status := Resolve(tree) status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusDefenderWon, status) require.Equal(t, gameTypes.GameStatusDefenderWon, status)
...@@ -36,7 +37,7 @@ func TestResolver_Resolve(t *testing.T) { ...@@ -36,7 +37,7 @@ func TestResolver_Resolve(t *testing.T) {
Defend(). // Challenger winning Defend(). // Challenger winning
Defend(). // Defender winning Defend(). // Defender winning
Attack() // Challenger winning Attack() // Challenger winning
tree := transform.CreateBidirectionalTree(builder.Game.Claims()) tree := transform.CreateBidirectionalTree(enrichClaims(builder.Game.Claims()))
status := Resolve(tree) status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusChallengerWon, status) require.Equal(t, gameTypes.GameStatusChallengerWon, status)
}) })
...@@ -48,7 +49,7 @@ func TestResolver_Resolve(t *testing.T) { ...@@ -48,7 +49,7 @@ func TestResolver_Resolve(t *testing.T) {
Attack(). // Defender winning Attack(). // Defender winning
Defend(). // Challenger winning Defend(). // Challenger winning
Defend() // Defender winning Defend() // Defender winning
tree := transform.CreateBidirectionalTree(builder.Game.Claims()) tree := transform.CreateBidirectionalTree(enrichClaims(builder.Game.Claims()))
status := Resolve(tree) status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusDefenderWon, status) require.Equal(t, gameTypes.GameStatusDefenderWon, status)
}) })
...@@ -68,7 +69,7 @@ func TestResolver_Resolve(t *testing.T) { ...@@ -68,7 +69,7 @@ func TestResolver_Resolve(t *testing.T) {
Step() // Defender winning Step() // Defender winning
forkPoint.Defend(test.WithValue(common.Hash{0xcc})). // Challenger winning forkPoint.Defend(test.WithValue(common.Hash{0xcc})). // Challenger winning
Defend() // Defender winning Defend() // Defender winning
tree := transform.CreateBidirectionalTree(builder.Game.Claims()) tree := transform.CreateBidirectionalTree(enrichClaims(builder.Game.Claims()))
status := Resolve(tree) status := Resolve(tree)
// First fork has an uncountered claim with challenger winning so that invalidates the parent and wins the game // First fork has an uncountered claim with challenger winning so that invalidates the parent and wins the game
require.Equal(t, gameTypes.GameStatusChallengerWon, status) require.Equal(t, gameTypes.GameStatusChallengerWon, status)
...@@ -89,7 +90,7 @@ func TestResolver_Resolve(t *testing.T) { ...@@ -89,7 +90,7 @@ func TestResolver_Resolve(t *testing.T) {
forkPoint.Defend(test.WithValue(common.Hash{0xcc})). // Challenger winning forkPoint.Defend(test.WithValue(common.Hash{0xcc})). // Challenger winning
Defend() // Defender winning Defend() // Defender winning
tree := transform.CreateBidirectionalTree(builder.Game.Claims()) tree := transform.CreateBidirectionalTree(enrichClaims(builder.Game.Claims()))
status := Resolve(tree) status := Resolve(tree)
// Defender won all forks // Defender won all forks
require.Equal(t, gameTypes.GameStatusDefenderWon, status) require.Equal(t, gameTypes.GameStatusDefenderWon, status)
...@@ -125,7 +126,7 @@ func TestResolver_Resolve(t *testing.T) { ...@@ -125,7 +126,7 @@ func TestResolver_Resolve(t *testing.T) {
Defend(test.WithClaimant(common.Address{0xee})). // Challenger winning Defend(test.WithClaimant(common.Address{0xee})). // Challenger winning
Defend(). // Defender winning Defend(). // Defender winning
Defend() // Challenger winning Defend() // Challenger winning
tree := transform.CreateBidirectionalTree(builder.Game.Claims()) tree := transform.CreateBidirectionalTree(enrichClaims(builder.Game.Claims()))
status := Resolve(tree) status := Resolve(tree)
// Defender won all forks // Defender won all forks
require.Equal(t, gameTypes.GameStatusChallengerWon, status) require.Equal(t, gameTypes.GameStatusChallengerWon, status)
...@@ -145,7 +146,7 @@ func TestResolver_Resolve(t *testing.T) { ...@@ -145,7 +146,7 @@ func TestResolver_Resolve(t *testing.T) {
claims := builder.Game.Claims() claims := builder.Game.Claims()
// Successful step so mark as countered // Successful step so mark as countered
claims[len(claims)-1].CounteredBy = common.Address{0xaa} claims[len(claims)-1].CounteredBy = common.Address{0xaa}
tree := transform.CreateBidirectionalTree(claims) tree := transform.CreateBidirectionalTree(enrichClaims(claims))
status := Resolve(tree) status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusChallengerWon, status) require.Equal(t, gameTypes.GameStatusChallengerWon, status)
}) })
...@@ -162,8 +163,16 @@ func TestResolver_Resolve(t *testing.T) { ...@@ -162,8 +163,16 @@ func TestResolver_Resolve(t *testing.T) {
claims := builder.Game.Claims() claims := builder.Game.Claims()
// Successful step so mark as countered // Successful step so mark as countered
claims[len(claims)-1].CounteredBy = common.Address{0xaa} claims[len(claims)-1].CounteredBy = common.Address{0xaa}
tree := transform.CreateBidirectionalTree(claims) tree := transform.CreateBidirectionalTree(enrichClaims(claims))
status := Resolve(tree) status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusDefenderWon, status) require.Equal(t, gameTypes.GameStatusDefenderWon, status)
}) })
} }
func enrichClaims(claims []faultTypes.Claim) []monTypes.EnrichedClaim {
enriched := make([]monTypes.EnrichedClaim, len(claims))
for i, claim := range claims {
enriched[i] = monTypes.EnrichedClaim{Claim: claim}
}
return enriched
}
...@@ -116,6 +116,9 @@ func (s *Service) initDelayCalculator() { ...@@ -116,6 +116,9 @@ 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(s.logger, s.game.CreateContract, s.factoryContract.GetGamesAtOrAfter,
// Note: Claim enricher should precede other enrichers to ensure the claim Resolved field
// is set by checking if the claim's bond amount is equal to the configured flag.
extract.NewClaimEnricher(),
extract.NewBondEnricher(), extract.NewBondEnricher(),
extract.NewBalanceEnricher(), extract.NewBalanceEnricher(),
extract.NewL1HeadBlockNumEnricher(s.l1Client), extract.NewL1HeadBlockNumEnricher(s.l1Client),
......
package transform package transform
import ( import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
) )
// CreateBidirectionalTree walks backwards through the list of claims and creates a bidirectional // CreateBidirectionalTree walks backwards through the list of claims and creates a bidirectional
// tree of claims. The root claim must be at index 0. The tree is returned as a flat array so it // tree of claims. The root claim must be at index 0. The tree is returned as a flat array so it
// can be easily traversed following the resolution process. // can be easily traversed following the resolution process.
func CreateBidirectionalTree(claims []types.Claim) *monTypes.BidirectionalTree { func CreateBidirectionalTree(claims []types.EnrichedClaim) *types.BidirectionalTree {
claimMap := make(map[int]*monTypes.BidirectionalClaim) claimMap := make(map[int]*types.BidirectionalClaim)
res := make([]*monTypes.BidirectionalClaim, 0, len(claims)) res := make([]*types.BidirectionalClaim, 0, len(claims))
for _, claim := range claims { for _, claim := range claims {
claim := claim claim := claim
bidirectionalClaim := &monTypes.BidirectionalClaim{ bidirectionalClaim := &types.BidirectionalClaim{
Claim: &claim, Claim: &claim.Claim,
} }
claimMap[claim.ContractIndex] = bidirectionalClaim claimMap[claim.ContractIndex] = bidirectionalClaim
if !claim.IsRoot() { if !claim.IsRoot() {
...@@ -24,5 +23,5 @@ func CreateBidirectionalTree(claims []types.Claim) *monTypes.BidirectionalTree { ...@@ -24,5 +23,5 @@ func CreateBidirectionalTree(claims []types.Claim) *monTypes.BidirectionalTree {
} }
res = append(res, bidirectionalClaim) res = append(res, bidirectionalClaim)
} }
return &monTypes.BidirectionalTree{Claims: res} return &types.BidirectionalTree{Claims: res}
} }
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
) )
func TestResolver_CreateBidirectionalTree(t *testing.T) { func TestResolver_CreateBidirectionalTree(t *testing.T) {
...@@ -17,7 +18,7 @@ func TestResolver_CreateBidirectionalTree(t *testing.T) { ...@@ -17,7 +18,7 @@ func TestResolver_CreateBidirectionalTree(t *testing.T) {
claims[0].CounteredBy = common.Address{} claims[0].CounteredBy = common.Address{}
tree := CreateBidirectionalTree(claims) tree := CreateBidirectionalTree(claims)
require.Len(t, tree.Claims, 1) require.Len(t, tree.Claims, 1)
require.Equal(t, claims[0], *tree.Claims[0].Claim) require.Equal(t, claims[0].Claim, *tree.Claims[0].Claim)
require.Empty(t, tree.Claims[0].Children) require.Empty(t, tree.Claims[0].Children)
}) })
...@@ -26,10 +27,10 @@ func TestResolver_CreateBidirectionalTree(t *testing.T) { ...@@ -26,10 +27,10 @@ func TestResolver_CreateBidirectionalTree(t *testing.T) {
claims[1].CounteredBy = common.Address{} claims[1].CounteredBy = common.Address{}
tree := CreateBidirectionalTree(claims) tree := CreateBidirectionalTree(claims)
require.Len(t, tree.Claims, 2) require.Len(t, tree.Claims, 2)
require.Equal(t, claims[0], *tree.Claims[0].Claim) require.Equal(t, claims[0].Claim, *tree.Claims[0].Claim)
require.Len(t, tree.Claims[0].Children, 1) require.Len(t, tree.Claims[0].Children, 1)
require.Equal(t, claims[1], *tree.Claims[0].Children[0].Claim) require.Equal(t, claims[1].Claim, *tree.Claims[0].Children[0].Claim)
require.Equal(t, claims[1], *tree.Claims[1].Claim) require.Equal(t, claims[1].Claim, *tree.Claims[1].Claim)
require.Empty(t, tree.Claims[1].Children) require.Empty(t, tree.Claims[1].Children)
}) })
...@@ -37,44 +38,50 @@ func TestResolver_CreateBidirectionalTree(t *testing.T) { ...@@ -37,44 +38,50 @@ func TestResolver_CreateBidirectionalTree(t *testing.T) {
claims := createDeepClaimList() claims := createDeepClaimList()
tree := CreateBidirectionalTree(claims) tree := CreateBidirectionalTree(claims)
require.Len(t, tree.Claims, 3) require.Len(t, tree.Claims, 3)
require.Equal(t, claims[0], *tree.Claims[0].Claim) require.Equal(t, claims[0].Claim, *tree.Claims[0].Claim)
require.Len(t, tree.Claims[0].Children, 1) require.Len(t, tree.Claims[0].Children, 1)
require.Equal(t, tree.Claims[0].Children[0], tree.Claims[1]) require.Equal(t, tree.Claims[0].Children[0], tree.Claims[1])
require.Equal(t, claims[1], *tree.Claims[1].Claim) require.Equal(t, claims[1].Claim, *tree.Claims[1].Claim)
require.Len(t, tree.Claims[1].Children, 1) require.Len(t, tree.Claims[1].Children, 1)
require.Equal(t, tree.Claims[1].Children[0], tree.Claims[2]) require.Equal(t, tree.Claims[1].Children[0], tree.Claims[2])
require.Equal(t, claims[2], *tree.Claims[2].Claim) require.Equal(t, claims[2].Claim, *tree.Claims[2].Claim)
require.Empty(t, tree.Claims[2].Children) require.Empty(t, tree.Claims[2].Children)
}) })
} }
func createDeepClaimList() []types.Claim { func createDeepClaimList() []monTypes.EnrichedClaim {
return []types.Claim{ return []monTypes.EnrichedClaim{
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Position: types.NewPosition(0, big.NewInt(0)), ClaimData: types.ClaimData{
Position: types.NewPosition(0, big.NewInt(0)),
},
ContractIndex: 0,
CounteredBy: common.HexToAddress("0x222222"),
ParentContractIndex: math.MaxInt64,
Claimant: common.HexToAddress("0x111111"),
}, },
ContractIndex: 0,
CounteredBy: common.HexToAddress("0x222222"),
ParentContractIndex: math.MaxInt64,
Claimant: common.HexToAddress("0x111111"),
}, },
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Position: types.NewPosition(1, big.NewInt(0)), ClaimData: types.ClaimData{
Position: types.NewPosition(1, big.NewInt(0)),
},
CounteredBy: common.HexToAddress("0x111111"),
ContractIndex: 1,
ParentContractIndex: 0,
Claimant: common.HexToAddress("0x222222"),
}, },
CounteredBy: common.HexToAddress("0x111111"),
ContractIndex: 1,
ParentContractIndex: 0,
Claimant: common.HexToAddress("0x222222"),
}, },
{ {
ClaimData: types.ClaimData{ Claim: types.Claim{
Position: types.NewPosition(2, big.NewInt(0)), ClaimData: types.ClaimData{
Position: types.NewPosition(2, big.NewInt(0)),
},
ContractIndex: 2,
ParentContractIndex: 1,
Claimant: common.HexToAddress("0x111111"),
}, },
ContractIndex: 2,
ParentContractIndex: 1,
Claimant: common.HexToAddress("0x111111"),
}, },
} }
} }
...@@ -8,8 +8,11 @@ import ( ...@@ -8,8 +8,11 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
// ResolvedBondAmount is the uint128 value where a bond is considered claimed. // EnrichedClaim extends the faultTypes.Claim with additional context.
var ResolvedBondAmount = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 128), big.NewInt(1)) type EnrichedClaim struct {
faultTypes.Claim
Resolved bool
}
type EnrichedGameData struct { type EnrichedGameData struct {
types.GameMetadata types.GameMetadata
...@@ -19,7 +22,7 @@ type EnrichedGameData struct { ...@@ -19,7 +22,7 @@ type EnrichedGameData struct {
RootClaim common.Hash RootClaim common.Hash
Status types.GameStatus Status types.GameStatus
Duration uint64 Duration uint64
Claims []faultTypes.Claim Claims []EnrichedClaim
// 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
......
package types
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestMaxValue(t *testing.T) {
require.Equal(t, ResolvedBondAmount.String(), "340282366920938463463374607431768211455")
}
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