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 uint64, finalizedEpoch uint64, finalizedRoot [32]byte) *ForkChoice { s := &Store{ justifiedEpoch: justifiedEpoch, finalizedEpoch: finalizedEpoch, finalizedRoot: finalizedRoot, nodes: make([]*Node, 0), nodeIndices: make(map[[32]byte]uint64), 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, finalizedEpoch uint64, justifiedRoot [32]byte, justifiedStateBalances []uint64, justifiedEpoch uint64) ([32]byte, error) { ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.Head") defer span.End() calledHeadCount.Inc() newBalances := justifiedStateBalances deltas, newVotes, err := computeDeltas(ctx, f.store.nodeIndices, 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() 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 [32]byte, parentRoot [32]byte, justifiedEpoch uint64, finalizedEpoch uint64) error { ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.ProcessBlock") defer span.End() return f.store.insert(ctx, slot, blockRoot, parentRoot, 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 { cpy := make([]*Node, len(f.store.nodes)) copy(cpy, f.store.nodes) return cpy }