mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2025-01-05 10:32:19 +00:00
209 lines
6.6 KiB
Go
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
|
|
}
|