2020-02-16 23:28:20 +00:00
|
|
|
package stategen
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-02-26 16:40:33 +00:00
|
|
|
"fmt"
|
2020-02-16 23:28:20 +00:00
|
|
|
|
2020-02-26 16:40:33 +00:00
|
|
|
"github.com/pkg/errors"
|
2020-02-16 23:28:20 +00:00
|
|
|
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
|
|
|
transition "github.com/prysmaticlabs/prysm/beacon-chain/core/state"
|
|
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/db/filters"
|
2020-02-26 16:40:33 +00:00
|
|
|
stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state"
|
2020-02-16 23:28:20 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
2020-02-26 16:40:33 +00:00
|
|
|
"go.opencensus.io/trace"
|
2020-02-16 23:28:20 +00:00
|
|
|
)
|
|
|
|
|
2020-02-18 05:54:56 +00:00
|
|
|
// ReplayBlocks replays the input blocks on the input state until the target slot is reached.
|
2020-10-10 03:54:31 +00:00
|
|
|
func (s *State) ReplayBlocks(ctx context.Context, state *stateTrie.BeaconState, signed []*ethpb.SignedBeaconBlock, targetSlot uint64) (*stateTrie.BeaconState, error) {
|
2020-03-16 19:07:07 +00:00
|
|
|
ctx, span := trace.StartSpan(ctx, "stateGen.ReplayBlocks")
|
|
|
|
defer span.End()
|
|
|
|
|
2020-02-16 23:28:20 +00:00
|
|
|
var err error
|
|
|
|
// The input block list is sorted in decreasing slots order.
|
2020-02-17 05:10:23 +00:00
|
|
|
if len(signed) > 0 {
|
|
|
|
for i := len(signed) - 1; i >= 0; i-- {
|
2020-10-21 17:06:48 +00:00
|
|
|
if ctx.Err() != nil {
|
|
|
|
return nil, ctx.Err()
|
|
|
|
}
|
2020-03-16 19:07:07 +00:00
|
|
|
if state.Slot() >= targetSlot {
|
|
|
|
break
|
|
|
|
}
|
2020-09-23 21:39:16 +00:00
|
|
|
// A node shouldn't process the block if the block slot is lower than the state slot.
|
|
|
|
if state.Slot() >= signed[i].Block.Slot {
|
|
|
|
continue
|
|
|
|
}
|
2020-10-14 16:18:40 +00:00
|
|
|
state, err = executeStateTransitionStateGen(ctx, state, signed[i])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2020-02-16 23:28:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there is skip slots at the end.
|
2020-03-16 19:07:07 +00:00
|
|
|
if targetSlot > state.Slot() {
|
2020-10-14 16:18:40 +00:00
|
|
|
state, err = processSlotsStateGen(ctx, state, targetSlot)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2020-02-26 16:40:33 +00:00
|
|
|
}
|
2020-02-16 23:28:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return state, nil
|
|
|
|
}
|
|
|
|
|
2020-02-18 05:54:56 +00:00
|
|
|
// LoadBlocks loads the blocks between start slot and end slot by recursively fetching from end block root.
|
2020-02-16 23:28:20 +00:00
|
|
|
// The Blocks are returned in slot-descending order.
|
2020-10-12 15:43:19 +00:00
|
|
|
func (s *State) LoadBlocks(ctx context.Context, startSlot, endSlot uint64, endBlockRoot [32]byte) ([]*ethpb.SignedBeaconBlock, error) {
|
2020-10-23 04:23:15 +00:00
|
|
|
// Nothing to load for invalid range.
|
|
|
|
// TODO(#7620): Return error for invalid range.
|
|
|
|
if endSlot < startSlot {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2020-02-16 23:28:20 +00:00
|
|
|
filter := filters.NewFilter().SetStartSlot(startSlot).SetEndSlot(endSlot)
|
2020-10-18 16:39:27 +00:00
|
|
|
blocks, blockRoots, err := s.beaconDB.Blocks(ctx, filter)
|
2020-02-16 23:28:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// The retrieved blocks and block roots have to be in the same length given same filter.
|
|
|
|
if len(blocks) != len(blockRoots) {
|
|
|
|
return nil, errors.New("length of blocks and roots don't match")
|
|
|
|
}
|
2020-03-08 06:24:57 +00:00
|
|
|
// Return early if there's no block given the input.
|
|
|
|
length := len(blocks)
|
|
|
|
if length == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2020-02-16 23:28:20 +00:00
|
|
|
|
|
|
|
// The last retrieved block root has to match input end block root.
|
2020-02-17 19:33:00 +00:00
|
|
|
// Covers the edge case if there's multiple blocks on the same end slot,
|
|
|
|
// the end root may not be the last index in `blockRoots`.
|
|
|
|
for length >= 3 && blocks[length-1].Block.Slot == blocks[length-2].Block.Slot && blockRoots[length-1] != endBlockRoot {
|
2020-10-21 17:06:48 +00:00
|
|
|
if ctx.Err() != nil {
|
|
|
|
return nil, ctx.Err()
|
|
|
|
}
|
2020-02-17 19:33:00 +00:00
|
|
|
length--
|
|
|
|
if blockRoots[length-2] == endBlockRoot {
|
|
|
|
length--
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-16 23:28:20 +00:00
|
|
|
if blockRoots[length-1] != endBlockRoot {
|
|
|
|
return nil, errors.New("end block roots don't match")
|
|
|
|
}
|
|
|
|
|
|
|
|
filteredBlocks := []*ethpb.SignedBeaconBlock{blocks[length-1]}
|
|
|
|
// Starting from second to last index because the last block is already in the filtered block list.
|
|
|
|
for i := length - 2; i >= 0; i-- {
|
2020-10-21 17:06:48 +00:00
|
|
|
if ctx.Err() != nil {
|
|
|
|
return nil, ctx.Err()
|
|
|
|
}
|
2020-02-16 23:28:20 +00:00
|
|
|
b := filteredBlocks[len(filteredBlocks)-1]
|
|
|
|
if bytesutil.ToBytes32(b.Block.ParentRoot) != blockRoots[i] {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
filteredBlocks = append(filteredBlocks, blocks[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
return filteredBlocks, nil
|
|
|
|
}
|
2020-02-26 16:40:33 +00:00
|
|
|
|
|
|
|
// executeStateTransitionStateGen applies state transition on input historical state and block for state gen usages.
|
|
|
|
// There's no signature verification involved given state gen only works with stored block and state in DB.
|
|
|
|
// If the objects are already in stored in DB, one can omit redundant signature checks and ssz hashing calculations.
|
|
|
|
// WARNING: This method should not be used on an unverified new block.
|
|
|
|
func executeStateTransitionStateGen(
|
|
|
|
ctx context.Context,
|
|
|
|
state *stateTrie.BeaconState,
|
|
|
|
signed *ethpb.SignedBeaconBlock,
|
|
|
|
) (*stateTrie.BeaconState, error) {
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
return nil, ctx.Err()
|
|
|
|
}
|
|
|
|
if signed == nil || signed.Block == nil {
|
2020-03-05 16:22:20 +00:00
|
|
|
return nil, errUnknownBlock
|
2020-02-26 16:40:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.ExecuteStateTransitionStateGen")
|
|
|
|
defer span.End()
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// Execute per slots transition.
|
|
|
|
// Given this is for state gen, a node uses the version process slots without skip slots cache.
|
|
|
|
state, err = processSlotsStateGen(ctx, state, signed.Block.Slot)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "could not process slot")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute per block transition.
|
|
|
|
// Given this is for state gen, a node only cares about the post state without proposer
|
|
|
|
// and randao signature verifications.
|
|
|
|
state, err = transition.ProcessBlockForStateRoot(ctx, state, signed)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "could not process block")
|
|
|
|
}
|
|
|
|
|
|
|
|
return state, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// processSlotsStateGen to process old slots for state gen usages.
|
|
|
|
// There's no skip slot cache involved given state gen only works with already stored block and state in DB.
|
|
|
|
// WARNING: This method should not be used for future slot.
|
|
|
|
func processSlotsStateGen(ctx context.Context, state *stateTrie.BeaconState, slot uint64) (*stateTrie.BeaconState, error) {
|
|
|
|
ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.ProcessSlotsStateGen")
|
|
|
|
defer span.End()
|
|
|
|
if state == nil {
|
2020-03-05 16:22:20 +00:00
|
|
|
return nil, errUnknownState
|
2020-02-26 16:40:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if state.Slot() > slot {
|
|
|
|
err := fmt.Errorf("expected state.slot %d < slot %d", state.Slot(), slot)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if state.Slot() == slot {
|
|
|
|
return state, nil
|
|
|
|
}
|
|
|
|
|
2020-07-03 06:33:58 +00:00
|
|
|
var err error
|
2020-02-26 16:40:33 +00:00
|
|
|
for state.Slot() < slot {
|
2020-07-03 06:33:58 +00:00
|
|
|
state, err = transition.ProcessSlot(ctx, state)
|
2020-02-26 16:40:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "could not process slot")
|
|
|
|
}
|
|
|
|
if transition.CanProcessEpoch(state) {
|
|
|
|
state, err = transition.ProcessEpochPrecompute(ctx, state)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "could not process epoch with optimizations")
|
|
|
|
}
|
|
|
|
}
|
2020-04-13 04:11:09 +00:00
|
|
|
if err := state.SetSlot(state.Slot() + 1); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-02-26 16:40:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return state, nil
|
|
|
|
}
|
2020-03-05 16:22:20 +00:00
|
|
|
|
|
|
|
// This finds the last saved block in DB from searching backwards from input slot,
|
|
|
|
// it returns the block root and the slot of the block.
|
|
|
|
// This is used by both hot and cold state management.
|
|
|
|
func (s *State) lastSavedBlock(ctx context.Context, slot uint64) ([32]byte, uint64, error) {
|
|
|
|
ctx, span := trace.StartSpan(ctx, "stateGen.lastSavedBlock")
|
|
|
|
defer span.End()
|
|
|
|
|
|
|
|
// Handle the genesis case where the input slot is 0.
|
|
|
|
if slot == 0 {
|
|
|
|
gRoot, err := s.genesisRoot(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return [32]byte{}, 0, err
|
|
|
|
}
|
|
|
|
return gRoot, 0, nil
|
|
|
|
}
|
|
|
|
|
2020-03-27 20:28:38 +00:00
|
|
|
lastSaved, err := s.beaconDB.HighestSlotBlocksBelow(ctx, slot+1)
|
2020-03-05 16:22:20 +00:00
|
|
|
if err != nil {
|
2020-04-13 16:32:02 +00:00
|
|
|
return [32]byte{}, 0, err
|
2020-03-05 16:22:20 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 20:28:38 +00:00
|
|
|
// Given this is used to query canonical block. There should only be one saved canonical block of a given slot.
|
|
|
|
if len(lastSaved) != 1 {
|
|
|
|
return [32]byte{}, 0, fmt.Errorf("highest saved block does not equal to 1, it equals to %d", len(lastSaved))
|
|
|
|
}
|
2020-03-31 16:23:39 +00:00
|
|
|
if lastSaved[0] == nil || lastSaved[0].Block == nil {
|
2020-04-13 16:32:02 +00:00
|
|
|
return [32]byte{}, 0, nil
|
2020-03-27 20:28:38 +00:00
|
|
|
}
|
2020-08-27 18:13:32 +00:00
|
|
|
r, err := lastSaved[0].Block.HashTreeRoot()
|
2020-03-05 16:22:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return [32]byte{}, 0, err
|
|
|
|
}
|
|
|
|
|
2020-03-27 20:28:38 +00:00
|
|
|
return r, lastSaved[0].Block.Slot, nil
|
2020-03-05 16:22:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This finds the last saved state in DB from searching backwards from input slot,
|
|
|
|
// it returns the block root of the block which was used to produce the state.
|
|
|
|
// This is used by both hot and cold state management.
|
2020-10-10 03:54:31 +00:00
|
|
|
func (s *State) lastSavedState(ctx context.Context, slot uint64) (*stateTrie.BeaconState, error) {
|
2020-03-05 16:22:20 +00:00
|
|
|
ctx, span := trace.StartSpan(ctx, "stateGen.lastSavedState")
|
|
|
|
defer span.End()
|
|
|
|
|
|
|
|
// Handle the genesis case where the input slot is 0.
|
|
|
|
if slot == 0 {
|
2020-03-27 20:28:38 +00:00
|
|
|
return s.beaconDB.GenesisState(ctx)
|
2020-03-05 16:22:20 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 20:28:38 +00:00
|
|
|
lastSaved, err := s.beaconDB.HighestSlotStatesBelow(ctx, slot+1)
|
2020-03-05 16:22:20 +00:00
|
|
|
if err != nil {
|
2020-04-21 14:44:43 +00:00
|
|
|
return nil, err
|
2020-03-05 16:22:20 +00:00
|
|
|
}
|
2020-03-27 20:28:38 +00:00
|
|
|
|
|
|
|
// Given this is used to query canonical state. There should only be one saved canonical block of a given slot.
|
|
|
|
if len(lastSaved) != 1 {
|
|
|
|
return nil, fmt.Errorf("highest saved state does not equal to 1, it equals to %d", len(lastSaved))
|
2020-03-05 16:22:20 +00:00
|
|
|
}
|
2020-03-31 16:23:39 +00:00
|
|
|
if lastSaved[0] == nil {
|
|
|
|
return nil, errUnknownState
|
|
|
|
}
|
2020-03-27 20:28:38 +00:00
|
|
|
|
|
|
|
return lastSaved[0], nil
|
2020-03-05 16:22:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This returns the genesis root.
|
|
|
|
func (s *State) genesisRoot(ctx context.Context) ([32]byte, error) {
|
|
|
|
b, err := s.beaconDB.GenesisBlock(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return [32]byte{}, err
|
|
|
|
}
|
2020-08-27 18:13:32 +00:00
|
|
|
return b.Block.HashTreeRoot()
|
2020-03-05 16:22:20 +00:00
|
|
|
}
|
2020-05-06 20:25:37 +00:00
|
|
|
|
2020-08-10 23:35:28 +00:00
|
|
|
// Given the start slot and the end slot, this returns the finalized beacon blocks in between.
|
|
|
|
// Since hot states don't have finalized blocks, this should ONLY be used for replaying cold state.
|
2020-10-12 15:43:19 +00:00
|
|
|
func (s *State) loadFinalizedBlocks(ctx context.Context, startSlot, endSlot uint64) ([]*ethpb.SignedBeaconBlock, error) {
|
2020-08-10 23:35:28 +00:00
|
|
|
f := filters.NewFilter().SetStartSlot(startSlot).SetEndSlot(endSlot)
|
2020-10-18 16:39:27 +00:00
|
|
|
bs, bRoots, err := s.beaconDB.Blocks(ctx, f)
|
2020-08-10 23:35:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(bs) != len(bRoots) {
|
|
|
|
return nil, errors.New("length of blocks and roots don't match")
|
|
|
|
}
|
|
|
|
fbs := make([]*ethpb.SignedBeaconBlock, 0, len(bs))
|
|
|
|
for i := len(bs) - 1; i >= 0; i-- {
|
|
|
|
if s.beaconDB.IsFinalizedBlock(ctx, bRoots[i]) {
|
|
|
|
fbs = append(fbs, bs[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fbs, nil
|
|
|
|
}
|