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

feat(op-dispute-mon): resolution delay metric refactors (#9563)

parent f2cf85a0
......@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/transform"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum/go-ethereum/log"
......@@ -15,7 +16,6 @@ var (
ErrContractCreation = errors.New("failed to create contract")
ErrMetadataFetch = errors.New("failed to fetch game metadata")
ErrClaimFetch = errors.New("failed to fetch game claims")
ErrResolver = errors.New("failed to resolve game")
ErrRootAgreement = errors.New("failed to check root agreement")
)
......@@ -78,11 +78,11 @@ func (f *forecast) forecastGame(ctx context.Context, game types.GameMetadata, me
return fmt.Errorf("%w: %w", ErrClaimFetch, err)
}
// Create the bidirectional tree of claims.
tree := transform.CreateBidirectionalTree(claims)
// Compute the resolution status of the game.
status, err = Resolve(claims)
if err != nil {
return fmt.Errorf("%w: %w", ErrResolver, err)
}
status = Resolve(tree)
// Check the root agreement.
agreement, expected, err := f.validator.CheckRootAgreement(ctx, l2BlockNum, rootClaim)
......
package mon
import (
"fmt"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/common"
)
type BidirectionalClaim struct {
Claim *faultTypes.Claim
Children []*BidirectionalClaim
}
// Resolve creates the bidirectional tree of claims and then computes the resolved game status.
func Resolve(claims []faultTypes.Claim) (types.GameStatus, error) {
flatBidireactionalTree, err := createBidirectionalTree(claims)
if err != nil {
return 0, fmt.Errorf("failed to create bidirectional tree: %w", err)
}
return resolveTree(flatBidireactionalTree), nil
}
// 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 []faultTypes.Claim) ([]*BidirectionalClaim, error) {
claimMap := make(map[int]*BidirectionalClaim)
res := make([]*BidirectionalClaim, 0, len(claims))
for _, claim := range claims {
claim := claim
bidirectionalClaim := &BidirectionalClaim{
Claim: &claim,
}
claimMap[claim.ContractIndex] = bidirectionalClaim
if !claim.IsRoot() {
// SAFETY: the parent must exist in the list prior to the current claim.
parent := claimMap[claim.ParentContractIndex]
parent.Children = append(parent.Children, bidirectionalClaim)
}
res = append(res, bidirectionalClaim)
}
return res, nil
}
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
)
// resolveTree iterates backwards over the bidirectional tree, iteratively
// Resolve iterates backwards over the bidirectional tree, iteratively
// checking the leftmost counter of each claim, and updating the claim's counter
// claimant. Once the root claim is reached, the resolution game status is returned.
func resolveTree(tree []*BidirectionalClaim) types.GameStatus {
for i := len(tree) - 1; i >= 0; i-- {
claim := tree[i]
func Resolve(tree *monTypes.BidirectionalTree) gameTypes.GameStatus {
for i := len(tree.Claims) - 1; i >= 0; i-- {
claim := tree.Claims[i]
counterClaimant := common.Address{}
for _, child := range claim.Children {
if child.Claim.CounteredBy == (common.Address{}) {
......@@ -58,9 +21,9 @@ func resolveTree(tree []*BidirectionalClaim) types.GameStatus {
}
claim.Claim.CounteredBy = counterClaimant
}
if (len(tree) == 0 || tree[0].Claim.CounteredBy == common.Address{}) {
return types.GameStatusDefenderWon
if (len(tree.Claims) == 0 || tree.Claims[0].Claim.CounteredBy == common.Address{}) {
return gameTypes.GameStatusDefenderWon
} else {
return types.GameStatusChallengerWon
return gameTypes.GameStatusChallengerWon
}
}
......@@ -9,118 +9,54 @@ import (
"github.com/stretchr/testify/require"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"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) {
t.Run("NoClaims", func(t *testing.T) {
status, err := Resolve([]faultTypes.Claim{})
require.NoError(t, err)
require.Equal(t, types.GameStatusDefenderWon, status)
})
t.Run("SingleClaim", func(t *testing.T) {
status, err := Resolve(createDeepClaimList()[:1])
require.NoError(t, err)
require.Equal(t, types.GameStatusDefenderWon, status)
})
t.Run("MultipleClaims", func(t *testing.T) {
status, err := Resolve(createDeepClaimList()[:2])
require.NoError(t, err)
require.Equal(t, types.GameStatusChallengerWon, status)
})
t.Run("MultipleClaimsAndChildren", func(t *testing.T) {
status, err := Resolve(createDeepClaimList())
require.NoError(t, err)
require.Equal(t, types.GameStatusDefenderWon, status)
})
}
func TestResolver_CreateBidirectionalTree(t *testing.T) {
t.Run("SingleClaim", func(t *testing.T) {
claims := createDeepClaimList()[:1]
claims[0].CounteredBy = common.Address{}
tree, err := createBidirectionalTree(claims)
require.NoError(t, err)
require.Len(t, tree, 1)
require.Equal(t, claims[0], *tree[0].Claim)
require.Empty(t, tree[0].Children)
})
t.Run("MultipleClaims", func(t *testing.T) {
claims := createDeepClaimList()[:2]
claims[1].CounteredBy = common.Address{}
tree, err := createBidirectionalTree(claims)
require.NoError(t, err)
require.Len(t, tree, 2)
require.Equal(t, claims[0], *tree[0].Claim)
require.Len(t, tree[0].Children, 1)
require.Equal(t, claims[1], *tree[0].Children[0].Claim)
require.Equal(t, claims[1], *tree[1].Claim)
require.Empty(t, tree[1].Children)
})
t.Run("MultipleClaimsAndChildren", func(t *testing.T) {
claims := createDeepClaimList()
tree, err := createBidirectionalTree(claims)
require.NoError(t, err)
require.Len(t, tree, 3)
require.Equal(t, claims[0], *tree[0].Claim)
require.Len(t, tree[0].Children, 1)
require.Equal(t, tree[0].Children[0], tree[1])
require.Equal(t, claims[1], *tree[1].Claim)
require.Len(t, tree[1].Children, 1)
require.Equal(t, tree[1].Children[0], tree[2])
require.Equal(t, claims[2], *tree[2].Claim)
require.Empty(t, tree[2].Children)
})
}
func TestResolver_ResolveTree(t *testing.T) {
t.Run("NoClaims", func(t *testing.T) {
status := resolveTree([]*BidirectionalClaim{})
require.Equal(t, types.GameStatusDefenderWon, status)
tree := &monTypes.BidirectionalTree{Claims: []*monTypes.BidirectionalClaim{}}
status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusDefenderWon, status)
})
t.Run("SingleRootClaim", func(t *testing.T) {
list := createBidirectionalClaimList()[:1]
list[0].Claim.CounteredBy = common.Address{}
status := resolveTree(list)
require.Equal(t, types.GameStatusDefenderWon, status)
tree := createBidirectionalTree(1)
tree.Claims[0].Claim.CounteredBy = common.Address{}
status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusDefenderWon, status)
})
t.Run("ChallengerWon", func(t *testing.T) {
list := createBidirectionalClaimList()[:2]
list[1].Claim.CounteredBy = common.Address{}
list[1].Children = make([]*BidirectionalClaim, 0)
status := resolveTree(list)
require.Equal(t, types.GameStatusChallengerWon, status)
t.Run("ManyClaims_ChallengerWon", func(t *testing.T) {
tree := createBidirectionalTree(2)
tree.Claims[1].Claim.CounteredBy = common.Address{}
tree.Claims[1].Children = make([]*monTypes.BidirectionalClaim, 0)
status := Resolve(tree)
require.Equal(t, gameTypes.GameStatusChallengerWon, status)
})
t.Run("DefenderWon", func(t *testing.T) {
status := resolveTree(createBidirectionalClaimList())
require.Equal(t, types.GameStatusDefenderWon, status)
t.Run("ManyClaims_DefenderWon", func(t *testing.T) {
status := Resolve(createBidirectionalTree(3))
require.Equal(t, gameTypes.GameStatusDefenderWon, status)
})
}
func createBidirectionalClaimList() []*BidirectionalClaim {
func createBidirectionalTree(claimCount uint64) *monTypes.BidirectionalTree {
claimList := createDeepClaimList()
bidirectionalClaimList := make([]*BidirectionalClaim, len(claimList))
bidirectionalClaimList[2] = &BidirectionalClaim{
bidirectionalClaimList := make([]*monTypes.BidirectionalClaim, len(claimList))
bidirectionalClaimList[2] = &monTypes.BidirectionalClaim{
Claim: &claimList[2],
Children: make([]*BidirectionalClaim, 0),
Children: make([]*monTypes.BidirectionalClaim, 0),
}
bidirectionalClaimList[1] = &BidirectionalClaim{
bidirectionalClaimList[1] = &monTypes.BidirectionalClaim{
Claim: &claimList[1],
Children: []*BidirectionalClaim{bidirectionalClaimList[2]},
Children: []*monTypes.BidirectionalClaim{bidirectionalClaimList[2]},
}
bidirectionalClaimList[0] = &BidirectionalClaim{
bidirectionalClaimList[0] = &monTypes.BidirectionalClaim{
Claim: &claimList[0],
Children: []*BidirectionalClaim{bidirectionalClaimList[1]},
Children: []*monTypes.BidirectionalClaim{bidirectionalClaimList[1]},
}
return bidirectionalClaimList
return &monTypes.BidirectionalTree{Claims: bidirectionalClaimList[:claimCount]}
}
func createDeepClaimList() []faultTypes.Claim {
......
package transform
import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
monTypes "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))
for _, claim := range claims {
claim := claim
bidirectionalClaim := &monTypes.BidirectionalClaim{
Claim: &claim,
}
claimMap[claim.ContractIndex] = bidirectionalClaim
if !claim.IsRoot() {
// SAFETY: the parent must exist in the list prior to the current claim.
parent := claimMap[claim.ParentContractIndex]
parent.Children = append(parent.Children, bidirectionalClaim)
}
res = append(res, bidirectionalClaim)
}
return &monTypes.BidirectionalTree{Claims: res}
}
package transform
import (
"math"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
)
func TestResolver_CreateBidirectionalTree(t *testing.T) {
t.Run("SingleClaim", func(t *testing.T) {
claims := createDeepClaimList()[:1]
claims[0].CounteredBy = common.Address{}
tree := CreateBidirectionalTree(claims)
require.Len(t, tree.Claims, 1)
require.Equal(t, claims[0], *tree.Claims[0].Claim)
require.Empty(t, tree.Claims[0].Children)
})
t.Run("MultipleClaims", func(t *testing.T) {
claims := createDeepClaimList()[:2]
claims[1].CounteredBy = common.Address{}
tree := CreateBidirectionalTree(claims)
require.Len(t, tree.Claims, 2)
require.Equal(t, claims[0], *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.Empty(t, tree.Claims[1].Children)
})
t.Run("MultipleClaimsAndChildren", func(t *testing.T) {
claims := createDeepClaimList()
tree := CreateBidirectionalTree(claims)
require.Len(t, tree.Claims, 3)
require.Equal(t, claims[0], *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.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.Empty(t, tree.Claims[2].Children)
})
}
func createDeepClaimList() []types.Claim {
return []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"),
},
{
ClaimData: types.ClaimData{
Position: types.NewPosition(1, big.NewInt(0)),
},
CounteredBy: common.HexToAddress("0x111111"),
ContractIndex: 1,
ParentContractIndex: 0,
Claimant: common.HexToAddress("0x222222"),
},
{
ClaimData: types.ClaimData{
Position: types.NewPosition(2, big.NewInt(0)),
},
ContractIndex: 2,
ParentContractIndex: 1,
Claimant: common.HexToAddress("0x111111"),
},
}
}
package types
import (
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/common"
)
......@@ -12,6 +13,17 @@ type EnrichedGameData struct {
Status types.GameStatus
}
// BidirectionalTree is a tree of claims represented as a flat list of claims.
// This keeps the tree structure identical to how claims are stored in the contract.
type BidirectionalTree struct {
Claims []*BidirectionalClaim
}
type BidirectionalClaim struct {
Claim *faultTypes.Claim
Children []*BidirectionalClaim
}
type StatusBatch struct {
InProgress int
DefenderWon int
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment