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

211 lines
6.6 KiB
Go

package forkchoice
import (
"context"
"sync"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
"github.com/ledgerwatch/erigon/cl/freezer"
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"
"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"
)
type checkpointComparable string
const (
checkpointsPerCache = 1024
allowedCachedStates = 8
)
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
headHash libcommon.Hash
headSlot uint64
genesisTime uint64
childrens map[libcommon.Hash]childrens
// Use go map because this is actually an unordered set
equivocatingIndicies map[uint64]struct{}
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 map[uint64]*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
mu sync.Mutex
// EL
engine execution_client.ExecutionEngine
// freezer
recorder freezer.Freezer
// operations pool
operationsPool pool.OperationsPool
beaconCfg *clparams.BeaconChainConfig
}
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
}
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[:])
}
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: map[uint64]struct{}{},
latestMessages: map[uint64]*LatestMessage{},
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),
}, 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) 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())
}
// 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()
}