erigon-pulse/cl/phase1/forkchoice/get_head.go
2024-01-06 21:49:23 +01:00

209 lines
6.6 KiB
Go

package forkchoice
import (
"bytes"
"fmt"
"sort"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/cl/cltypes"
)
// GetHead fetches the current head.
func (f *ForkChoiceStore) GetHead() (libcommon.Hash, uint64, error) {
f.mu.Lock()
defer f.mu.Unlock()
return f.getHead()
}
// accountWeights updates the weights of the validators, given the vote and given an head leaf.
func (f *ForkChoiceStore) accountWeights(votes, weights map[libcommon.Hash]uint64, justifedRoot, leaf libcommon.Hash) {
curr := leaf
accumulated := uint64(0)
for curr != justifedRoot {
accumulated += votes[curr]
votes[curr] = 0 // make sure we don't double count
weights[curr] += accumulated
header, has := f.forkGraph.GetHeader(curr)
if !has {
return
}
curr = header.ParentRoot
}
return
}
func (f *ForkChoiceStore) getHead() (libcommon.Hash, uint64, error) {
if f.headHash != (libcommon.Hash{}) {
return f.headHash, f.headSlot, nil
}
// Retrieve att
f.headHash = f.justifiedCheckpoint.BlockRoot()
blocks := f.getFilteredBlockTree(f.headHash)
// See which validators can be used for attestation score
justificationState, err := f.getCheckpointState(f.justifiedCheckpoint)
if err != nil {
return libcommon.Hash{}, 0, err
}
// Do a simple scan to determine the fork votes.
votes := make(map[libcommon.Hash]uint64)
for validatorIndex, message := range f.latestMessages {
if message == (LatestMessage{}) {
continue
}
if !readFromBitset(justificationState.actives, validatorIndex) || readFromBitset(justificationState.slasheds, validatorIndex) {
continue
}
if _, hasLatestMessage := f.getLatestMessage(uint64(validatorIndex)); !hasLatestMessage {
continue
}
if f.isUnequivocating(uint64(validatorIndex)) {
continue
}
votes[message.Root] += justificationState.balances[validatorIndex]
}
if f.proposerBoostRoot != (libcommon.Hash{}) {
boost := justificationState.activeBalance / justificationState.beaconConfig.SlotsPerEpoch
votes[f.proposerBoostRoot] += (boost * justificationState.beaconConfig.ProposerScoreBoost) / 100
}
// Account for weights on each head fork
f.weights = make(map[libcommon.Hash]uint64)
for head := range f.headSet {
f.accountWeights(votes, f.weights, f.justifiedCheckpoint.BlockRoot(), head)
}
for {
// Filter out current head children.
unfilteredChildren := f.children(f.headHash)
children := []libcommon.Hash{}
for _, child := range unfilteredChildren {
if _, ok := blocks[child]; ok {
children = append(children, child)
}
}
// Stop if we dont have any more children
if len(children) == 0 {
header, hasHeader := f.forkGraph.GetHeader(f.headHash)
if !hasHeader {
return libcommon.Hash{}, 0, fmt.Errorf("no slot for head is stored")
}
f.headSlot = header.Slot
return f.headHash, f.headSlot, nil
}
// Average case scenario.
if len(children) == 1 {
f.headHash = children[0]
continue
}
// Sort children by lexigographical order
sort.Slice(children, func(i, j int) bool {
childA := children[i]
childB := children[j]
return bytes.Compare(childA[:], childB[:]) < 0
})
// After sorting is done determine best fit.
f.headHash = children[0]
maxWeight := f.weights[children[0]]
for i := 1; i < len(children); i++ {
weight := f.weights[children[i]]
// Lexicographical order is king.
if weight >= maxWeight {
f.headHash = children[i]
maxWeight = weight
}
}
}
}
// filterValidatorSetForAttestationScores preliminarly filter the validator set obliging to consensus rules.
func (f *ForkChoiceStore) filterValidatorSetForAttestationScores(c *checkpointState, epoch uint64) []uint64 {
filtered := make([]uint64, 0, c.validatorSetSize)
for validatorIndex := 0; validatorIndex < c.validatorSetSize; validatorIndex++ {
if !readFromBitset(c.actives, validatorIndex) || readFromBitset(c.slasheds, validatorIndex) {
continue
}
if _, hasLatestMessage := f.getLatestMessage(uint64(validatorIndex)); !hasLatestMessage {
continue
}
if f.isUnequivocating(uint64(validatorIndex)) {
continue
}
filtered = append(filtered, uint64(validatorIndex))
}
return filtered
}
// getWeight computes weight in head decision of canonical chain.
func (f *ForkChoiceStore) getWeight(root libcommon.Hash, indicies []uint64, state *checkpointState) uint64 {
header, has := f.forkGraph.GetHeader(root)
if !has {
return 0
}
// Compute attestation score
var attestationScore uint64
for _, validatorIndex := range indicies {
if f.Ancestor(f.latestMessages[validatorIndex].Root, header.Slot) != root {
continue
}
attestationScore += state.balances[validatorIndex]
}
if f.proposerBoostRoot == (libcommon.Hash{}) {
return attestationScore
}
// Boost is applied if root is an ancestor of proposer_boost_root
if f.Ancestor(f.proposerBoostRoot, header.Slot) == root {
committeeWeight := state.activeBalance / state.beaconConfig.SlotsPerEpoch
attestationScore += (committeeWeight * state.beaconConfig.ProposerScoreBoost) / 100
}
return attestationScore
}
// getFilteredBlockTree filters out dumb blocks.
func (f *ForkChoiceStore) getFilteredBlockTree(base libcommon.Hash) map[libcommon.Hash]*cltypes.BeaconBlockHeader {
blocks := make(map[libcommon.Hash]*cltypes.BeaconBlockHeader)
f.getFilterBlockTree(base, blocks)
return blocks
}
// getFilterBlockTree recursively traverses the block tree to identify viable blocks.
// It takes a block hash and a map of viable blocks as input parameters, and returns a boolean value indicating
// whether the current block is viable.
func (f *ForkChoiceStore) getFilterBlockTree(blockRoot libcommon.Hash, blocks map[libcommon.Hash]*cltypes.BeaconBlockHeader) bool {
header, has := f.forkGraph.GetHeader(blockRoot)
if !has {
return false
}
children := f.children(blockRoot)
// If there are children iterate down recursively and see which branches are viable.
if len(children) > 0 {
isAnyViable := false
for _, child := range children {
if f.getFilterBlockTree(child, blocks) {
isAnyViable = true
}
}
if isAnyViable {
blocks[blockRoot] = header
}
return isAnyViable
}
currentJustifiedCheckpoint, has := f.forkGraph.GetCurrentJustifiedCheckpoint(blockRoot)
if !has {
return false
}
finalizedJustifiedCheckpoint, has := f.forkGraph.GetFinalizedCheckpoint(blockRoot)
if !has {
return false
}
genesisEpoch := f.beaconCfg.GenesisEpoch
if (f.justifiedCheckpoint.Epoch() == genesisEpoch || currentJustifiedCheckpoint.Equal(f.justifiedCheckpoint)) &&
(f.finalizedCheckpoint.Epoch() == genesisEpoch || finalizedJustifiedCheckpoint.Equal(f.finalizedCheckpoint)) {
blocks[blockRoot] = header
return true
}
return false
}