Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NIT-2489] Honest Strategy Revamp to Consider Path Weights #634

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
13 changes: 13 additions & 0 deletions chain-abstraction/sol-implementation/assertion_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,19 @@ func (a *AssertionChain) GetCallOptsWithDesiredRpcHeadBlockNumber(opts *bind.Cal
return opts
}

func (a *AssertionChain) GetCallOptsWithSafeBlockNumber(opts *bind.CallOpts) *bind.CallOpts {
if opts == nil {
opts = &bind.CallOpts{}
}
// If we are running tests, we want to use the latest block number since
// simulated backends only support the latest block number.
if flag.Lookup("test.v") != nil {
return opts
}
opts.BlockNumber = big.NewInt(int64(rpc.SafeBlockNumber))
return opts
}

func (a *AssertionChain) GetDesiredRpcHeadBlockNumber() *big.Int {
// If we are running tests, we want to use the latest block number since
// simulated backends only support the latest block number.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ func (cm *specChallengeManager) GetEdge(
}

func (e *specEdge) SafeHeadInheritedTimer(ctx context.Context) (protocol.InheritedTimer, error) {
edge, err := e.manager.caller.GetEdge(e.manager.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), e.id)
edge, err := e.manager.caller.GetEdge(e.manager.assertionChain.GetCallOptsWithSafeBlockNumber(&bind.CallOpts{Context: ctx}), e.id)
if err != nil {
return 0, err
}
Expand Down
28 changes: 28 additions & 0 deletions challenge-manager/chain-watcher/watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,34 @@ func (w *Watcher) ComputeAncestors(
return chal.honestEdgeTree.ComputeAncestors(ctx, edgeId, blockHeader.Number.Uint64())
}

func (w *Watcher) IsConfirmableEssentialNode(
ctx context.Context,
challengedAssertionHash protocol.AssertionHash,
essentialNodeId protocol.EdgeId,
confirmationThreshold uint64,
) (bool, []challengetree.EssentialPath, uint64, error) {
chal, ok := w.challenges.TryGet(challengedAssertionHash)
if !ok {
return false, nil, 0, fmt.Errorf("could not get challenge for top level assertion %#x", challengedAssertionHash)
}
blockHeader, err := w.chain.Backend().HeaderByNumber(ctx, w.chain.GetDesiredRpcHeadBlockNumber())
if err != nil {
return false, nil, 0, err
}
if !blockHeader.Number.IsUint64() {
return false, nil, 0, errors.New("block number is not uint64")
}
confirmable, essentialPaths, timer, err := chal.honestEdgeTree.IsConfirmableEssentialNode(
ctx,
challengetree.IsConfirmableArgs{
EssentialNode: essentialNodeId,
BlockNum: blockHeader.Number.Uint64(),
ConfirmationThreshold: confirmationThreshold,
},
)
return confirmable, essentialPaths, timer, err
}

func (w *Watcher) PathWeightToClosestEssentialAncestor(
ctx context.Context,
challengedAssertionHash protocol.AssertionHash,
Expand Down
102 changes: 24 additions & 78 deletions challenge-manager/challenge-tree/paths.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package challengetree

import (
"container/heap"
"container/list"
"context"
"fmt"
"math"

protocol "github.com/offchainlabs/bold/chain-abstraction"
"github.com/offchainlabs/bold/containers"
Expand Down Expand Up @@ -64,12 +64,12 @@ func (ht *RoyalChallengeTree) ComputePathWeight(
return pathWeight, nil
}

type essentialPath []protocol.EdgeId
type EssentialPath []protocol.EdgeId

type isConfirmableArgs struct {
essentialNode protocol.EdgeId
confirmationThreshold uint64
blockNum uint64
type IsConfirmableArgs struct {
EssentialNode protocol.EdgeId
ConfirmationThreshold uint64
BlockNum uint64
}

// Find all the paths down from an essential node, and
Expand All @@ -80,27 +80,26 @@ type isConfirmableArgs struct {
// or non-essential.
//
// After the paths are computed, we then compute the path weight of each
// and insert each weight into a min-heap. If the min element of this heap
// has a weight >= the confirmation threshold, the
// essential node is then confirmable.
// and if the min element of this list has a weight >= the confirmation threshold,
// the essential node is then confirmable.
//
// Note: the specified argument essential node must indeed be essential, otherwise,
// this function will error.
func (ht *RoyalChallengeTree) IsConfirmableEssentialNode(
ctx context.Context,
args isConfirmableArgs,
) (bool, []essentialPath, uint64, error) {
essentialNode, ok := ht.edges.TryGet(args.essentialNode)
args IsConfirmableArgs,
) (bool, []EssentialPath, uint64, error) {
essentialNode, ok := ht.edges.TryGet(args.EssentialNode)
if !ok {
return false, nil, 0, fmt.Errorf("essential node not found")
}
if essentialNode.ClaimId().IsNone() {
return false, nil, 0, fmt.Errorf("specified input argument %#x is not essential", args.essentialNode.Hash)
return false, nil, 0, fmt.Errorf("specified input argument %#x is not essential", args.EssentialNode.Hash)
}
essentialPaths, essentialTimers, err := ht.findEssentialPaths(
ctx,
essentialNode,
args.blockNum,
args.BlockNum,
)
if err != nil {
return false, nil, 0, err
Expand All @@ -110,22 +109,19 @@ func (ht *RoyalChallengeTree) IsConfirmableEssentialNode(
}
// An essential node is confirmable if all of its essential paths
// down the tree have a path weight >= the confirmation threshold.
// To do this, we compute the path weight of each path and insert
// into a min heap. Then, it is sufficient to check that the minimum
// element of the heap is >= the confirmation threshold.
pathWeights := newPathWeightMinHeap()
// To do this, we compute the path weight of each path and find the minimum.
// Then, it is sufficient to check that the minimum is >= the confirmation threshold.
minWeight := uint64(math.MaxUint64)
for _, timers := range essentialTimers {
pathWeight := uint64(0)
for _, timer := range timers {
pathWeight += timer
}
pathWeights.Push(pathWeight)
}
if pathWeights.items.Len() == 0 {
return false, nil, 0, fmt.Errorf("no path weights computed")
if pathWeight < minWeight {
minWeight = pathWeight
}
}
minWeight := pathWeights.Pop()
allEssentialPathsConfirmable := minWeight >= args.confirmationThreshold
allEssentialPathsConfirmable := minWeight >= args.ConfirmationThreshold
return allEssentialPathsConfirmable, essentialPaths, minWeight, nil
}

Expand All @@ -140,13 +136,13 @@ func (ht *RoyalChallengeTree) findEssentialPaths(
ctx context.Context,
essentialNode protocol.ReadOnlyEdge,
blockNum uint64,
) ([]essentialPath, []essentialLocalTimers, error) {
allPaths := make([]essentialPath, 0)
) ([]EssentialPath, []essentialLocalTimers, error) {
allPaths := make([]EssentialPath, 0)
allTimers := make([]essentialLocalTimers, 0)

type visited struct {
essentialNode protocol.ReadOnlyEdge
path essentialPath
path EssentialPath
localTimers essentialLocalTimers
}
stack := newStack[*visited]()
Expand All @@ -158,7 +154,7 @@ func (ht *RoyalChallengeTree) findEssentialPaths(

stack.push(&visited{
essentialNode: essentialNode,
path: essentialPath{essentialNode.Id()},
path: EssentialPath{essentialNode.Id()},
localTimers: essentialLocalTimers{localTimer},
})

Expand Down Expand Up @@ -264,56 +260,6 @@ func isProofNode(ctx context.Context, edge protocol.ReadOnlyEdge) bool {
return isSmallStep && hasLengthOne(edge)
}

type uint64Heap []uint64

func (h uint64Heap) Len() int { return len(h) }
func (h uint64Heap) Less(i, j int) bool { return h[i] < h[j] }
func (h uint64Heap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h uint64Heap) Peek() uint64 {
return h[0]
}

func (h *uint64Heap) Push(x any) {
*h = append(*h, x.(uint64))
}

func (h *uint64Heap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}

type pathWeightMinHeap struct {
items *uint64Heap
}

func newPathWeightMinHeap() *pathWeightMinHeap {
items := &uint64Heap{}
heap.Init(items)
return &pathWeightMinHeap{items}
}

func (h *pathWeightMinHeap) Len() int {
return h.items.Len()
}

func (h *pathWeightMinHeap) Push(item uint64) {
heap.Push(h.items, item)
}

func (h *pathWeightMinHeap) Pop() uint64 {
return heap.Pop(h.items).(uint64)
}

func (h *pathWeightMinHeap) Peek() option.Option[uint64] {
if h.items.Len() == 0 {
return option.None[uint64]()
}
return option.Some(h.items.Peek())
}

type stack[T any] struct {
dll *list.List
}
Expand Down
40 changes: 13 additions & 27 deletions challenge-manager/challenge-tree/paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ func TestIsConfirmableEssentialNode(t *testing.T) {
// understand the setup of the challenge tree.
_, _, _, err := tree.IsConfirmableEssentialNode(
ctx,
isConfirmableArgs{
essentialNode: protocol.EdgeId{},
IsConfirmableArgs{
EssentialNode: protocol.EdgeId{},
},
)
require.ErrorContains(t, err, "essential node not found")
Expand All @@ -32,10 +32,10 @@ func TestIsConfirmableEssentialNode(t *testing.T) {
blockNum := uint64(10)
isConfirmable, _, minPathWeight, err := tree.IsConfirmableEssentialNode(
ctx,
isConfirmableArgs{
confirmationThreshold: 10,
essentialNode: essentialHonestRoot.Id(),
blockNum: blockNum,
IsConfirmableArgs{
ConfirmationThreshold: 10,
EssentialNode: essentialHonestRoot.Id(),
BlockNum: blockNum,
},
)
require.NoError(t, err)
Expand All @@ -47,10 +47,10 @@ func TestIsConfirmableEssentialNode(t *testing.T) {
blockNum = uint64(14)
isConfirmable, _, minPathWeight, err = tree.IsConfirmableEssentialNode(
ctx,
isConfirmableArgs{
confirmationThreshold: 10,
essentialNode: essentialHonestRoot.Id(),
blockNum: blockNum,
IsConfirmableArgs{
ConfirmationThreshold: 10,
EssentialNode: essentialHonestRoot.Id(),
BlockNum: blockNum,
},
)
require.NoError(t, err)
Expand Down Expand Up @@ -93,7 +93,7 @@ func Test_findEssentialPaths(t *testing.T) {
require.Equal(t, 3, len(paths))
require.Equal(t, 3, len(pathLocalTimers))

wantPathA := essentialPath{
wantPathA := EssentialPath{
honestEdges["blk-3.a-4.a"].Id(),
honestEdges["blk-2.a-4.a"].Id(),
honestEdges["blk-0.a-4.a"].Id(),
Expand All @@ -102,7 +102,7 @@ func Test_findEssentialPaths(t *testing.T) {
require.Equal(t, wantPathA, paths[0])
require.Equal(t, wantATimers, pathLocalTimers[0])

wantPathB := essentialPath{
wantPathB := EssentialPath{
honestEdges["big-0.a-4.a"].Id(),
honestEdges["blk-2.a-3.a"].Id(),
honestEdges["blk-2.a-4.a"].Id(),
Expand All @@ -112,7 +112,7 @@ func Test_findEssentialPaths(t *testing.T) {
require.Equal(t, wantPathB, paths[1])
require.Equal(t, wantBTimers, pathLocalTimers[1])

wantPathC := essentialPath{
wantPathC := EssentialPath{
honestEdges["blk-0.a-2.a"].Id(),
honestEdges["blk-0.a-4.a"].Id(),
}
Expand All @@ -121,20 +121,6 @@ func Test_findEssentialPaths(t *testing.T) {
require.Equal(t, wantCTimers, pathLocalTimers[2])
}

func Test_pathWeightMinHeap(t *testing.T) {
h := newPathWeightMinHeap()
require.Equal(t, 0, h.Len())
h.Push(uint64(3))
h.Push(uint64(1))
h.Push(uint64(2))
require.Equal(t, uint64(1), h.Peek().Unwrap())
require.Equal(t, uint64(1), h.Pop())
require.Equal(t, uint64(2), h.Pop())
require.Equal(t, uint64(3), h.Pop())
require.Equal(t, 0, h.Len())
require.True(t, h.Peek().IsNone())
}

func Test_stack(t *testing.T) {
s := newStack[int]()
require.Equal(t, 0, s.len())
Expand Down
26 changes: 14 additions & 12 deletions challenge-manager/challenge-tree/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,14 @@ func buildEdgeCreationTimeKey(originId protocol.OriginId, mutualId protocol.Mutu
// RoyalChallengeTree keeps track of royal edges the honest node agrees with in a particular challenge.
// All edges tracked in this data structure are part of the same, top-level assertion challenge.
type RoyalChallengeTree struct {
edges *threadsafe.Map[protocol.EdgeId, protocol.SpecEdge]
edgeCreationTimes *threadsafe.Map[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]]
topLevelAssertionHash protocol.AssertionHash
metadataReader MetadataReader
histChecker l2stateprovider.HistoryChecker
validatorName string
totalChallengeLevels uint8
royalRootEdgesByLevel *threadsafe.Map[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]]
essentialNodePathWeights map[protocol.EdgeId]*pathWeightMinHeap
edges *threadsafe.Map[protocol.EdgeId, protocol.SpecEdge]
edgeCreationTimes *threadsafe.Map[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]]
topLevelAssertionHash protocol.AssertionHash
metadataReader MetadataReader
histChecker l2stateprovider.HistoryChecker
validatorName string
totalChallengeLevels uint8
royalRootEdgesByLevel *threadsafe.Map[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]]
}

func New(
Expand All @@ -71,9 +70,8 @@ func New(
histChecker: histChecker,
validatorName: validatorName,
// The total number of challenge levels include block challenges, small step challenges, and N big step challenges.
totalChallengeLevels: numBigStepLevels + 2,
royalRootEdgesByLevel: threadsafe.NewMap[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]](threadsafe.MapWithMetric[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]]("royalRootEdgesByLevel")),
essentialNodePathWeights: make(map[protocol.EdgeId]*pathWeightMinHeap),
totalChallengeLevels: numBigStepLevels + 2,
royalRootEdgesByLevel: threadsafe.NewMap[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]](threadsafe.MapWithMetric[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]]("royalRootEdgesByLevel")),
}
}

Expand Down Expand Up @@ -104,6 +102,10 @@ func (ht *RoyalChallengeTree) GetEdges() *threadsafe.Map[protocol.EdgeId, protoc
return ht.edges
}

func (ht *RoyalChallengeTree) GetEdge(edgeId protocol.EdgeId) (protocol.SpecEdge, bool) {
return ht.edges.TryGet(edgeId)
}

func (ht *RoyalChallengeTree) HasRoyalEdge(edgeId protocol.EdgeId) bool {
return ht.edges.Has(edgeId)
}
Expand Down
1 change: 1 addition & 0 deletions challenge-manager/edge-tracker/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//chain-abstraction:protocol",
"//challenge-manager/challenge-tree",
"//containers",
"//containers/events",
"//containers/fsm",
Expand Down
Loading
Loading