mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-27 21:57:16 +00:00
5fd03f8fb0
Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>
367 lines
10 KiB
Go
367 lines
10 KiB
Go
package kv
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
|
|
"github.com/pkg/errors"
|
|
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
|
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
|
|
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
|
bolt "go.etcd.io/bbolt"
|
|
"go.opencensus.io/trace"
|
|
)
|
|
|
|
// State returns the saved state using block's signing root,
|
|
// this particular block was used to generate the state.
|
|
func (s *Store) State(ctx context.Context, blockRoot [32]byte) (*state.BeaconState, error) {
|
|
ctx, span := trace.StartSpan(ctx, "BeaconDB.State")
|
|
defer span.End()
|
|
var st *pb.BeaconState
|
|
enc, err := s.stateBytes(ctx, blockRoot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(enc) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
st, err = createState(ctx, enc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return state.InitializeFromProtoUnsafe(st)
|
|
}
|
|
|
|
// GenesisState returns the genesis state in beacon chain.
|
|
func (s *Store) GenesisState(ctx context.Context) (*state.BeaconState, error) {
|
|
ctx, span := trace.StartSpan(ctx, "BeaconDB.GenesisState")
|
|
defer span.End()
|
|
var st *pb.BeaconState
|
|
err := s.db.View(func(tx *bolt.Tx) error {
|
|
// Retrieve genesis block's signing root from blocks bucket,
|
|
// to look up what the genesis state is.
|
|
bucket := tx.Bucket(blocksBucket)
|
|
genesisBlockRoot := bucket.Get(genesisBlockRootKey)
|
|
|
|
bucket = tx.Bucket(stateBucket)
|
|
enc := bucket.Get(genesisBlockRoot)
|
|
if enc == nil {
|
|
return nil
|
|
}
|
|
|
|
var err error
|
|
st, err = createState(ctx, enc)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if st == nil {
|
|
return nil, nil
|
|
}
|
|
return state.InitializeFromProtoUnsafe(st)
|
|
}
|
|
|
|
// SaveState stores a state to the db using block's signing root which was used to generate the state.
|
|
func (s *Store) SaveState(ctx context.Context, st *state.BeaconState, blockRoot [32]byte) error {
|
|
ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveState")
|
|
defer span.End()
|
|
|
|
return s.SaveStates(ctx, []*state.BeaconState{st}, [][32]byte{blockRoot})
|
|
}
|
|
|
|
// SaveStates stores multiple states to the db using the provided corresponding roots.
|
|
func (s *Store) SaveStates(ctx context.Context, states []*state.BeaconState, blockRoots [][32]byte) error {
|
|
ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveStates")
|
|
defer span.End()
|
|
if states == nil {
|
|
return errors.New("nil state")
|
|
}
|
|
var err error
|
|
multipleEncs := make([][]byte, len(states))
|
|
for i, st := range states {
|
|
multipleEncs[i], err = encode(ctx, st.InnerStateUnsafe())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return s.db.Update(func(tx *bolt.Tx) error {
|
|
bucket := tx.Bucket(stateBucket)
|
|
for i, rt := range blockRoots {
|
|
indicesByBucket := createStateIndicesFromStateSlot(ctx, states[i].Slot())
|
|
if err := updateValueForIndices(ctx, indicesByBucket, rt[:], tx); err != nil {
|
|
return errors.Wrap(err, "could not update DB indices")
|
|
}
|
|
if err := bucket.Put(rt[:], multipleEncs[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// HasState checks if a state by root exists in the db.
|
|
func (s *Store) HasState(ctx context.Context, blockRoot [32]byte) bool {
|
|
ctx, span := trace.StartSpan(ctx, "BeaconDB.HasState")
|
|
defer span.End()
|
|
enc, err := s.stateBytes(ctx, blockRoot)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return len(enc) > 0
|
|
}
|
|
|
|
// DeleteState by block root.
|
|
func (s *Store) DeleteState(ctx context.Context, blockRoot [32]byte) error {
|
|
ctx, span := trace.StartSpan(ctx, "BeaconDB.DeleteState")
|
|
defer span.End()
|
|
|
|
return s.db.Update(func(tx *bolt.Tx) error {
|
|
bkt := tx.Bucket(blocksBucket)
|
|
genesisBlockRoot := bkt.Get(genesisBlockRootKey)
|
|
|
|
bkt = tx.Bucket(checkpointBucket)
|
|
enc := bkt.Get(finalizedCheckpointKey)
|
|
checkpoint := ðpb.Checkpoint{}
|
|
if enc == nil {
|
|
checkpoint = ðpb.Checkpoint{Root: genesisBlockRoot}
|
|
} else if err := decode(ctx, enc, checkpoint); err != nil {
|
|
return err
|
|
}
|
|
|
|
blockBkt := tx.Bucket(blocksBucket)
|
|
headBlkRoot := blockBkt.Get(headBlockRootKey)
|
|
bkt = tx.Bucket(stateBucket)
|
|
// Safe guard against deleting genesis, finalized, head state.
|
|
if bytes.Equal(blockRoot[:], checkpoint.Root) || bytes.Equal(blockRoot[:], genesisBlockRoot) || bytes.Equal(blockRoot[:], headBlkRoot) {
|
|
return errors.New("cannot delete genesis, finalized, or head state")
|
|
}
|
|
|
|
slot, err := slotByBlockRoot(ctx, tx, blockRoot[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
indicesByBucket := createStateIndicesFromStateSlot(ctx, slot)
|
|
if err := deleteValueForIndices(ctx, indicesByBucket, blockRoot[:], tx); err != nil {
|
|
return errors.Wrap(err, "could not delete root for DB indices")
|
|
}
|
|
|
|
return bkt.Delete(blockRoot[:])
|
|
})
|
|
}
|
|
|
|
// DeleteStates by block roots.
|
|
func (s *Store) DeleteStates(ctx context.Context, blockRoots [][32]byte) error {
|
|
ctx, span := trace.StartSpan(ctx, "BeaconDB.DeleteStates")
|
|
defer span.End()
|
|
|
|
for _, r := range blockRoots {
|
|
if err := s.DeleteState(ctx, r); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// creates state from marshaled proto state bytes.
|
|
func createState(ctx context.Context, enc []byte) (*pb.BeaconState, error) {
|
|
protoState := &pb.BeaconState{}
|
|
if err := decode(ctx, enc, protoState); err != nil {
|
|
return nil, errors.Wrap(err, "failed to unmarshal encoding")
|
|
}
|
|
return protoState, nil
|
|
}
|
|
|
|
// HasState checks if a state by root exists in the db.
|
|
func (s *Store) stateBytes(ctx context.Context, blockRoot [32]byte) ([]byte, error) {
|
|
ctx, span := trace.StartSpan(ctx, "BeaconDB.stateBytes")
|
|
defer span.End()
|
|
var dst []byte
|
|
err := s.db.View(func(tx *bolt.Tx) error {
|
|
bkt := tx.Bucket(stateBucket)
|
|
dst = bkt.Get(blockRoot[:])
|
|
return nil
|
|
})
|
|
return dst, err
|
|
}
|
|
|
|
// slotByBlockRoot retrieves the corresponding slot of the input block root.
|
|
func slotByBlockRoot(ctx context.Context, tx *bolt.Tx, blockRoot []byte) (uint64, error) {
|
|
ctx, span := trace.StartSpan(ctx, "BeaconDB.slotByBlockRoot")
|
|
defer span.End()
|
|
|
|
bkt := tx.Bucket(stateSummaryBucket)
|
|
enc := bkt.Get(blockRoot)
|
|
|
|
if enc == nil {
|
|
// Fall back to check the block.
|
|
bkt := tx.Bucket(blocksBucket)
|
|
enc := bkt.Get(blockRoot)
|
|
|
|
if enc == nil {
|
|
// Fallback and check the state.
|
|
bkt = tx.Bucket(stateBucket)
|
|
enc = bkt.Get(blockRoot)
|
|
if enc == nil {
|
|
return 0, errors.New("state enc can't be nil")
|
|
}
|
|
s, err := createState(ctx, enc)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if s == nil {
|
|
return 0, errors.New("state can't be nil")
|
|
}
|
|
return s.Slot, nil
|
|
}
|
|
b := ðpb.SignedBeaconBlock{}
|
|
err := decode(ctx, enc, b)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if b.Block == nil {
|
|
return 0, errors.New("block can't be nil")
|
|
}
|
|
return b.Block.Slot, nil
|
|
}
|
|
stateSummary := &pb.StateSummary{}
|
|
if err := decode(ctx, enc, stateSummary); err != nil {
|
|
return 0, err
|
|
}
|
|
return stateSummary.Slot, nil
|
|
}
|
|
|
|
// HighestSlotStatesBelow returns the states with the highest slot below the input slot
|
|
// from the db. Ideally there should just be one state per slot, but given validator
|
|
// can double propose, a single slot could have multiple block roots and
|
|
// results states. This returns a list of states.
|
|
func (s *Store) HighestSlotStatesBelow(ctx context.Context, slot uint64) ([]*state.BeaconState, error) {
|
|
ctx, span := trace.StartSpan(ctx, "BeaconDB.HighestSlotStatesBelow")
|
|
defer span.End()
|
|
|
|
var best []byte
|
|
if err := s.db.View(func(tx *bolt.Tx) error {
|
|
bkt := tx.Bucket(stateSlotIndicesBucket)
|
|
c := bkt.Cursor()
|
|
for s, root := c.First(); s != nil; s, root = c.Next() {
|
|
if ctx.Err() != nil {
|
|
return ctx.Err()
|
|
}
|
|
key := bytesutil.BytesToUint64BigEndian(s)
|
|
if root == nil {
|
|
continue
|
|
}
|
|
if key >= slot {
|
|
break
|
|
}
|
|
best = root
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var st *state.BeaconState
|
|
var err error
|
|
if best != nil {
|
|
st, err = s.State(ctx, bytesutil.ToBytes32(best))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if st == nil {
|
|
st, err = s.GenesisState(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return []*state.BeaconState{st}, nil
|
|
}
|
|
|
|
// createBlockIndicesFromBlock takes in a beacon block and returns
|
|
// a map of bolt DB index buckets corresponding to each particular key for indices for
|
|
// data, such as (shard indices bucket -> shard 5).
|
|
func createStateIndicesFromStateSlot(ctx context.Context, slot uint64) map[string][]byte {
|
|
ctx, span := trace.StartSpan(ctx, "BeaconDB.createStateIndicesFromState")
|
|
defer span.End()
|
|
indicesByBucket := make(map[string][]byte)
|
|
// Every index has a unique bucket for fast, binary-search
|
|
// range scans for filtering across keys.
|
|
buckets := [][]byte{
|
|
stateSlotIndicesBucket,
|
|
}
|
|
|
|
indices := [][]byte{
|
|
bytesutil.Uint64ToBytesBigEndian(slot),
|
|
}
|
|
for i := 0; i < len(buckets); i++ {
|
|
indicesByBucket[string(buckets[i])] = indices[i]
|
|
}
|
|
return indicesByBucket
|
|
}
|
|
|
|
// CleanUpDirtyStates removes states in DB that falls to under archived point interval rules.
|
|
// Only following states would be kept:
|
|
// 1.) state_slot % archived_interval == 0. (e.g. archived_interval=2048, states with slot 2048, 4096... etc)
|
|
// 2.) archived_interval - archived_interval/3 < state_slot % archived_interval
|
|
// (e.g. archived_interval=2048, states with slots after 1365).
|
|
// This is to tolerate skip slots. Not every state lays on the boundary.
|
|
// 3.) state with current finalized root
|
|
// 4.) unfinalized States
|
|
func (s *Store) CleanUpDirtyStates(ctx context.Context, slotsPerArchivedPoint uint64) error {
|
|
ctx, span := trace.StartSpan(ctx, "BeaconDB. CleanUpDirtyStates")
|
|
defer span.End()
|
|
|
|
f, err := s.FinalizedCheckpoint(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
finalizedSlot, err := helpers.StartSlot(f.Epoch)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
deletedRoots := make([][32]byte, 0)
|
|
|
|
err = s.db.View(func(tx *bolt.Tx) error {
|
|
bkt := tx.Bucket(stateSlotIndicesBucket)
|
|
return bkt.ForEach(func(k, v []byte) error {
|
|
if ctx.Err() != nil {
|
|
return ctx.Err()
|
|
}
|
|
|
|
finalizedChkpt := bytesutil.ToBytes32(f.Root) == bytesutil.ToBytes32(v)
|
|
slot := bytesutil.BytesToUint64BigEndian(k)
|
|
mod := slot % slotsPerArchivedPoint
|
|
nonFinalized := slot > finalizedSlot
|
|
|
|
// The following conditions cover 1, 2, 3 and 4 above.
|
|
if mod != 0 && mod <= slotsPerArchivedPoint-slotsPerArchivedPoint/3 && !finalizedChkpt && !nonFinalized {
|
|
deletedRoots = append(deletedRoots, bytesutil.ToBytes32(v))
|
|
}
|
|
return nil
|
|
})
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Length of to be deleted roots is 0. Nothing to do.
|
|
if len(deletedRoots) == 0 {
|
|
return nil
|
|
}
|
|
|
|
log.WithField("count", len(deletedRoots)).Info("Cleaning up dirty states")
|
|
if err := s.DeleteStates(ctx, deletedRoots); err != nil {
|
|
return err
|
|
}
|
|
|
|
return err
|
|
}
|