2022-03-09 03:05:51 +00:00
|
|
|
package doublylinkedtree
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
|
2022-06-05 17:48:21 +00:00
|
|
|
"github.com/pkg/errors"
|
2023-03-17 18:52:56 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/v4/config/params"
|
|
|
|
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
|
|
|
v1 "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
|
|
|
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
2022-03-09 03:05:51 +00:00
|
|
|
)
|
|
|
|
|
2023-02-18 10:37:03 +00:00
|
|
|
// orphanLateBlockFirstThreshold is the number of seconds after which we
|
|
|
|
// consider a block to be late, and thus a candidate to being reorged.
|
|
|
|
const orphanLateBlockFirstThreshold = 4
|
|
|
|
|
2023-03-09 01:51:50 +00:00
|
|
|
// ProcessAttestationsThreshold is the number of seconds after which we
|
2023-02-18 10:37:03 +00:00
|
|
|
// process attestations for the current slot
|
2023-03-09 01:51:50 +00:00
|
|
|
const ProcessAttestationsThreshold = 10
|
2023-02-18 10:37:03 +00:00
|
|
|
|
2022-03-09 03:05:51 +00:00
|
|
|
// applyWeightChanges recomputes the weight of the node passed as an argument and all of its descendants,
|
2023-03-02 12:10:52 +00:00
|
|
|
// using the current balance stored in each node.
|
2022-03-09 03:05:51 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-08-19 14:06:57 +00:00
|
|
|
// updateBestDescendant updates the best descendant of this node and its
|
2023-03-02 12:10:52 +00:00
|
|
|
// children.
|
2023-01-26 14:40:12 +00:00
|
|
|
func (n *Node) updateBestDescendant(ctx context.Context, justifiedEpoch, finalizedEpoch, currentEpoch primitives.Epoch) error {
|
2022-03-09 03:05:51 +00:00
|
|
|
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 {
|
2022-06-05 17:48:21 +00:00
|
|
|
return errors.Wrap(ErrNilNode, "could not update best descendant")
|
2022-03-09 03:05:51 +00:00
|
|
|
}
|
2022-08-30 00:48:25 +00:00
|
|
|
if err := child.updateBestDescendant(ctx, justifiedEpoch, finalizedEpoch, currentEpoch); err != nil {
|
2022-03-09 03:05:51 +00:00
|
|
|
return err
|
|
|
|
}
|
2023-02-05 12:40:07 +00:00
|
|
|
childLeadsToViableHead := child.leadsToViableHead(justifiedEpoch, currentEpoch)
|
2022-03-09 03:05:51 +00:00
|
|
|
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.
|
2023-02-05 12:40:07 +00:00
|
|
|
func (n *Node) viableForHead(justifiedEpoch, currentEpoch primitives.Epoch) bool {
|
2022-03-09 03:05:51 +00:00
|
|
|
justified := justifiedEpoch == n.justifiedEpoch || justifiedEpoch == 0
|
2023-03-16 17:27:30 +00:00
|
|
|
if !justified && justifiedEpoch+1 == currentEpoch {
|
2023-02-05 12:40:07 +00:00
|
|
|
if n.unrealizedJustifiedEpoch+1 >= currentEpoch && n.justifiedEpoch+2 >= currentEpoch {
|
2022-08-30 00:48:25 +00:00
|
|
|
justified = true
|
2022-09-05 02:26:10 +00:00
|
|
|
}
|
2022-08-30 00:48:25 +00:00
|
|
|
}
|
2023-02-05 12:40:07 +00:00
|
|
|
return justified
|
2022-03-09 03:05:51 +00:00
|
|
|
}
|
|
|
|
|
2023-02-05 12:40:07 +00:00
|
|
|
func (n *Node) leadsToViableHead(justifiedEpoch, currentEpoch primitives.Epoch) bool {
|
2022-03-09 03:05:51 +00:00
|
|
|
if n.bestDescendant == nil {
|
2023-02-05 12:40:07 +00:00
|
|
|
return n.viableForHead(justifiedEpoch, currentEpoch)
|
2022-03-09 03:05:51 +00:00
|
|
|
}
|
2023-02-05 12:40:07 +00:00
|
|
|
return n.bestDescendant.viableForHead(justifiedEpoch, currentEpoch)
|
2022-03-09 03:05:51 +00:00
|
|
|
}
|
|
|
|
|
2022-04-06 14:18:30 +00:00
|
|
|
// setNodeAndParentValidated sets the current node and all the ancestors as validated (i.e. non-optimistic).
|
2022-03-09 03:05:51 +00:00
|
|
|
func (n *Node) setNodeAndParentValidated(ctx context.Context) error {
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
|
2022-08-24 19:30:45 +00:00
|
|
|
if !n.optimistic {
|
2022-03-09 03:05:51 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
n.optimistic = false
|
2022-08-24 19:30:45 +00:00
|
|
|
|
|
|
|
if n.parent == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2022-03-09 03:05:51 +00:00
|
|
|
return n.parent.setNodeAndParentValidated(ctx)
|
|
|
|
}
|
2022-08-25 23:37:23 +00:00
|
|
|
|
2023-02-18 10:37:03 +00:00
|
|
|
// arrivedEarly returns whether this node was inserted before the first
|
|
|
|
// threshold to orphan a block.
|
|
|
|
// Note that genesisTime has seconds granularity, therefore we use a strict
|
|
|
|
// inequality < here. For example a block that arrives 3.9999 seconds into the
|
|
|
|
// slot will have secs = 3 below.
|
|
|
|
func (n *Node) arrivedEarly(genesisTime uint64) (bool, error) {
|
|
|
|
secs, err := slots.SecondsSinceSlotStart(n.slot, genesisTime, n.timestamp)
|
|
|
|
return secs < orphanLateBlockFirstThreshold, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// arrivedAfterOrphanCheck returns whether this block was inserted after the
|
|
|
|
// intermediate checkpoint to check for candidate of being orphaned.
|
|
|
|
// Note that genesisTime has seconds granularity, therefore we use an
|
|
|
|
// inequality >= here. For example a block that arrives 10.00001 seconds into the
|
|
|
|
// slot will have secs = 10 below.
|
|
|
|
func (n *Node) arrivedAfterOrphanCheck(genesisTime uint64) (bool, error) {
|
|
|
|
secs, err := slots.SecondsSinceSlotStart(n.slot, genesisTime, n.timestamp)
|
2023-03-09 01:51:50 +00:00
|
|
|
return secs >= ProcessAttestationsThreshold, err
|
2023-02-18 10:37:03 +00:00
|
|
|
}
|
|
|
|
|
2022-08-25 23:37:23 +00:00
|
|
|
// nodeTreeDump appends to the given list all the nodes descending from this one
|
2022-08-26 14:54:32 +00:00
|
|
|
func (n *Node) nodeTreeDump(ctx context.Context, nodes []*v1.ForkChoiceNode) ([]*v1.ForkChoiceNode, error) {
|
2022-08-25 23:37:23 +00:00
|
|
|
if ctx.Err() != nil {
|
|
|
|
return nil, ctx.Err()
|
|
|
|
}
|
|
|
|
var parentRoot [32]byte
|
|
|
|
if n.parent != nil {
|
|
|
|
parentRoot = n.parent.root
|
|
|
|
}
|
2022-08-26 14:54:32 +00:00
|
|
|
thisNode := &v1.ForkChoiceNode{
|
2022-08-25 23:37:23 +00:00
|
|
|
Slot: n.slot,
|
2022-11-28 19:17:53 +00:00
|
|
|
BlockRoot: n.root[:],
|
2022-08-25 23:37:23 +00:00
|
|
|
ParentRoot: parentRoot[:],
|
|
|
|
JustifiedEpoch: n.justifiedEpoch,
|
|
|
|
FinalizedEpoch: n.finalizedEpoch,
|
|
|
|
UnrealizedJustifiedEpoch: n.unrealizedJustifiedEpoch,
|
|
|
|
UnrealizedFinalizedEpoch: n.unrealizedFinalizedEpoch,
|
|
|
|
Balance: n.balance,
|
|
|
|
Weight: n.weight,
|
|
|
|
ExecutionOptimistic: n.optimistic,
|
2022-11-28 19:17:53 +00:00
|
|
|
ExecutionBlockHash: n.payloadHash[:],
|
2022-08-29 14:49:02 +00:00
|
|
|
Timestamp: n.timestamp,
|
2022-08-25 23:37:23 +00:00
|
|
|
}
|
2022-11-28 19:17:53 +00:00
|
|
|
if n.optimistic {
|
|
|
|
thisNode.Validity = v1.ForkChoiceNodeValidity_OPTIMISTIC
|
|
|
|
} else {
|
|
|
|
thisNode.Validity = v1.ForkChoiceNodeValidity_VALID
|
|
|
|
}
|
2022-08-25 23:37:23 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|