mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-27 21:57:16 +00:00
228 lines
6.8 KiB
Go
228 lines
6.8 KiB
Go
package protoarray
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
|
"go.opencensus.io/trace"
|
|
)
|
|
|
|
// This defines the minimal number of block nodes that can be in the tree
|
|
// before getting pruned upon new finalization.
|
|
const defaultPruneThreshold = 256
|
|
|
|
// This tracks the last reported head root. Used for metrics.
|
|
var lastHeadRoot [32]byte
|
|
|
|
// New initializes a new fork choice store.
|
|
func New(justifiedEpoch, finalizedEpoch uint64, finalizedRoot [32]byte) *ForkChoice {
|
|
s := &Store{
|
|
justifiedEpoch: justifiedEpoch,
|
|
finalizedEpoch: finalizedEpoch,
|
|
finalizedRoot: finalizedRoot,
|
|
nodes: make([]*Node, 0),
|
|
nodesIndices: make(map[[32]byte]uint64),
|
|
canonicalNodes: make(map[[32]byte]bool),
|
|
pruneThreshold: defaultPruneThreshold,
|
|
}
|
|
|
|
b := make([]uint64, 0)
|
|
v := make([]Vote, 0)
|
|
|
|
return &ForkChoice{store: s, balances: b, votes: v}
|
|
}
|
|
|
|
// Head returns the head root from fork choice store.
|
|
// It firsts computes validator's balance changes then recalculates block tree from leaves to root.
|
|
func (f *ForkChoice) Head(ctx context.Context, justifiedEpoch uint64, justifiedRoot [32]byte, justifiedStateBalances []uint64, finalizedEpoch uint64) ([32]byte, error) {
|
|
ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.Head")
|
|
defer span.End()
|
|
f.votesLock.Lock()
|
|
defer f.votesLock.Unlock()
|
|
|
|
calledHeadCount.Inc()
|
|
|
|
newBalances := justifiedStateBalances
|
|
|
|
// Using the write lock here because `updateCanonicalNodes` that gets called subsequently requires a write operation.
|
|
f.store.nodesLock.Lock()
|
|
defer f.store.nodesLock.Unlock()
|
|
deltas, newVotes, err := computeDeltas(ctx, f.store.nodesIndices, f.votes, f.balances, newBalances)
|
|
if err != nil {
|
|
return [32]byte{}, errors.Wrap(err, "Could not compute deltas")
|
|
}
|
|
f.votes = newVotes
|
|
|
|
if err := f.store.applyWeightChanges(ctx, justifiedEpoch, finalizedEpoch, deltas); err != nil {
|
|
return [32]byte{}, errors.Wrap(err, "Could not apply score changes")
|
|
}
|
|
f.balances = newBalances
|
|
|
|
return f.store.head(ctx, justifiedRoot)
|
|
}
|
|
|
|
// ProcessAttestation processes attestation for vote accounting, it iterates around validator indices
|
|
// and update their votes accordingly.
|
|
func (f *ForkChoice) ProcessAttestation(ctx context.Context, validatorIndices []uint64, blockRoot [32]byte, targetEpoch uint64) {
|
|
ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.ProcessAttestation")
|
|
defer span.End()
|
|
f.votesLock.Lock()
|
|
defer f.votesLock.Unlock()
|
|
|
|
for _, index := range validatorIndices {
|
|
// Validator indices will grow the vote cache.
|
|
for index >= uint64(len(f.votes)) {
|
|
f.votes = append(f.votes, Vote{currentRoot: params.BeaconConfig().ZeroHash, nextRoot: params.BeaconConfig().ZeroHash})
|
|
}
|
|
|
|
// Newly allocated vote if the root fields are untouched.
|
|
newVote := f.votes[index].nextRoot == params.BeaconConfig().ZeroHash &&
|
|
f.votes[index].currentRoot == params.BeaconConfig().ZeroHash
|
|
|
|
// Vote gets updated if it's newly allocated or high target epoch.
|
|
if newVote || targetEpoch > f.votes[index].nextEpoch {
|
|
f.votes[index].nextEpoch = targetEpoch
|
|
f.votes[index].nextRoot = blockRoot
|
|
}
|
|
}
|
|
|
|
processedAttestationCount.Inc()
|
|
}
|
|
|
|
// ProcessBlock processes a new block by inserting it to the fork choice store.
|
|
func (f *ForkChoice) ProcessBlock(ctx context.Context, slot uint64, blockRoot, parentRoot, graffiti [32]byte, justifiedEpoch, finalizedEpoch uint64) error {
|
|
ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.ProcessBlock")
|
|
defer span.End()
|
|
|
|
return f.store.insert(ctx, slot, blockRoot, parentRoot, graffiti, justifiedEpoch, finalizedEpoch)
|
|
}
|
|
|
|
// Prune prunes the fork choice store with the new finalized root. The store is only pruned if the input
|
|
// root is different than the current store finalized root, and the number of the store has met prune threshold.
|
|
func (f *ForkChoice) Prune(ctx context.Context, finalizedRoot [32]byte) error {
|
|
return f.store.prune(ctx, finalizedRoot)
|
|
}
|
|
|
|
// Nodes returns the copied list of block nodes in the fork choice store.
|
|
func (f *ForkChoice) Nodes() []*Node {
|
|
f.store.nodesLock.RLock()
|
|
defer f.store.nodesLock.RUnlock()
|
|
|
|
cpy := make([]*Node, len(f.store.nodes))
|
|
copy(cpy, f.store.nodes)
|
|
return cpy
|
|
}
|
|
|
|
// Store returns the fork choice store object which contains all the information regarding proto array fork choice.
|
|
func (f *ForkChoice) Store() *Store {
|
|
f.store.nodesLock.Lock()
|
|
defer f.store.nodesLock.Unlock()
|
|
return f.store
|
|
}
|
|
|
|
// Node returns the copied node in the fork choice store.
|
|
func (f *ForkChoice) Node(root [32]byte) *Node {
|
|
f.store.nodesLock.RLock()
|
|
defer f.store.nodesLock.RUnlock()
|
|
|
|
index, ok := f.store.nodesIndices[root]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return copyNode(f.store.nodes[index])
|
|
}
|
|
|
|
// HasNode returns true if the node exists in fork choice store,
|
|
// false else wise.
|
|
func (f *ForkChoice) HasNode(root [32]byte) bool {
|
|
f.store.nodesLock.RLock()
|
|
defer f.store.nodesLock.RUnlock()
|
|
|
|
_, ok := f.store.nodesIndices[root]
|
|
return ok
|
|
}
|
|
|
|
// HasParent returns true if the node parent exists in fork choice store,
|
|
// false else wise.
|
|
func (f *ForkChoice) HasParent(root [32]byte) bool {
|
|
f.store.nodesLock.RLock()
|
|
defer f.store.nodesLock.RUnlock()
|
|
|
|
i, ok := f.store.nodesIndices[root]
|
|
if !ok || i >= uint64(len(f.store.nodes)) {
|
|
return false
|
|
}
|
|
|
|
return f.store.nodes[i].parent != NonExistentNode
|
|
}
|
|
|
|
// IsCanonical returns true if the given root is part of the canonical chain.
|
|
func (f *ForkChoice) IsCanonical(root [32]byte) bool {
|
|
f.store.nodesLock.RLock()
|
|
defer f.store.nodesLock.RUnlock()
|
|
|
|
return f.store.canonicalNodes[root]
|
|
}
|
|
|
|
// AncestorRoot returns the ancestor root of input block root at a given slot.
|
|
func (f *ForkChoice) AncestorRoot(ctx context.Context, root [32]byte, slot uint64) ([]byte, error) {
|
|
ctx, span := trace.StartSpan(ctx, "protoArray.AncestorRoot")
|
|
defer span.End()
|
|
|
|
f.store.nodesLock.RLock()
|
|
defer f.store.nodesLock.RUnlock()
|
|
|
|
i, ok := f.store.nodesIndices[root]
|
|
if !ok {
|
|
return nil, errors.New("node does not exist")
|
|
}
|
|
if i >= uint64(len(f.store.nodes)) {
|
|
return nil, errors.New("node index out of range")
|
|
}
|
|
|
|
for f.store.nodes[i].slot > slot {
|
|
if ctx.Err() != nil {
|
|
return nil, ctx.Err()
|
|
}
|
|
|
|
i = f.store.nodes[i].parent
|
|
|
|
if i >= uint64(len(f.store.nodes)) {
|
|
return nil, errors.New("node index out of range")
|
|
}
|
|
}
|
|
|
|
return f.store.nodes[i].root[:], nil
|
|
}
|
|
|
|
// PruneThreshold of fork choice store.
|
|
func (s *Store) PruneThreshold() uint64 {
|
|
return s.pruneThreshold
|
|
}
|
|
|
|
// JustifiedEpoch of fork choice store.
|
|
func (s *Store) JustifiedEpoch() uint64 {
|
|
return s.justifiedEpoch
|
|
}
|
|
|
|
// FinalizedEpoch of fork choice store.
|
|
func (s *Store) FinalizedEpoch() uint64 {
|
|
return s.finalizedEpoch
|
|
}
|
|
|
|
// Nodes of fork choice store.
|
|
func (s *Store) Nodes() []*Node {
|
|
s.nodesLock.RLock()
|
|
defer s.nodesLock.RUnlock()
|
|
return s.nodes
|
|
}
|
|
|
|
// NodesIndices of fork choice store.
|
|
func (s *Store) NodesIndices() map[[32]byte]uint64 {
|
|
s.nodesLock.RLock()
|
|
defer s.nodesLock.RUnlock()
|
|
return s.nodesIndices
|
|
}
|