erigon-pulse/cl/phase1/forkchoice/get_head.go
2023-10-21 23:10:58 +02:00

167 lines
5.3 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()
}
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
}
// Filter all validators deemed as bad
filteredIndicies := f.filterValidatorSetForAttestationScores(justificationState, justificationState.epoch)
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.getWeight(children[0], filteredIndicies, justificationState)
for i := 1; i < len(children); i++ {
weight := f.getWeight(children[i], filteredIndicies, justificationState)
// 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.latestMessages[uint64(validatorIndex)]; !hasLatestMessage {
continue
}
if _, isUnequivocating := f.equivocatingIndicies[uint64(validatorIndex)]; isUnequivocating {
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
}