package doublylinkedtree import ( "bytes" "context" "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v3/config/params" types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" ) // depth returns the length of the path to the root of Fork Choice func (n *Node) depth() uint64 { ret := uint64(0) for node := n.parent; node != nil; node = node.parent { ret += 1 } return ret } // applyWeightChanges recomputes the weight of the node passed as an argument and all of its descendants, // using the current balance stored in each node. This function requires a lock // in Store.nodesLock func (n *Node) applyWeightChanges(ctx context.Context) error { // Recursively calling the children to sum their weights. childrenWeight := uint64(0) for _, child := range n.children { if ctx.Err() != nil { return ctx.Err() } if err := child.applyWeightChanges(ctx); err != nil { return err } childrenWeight += child.weight } if n.root == params.BeaconConfig().ZeroHash { return nil } n.weight = n.balance + childrenWeight return nil } // updateBestDescendant updates the best descendant of this node and its // children. This function assumes the caller has a lock on Store.nodesLock func (n *Node) updateBestDescendant(ctx context.Context, justifiedEpoch, finalizedEpoch types.Epoch) error { if ctx.Err() != nil { return ctx.Err() } if len(n.children) == 0 { n.bestDescendant = nil return nil } var bestChild *Node bestWeight := uint64(0) hasViableDescendant := false for _, child := range n.children { if child == nil { return errors.Wrap(ErrNilNode, "could not update best descendant") } if err := child.updateBestDescendant(ctx, justifiedEpoch, finalizedEpoch); err != nil { return err } childLeadsToViableHead := child.leadsToViableHead(justifiedEpoch, finalizedEpoch) if childLeadsToViableHead && !hasViableDescendant { // The child leads to a viable head, but the current // parent's best child doesn't. bestWeight = child.weight bestChild = child hasViableDescendant = true } else if childLeadsToViableHead { // If both are viable, compare their weights. if child.weight == bestWeight { // Tie-breaker of equal weights by root. if bytes.Compare(child.root[:], bestChild.root[:]) > 0 { bestChild = child } } else if child.weight > bestWeight { bestChild = child bestWeight = child.weight } } } if hasViableDescendant { if bestChild.bestDescendant == nil { n.bestDescendant = bestChild } else { n.bestDescendant = bestChild.bestDescendant } } else { n.bestDescendant = nil } return nil } // viableForHead returns true if the node is viable to head. // Any node with different finalized or justified epoch than // the ones in fork choice store should not be viable to head. func (n *Node) viableForHead(justifiedEpoch, finalizedEpoch types.Epoch) bool { justified := justifiedEpoch == n.justifiedEpoch || justifiedEpoch == 0 finalized := finalizedEpoch == n.finalizedEpoch || finalizedEpoch == 0 return justified && finalized } func (n *Node) leadsToViableHead(justifiedEpoch, finalizedEpoch types.Epoch) bool { if n.bestDescendant == nil { return n.viableForHead(justifiedEpoch, finalizedEpoch) } return n.bestDescendant.viableForHead(justifiedEpoch, finalizedEpoch) } // setNodeAndParentValidated sets the current node and all the ancestors as validated (i.e. non-optimistic). func (n *Node) setNodeAndParentValidated(ctx context.Context) error { if ctx.Err() != nil { return ctx.Err() } if !n.optimistic { return nil } n.optimistic = false if n.parent == nil { return nil } return n.parent.setNodeAndParentValidated(ctx) } // nodeTreeDump appends to the given list all the nodes descending from this one func (n *Node) nodeTreeDump(ctx context.Context, nodes []*ethpb.ForkChoiceNode) ([]*ethpb.ForkChoiceNode, error) { if ctx.Err() != nil { return nil, ctx.Err() } var parentRoot [32]byte if n.parent != nil { parentRoot = n.parent.root } thisNode := ðpb.ForkChoiceNode{ Slot: n.slot, Root: n.root[:], ParentRoot: parentRoot[:], JustifiedEpoch: n.justifiedEpoch, FinalizedEpoch: n.finalizedEpoch, UnrealizedJustifiedEpoch: n.unrealizedJustifiedEpoch, UnrealizedFinalizedEpoch: n.unrealizedFinalizedEpoch, Balance: n.balance, Weight: n.weight, ExecutionOptimistic: n.optimistic, ExecutionPayload: n.payloadHash[:], } nodes = append(nodes, thisNode) var err error for _, child := range n.children { nodes, err = child.nodeTreeDump(ctx, nodes) if err != nil { return nil, err } } return nodes, nil }