mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-25 21:07:18 +00:00
6eb006175a
* capture missing events needed for eth2 apis * handle underflow * no hash tree roots * gazelle * static * race tests passing * simplify * correctness and abstracting method for forwarding to state feed * genesis root * add in slotutil
363 lines
12 KiB
Go
363 lines
12 KiB
Go
package blockchain
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/pkg/errors"
|
|
types "github.com/prysmaticlabs/eth2-types"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
|
|
statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/protoarray"
|
|
iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
|
|
ethpbv1 "github.com/prysmaticlabs/prysm/proto/eth/v1"
|
|
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
|
"github.com/prysmaticlabs/prysm/shared/interfaces"
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
|
"github.com/prysmaticlabs/prysm/shared/slotutil"
|
|
"github.com/sirupsen/logrus"
|
|
"go.opencensus.io/trace"
|
|
)
|
|
|
|
// This defines the current chain service's view of head.
|
|
type head struct {
|
|
slot types.Slot // current head slot.
|
|
root [32]byte // current head root.
|
|
block interfaces.SignedBeaconBlock // current head block.
|
|
state iface.BeaconState // current head state.
|
|
}
|
|
|
|
// Determined the head from the fork choice service and saves its new data
|
|
// (head root, head block, and head state) to the local service cache.
|
|
func (s *Service) updateHead(ctx context.Context, balances []uint64) error {
|
|
ctx, span := trace.StartSpan(ctx, "blockChain.updateHead")
|
|
defer span.End()
|
|
|
|
// To get the proper head update, a node first checks its best justified
|
|
// can become justified. This is designed to prevent bounce attack and
|
|
// ensure head gets its best justified info.
|
|
if s.bestJustifiedCheckpt.Epoch > s.justifiedCheckpt.Epoch {
|
|
s.justifiedCheckpt = s.bestJustifiedCheckpt
|
|
if err := s.cacheJustifiedStateBalances(ctx, bytesutil.ToBytes32(s.justifiedCheckpt.Root)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Get head from the fork choice service.
|
|
f := s.finalizedCheckpt
|
|
j := s.justifiedCheckpt
|
|
// To get head before the first justified epoch, the fork choice will start with genesis root
|
|
// instead of zero hashes.
|
|
headStartRoot := bytesutil.ToBytes32(j.Root)
|
|
if headStartRoot == params.BeaconConfig().ZeroHash {
|
|
headStartRoot = s.genesisRoot
|
|
}
|
|
|
|
// In order to process head, fork choice store requires justified info.
|
|
// If the fork choice store is missing justified block info, a node should
|
|
// re-initiate fork choice store using the latest justified info.
|
|
// This recovers a fatal condition and should not happen in run time.
|
|
if !s.cfg.ForkChoiceStore.HasNode(headStartRoot) {
|
|
jb, err := s.cfg.BeaconDB.Block(ctx, headStartRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.cfg.ForkChoiceStore = protoarray.New(j.Epoch, f.Epoch, bytesutil.ToBytes32(f.Root))
|
|
if err := s.insertBlockToForkChoiceStore(ctx, jb.Block(), headStartRoot, f, j); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
headRoot, err := s.cfg.ForkChoiceStore.Head(ctx, j.Epoch, headStartRoot, balances, f.Epoch)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Save head to the local service cache.
|
|
return s.saveHead(ctx, headRoot)
|
|
}
|
|
|
|
// This saves head info to the local service cache, it also saves the
|
|
// new head root to the DB.
|
|
func (s *Service) saveHead(ctx context.Context, headRoot [32]byte) error {
|
|
ctx, span := trace.StartSpan(ctx, "blockChain.saveHead")
|
|
defer span.End()
|
|
|
|
// Do nothing if head hasn't changed.
|
|
r, err := s.HeadRoot(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if headRoot == bytesutil.ToBytes32(r) {
|
|
return nil
|
|
}
|
|
|
|
// If the head state is not available, just return nil.
|
|
// There's nothing to cache
|
|
if !s.cfg.BeaconDB.HasStateSummary(ctx, headRoot) {
|
|
return nil
|
|
}
|
|
|
|
// Get the new head block from DB.
|
|
newHeadBlock, err := s.cfg.BeaconDB.Block(ctx, headRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if newHeadBlock == nil || newHeadBlock.IsNil() || newHeadBlock.Block().IsNil() {
|
|
return errors.New("cannot save nil head block")
|
|
}
|
|
|
|
// Get the new head state from cached state or DB.
|
|
newHeadState, err := s.cfg.StateGen.StateByRoot(ctx, headRoot)
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not retrieve head state in DB")
|
|
}
|
|
if newHeadState == nil || newHeadState.IsNil() {
|
|
return errors.New("cannot save nil head state")
|
|
}
|
|
|
|
// A chain re-org occurred, so we fire an event notifying the rest of the services.
|
|
headSlot := s.HeadSlot()
|
|
newHeadSlot := newHeadBlock.Block().Slot()
|
|
oldHeadRoot := s.headRoot()
|
|
oldStateRoot := s.headBlock().Block().StateRoot()
|
|
newStateRoot := newHeadBlock.Block().StateRoot()
|
|
if bytesutil.ToBytes32(newHeadBlock.Block().ParentRoot()) != bytesutil.ToBytes32(r) {
|
|
log.WithFields(logrus.Fields{
|
|
"newSlot": fmt.Sprintf("%d", newHeadSlot),
|
|
"oldSlot": fmt.Sprintf("%d", headSlot),
|
|
}).Debug("Chain reorg occurred")
|
|
absoluteSlotDifference := slotutil.AbsoluteValueSlotDifference(newHeadSlot, headSlot)
|
|
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
|
|
Type: statefeed.Reorg,
|
|
Data: ðpbv1.EventChainReorg{
|
|
Slot: newHeadSlot,
|
|
Depth: absoluteSlotDifference,
|
|
OldHeadBlock: oldHeadRoot[:],
|
|
NewHeadBlock: headRoot[:],
|
|
OldHeadState: oldStateRoot,
|
|
NewHeadState: newStateRoot,
|
|
Epoch: helpers.SlotToEpoch(newHeadSlot),
|
|
},
|
|
})
|
|
|
|
reorgCount.Inc()
|
|
}
|
|
|
|
// Cache the new head info.
|
|
s.setHead(headRoot, newHeadBlock, newHeadState)
|
|
|
|
// Save the new head root to DB.
|
|
if err := s.cfg.BeaconDB.SaveHeadBlockRoot(ctx, headRoot); err != nil {
|
|
return errors.Wrap(err, "could not save head root in DB")
|
|
}
|
|
|
|
// Forward an event capturing a new chain head over a common event feed
|
|
// done in a goroutine to avoid blocking the critical runtime main routine.
|
|
go func() {
|
|
if err := s.notifyNewHeadEvent(newHeadSlot, newHeadState, newStateRoot, headRoot[:]); err != nil {
|
|
log.WithError(err).Error("Could not notify event feed of new chain head")
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// This gets called to update canonical root mapping. It does not save head block
|
|
// root in DB. With the inception of initial-sync-cache-state flag, it uses finalized
|
|
// check point as anchors to resume sync therefore head is no longer needed to be saved on per slot basis.
|
|
func (s *Service) saveHeadNoDB(ctx context.Context, b interfaces.SignedBeaconBlock, r [32]byte, hs iface.BeaconState) error {
|
|
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
|
|
return err
|
|
}
|
|
cachedHeadRoot, err := s.HeadRoot(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not get head root from cache")
|
|
}
|
|
if bytes.Equal(r[:], cachedHeadRoot) {
|
|
return nil
|
|
}
|
|
|
|
s.setHeadInitialSync(r, b.Copy(), hs)
|
|
return nil
|
|
}
|
|
|
|
// This sets head view object which is used to track the head slot, root, block and state.
|
|
func (s *Service) setHead(root [32]byte, block interfaces.SignedBeaconBlock, state iface.BeaconState) {
|
|
s.headLock.Lock()
|
|
defer s.headLock.Unlock()
|
|
|
|
// This does a full copy of the block and state.
|
|
s.head = &head{
|
|
slot: block.Block().Slot(),
|
|
root: root,
|
|
block: block.Copy(),
|
|
state: state.Copy(),
|
|
}
|
|
}
|
|
|
|
// This sets head view object which is used to track the head slot, root, block and state. The method
|
|
// assumes that state being passed into the method will not be modified by any other alternate
|
|
// caller which holds the state's reference.
|
|
func (s *Service) setHeadInitialSync(root [32]byte, block interfaces.SignedBeaconBlock, state iface.BeaconState) {
|
|
s.headLock.Lock()
|
|
defer s.headLock.Unlock()
|
|
|
|
// This does a full copy of the block only.
|
|
s.head = &head{
|
|
slot: block.Block().Slot(),
|
|
root: root,
|
|
block: block.Copy(),
|
|
state: state,
|
|
}
|
|
}
|
|
|
|
// This returns the head slot.
|
|
// This is a lock free version.
|
|
func (s *Service) headSlot() types.Slot {
|
|
return s.head.slot
|
|
}
|
|
|
|
// This returns the head root.
|
|
// It does a full copy on head root for immutability.
|
|
// This is a lock free version.
|
|
func (s *Service) headRoot() [32]byte {
|
|
if s.head == nil {
|
|
return params.BeaconConfig().ZeroHash
|
|
}
|
|
|
|
return s.head.root
|
|
}
|
|
|
|
// This returns the head block.
|
|
// It does a full copy on head block for immutability.
|
|
// This is a lock free version.
|
|
func (s *Service) headBlock() interfaces.SignedBeaconBlock {
|
|
return s.head.block.Copy()
|
|
}
|
|
|
|
// This returns the head state.
|
|
// It does a full copy on head state for immutability.
|
|
// This is a lock free version.
|
|
func (s *Service) headState(ctx context.Context) iface.BeaconState {
|
|
ctx, span := trace.StartSpan(ctx, "blockChain.headState")
|
|
defer span.End()
|
|
|
|
return s.head.state.Copy()
|
|
}
|
|
|
|
// This returns the genesis validator root of the head state.
|
|
// This is a lock free version.
|
|
func (s *Service) headGenesisValidatorRoot() [32]byte {
|
|
return bytesutil.ToBytes32(s.head.state.GenesisValidatorRoot())
|
|
}
|
|
|
|
// Returns true if head state exists.
|
|
// This is the lock free version.
|
|
func (s *Service) hasHeadState() bool {
|
|
return s.head != nil && s.head.state != nil
|
|
}
|
|
|
|
// This caches justified state balances to be used for fork choice.
|
|
func (s *Service) cacheJustifiedStateBalances(ctx context.Context, justifiedRoot [32]byte) error {
|
|
if err := s.cfg.BeaconDB.SaveBlocks(ctx, s.getInitSyncBlocks()); err != nil {
|
|
return err
|
|
}
|
|
|
|
s.clearInitSyncBlocks()
|
|
|
|
var justifiedState iface.BeaconState
|
|
var err error
|
|
if justifiedRoot == s.genesisRoot {
|
|
justifiedState, err = s.cfg.BeaconDB.GenesisState(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
justifiedState, err = s.cfg.StateGen.StateByRoot(ctx, justifiedRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if justifiedState == nil || justifiedState.IsNil() {
|
|
return errors.New("justified state can't be nil")
|
|
}
|
|
|
|
epoch := helpers.CurrentEpoch(justifiedState)
|
|
|
|
justifiedBalances := make([]uint64, justifiedState.NumValidators())
|
|
if err := justifiedState.ReadFromEveryValidator(func(idx int, val iface.ReadOnlyValidator) error {
|
|
if helpers.IsActiveValidatorUsingTrie(val, epoch) {
|
|
justifiedBalances[idx] = val.EffectiveBalance()
|
|
} else {
|
|
justifiedBalances[idx] = 0
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
s.justifiedBalancesLock.Lock()
|
|
defer s.justifiedBalancesLock.Unlock()
|
|
s.justifiedBalances = justifiedBalances
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) getJustifiedBalances() []uint64 {
|
|
s.justifiedBalancesLock.RLock()
|
|
defer s.justifiedBalancesLock.RUnlock()
|
|
return s.justifiedBalances
|
|
}
|
|
|
|
// Notifies a common event feed of a new chain head event. Called right after a new
|
|
// chain head is determined, set, and saved to disk.
|
|
func (s *Service) notifyNewHeadEvent(
|
|
newHeadSlot types.Slot,
|
|
newHeadState iface.BeaconState,
|
|
newHeadStateRoot,
|
|
newHeadRoot []byte,
|
|
) error {
|
|
previousDutyDependentRoot := s.genesisRoot[:]
|
|
currentDutyDependentRoot := s.genesisRoot[:]
|
|
|
|
var previousDutyEpoch types.Epoch
|
|
currentDutyEpoch := helpers.SlotToEpoch(newHeadSlot)
|
|
if currentDutyEpoch > 0 {
|
|
previousDutyEpoch = currentDutyEpoch.Sub(1)
|
|
}
|
|
currentDutySlot, err := helpers.StartSlot(currentDutyEpoch)
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not get duty slot")
|
|
}
|
|
previousDutySlot, err := helpers.StartSlot(previousDutyEpoch)
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not get duty slot")
|
|
}
|
|
if currentDutySlot > 0 {
|
|
currentDutyDependentRoot, err = helpers.BlockRootAtSlot(newHeadState, currentDutySlot-1)
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not get duty dependent root")
|
|
}
|
|
}
|
|
if previousDutySlot > 0 {
|
|
previousDutyDependentRoot, err = helpers.BlockRootAtSlot(newHeadState, previousDutySlot-1)
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not get duty dependent root")
|
|
}
|
|
}
|
|
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
|
|
Type: statefeed.NewHead,
|
|
Data: ðpbv1.EventHead{
|
|
Slot: newHeadSlot,
|
|
Block: newHeadRoot,
|
|
State: newHeadStateRoot,
|
|
EpochTransition: helpers.IsEpochEnd(newHeadSlot),
|
|
PreviousDutyDependentRoot: previousDutyDependentRoot,
|
|
CurrentDutyDependentRoot: currentDutyDependentRoot,
|
|
},
|
|
})
|
|
return nil
|
|
}
|