mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2025-01-11 21:40:05 +00:00
489 lines
15 KiB
Go
489 lines
15 KiB
Go
package forkchoice
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/ledgerwatch/erigon/cl/clparams"
|
|
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
|
|
"github.com/ledgerwatch/erigon/cl/freezer"
|
|
"github.com/ledgerwatch/erigon/cl/phase1/core/state"
|
|
state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state"
|
|
"github.com/ledgerwatch/erigon/cl/phase1/execution_client"
|
|
"github.com/ledgerwatch/erigon/cl/phase1/forkchoice/fork_graph"
|
|
"github.com/ledgerwatch/erigon/cl/pool"
|
|
"github.com/ledgerwatch/erigon/cl/transition/impl/eth2"
|
|
"golang.org/x/exp/slices"
|
|
|
|
lru "github.com/hashicorp/golang-lru/v2"
|
|
libcommon "github.com/ledgerwatch/erigon-lib/common"
|
|
"github.com/ledgerwatch/erigon-lib/common/length"
|
|
)
|
|
|
|
// Schema
|
|
/*
|
|
{
|
|
"slot": "1",
|
|
"block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
|
"parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
|
"justified_epoch": "1",
|
|
"finalized_epoch": "1",
|
|
"weight": "1",
|
|
"validity": "valid",
|
|
"execution_block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
|
"extra_data": {}
|
|
}
|
|
*/
|
|
type ForkNode struct {
|
|
Slot uint64 `json:"slot,string"`
|
|
BlockRoot libcommon.Hash `json:"block_root"`
|
|
ParentRoot libcommon.Hash `json:"parent_root"`
|
|
JustifiedEpoch uint64 `json:"justified_epoch,string"`
|
|
FinalizedEpoch uint64 `json:"finalized_epoch,string"`
|
|
Weight uint64 `json:"weight,string"`
|
|
Validity string `json:"validity"`
|
|
ExecutionBlock libcommon.Hash `json:"execution_block_hash"`
|
|
}
|
|
|
|
type checkpointComparable string
|
|
|
|
const (
|
|
checkpointsPerCache = 1024
|
|
allowedCachedStates = 8
|
|
)
|
|
|
|
type randaoDelta struct {
|
|
epoch uint64
|
|
delta libcommon.Hash
|
|
}
|
|
|
|
type finalityCheckpoints struct {
|
|
finalizedCheckpoint solid.Checkpoint
|
|
currentJustifiedCheckpoint solid.Checkpoint
|
|
previousJustifiedCheckpoint solid.Checkpoint
|
|
}
|
|
|
|
type preverifiedAppendListsSizes struct {
|
|
validatorLength uint64
|
|
historicalRootsLength uint64
|
|
historicalSummariesLength uint64
|
|
}
|
|
|
|
type ForkChoiceStore struct {
|
|
ctx context.Context
|
|
time uint64
|
|
highestSeen uint64
|
|
justifiedCheckpoint solid.Checkpoint
|
|
finalizedCheckpoint solid.Checkpoint
|
|
unrealizedJustifiedCheckpoint solid.Checkpoint
|
|
unrealizedFinalizedCheckpoint solid.Checkpoint
|
|
proposerBoostRoot libcommon.Hash
|
|
// attestations that are not yet processed
|
|
attestationSet sync.Map
|
|
// head data
|
|
headHash libcommon.Hash
|
|
headSlot uint64
|
|
genesisTime uint64
|
|
weights map[libcommon.Hash]uint64
|
|
headSet map[libcommon.Hash]struct{}
|
|
// childrens
|
|
childrens map[libcommon.Hash]childrens
|
|
|
|
// Use go map because this is actually an unordered set
|
|
equivocatingIndicies []byte
|
|
forkGraph fork_graph.ForkGraph
|
|
// I use the cache due to the convenient auto-cleanup feauture.
|
|
checkpointStates map[checkpointComparable]*checkpointState // We keep ssz snappy of it as the full beacon state is full of rendundant data.
|
|
latestMessages []LatestMessage
|
|
anchorPublicKeys []byte
|
|
// We keep track of them so that we can forkchoice with EL.
|
|
eth2Roots *lru.Cache[libcommon.Hash, libcommon.Hash] // ETH2 root -> ETH1 hash
|
|
// preverifid sizes and other data collection
|
|
preverifiedSizes *lru.Cache[libcommon.Hash, preverifiedAppendListsSizes]
|
|
finalityCheckpoints *lru.Cache[libcommon.Hash, finalityCheckpoints]
|
|
totalActiveBalances *lru.Cache[libcommon.Hash, uint64]
|
|
// Randao mixes
|
|
randaoMixesLists *lru.Cache[libcommon.Hash, solid.HashListSSZ] // limited randao mixes full list (only 16 elements)
|
|
randaoDeltas *lru.Cache[libcommon.Hash, randaoDelta] // small entry can be lots of elements.
|
|
// participation tracking
|
|
participation *lru.Cache[uint64, *solid.BitList] // epoch -> [partecipation]
|
|
|
|
mu sync.Mutex
|
|
// EL
|
|
engine execution_client.ExecutionEngine
|
|
// freezer
|
|
recorder freezer.Freezer
|
|
// operations pool
|
|
operationsPool pool.OperationsPool
|
|
beaconCfg *clparams.BeaconChainConfig
|
|
|
|
synced atomic.Bool
|
|
}
|
|
|
|
type LatestMessage struct {
|
|
Epoch uint64
|
|
Root libcommon.Hash
|
|
}
|
|
|
|
type childrens struct {
|
|
childrenHashes []libcommon.Hash
|
|
parentSlot uint64 // we keep this one for pruning
|
|
}
|
|
|
|
// NewForkChoiceStore initialize a new store from the given anchor state, either genesis or checkpoint sync state.
|
|
func NewForkChoiceStore(ctx context.Context, anchorState *state2.CachingBeaconState, engine execution_client.ExecutionEngine, recorder freezer.Freezer, operationsPool pool.OperationsPool, forkGraph fork_graph.ForkGraph) (*ForkChoiceStore, error) {
|
|
anchorRoot, err := anchorState.BlockRoot()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
anchorCheckpoint := solid.NewCheckpointFromParameters(
|
|
anchorRoot,
|
|
state2.Epoch(anchorState.BeaconState),
|
|
)
|
|
|
|
eth2Roots, err := lru.New[libcommon.Hash, libcommon.Hash](checkpointsPerCache)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
randaoMixesLists, err := lru.New[libcommon.Hash, solid.HashListSSZ](allowedCachedStates)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
randaoDeltas, err := lru.New[libcommon.Hash, randaoDelta](checkpointsPerCache)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
finalityCheckpoints, err := lru.New[libcommon.Hash, finalityCheckpoints](checkpointsPerCache)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
anchorPublicKeys := make([]byte, anchorState.ValidatorLength()*length.Bytes48)
|
|
for idx := 0; idx < anchorState.ValidatorLength(); idx++ {
|
|
pk, err := anchorState.ValidatorPublicKey(idx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
copy(anchorPublicKeys[idx*length.Bytes48:], pk[:])
|
|
}
|
|
|
|
preverifiedSizes, err := lru.New[libcommon.Hash, preverifiedAppendListsSizes](checkpointsPerCache * 10)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
preverifiedSizes.Add(anchorRoot, preverifiedAppendListsSizes{
|
|
validatorLength: uint64(anchorState.ValidatorLength()),
|
|
historicalRootsLength: anchorState.HistoricalRootsLength(),
|
|
historicalSummariesLength: anchorState.HistoricalSummariesLength(),
|
|
})
|
|
|
|
totalActiveBalances, err := lru.New[libcommon.Hash, uint64](checkpointsPerCache * 10)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
participation, err := lru.New[uint64, *solid.BitList](16)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
participation.Add(state.Epoch(anchorState.BeaconState), anchorState.CurrentEpochParticipation().Copy())
|
|
|
|
totalActiveBalances.Add(anchorRoot, anchorState.GetTotalActiveBalance())
|
|
r := solid.NewHashVector(int(anchorState.BeaconConfig().EpochsPerHistoricalVector))
|
|
anchorState.RandaoMixes().CopyTo(r)
|
|
randaoMixesLists.Add(anchorRoot, r)
|
|
headSet := make(map[libcommon.Hash]struct{})
|
|
headSet[anchorRoot] = struct{}{}
|
|
return &ForkChoiceStore{
|
|
ctx: ctx,
|
|
highestSeen: anchorState.Slot(),
|
|
time: anchorState.GenesisTime() + anchorState.BeaconConfig().SecondsPerSlot*anchorState.Slot(),
|
|
justifiedCheckpoint: anchorCheckpoint.Copy(),
|
|
finalizedCheckpoint: anchorCheckpoint.Copy(),
|
|
unrealizedJustifiedCheckpoint: anchorCheckpoint.Copy(),
|
|
unrealizedFinalizedCheckpoint: anchorCheckpoint.Copy(),
|
|
forkGraph: forkGraph,
|
|
equivocatingIndicies: make([]byte, anchorState.ValidatorLength(), anchorState.ValidatorLength()*2),
|
|
latestMessages: make([]LatestMessage, anchorState.ValidatorLength(), anchorState.ValidatorLength()*2),
|
|
checkpointStates: make(map[checkpointComparable]*checkpointState),
|
|
eth2Roots: eth2Roots,
|
|
engine: engine,
|
|
recorder: recorder,
|
|
operationsPool: operationsPool,
|
|
anchorPublicKeys: anchorPublicKeys,
|
|
genesisTime: anchorState.GenesisTime(),
|
|
beaconCfg: anchorState.BeaconConfig(),
|
|
childrens: make(map[libcommon.Hash]childrens),
|
|
preverifiedSizes: preverifiedSizes,
|
|
finalityCheckpoints: finalityCheckpoints,
|
|
totalActiveBalances: totalActiveBalances,
|
|
randaoMixesLists: randaoMixesLists,
|
|
randaoDeltas: randaoDeltas,
|
|
headSet: headSet,
|
|
weights: make(map[libcommon.Hash]uint64),
|
|
participation: participation,
|
|
}, nil
|
|
}
|
|
|
|
// Highest seen returns highest seen slot
|
|
func (f *ForkChoiceStore) HighestSeen() uint64 {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.highestSeen
|
|
}
|
|
|
|
func (f *ForkChoiceStore) children(parent libcommon.Hash) []libcommon.Hash {
|
|
children, ok := f.childrens[parent]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return children.childrenHashes
|
|
}
|
|
|
|
// updateChildren adds a new child to the parent node hash.
|
|
func (f *ForkChoiceStore) updateChildren(parentSlot uint64, parent, child libcommon.Hash) {
|
|
c, ok := f.childrens[parent]
|
|
if !ok {
|
|
c = childrens{}
|
|
}
|
|
c.parentSlot = parentSlot // can be innacurate.
|
|
if slices.Contains(c.childrenHashes, child) {
|
|
return
|
|
}
|
|
c.childrenHashes = append(c.childrenHashes, child)
|
|
f.childrens[parent] = c
|
|
}
|
|
|
|
// AdvanceHighestSeen advances the highest seen block by n and returns the new slot after the change
|
|
func (f *ForkChoiceStore) AdvanceHighestSeen(n uint64) uint64 {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.highestSeen += n
|
|
return f.highestSeen
|
|
}
|
|
|
|
// Time returns current time
|
|
func (f *ForkChoiceStore) Time() uint64 {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.time
|
|
}
|
|
|
|
// ProposerBoostRoot returns proposer boost root
|
|
func (f *ForkChoiceStore) ProposerBoostRoot() libcommon.Hash {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.proposerBoostRoot
|
|
}
|
|
|
|
// JustifiedCheckpoint returns justified checkpoint
|
|
func (f *ForkChoiceStore) JustifiedCheckpoint() solid.Checkpoint {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.justifiedCheckpoint
|
|
}
|
|
|
|
// FinalizedCheckpoint returns justified checkpoint
|
|
func (f *ForkChoiceStore) JustifiedSlot() uint64 {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.computeStartSlotAtEpoch(f.justifiedCheckpoint.Epoch())
|
|
}
|
|
|
|
// FinalizedCheckpoint returns justified checkpoint
|
|
func (f *ForkChoiceStore) FinalizedCheckpoint() solid.Checkpoint {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.finalizedCheckpoint
|
|
}
|
|
|
|
// FinalizedCheckpoint returns justified checkpoint
|
|
func (f *ForkChoiceStore) FinalizedSlot() uint64 {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.computeStartSlotAtEpoch(f.finalizedCheckpoint.Epoch()) + (f.beaconCfg.SlotsPerEpoch - 1)
|
|
}
|
|
|
|
// FinalizedCheckpoint returns justified checkpoint
|
|
func (f *ForkChoiceStore) Engine() execution_client.ExecutionEngine {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.engine
|
|
}
|
|
|
|
// FinalizedCheckpoint returns justified checkpoint
|
|
func (f *ForkChoiceStore) GetEth1Hash(eth2Root libcommon.Hash) libcommon.Hash {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
ret, _ := f.eth2Roots.Get(eth2Root)
|
|
return ret
|
|
}
|
|
|
|
// FinalizedCheckpoint returns justified checkpoint
|
|
func (f *ForkChoiceStore) AnchorSlot() uint64 {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.forkGraph.AnchorSlot()
|
|
}
|
|
|
|
func (f *ForkChoiceStore) GetStateAtBlockRoot(blockRoot libcommon.Hash, alwaysCopy bool) (*state2.CachingBeaconState, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.forkGraph.GetState(blockRoot, alwaysCopy)
|
|
}
|
|
func (f *ForkChoiceStore) GetStateAtStateRoot(stateRoot libcommon.Hash, alwaysCopy bool) (*state2.CachingBeaconState, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.forkGraph.GetState(stateRoot, alwaysCopy)
|
|
}
|
|
func (f *ForkChoiceStore) GetStateAtSlot(slot uint64, alwaysCopy bool) (*state.CachingBeaconState, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.forkGraph.GetStateAtSlot(slot, alwaysCopy)
|
|
}
|
|
|
|
func (f *ForkChoiceStore) PreverifiedValidator(blockRoot libcommon.Hash) uint64 {
|
|
if ret, ok := f.preverifiedSizes.Get(blockRoot); ok {
|
|
return ret.validatorLength
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (f *ForkChoiceStore) PreverifiedHistoricalRoots(blockRoot libcommon.Hash) uint64 {
|
|
if ret, ok := f.preverifiedSizes.Get(blockRoot); ok {
|
|
return ret.historicalRootsLength
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (f *ForkChoiceStore) PreverifiedHistoricalSummaries(blockRoot libcommon.Hash) uint64 {
|
|
if ret, ok := f.preverifiedSizes.Get(blockRoot); ok {
|
|
return ret.historicalSummariesLength
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (f *ForkChoiceStore) GetFinalityCheckpoints(blockRoot libcommon.Hash) (bool, solid.Checkpoint, solid.Checkpoint, solid.Checkpoint) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if ret, ok := f.finalityCheckpoints.Get(blockRoot); ok {
|
|
return true, ret.finalizedCheckpoint, ret.currentJustifiedCheckpoint, ret.previousJustifiedCheckpoint
|
|
}
|
|
return false, solid.Checkpoint{}, solid.Checkpoint{}, solid.Checkpoint{}
|
|
}
|
|
|
|
func (f *ForkChoiceStore) GetSyncCommittees(blockRoot libcommon.Hash) (*solid.SyncCommittee, *solid.SyncCommittee, bool) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.forkGraph.GetSyncCommittees(blockRoot)
|
|
}
|
|
|
|
func (f *ForkChoiceStore) BlockRewards(root libcommon.Hash) (*eth2.BlockRewardsCollector, bool) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.forkGraph.GetBlockRewards(root)
|
|
}
|
|
|
|
func (f *ForkChoiceStore) TotalActiveBalance(root libcommon.Hash) (uint64, bool) {
|
|
return f.totalActiveBalances.Get(root)
|
|
}
|
|
|
|
func (f *ForkChoiceStore) LowestAvaiableSlot() uint64 {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.forkGraph.LowestAvaiableSlot()
|
|
}
|
|
|
|
func (f *ForkChoiceStore) RandaoMixes(blockRoot libcommon.Hash, out solid.HashListSSZ) bool {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
relevantDeltas := map[uint64]randaoDelta{}
|
|
currentBlockRoot := blockRoot
|
|
var currentSlot uint64
|
|
for {
|
|
h, ok := f.forkGraph.GetHeader(currentBlockRoot)
|
|
if !ok {
|
|
return false
|
|
}
|
|
currentSlot = h.Slot
|
|
if f.randaoMixesLists.Contains(currentBlockRoot) {
|
|
break
|
|
}
|
|
randaoDelta, ok := f.randaoDeltas.Get(currentBlockRoot)
|
|
if !ok {
|
|
return false
|
|
}
|
|
currentBlockRoot = h.ParentRoot
|
|
if _, ok := relevantDeltas[currentSlot/f.beaconCfg.SlotsPerEpoch]; !ok {
|
|
relevantDeltas[currentSlot/f.beaconCfg.SlotsPerEpoch] = randaoDelta
|
|
}
|
|
}
|
|
randaoMixes, ok := f.randaoMixesLists.Get(currentBlockRoot)
|
|
if !ok {
|
|
return false
|
|
}
|
|
randaoMixes.CopyTo(out)
|
|
for epoch, delta := range relevantDeltas {
|
|
out.Set(int(epoch%f.beaconCfg.EpochsPerHistoricalVector), delta.delta)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (f *ForkChoiceStore) Partecipation(epoch uint64) (*solid.BitList, bool) {
|
|
return f.participation.Get(epoch)
|
|
}
|
|
|
|
func (f *ForkChoiceStore) ForkNodes() []ForkNode {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
forkNodes := make([]ForkNode, 0, len(f.weights))
|
|
for blockRoot, weight := range f.weights {
|
|
header, has := f.forkGraph.GetHeader(blockRoot)
|
|
if !has {
|
|
continue
|
|
}
|
|
justifiedCheckpoint, has := f.forkGraph.GetCurrentJustifiedCheckpoint(blockRoot)
|
|
if !has {
|
|
continue
|
|
}
|
|
finalizedCheckpoint, has := f.forkGraph.GetFinalizedCheckpoint(blockRoot)
|
|
if !has {
|
|
continue
|
|
}
|
|
blockHash, _ := f.eth2Roots.Get(blockRoot)
|
|
|
|
forkNodes = append(forkNodes, ForkNode{
|
|
Weight: weight,
|
|
BlockRoot: blockRoot,
|
|
ParentRoot: header.ParentRoot,
|
|
JustifiedEpoch: justifiedCheckpoint.Epoch(),
|
|
FinalizedEpoch: finalizedCheckpoint.Epoch(),
|
|
Slot: header.Slot,
|
|
Validity: "valid",
|
|
ExecutionBlock: blockHash,
|
|
})
|
|
}
|
|
sort.Slice(forkNodes, func(i, j int) bool {
|
|
return forkNodes[i].Slot < forkNodes[j].Slot
|
|
})
|
|
return forkNodes
|
|
}
|
|
|
|
func (f *ForkChoiceStore) Synced() bool {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.synced.Load()
|
|
}
|
|
|
|
func (f *ForkChoiceStore) SetSynced(s bool) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.synced.Store(s)
|
|
}
|