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
}
}
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) {
tree := transform.CreateBidirectionalTree(game.Claims())
tree := transform.CreateBidirectionalTree(enrichClaims(game.Claims()))
result := resolution.Resolve(tree)
resolvedClaims := make([]types.Claim, 0, len(tree.Claims))
for _, claim := range tree.Claims {
......
......@@ -38,7 +38,7 @@ func CalculateRequiredCollateral(games []*monTypes.EnrichedGameData) map[common.
func requiredCollateralForGame(game *monTypes.EnrichedGameData) *big.Int {
required := big.NewInt(0)
for _, claim := range game.Claims {
if monTypes.ResolvedBondAmount.Cmp(claim.Bond) != 0 {
if claim.Resolved {
required = new(big.Int).Add(required, claim.Bond)
}
}
......
......@@ -16,27 +16,34 @@ func TestCalculateRequiredCollateral(t *testing.T) {
weth2 := common.Address{0x2b}
weth2Balance := big.NewInt(6000)
game1 := &monTypes.EnrichedGameData{
Claims: []types.Claim{
Claims: []monTypes.EnrichedClaim{
{
ClaimData: types.ClaimData{
Bond: monTypes.ResolvedBondAmount,
Claim: types.Claim{
ClaimData: types.ClaimData{
Bond: big.NewInt(17),
},
Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
},
Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
Resolved: true,
},
{
ClaimData: types.ClaimData{
Bond: big.NewInt(5),
Claim: types.Claim{
ClaimData: types.ClaimData{
Bond: big.NewInt(5),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
{
ClaimData: types.ClaimData{
Bond: big.NewInt(7),
Claim: types.Claim{
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{
......@@ -47,27 +54,34 @@ func TestCalculateRequiredCollateral(t *testing.T) {
ETHCollateral: weth1Balance,
}
game2 := &monTypes.EnrichedGameData{
Claims: []types.Claim{
Claims: []monTypes.EnrichedClaim{
{
ClaimData: types.ClaimData{
Bond: monTypes.ResolvedBondAmount,
Claim: types.Claim{
ClaimData: types.ClaimData{
Bond: big.NewInt(10),
},
Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
},
Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
Resolved: true,
},
{
ClaimData: types.ClaimData{
Bond: big.NewInt(6),
Claim: types.Claim{
ClaimData: types.ClaimData{
Bond: big.NewInt(6),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
{
ClaimData: types.ClaimData{
Bond: big.NewInt(9),
Claim: types.Claim{
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{
......@@ -78,13 +92,16 @@ func TestCalculateRequiredCollateral(t *testing.T) {
ETHCollateral: weth1Balance,
}
game3 := &monTypes.EnrichedGameData{
Claims: []types.Claim{
Claims: []monTypes.EnrichedClaim{
{
ClaimData: types.ClaimData{
Bond: big.NewInt(23),
Claim: types.Claim{
ClaimData: types.ClaimData{
Bond: big.NewInt(23),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
Resolved: true,
},
},
Credits: map[common.Address]*big.Int{
......
......@@ -16,27 +16,31 @@ import (
func TestBondEnricher(t *testing.T) {
makeGame := func() *monTypes.EnrichedGameData {
return &monTypes.EnrichedGameData{
Claims: []faultTypes.Claim{
Claims: []monTypes.EnrichedClaim{
{
ClaimData: faultTypes.ClaimData{
Bond: monTypes.ResolvedBondAmount,
Claim: faultTypes.Claim{
Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
},
Claimant: common.Address{0x01},
CounteredBy: common.Address{0x02},
Resolved: true,
},
{
ClaimData: faultTypes.ClaimData{
Bond: big.NewInt(5),
Claim: faultTypes.Claim{
ClaimData: faultTypes.ClaimData{
Bond: big.NewInt(5),
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
Claimant: common.Address{0x03},
CounteredBy: common.Address{},
},
{
ClaimData: faultTypes.ClaimData{
Bond: big.NewInt(7),
Claim: faultTypes.Claim{
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 (
"github.com/ethereum/go-ethereum/common"
"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/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"
......@@ -22,7 +23,7 @@ type GameCallerMetrics interface {
contractMetrics.ContractMetricer
}
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)
BondCaller
BalanceCaller
......@@ -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 {
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
e.logger.Error("Failed to fetch game claims", "err", err)
continue
}
enrichedClaims := make([]monTypes.EnrichedClaim, len(claims))
for i, claim := range claims {
enrichedClaims[i] = monTypes.EnrichedClaim{Claim: claim}
}
enrichedGame := &monTypes.EnrichedGameData{
GameMetadata: game,
L1Head: l1Head,
......@@ -68,7 +72,7 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game
RootClaim: rootClaim,
Status: status,
Duration: duration,
Claims: claims,
Claims: enrichedClaims,
}
if err := e.applyEnrichers(ctx, blockHash, caller, enrichedGame); err != nil {
e.logger.Error("Failed to enrich game", "err", err)
......
......@@ -217,7 +217,7 @@ func TestForecast_Forecast_MultipleGames(t *testing.T) {
types.GameStatusChallengerWon,
types.GameStatusChallengerWon,
}
claims := [][]faultTypes.Claim{
claims := [][]monTypes.EnrichedClaim{
createDeepClaimList()[:1],
createDeepClaimList()[:2],
createDeepClaimList()[:2],
......@@ -289,31 +289,37 @@ func (m *mockForecastMetrics) RecordClaimResolutionDelayMax(delay float64) {
m.claimResolutionDelayMax = delay
}
func createDeepClaimList() []faultTypes.Claim {
return []faultTypes.Claim{
func createDeepClaimList() []monTypes.EnrichedClaim {
return []monTypes.EnrichedClaim{
{
ClaimData: faultTypes.ClaimData{
Position: faultTypes.NewPosition(0, big.NewInt(0)),
Claim: faultTypes.Claim{
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{
Position: faultTypes.NewPosition(1, big.NewInt(0)),
Claim: faultTypes.Claim{
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{
Position: faultTypes.NewPosition(2, big.NewInt(0)),
Claim: faultTypes.Claim{
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
import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/clock"
)
......@@ -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
for _, game := range games {
maxDelay = max(d.getMaxResolutionDelay(game), maxDelay)
......@@ -30,7 +29,7 @@ func (d *DelayCalculator) RecordClaimResolutionDelayMax(games []*monTypes.Enrich
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
for _, claim := range game.Claims {
maxDelay = max(d.getOverflowTime(game.Duration, &claim), maxDelay)
......@@ -38,9 +37,8 @@ func (d *DelayCalculator) getMaxResolutionDelay(game *monTypes.EnrichedGameData)
return maxDelay
}
func (d *DelayCalculator) getOverflowTime(maxGameDuration uint64, claim *types.Claim) uint64 {
// If the bond amount is the max uint128 value, the claim is resolved.
if monTypes.ResolvedBondAmount.Cmp(claim.ClaimData.Bond) == 0 {
func (d *DelayCalculator) getOverflowTime(maxGameDuration uint64, claim *types.EnrichedClaim) uint64 {
if claim.Resolved {
return 0
}
maxChessTime := maxGameDuration / 2
......
......@@ -19,10 +19,8 @@ var (
func TestDelayCalculator_getOverflowTime(t *testing.T) {
t.Run("NoClock", func(t *testing.T) {
d, metrics, _ := setupDelayCalculatorTest(t)
claim := &types.Claim{
ClaimData: types.ClaimData{
Bond: monTypes.ResolvedBondAmount,
},
claim := &monTypes.EnrichedClaim{
Resolved: true,
}
delay := d.getOverflowTime(maxGameDuration, claim)
require.Equal(t, uint64(0), delay)
......@@ -33,11 +31,13 @@ func TestDelayCalculator_getOverflowTime(t *testing.T) {
d, metrics, cl := setupDelayCalculatorTest(t)
duration := uint64(3 * 60)
timestamp := uint64(cl.Now().Add(-time.Minute).Unix())
claim := &types.Claim{
ClaimData: types.ClaimData{
Bond: big.NewInt(5),
claim := &monTypes.EnrichedClaim{
Claim: types.Claim{
ClaimData: types.ClaimData{
Bond: big.NewInt(5),
},
Clock: types.NewClock(duration, timestamp),
},
Clock: types.NewClock(duration, timestamp),
}
delay := d.getOverflowTime(maxGameDuration, claim)
require.Equal(t, uint64(0), delay)
......@@ -48,11 +48,13 @@ func TestDelayCalculator_getOverflowTime(t *testing.T) {
d, metrics, cl := setupDelayCalculatorTest(t)
duration := maxGameDuration / 2
timestamp := uint64(cl.Now().Add(4 * -time.Minute).Unix())
claim := &types.Claim{
ClaimData: types.ClaimData{
Bond: big.NewInt(5),
claim := &monTypes.EnrichedClaim{
Claim: types.Claim{
ClaimData: types.ClaimData{
Bond: big.NewInt(5),
},
Clock: types.NewClock(duration, timestamp),
},
Clock: types.NewClock(duration, timestamp),
}
delay := d.getOverflowTime(maxGameDuration, claim)
require.Equal(t, uint64(240), delay)
......@@ -63,10 +65,10 @@ func TestDelayCalculator_getOverflowTime(t *testing.T) {
func TestDelayCalculator_getMaxResolutionDelay(t *testing.T) {
tests := []struct {
name string
claims []types.Claim
claims []monTypes.EnrichedClaim
want uint64
}{
{"NoClaims", []types.Claim{}, 0},
{"NoClaims", []monTypes.EnrichedClaim{}, 0},
{"SingleClaim", createClaimList()[:1], 180},
{"MultipleClaims", createClaimList()[:2], 300},
{"ClaimsWithMaxUint128", createClaimList(), 300},
......@@ -133,37 +135,42 @@ func createGameWithClaimsList() []*monTypes.EnrichedGameData {
}
}
func createClaimList() []types.Claim {
func createClaimList() []monTypes.EnrichedClaim {
newClock := func(multiplier int) *types.Clock {
duration := maxGameDuration / 2
timestamp := uint64(frozen.Add(-time.Minute * time.Duration(multiplier)).Unix())
return types.NewClock(duration, timestamp)
}
return []types.Claim{
return []monTypes.EnrichedClaim{
{
ClaimData: types.ClaimData{
Bond: big.NewInt(5),
Claim: types.Claim{
ClaimData: types.ClaimData{
Bond: big.NewInt(5),
},
Clock: newClock(3),
},
Clock: newClock(3),
},
{
ClaimData: types.ClaimData{
Bond: big.NewInt(10),
Claim: types.Claim{
ClaimData: types.ClaimData{
Bond: big.NewInt(10),
},
Clock: newClock(5),
},
Clock: newClock(5),
},
{
ClaimData: types.ClaimData{
Bond: big.NewInt(100),
Claim: types.Claim{
ClaimData: types.ClaimData{
Bond: big.NewInt(100),
},
Clock: newClock(2),
},
Clock: newClock(2),
},
{
// This claim should be skipped because it's resolved.
ClaimData: types.ClaimData{
Bond: monTypes.ResolvedBondAmount,
Claim: types.Claim{
Clock: newClock(10),
},
Clock: newClock(10),
Resolved: true,
},
}
}
......
......@@ -11,18 +11,19 @@ import (
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/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) {
t.Run("NoClaims", func(t *testing.T) {
tree := transform.CreateBidirectionalTree([]faultTypes.Claim{})
tree := transform.CreateBidirectionalTree([]monTypes.EnrichedClaim{})
status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusDefenderWon, status)
})
t.Run("SingleRootClaim", func(t *testing.T) {
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{}
status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusDefenderWon, status)
......@@ -36,7 +37,7 @@ func TestResolver_Resolve(t *testing.T) {
Defend(). // Challenger winning
Defend(). // Defender winning
Attack() // Challenger winning
tree := transform.CreateBidirectionalTree(builder.Game.Claims())
tree := transform.CreateBidirectionalTree(enrichClaims(builder.Game.Claims()))
status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusChallengerWon, status)
})
......@@ -48,7 +49,7 @@ func TestResolver_Resolve(t *testing.T) {
Attack(). // Defender winning
Defend(). // Challenger winning
Defend() // Defender winning
tree := transform.CreateBidirectionalTree(builder.Game.Claims())
tree := transform.CreateBidirectionalTree(enrichClaims(builder.Game.Claims()))
status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusDefenderWon, status)
})
......@@ -68,7 +69,7 @@ func TestResolver_Resolve(t *testing.T) {
Step() // Defender winning
forkPoint.Defend(test.WithValue(common.Hash{0xcc})). // Challenger winning
Defend() // Defender winning
tree := transform.CreateBidirectionalTree(builder.Game.Claims())
tree := transform.CreateBidirectionalTree(enrichClaims(builder.Game.Claims()))
status := Resolve(tree)
// First fork has an uncountered claim with challenger winning so that invalidates the parent and wins the game
require.Equal(t, gameTypes.GameStatusChallengerWon, status)
......@@ -89,7 +90,7 @@ func TestResolver_Resolve(t *testing.T) {
forkPoint.Defend(test.WithValue(common.Hash{0xcc})). // Challenger winning
Defend() // Defender winning
tree := transform.CreateBidirectionalTree(builder.Game.Claims())
tree := transform.CreateBidirectionalTree(enrichClaims(builder.Game.Claims()))
status := Resolve(tree)
// Defender won all forks
require.Equal(t, gameTypes.GameStatusDefenderWon, status)
......@@ -125,7 +126,7 @@ func TestResolver_Resolve(t *testing.T) {
Defend(test.WithClaimant(common.Address{0xee})). // Challenger winning
Defend(). // Defender winning
Defend() // Challenger winning
tree := transform.CreateBidirectionalTree(builder.Game.Claims())
tree := transform.CreateBidirectionalTree(enrichClaims(builder.Game.Claims()))
status := Resolve(tree)
// Defender won all forks
require.Equal(t, gameTypes.GameStatusChallengerWon, status)
......@@ -145,7 +146,7 @@ func TestResolver_Resolve(t *testing.T) {
claims := builder.Game.Claims()
// Successful step so mark as countered
claims[len(claims)-1].CounteredBy = common.Address{0xaa}
tree := transform.CreateBidirectionalTree(claims)
tree := transform.CreateBidirectionalTree(enrichClaims(claims))
status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusChallengerWon, status)
})
......@@ -162,8 +163,16 @@ func TestResolver_Resolve(t *testing.T) {
claims := builder.Game.Claims()
// Successful step so mark as countered
claims[len(claims)-1].CounteredBy = common.Address{0xaa}
tree := transform.CreateBidirectionalTree(claims)
tree := transform.CreateBidirectionalTree(enrichClaims(claims))
status := Resolve(tree)
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() {
func (s *Service) initExtractor() {
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.NewBalanceEnricher(),
extract.NewL1HeadBlockNumEnricher(s.l1Client),
......
package transform
import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
)
// 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
// can be easily traversed following the resolution process.
func CreateBidirectionalTree(claims []types.Claim) *monTypes.BidirectionalTree {
claimMap := make(map[int]*monTypes.BidirectionalClaim)
res := make([]*monTypes.BidirectionalClaim, 0, len(claims))
func CreateBidirectionalTree(claims []types.EnrichedClaim) *types.BidirectionalTree {
claimMap := make(map[int]*types.BidirectionalClaim)
res := make([]*types.BidirectionalClaim, 0, len(claims))
for _, claim := range claims {
claim := claim
bidirectionalClaim := &monTypes.BidirectionalClaim{
Claim: &claim,
bidirectionalClaim := &types.BidirectionalClaim{
Claim: &claim.Claim,
}
claimMap[claim.ContractIndex] = bidirectionalClaim
if !claim.IsRoot() {
......@@ -24,5 +23,5 @@ func CreateBidirectionalTree(claims []types.Claim) *monTypes.BidirectionalTree {
}
res = append(res, bidirectionalClaim)
}
return &monTypes.BidirectionalTree{Claims: res}
return &types.BidirectionalTree{Claims: res}
}
......@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"
"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) {
......@@ -17,7 +18,7 @@ func TestResolver_CreateBidirectionalTree(t *testing.T) {
claims[0].CounteredBy = common.Address{}
tree := CreateBidirectionalTree(claims)
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)
})
......@@ -26,10 +27,10 @@ func TestResolver_CreateBidirectionalTree(t *testing.T) {
claims[1].CounteredBy = common.Address{}
tree := CreateBidirectionalTree(claims)
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.Equal(t, claims[1], *tree.Claims[0].Children[0].Claim)
require.Equal(t, claims[1], *tree.Claims[1].Claim)
require.Equal(t, claims[1].Claim, *tree.Claims[0].Children[0].Claim)
require.Equal(t, claims[1].Claim, *tree.Claims[1].Claim)
require.Empty(t, tree.Claims[1].Children)
})
......@@ -37,44 +38,50 @@ func TestResolver_CreateBidirectionalTree(t *testing.T) {
claims := createDeepClaimList()
tree := CreateBidirectionalTree(claims)
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.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.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)
})
}
func createDeepClaimList() []types.Claim {
return []types.Claim{
func createDeepClaimList() []monTypes.EnrichedClaim {
return []monTypes.EnrichedClaim{
{
ClaimData: types.ClaimData{
Position: types.NewPosition(0, big.NewInt(0)),
Claim: types.Claim{
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{
Position: types.NewPosition(1, big.NewInt(0)),
Claim: types.Claim{
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{
Position: types.NewPosition(2, big.NewInt(0)),
Claim: types.Claim{
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 (
"github.com/ethereum/go-ethereum/common"
)
// ResolvedBondAmount is the uint128 value where a bond is considered claimed.
var ResolvedBondAmount = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 128), big.NewInt(1))
// EnrichedClaim extends the faultTypes.Claim with additional context.
type EnrichedClaim struct {
faultTypes.Claim
Resolved bool
}
type EnrichedGameData struct {
types.GameMetadata
......@@ -19,7 +22,7 @@ type EnrichedGameData struct {
RootClaim common.Hash
Status types.GameStatus
Duration uint64
Claims []faultTypes.Claim
Claims []EnrichedClaim
// Credits records the paid out bonds for the game, keyed by recipient.
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