mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-22 11:32:09 +00:00
Checkpoint Sync 4/5 - enable checkpoint sync to be used by beacon node (#10386)
* enable checkpoint sync in beacon node * lint fix * rm unused error * addressing PR feedback from Radek * consistent slice -> fixed conversion Co-authored-by: kasey <kasey@users.noreply.github.com>
This commit is contained in:
parent
1af3c07ec5
commit
7920528ede
@ -257,7 +257,7 @@ func (s *Service) StartFromSavedState(saved state.BeaconState) error {
|
||||
|
||||
func (s *Service) originRootFromSavedState(ctx context.Context) ([32]byte, error) {
|
||||
// first check if we have started from checkpoint sync and have a root
|
||||
originRoot, err := s.cfg.BeaconDB.OriginBlockRoot(ctx)
|
||||
originRoot, err := s.cfg.BeaconDB.OriginCheckpointBlockRoot(ctx)
|
||||
if err == nil {
|
||||
return originRoot, nil
|
||||
}
|
||||
@ -265,7 +265,7 @@ func (s *Service) originRootFromSavedState(ctx context.Context) ([32]byte, error
|
||||
return originRoot, errors.Wrap(err, "could not retrieve checkpoint sync chain origin data from db")
|
||||
}
|
||||
|
||||
// we got here because OriginBlockRoot gave us an ErrNotFound. this means the node was started from a genesis state,
|
||||
// we got here because OriginCheckpointBlockRoot gave us an ErrNotFound. this means the node was started from a genesis state,
|
||||
// so we should have a value for GenesisBlock
|
||||
genesisBlock, err := s.cfg.BeaconDB.GenesisBlock(ctx)
|
||||
if err != nil {
|
||||
|
@ -36,6 +36,7 @@ func NewWeakSubjectivityVerifier(wsc *ethpb.Checkpoint, db weakSubjectivityDB) (
|
||||
// per 7342, a nil checkpoint, zero-root or zero-epoch should all fail validation
|
||||
// and return an error instead of creating a WeakSubjectivityVerifier that permits any chain history.
|
||||
if wsc == nil || len(wsc.Root) == 0 || wsc.Epoch == 0 {
|
||||
log.Warn("No valid weak subjectivity checkpoint specified, running without weak subjectivity verification")
|
||||
return &WeakSubjectivityVerifier{
|
||||
enabled: false,
|
||||
}, nil
|
||||
@ -79,15 +80,17 @@ func (v *WeakSubjectivityVerifier) VerifyWeakSubjectivity(ctx context.Context, f
|
||||
if !v.db.HasBlock(ctx, v.root) {
|
||||
return errors.Wrap(errWSBlockNotFound, fmt.Sprintf("missing root %#x", v.root))
|
||||
}
|
||||
filter := filters.NewFilter().SetStartSlot(v.slot).SetEndSlot(v.slot + params.BeaconConfig().SlotsPerEpoch)
|
||||
endSlot := v.slot + params.BeaconConfig().SlotsPerEpoch
|
||||
filter := filters.NewFilter().SetStartSlot(v.slot).SetEndSlot(endSlot)
|
||||
// A node should have the weak subjectivity block corresponds to the correct epoch in the DB.
|
||||
log.Infof("Searching block roots for weak subjectivity root=%#x, between slots %d-%d", v.root, v.slot, endSlot)
|
||||
roots, err := v.db.BlockRoots(ctx, filter)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error while retrieving block roots to verify weak subjectivity")
|
||||
}
|
||||
for _, root := range roots {
|
||||
if v.root == root {
|
||||
log.Info("Weak subjectivity check has passed")
|
||||
log.Info("Weak subjectivity check has passed!!")
|
||||
v.verified = true
|
||||
return nil
|
||||
}
|
||||
|
@ -14,60 +14,65 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
)
|
||||
|
||||
func TestService_VerifyWeakSubjectivityRoot(t *testing.T) {
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = 32
|
||||
b.Block.Slot = 1792480
|
||||
wsb, err := wrapper.WrappedSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveBlock(context.Background(), wsb))
|
||||
r, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
blockEpoch := slots.ToEpoch(b.Block.Slot)
|
||||
tests := []struct {
|
||||
wsVerified bool
|
||||
disabled bool
|
||||
wantErr error
|
||||
checkpt *ethpb.Checkpoint
|
||||
finalizedEpoch types.Epoch
|
||||
name string
|
||||
}{
|
||||
{
|
||||
name: "nil root and epoch",
|
||||
},
|
||||
{
|
||||
name: "already verified",
|
||||
checkpt: ðpb.Checkpoint{Epoch: 2},
|
||||
finalizedEpoch: 2,
|
||||
wsVerified: true,
|
||||
name: "nil root and epoch",
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
name: "not yet to verify, ws epoch higher than finalized epoch",
|
||||
checkpt: ðpb.Checkpoint{Epoch: 2},
|
||||
finalizedEpoch: 1,
|
||||
checkpt: ðpb.Checkpoint{Root: bytesutil.PadTo([]byte{'a'}, 32), Epoch: blockEpoch},
|
||||
finalizedEpoch: blockEpoch - 1,
|
||||
},
|
||||
{
|
||||
name: "can't find the block in DB",
|
||||
checkpt: ðpb.Checkpoint{Root: bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength), Epoch: 1},
|
||||
finalizedEpoch: 3,
|
||||
finalizedEpoch: blockEpoch + 1,
|
||||
wantErr: errWSBlockNotFound,
|
||||
},
|
||||
{
|
||||
name: "can't find the block corresponds to ws epoch in DB",
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: 2}, // Root belongs in epoch 1.
|
||||
finalizedEpoch: 3,
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: blockEpoch - 2}, // Root belongs in epoch 1.
|
||||
finalizedEpoch: blockEpoch - 1,
|
||||
wantErr: errWSBlockNotFoundInEpoch,
|
||||
},
|
||||
{
|
||||
name: "can verify and pass",
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: 1},
|
||||
finalizedEpoch: 3,
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: blockEpoch},
|
||||
finalizedEpoch: blockEpoch + 1,
|
||||
},
|
||||
{
|
||||
name: "equal epoch",
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: blockEpoch},
|
||||
finalizedEpoch: blockEpoch,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
wv, err := NewWeakSubjectivityVerifier(tt.checkpt, beaconDB)
|
||||
require.Equal(t, !tt.disabled, wv.enabled)
|
||||
require.NoError(t, err)
|
||||
s := &Service{
|
||||
cfg: &config{BeaconDB: beaconDB, WeakSubjectivityCheckpt: tt.checkpt},
|
||||
|
@ -13,3 +13,9 @@ var ErrNotFoundState = kv.ErrNotFoundState
|
||||
|
||||
// ErrNotFoundOriginBlockRoot wraps ErrNotFound for an error specific to the origin block root.
|
||||
var ErrNotFoundOriginBlockRoot = kv.ErrNotFoundOriginBlockRoot
|
||||
|
||||
// ErrNotFoundOriginBlockRoot wraps ErrNotFound for an error specific to the origin block root.
|
||||
var ErrNotFoundBackfillBlockRoot = kv.ErrNotFoundBackfillBlockRoot
|
||||
|
||||
// ErrNotFoundGenesisBlockRoot means no genesis block root was found, indicating the db was not initialized with genesis
|
||||
var ErrNotFoundGenesisBlockRoot = kv.ErrNotFoundGenesisBlockRoot
|
||||
|
@ -27,6 +27,7 @@ type ReadOnlyDatabase interface {
|
||||
BlockRootsBySlot(ctx context.Context, slot types.Slot) (bool, [][32]byte, error)
|
||||
HasBlock(ctx context.Context, blockRoot [32]byte) bool
|
||||
GenesisBlock(ctx context.Context) (block.SignedBeaconBlock, error)
|
||||
GenesisBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
IsFinalizedBlock(ctx context.Context, blockRoot [32]byte) bool
|
||||
FinalizedChildBlock(ctx context.Context, blockRoot [32]byte) (block.SignedBeaconBlock, error)
|
||||
HighestSlotBlocksBelow(ctx context.Context, slot types.Slot) ([]block.SignedBeaconBlock, error)
|
||||
@ -53,7 +54,8 @@ type ReadOnlyDatabase interface {
|
||||
// Fee reicipients operations.
|
||||
FeeRecipientByValidatorID(ctx context.Context, id types.ValidatorIndex) (common.Address, error)
|
||||
// origin checkpoint sync support
|
||||
OriginBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
OriginCheckpointBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
BackfillBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
}
|
||||
|
||||
// NoHeadAccessDatabase defines a struct without access to chain head data.
|
||||
@ -102,7 +104,8 @@ type HeadAccessDatabase interface {
|
||||
EnsureEmbeddedGenesis(ctx context.Context) error
|
||||
|
||||
// initialization method needed for origin checkpoint sync
|
||||
SaveOrigin(ctx context.Context, state io.Reader, block io.Reader) error
|
||||
SaveOrigin(ctx context.Context, serState, serBlock []byte) error
|
||||
SaveBackfillBlockRoot(ctx context.Context, blockRoot [32]byte) error
|
||||
}
|
||||
|
||||
// SlasherDatabase interface for persisting data related to detecting slashable offenses on Ethereum.
|
||||
|
@ -48,18 +48,18 @@ func (s *Store) Block(ctx context.Context, blockRoot [32]byte) (block.SignedBeac
|
||||
return blk, err
|
||||
}
|
||||
|
||||
// OriginBlockRoot returns the value written to the db in SaveOriginBlockRoot
|
||||
// OriginCheckpointBlockRoot returns the value written to the db in SaveOriginCheckpointBlockRoot
|
||||
// This is the root of a finalized block within the weak subjectivity period
|
||||
// at the time the chain was started, used to initialize the database and chain
|
||||
// without syncing from genesis.
|
||||
func (s *Store) OriginBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.OriginBlockRoot")
|
||||
func (s *Store) OriginCheckpointBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.OriginCheckpointBlockRoot")
|
||||
defer span.End()
|
||||
|
||||
var root [32]byte
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(blocksBucket)
|
||||
rootSlice := bkt.Get(originBlockRootKey)
|
||||
rootSlice := bkt.Get(originCheckpointBlockRootKey)
|
||||
if rootSlice == nil {
|
||||
return ErrNotFoundOriginBlockRoot
|
||||
}
|
||||
@ -70,6 +70,25 @@ func (s *Store) OriginBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
return root, err
|
||||
}
|
||||
|
||||
// BackfillBlockRoot keeps track of the highest block available before the OriginCheckpointBlockRoot
|
||||
func (s *Store) BackfillBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.BackfillBlockRoot")
|
||||
defer span.End()
|
||||
|
||||
var root [32]byte
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(blocksBucket)
|
||||
rootSlice := bkt.Get(backfillBlockRootKey)
|
||||
if len(rootSlice) == 0 {
|
||||
return ErrNotFoundBackfillBlockRoot
|
||||
}
|
||||
root = bytesutil.ToBytes32(rootSlice)
|
||||
return nil
|
||||
})
|
||||
|
||||
return root, err
|
||||
}
|
||||
|
||||
// HeadBlock returns the latest canonical block in the Ethereum Beacon Chain.
|
||||
func (s *Store) HeadBlock(ctx context.Context) (block.SignedBeaconBlock, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "BeaconDB.HeadBlock")
|
||||
@ -325,6 +344,22 @@ func (s *Store) GenesisBlock(ctx context.Context) (block.SignedBeaconBlock, erro
|
||||
return blk, err
|
||||
}
|
||||
|
||||
func (s *Store) GenesisBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "BeaconDB.GenesisBlockRoot")
|
||||
defer span.End()
|
||||
var root [32]byte
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(blocksBucket)
|
||||
r := bkt.Get(genesisBlockRootKey)
|
||||
if len(r) == 0 {
|
||||
return ErrNotFoundGenesisBlockRoot
|
||||
}
|
||||
root = bytesutil.ToBytes32(r)
|
||||
return nil
|
||||
})
|
||||
return root, err
|
||||
}
|
||||
|
||||
// SaveGenesisBlockRoot to the db.
|
||||
func (s *Store) SaveGenesisBlockRoot(ctx context.Context, blockRoot [32]byte) error {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.SaveGenesisBlockRoot")
|
||||
@ -335,16 +370,27 @@ func (s *Store) SaveGenesisBlockRoot(ctx context.Context, blockRoot [32]byte) er
|
||||
})
|
||||
}
|
||||
|
||||
// SaveOriginBlockRoot is used to keep track of the block root used for origin sync.
|
||||
// SaveOriginCheckpointBlockRoot is used to keep track of the block root used for syncing from a checkpoint origin.
|
||||
// This should be a finalized block from within the current weak subjectivity period.
|
||||
// This value is used by a running beacon chain node to locate the state at the beginning
|
||||
// of the chain history, in places where genesis would typically be used.
|
||||
func (s *Store) SaveOriginBlockRoot(ctx context.Context, blockRoot [32]byte) error {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.SaveOriginBlockRoot")
|
||||
func (s *Store) SaveOriginCheckpointBlockRoot(ctx context.Context, blockRoot [32]byte) error {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.SaveOriginCheckpointBlockRoot")
|
||||
defer span.End()
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(blocksBucket)
|
||||
return bucket.Put(originBlockRootKey, blockRoot[:])
|
||||
return bucket.Put(originCheckpointBlockRootKey, blockRoot[:])
|
||||
})
|
||||
}
|
||||
|
||||
// SaveBackfillBlockRoot is used to keep track of the most recently backfilled block root when
|
||||
// the node was initialized via checkpoint sync.
|
||||
func (s *Store) SaveBackfillBlockRoot(ctx context.Context, blockRoot [32]byte) error {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.SaveBackfillBlockRoot")
|
||||
defer span.End()
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(blocksBucket)
|
||||
return bucket.Put(backfillBlockRootKey, blockRoot[:])
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -58,6 +58,23 @@ var blockTests = []struct {
|
||||
},
|
||||
}
|
||||
|
||||
func TestStore_SaveBackfillBlockRoot(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := db.BackfillBlockRoot(ctx)
|
||||
require.ErrorIs(t, err, ErrNotFoundBackfillBlockRoot)
|
||||
|
||||
expected := [32]byte{}
|
||||
copy(expected[:], []byte{0x23})
|
||||
err = db.SaveBackfillBlockRoot(ctx, expected)
|
||||
require.NoError(t, err)
|
||||
actual, err := db.BackfillBlockRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, actual)
|
||||
|
||||
}
|
||||
|
||||
func TestStore_SaveBlock_NoDuplicates(t *testing.T) {
|
||||
BlockCacheSize = 1
|
||||
slot := types.Slot(20)
|
||||
|
@ -13,5 +13,11 @@ var ErrNotFoundState = errors.Wrap(ErrNotFound, "state not found")
|
||||
// ErrNotFoundOriginBlockRoot is an error specifically for the origin block root getter
|
||||
var ErrNotFoundOriginBlockRoot = errors.Wrap(ErrNotFound, "OriginBlockRoot")
|
||||
|
||||
// ErrNotFoundGenesisBlockRoot means no genesis block root was found, indicating the db was not initialized with genesis
|
||||
var ErrNotFoundGenesisBlockRoot = errors.Wrap(ErrNotFound, "OriginGenesisRoot")
|
||||
|
||||
// ErrNotFoundOriginBlockRoot is an error specifically for the origin block root getter
|
||||
var ErrNotFoundBackfillBlockRoot = errors.Wrap(ErrNotFound, "BackfillBlockRoot")
|
||||
|
||||
// ErrNotFoundFeeRecipient is a not found error specifically for the fee recipient getter
|
||||
var ErrNotFoundFeeRecipient = errors.Wrap(ErrNotFound, "fee recipient")
|
||||
|
@ -32,7 +32,7 @@ var containerFinalizedButNotCanonical = []byte("recent block needs reindexing to
|
||||
// - De-index all finalized beacon block roots from previous_finalized_epoch to
|
||||
// new_finalized_epoch. (I.e. delete these roots from the index, to be re-indexed.)
|
||||
// - Build the canonical finalized chain by walking up the ancestry chain from the finalized block
|
||||
// root until a parent is found in the index or the parent is genesis.
|
||||
// root until a parent is found in the index, or the parent is genesis or the origin checkpoint.
|
||||
// - Add all block roots in the database where epoch(block.slot) == checkpoint.epoch.
|
||||
//
|
||||
// This method ensures that all blocks from the current finalized epoch are considered "final" while
|
||||
@ -46,6 +46,7 @@ func (s *Store) updateFinalizedBlockRoots(ctx context.Context, tx *bolt.Tx, chec
|
||||
root := checkpoint.Root
|
||||
var previousRoot []byte
|
||||
genesisRoot := tx.Bucket(blocksBucket).Get(genesisBlockRootKey)
|
||||
initCheckpointRoot := tx.Bucket(blocksBucket).Get(originCheckpointBlockRootKey)
|
||||
|
||||
// De-index recent finalized block roots, to be re-indexed.
|
||||
previousFinalizedCheckpoint := ðpb.Checkpoint{}
|
||||
@ -74,7 +75,7 @@ func (s *Store) updateFinalizedBlockRoots(ctx context.Context, tx *bolt.Tx, chec
|
||||
// Walk up the ancestry chain until we reach a block root present in the finalized block roots
|
||||
// index bucket or genesis block root.
|
||||
for {
|
||||
if bytes.Equal(root, genesisRoot) {
|
||||
if bytes.Equal(root, genesisRoot) || bytes.Equal(root, initCheckpointRoot) {
|
||||
break
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,9 @@ var (
|
||||
altairKey = []byte("altair")
|
||||
bellatrixKey = []byte("merge")
|
||||
// block root included in the beacon state used by weak subjectivity initial sync
|
||||
originBlockRootKey = []byte("origin-block-root")
|
||||
originCheckpointBlockRootKey = []byte("origin-checkpoint-block-root")
|
||||
// block root tracking the progress of backfill, or pointing at genesis if backfill has not been initiated
|
||||
backfillBlockRootKey = []byte("backfill-block-root")
|
||||
|
||||
// Deprecated: This index key was migrated in PR 6461. Do not use, except for migrations.
|
||||
lastArchivedIndexKey = []byte("last-archived")
|
||||
|
@ -2,69 +2,76 @@ package kv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/encoding/ssz/detect"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/runtime/version"
|
||||
)
|
||||
|
||||
// SaveOrigin loads an ssz serialized Block & BeaconState from an io.Reader
|
||||
// (ex: an open file) prepares the database so that the beacon node can begin
|
||||
// syncing, using the provided values as their point of origin. This is an alternative
|
||||
// to syncing from genesis, and should only be run on an empty database.
|
||||
func (s *Store) SaveOrigin(ctx context.Context, stateReader, blockReader io.Reader) error {
|
||||
sb, err := ioutil.ReadAll(stateReader)
|
||||
func (s *Store) SaveOrigin(ctx context.Context, serState, serBlock []byte) error {
|
||||
genesisRoot, err := s.GenesisBlockRoot(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read origin state bytes")
|
||||
if errors.Is(err, ErrNotFoundGenesisBlockRoot) {
|
||||
return errors.Wrap(err, "genesis block root not found: genesis must be provided for checkpoint sync")
|
||||
}
|
||||
return errors.Wrap(err, "genesis block root query error: checkpoint sync must verify genesis to proceed")
|
||||
}
|
||||
bb, err := ioutil.ReadAll(blockReader)
|
||||
err = s.SaveBackfillBlockRoot(ctx, genesisRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error reading block given to SaveOrigin")
|
||||
return errors.Wrap(err, "unable to save genesis root as initial backfill starting point for checkpoint sync")
|
||||
}
|
||||
|
||||
cf, err := detect.FromState(sb)
|
||||
cf, err := detect.FromState(serState)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to detect config and fork for origin state")
|
||||
return errors.Wrap(err, "could not sniff config+fork for origin state bytes")
|
||||
}
|
||||
bs, err := cf.UnmarshalBeaconState(sb)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not unmarshal origin state")
|
||||
}
|
||||
wblk, err := cf.UnmarshalBeaconBlock(bb)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to unmarshal origin SignedBeaconBlock")
|
||||
_, ok := params.BeaconConfig().ForkVersionSchedule[cf.Version]
|
||||
if !ok {
|
||||
return fmt.Errorf("config mismatch, beacon node configured to connect to %s, detected state is for %s", params.BeaconConfig().ConfigName, cf.Config.ConfigName)
|
||||
}
|
||||
|
||||
blockRoot, err := wblk.Block().HashTreeRoot()
|
||||
log.Infof("detected supported config for state & block version, config name=%s, fork name=%s", cf.Config.ConfigName, version.String(cf.Fork))
|
||||
state, err := cf.UnmarshalBeaconState(serState)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize origin state w/ bytes + config+fork")
|
||||
}
|
||||
|
||||
wblk, err := cf.UnmarshalBeaconBlock(serBlock)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize origin block w/ bytes + config+fork")
|
||||
}
|
||||
blk := wblk.Block()
|
||||
|
||||
// save block
|
||||
blockRoot, err := blk.HashTreeRoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not compute HashTreeRoot of checkpoint block")
|
||||
}
|
||||
// save block
|
||||
log.Infof("saving checkpoint block to db, w/ root=%#x", blockRoot)
|
||||
if err := s.SaveBlock(ctx, wblk); err != nil {
|
||||
return errors.Wrap(err, "could not save checkpoint block")
|
||||
}
|
||||
|
||||
// save state
|
||||
if err = s.SaveState(ctx, bs, blockRoot); err != nil {
|
||||
log.Infof("calling SaveState w/ blockRoot=%x", blockRoot)
|
||||
if err = s.SaveState(ctx, state, blockRoot); err != nil {
|
||||
return errors.Wrap(err, "could not save state")
|
||||
}
|
||||
if err = s.SaveStateSummary(ctx, ðpb.StateSummary{
|
||||
Slot: bs.Slot(),
|
||||
Slot: state.Slot(),
|
||||
Root: blockRoot[:],
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "could not save state summary")
|
||||
}
|
||||
|
||||
// save origin block root in special key, to be used when the canonical
|
||||
// origin (start of chain, ie alternative to genesis) block or state is needed
|
||||
if err = s.SaveOriginBlockRoot(ctx, blockRoot); err != nil {
|
||||
return errors.Wrap(err, "could not save origin block root")
|
||||
}
|
||||
|
||||
// mark block as head of chain, so that processing will pick up from this point
|
||||
if err = s.SaveHeadBlockRoot(ctx, blockRoot); err != nil {
|
||||
return errors.Wrap(err, "could not save head block root")
|
||||
@ -87,5 +94,11 @@ func (s *Store) SaveOrigin(ctx context.Context, stateReader, blockReader io.Read
|
||||
return errors.Wrap(err, "could not mark checkpoint sync block as finalized")
|
||||
}
|
||||
|
||||
// save origin block root in a special key, to be used when the canonical
|
||||
// origin (start of chain, ie alternative to genesis) block or state is needed
|
||||
if err = s.SaveOriginCheckpointBlockRoot(ctx, blockRoot); err != nil {
|
||||
return errors.Wrap(err, "could not save origin block root")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ go_library(
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//beacon-chain/sync/backfill:go_default_library",
|
||||
"//beacon-chain/sync/checkpoint:go_default_library",
|
||||
"//beacon-chain/sync/initial-sync:go_default_library",
|
||||
"//cmd:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
|
@ -44,6 +44,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
regularsync "github.com/prysmaticlabs/prysm/beacon-chain/sync"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/sync/backfill"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/sync/checkpoint"
|
||||
initialsync "github.com/prysmaticlabs/prysm/beacon-chain/sync/initial-sync"
|
||||
"github.com/prysmaticlabs/prysm/cmd"
|
||||
"github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags"
|
||||
@ -101,6 +103,8 @@ type BeaconNode struct {
|
||||
slasherAttestationsFeed *event.Feed
|
||||
finalizedStateAtStartUp state.BeaconState
|
||||
serviceFlagOpts *serviceFlagOpts
|
||||
blockchainFlagOpts []blockchain.Option
|
||||
CheckpointInitializer checkpoint.Initializer
|
||||
}
|
||||
|
||||
// New creates a new node instance, sets up configuration options, and registers
|
||||
@ -162,13 +166,19 @@ func New(cliCtx *cli.Context, opts ...Option) (*BeaconNode, error) {
|
||||
if err := beacon.startDB(cliCtx, depositAddress); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugln("Starting Slashing DB")
|
||||
if err := beacon.startSlasherDB(cliCtx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bfs := backfill.NewStatus(beacon.db)
|
||||
if err := bfs.Reload(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "backfill status initialization error")
|
||||
}
|
||||
|
||||
log.Debugln("Starting State Gen")
|
||||
if err := beacon.startStateGen(); err != nil {
|
||||
if err := beacon.startStateGen(ctx, bfs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -239,7 +249,7 @@ func New(cliCtx *cli.Context, opts ...Option) (*BeaconNode, error) {
|
||||
|
||||
// db.DatabasePath is the path to the containing directory
|
||||
// db.NewDBFilename expands that to the canonical full path using
|
||||
// the same constuction as NewDB()
|
||||
// the same construction as NewDB()
|
||||
c, err := newBeaconNodePromCollector(db.NewDBFilename(beacon.db.DatabasePath()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -396,6 +406,13 @@ func (b *BeaconNode) startDB(cliCtx *cli.Context, depositAddress string) error {
|
||||
if err := b.db.EnsureEmbeddedGenesis(b.ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.CheckpointInitializer != nil {
|
||||
if err := b.CheckpointInitializer.Initialize(b.ctx, d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
knownContract, err := b.db.DepositContractAddress(b.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -463,10 +480,11 @@ func (b *BeaconNode) startSlasherDB(cliCtx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BeaconNode) startStateGen() error {
|
||||
b.stateGen = stategen.New(b.db)
|
||||
func (b *BeaconNode) startStateGen(ctx context.Context, bfs *backfill.Status) error {
|
||||
opts := []stategen.StateGenOption{stategen.WithBackfillStatus(bfs)}
|
||||
sg := stategen.New(b.db, opts...)
|
||||
|
||||
cp, err := b.db.FinalizedCheckpoint(b.ctx)
|
||||
cp, err := b.db.FinalizedCheckpoint(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -474,7 +492,7 @@ func (b *BeaconNode) startStateGen() error {
|
||||
r := bytesutil.ToBytes32(cp.Root)
|
||||
// Consider edge case where finalized root are zeros instead of genesis root hash.
|
||||
if r == params.BeaconConfig().ZeroHash {
|
||||
genesisBlock, err := b.db.GenesisBlock(b.ctx)
|
||||
genesisBlock, err := b.db.GenesisBlock(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -486,10 +504,12 @@ func (b *BeaconNode) startStateGen() error {
|
||||
}
|
||||
}
|
||||
|
||||
b.finalizedStateAtStartUp, err = b.stateGen.StateByRoot(b.ctx, r)
|
||||
b.finalizedStateAtStartUp, err = sg.StateByRoot(ctx, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.stateGen = sg
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
// PrepareStateFetchGRPCError returns an appropriate gRPC error based on the supplied argument.
|
||||
// The argument error should be a result of fetching state.
|
||||
func PrepareStateFetchGRPCError(err error) error {
|
||||
if errors.Is(err, stategen.ErrSlotBeforeOrigin) {
|
||||
if errors.Is(err, stategen.ErrNoDataForSlot) {
|
||||
return status.Errorf(codes.NotFound, "lacking historical data needed to fulfill request")
|
||||
}
|
||||
if stateNotFoundErr, ok := err.(*statefetcher.StateNotFoundError); ok {
|
||||
|
@ -28,6 +28,7 @@ go_library(
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/db/filters:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/sync/backfill:go_default_library",
|
||||
"//cache/lru:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
@ -12,7 +13,7 @@ import (
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
var ErrSlotBeforeOrigin = errors.New("cannot retrieve data for slots before sync origin")
|
||||
var ErrNoDataForSlot = errors.New("cannot retrieve data for slot")
|
||||
|
||||
// HasState returns true if the state exists in cache or in DB.
|
||||
func (s *State) HasState(ctx context.Context, blockRoot [32]byte) (bool, error) {
|
||||
@ -230,16 +231,17 @@ func (s *State) LastAncestorState(ctx context.Context, root [32]byte) (state.Bea
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
// return an error if we have rewound to before the checkpoint sync slot
|
||||
if (b.Block().Slot() - 1) < s.minimumSlot() {
|
||||
return nil, errors.Wrapf(ErrSlotBeforeOrigin, "no blocks in db prior to slot %d", s.minimumSlot())
|
||||
}
|
||||
// Is the state a genesis state.
|
||||
parentRoot := bytesutil.ToBytes32(b.Block().ParentRoot())
|
||||
if parentRoot == params.BeaconConfig().ZeroHash {
|
||||
return s.beaconDB.GenesisState(ctx)
|
||||
}
|
||||
|
||||
// return an error if slot hasn't been covered by checkpoint sync backfill
|
||||
ps := b.Block().Slot() - 1
|
||||
if !s.slotAvailable(ps) {
|
||||
return nil, errors.Wrapf(ErrNoDataForSlot, "slot %d not in db due to checkpoint sync", ps)
|
||||
}
|
||||
// Does the state exist in the hot state cache.
|
||||
if s.hotStateCache.has(parentRoot) {
|
||||
return s.hotStateCache.get(parentRoot), nil
|
||||
@ -283,3 +285,11 @@ func (s *State) CombinedCache() *CombinedCache {
|
||||
}
|
||||
return &CombinedCache{getters: getters}
|
||||
}
|
||||
|
||||
func (s *State) slotAvailable(slot types.Slot) bool {
|
||||
// default to assuming node was initialized from genesis - backfill only needs to be specified for checkpoint sync
|
||||
if s.backfillStatus == nil {
|
||||
return true
|
||||
}
|
||||
return s.backfillStatus.SlotCovered(slot)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/sync/backfill"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
@ -48,8 +49,7 @@ type State struct {
|
||||
finalizedInfo *finalizedInfo
|
||||
epochBoundaryStateCache *epochBoundaryState
|
||||
saveHotStateDB *saveHotStateDbConfig
|
||||
minSlot types.Slot
|
||||
beaconDBInitType BeaconDBInitType
|
||||
backfillStatus *backfill.Status
|
||||
}
|
||||
|
||||
// This tracks the config in the event of long non-finality,
|
||||
@ -71,28 +71,15 @@ type finalizedInfo struct {
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func WithMinimumSlot(min types.Slot) StateGenOption {
|
||||
return func(s *State) {
|
||||
s.minSlot = min
|
||||
}
|
||||
}
|
||||
|
||||
type BeaconDBInitType uint
|
||||
|
||||
const (
|
||||
BeaconDBInitTypeGenesisState = iota
|
||||
BeaconDBInitTypeCheckpoint
|
||||
)
|
||||
|
||||
func WithInitType(t BeaconDBInitType) StateGenOption {
|
||||
return func(s *State) {
|
||||
s.beaconDBInitType = t
|
||||
}
|
||||
}
|
||||
|
||||
// StateGenOption is a functional option for controlling the initialization of a *State value
|
||||
type StateGenOption func(*State)
|
||||
|
||||
func WithBackfillStatus(bfs *backfill.Status) StateGenOption {
|
||||
return func(sg *State) {
|
||||
sg.backfillStatus = bfs
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new state management object.
|
||||
func New(beaconDB db.NoHeadAccessDatabase, opts ...StateGenOption) *State {
|
||||
s := &State{
|
||||
@ -104,12 +91,11 @@ func New(beaconDB db.NoHeadAccessDatabase, opts ...StateGenOption) *State {
|
||||
saveHotStateDB: &saveHotStateDbConfig{
|
||||
duration: defaultHotStateDBInterval,
|
||||
},
|
||||
// defaults to minimumSlot of zero (genesis), overridden by checkpoint sync
|
||||
minSlot: params.BeaconConfig().GenesisSlot,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(s)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@ -167,7 +153,3 @@ func (s *State) finalizedState() state.BeaconState {
|
||||
defer s.finalizedInfo.lock.RUnlock()
|
||||
return s.finalizedInfo.state.Copy()
|
||||
}
|
||||
|
||||
func (s *State) minimumSlot() types.Slot {
|
||||
return s.minSlot
|
||||
}
|
||||
|
32
beacon-chain/sync/backfill/BUILD.bazel
Normal file
32
beacon-chain/sync/backfill/BUILD.bazel
Normal file
@ -0,0 +1,32 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["status.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/sync/backfill",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["status_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"//proto/prysm/v1alpha1/wrapper:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
],
|
||||
)
|
122
beacon-chain/sync/backfill/status.go
Normal file
122
beacon-chain/sync/backfill/status.go
Normal file
@ -0,0 +1,122 @@
|
||||
package backfill
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
)
|
||||
|
||||
// NewStatus correctly initializes a Status value with the required database value.
|
||||
func NewStatus(store BackfillDB) *Status {
|
||||
return &Status{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
// Status provides a way to update and query the status of a backfill process that may be necessary to track when
|
||||
// a node was initialized via checkpoint sync. With checkpoint sync, there will be a gap in node history from genesis
|
||||
// until the checkpoint sync origin block. Status provides the means to update the value keeping track of the lower
|
||||
// end of the missing block range via the Advance() method, to check whether a Slot is missing from the database
|
||||
// via the SlotCovered() method, and to see the current StartGap() and EndGap().
|
||||
type Status struct {
|
||||
start types.Slot
|
||||
end types.Slot
|
||||
store BackfillDB
|
||||
genesisSync bool
|
||||
}
|
||||
|
||||
// SlotCovered uses StartGap() and EndGap() to determine if the given slot is covered by the current chain history.
|
||||
// If the slot is <= StartGap(), or >= EndGap(), the result is true.
|
||||
// If the slot is between StartGap() and EndGap(), the result is false.
|
||||
func (s *Status) SlotCovered(sl types.Slot) bool {
|
||||
// short circuit if the node was synced from genesis
|
||||
if s.genesisSync {
|
||||
return true
|
||||
}
|
||||
if s.StartGap() < sl && sl < s.EndGap() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// StartGap returns the slot at the beginning of the range that needs to be backfilled.
|
||||
func (s *Status) StartGap() types.Slot {
|
||||
return s.start
|
||||
}
|
||||
|
||||
// EndGap returns the slot at the end of the range that needs to be backfilled.
|
||||
func (s *Status) EndGap() types.Slot {
|
||||
return s.end
|
||||
}
|
||||
|
||||
var ErrAdvancePastOrigin = errors.New("cannot advance backfill Status beyond the origin checkpoint slot")
|
||||
|
||||
// Advance advances the backfill position to the given slot & root.
|
||||
// It updates the backfill block root entry in the database,
|
||||
// and also updates the Status value's copy of the backfill position slot.
|
||||
func (s *Status) Advance(ctx context.Context, upTo types.Slot, root [32]byte) error {
|
||||
if upTo > s.end {
|
||||
return errors.Wrapf(ErrAdvancePastOrigin, "advance slot=%d, origin slot=%d", upTo, s.end)
|
||||
}
|
||||
s.start = upTo
|
||||
return s.store.SaveBackfillBlockRoot(ctx, root)
|
||||
}
|
||||
|
||||
// Reload queries the database for backfill status, initializing the internal data and validating the database state.
|
||||
func (s *Status) Reload(ctx context.Context) error {
|
||||
cpRoot, err := s.store.OriginCheckpointBlockRoot(ctx)
|
||||
if err != nil {
|
||||
// mark genesis sync and short circuit further lookups
|
||||
if errors.Is(err, db.ErrNotFoundOriginBlockRoot) {
|
||||
s.genesisSync = true
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
cpBlock, err := s.store.Block(ctx, cpRoot)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving block for origin checkpoint root=%#x", cpRoot)
|
||||
}
|
||||
if err := helpers.BeaconBlockIsNil(cpBlock); err != nil {
|
||||
return err
|
||||
}
|
||||
s.end = cpBlock.Block().Slot()
|
||||
|
||||
_, err = s.store.GenesisBlockRoot(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNotFoundGenesisBlockRoot) {
|
||||
return errors.Wrap(err, "genesis block root required for checkpoint sync")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
bfRoot, err := s.store.BackfillBlockRoot(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNotFoundBackfillBlockRoot) {
|
||||
return errors.Wrap(err, "found origin checkpoint block root, but no backfill block root")
|
||||
}
|
||||
return err
|
||||
}
|
||||
bfBlock, err := s.store.Block(ctx, bfRoot)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving block for backfill root=%#x", bfRoot)
|
||||
}
|
||||
if err := helpers.BeaconBlockIsNil(bfBlock); err != nil {
|
||||
return err
|
||||
}
|
||||
s.start = bfBlock.Block().Slot()
|
||||
return nil
|
||||
}
|
||||
|
||||
// BackfillDB describes the set of DB methods that the Status type needs to function.
|
||||
type BackfillDB interface {
|
||||
SaveBackfillBlockRoot(ctx context.Context, blockRoot [32]byte) error
|
||||
GenesisBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
OriginCheckpointBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
BackfillBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
Block(ctx context.Context, blockRoot [32]byte) (block.SignedBeaconBlock, error)
|
||||
}
|
359
beacon-chain/sync/backfill/status_test.go
Normal file
359
beacon-chain/sync/backfill/status_test.go
Normal file
@ -0,0 +1,359 @@
|
||||
package backfill
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
)
|
||||
|
||||
var errEmptyMockDBMethod = errors.New("uninitialized mock db method called")
|
||||
|
||||
type mockBackfillDB struct {
|
||||
saveBackfillBlockRoot func(ctx context.Context, blockRoot [32]byte) error
|
||||
genesisBlockRoot func(ctx context.Context) ([32]byte, error)
|
||||
originCheckpointBlockRoot func(ctx context.Context) ([32]byte, error)
|
||||
backfillBlockRoot func(ctx context.Context) ([32]byte, error)
|
||||
block func(ctx context.Context, blockRoot [32]byte) (block.SignedBeaconBlock, error)
|
||||
}
|
||||
|
||||
var _ BackfillDB = &mockBackfillDB{}
|
||||
|
||||
func (db *mockBackfillDB) SaveBackfillBlockRoot(ctx context.Context, blockRoot [32]byte) error {
|
||||
if db.saveBackfillBlockRoot != nil {
|
||||
return db.saveBackfillBlockRoot(ctx, blockRoot)
|
||||
}
|
||||
return errEmptyMockDBMethod
|
||||
}
|
||||
|
||||
func (db *mockBackfillDB) GenesisBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
if db.genesisBlockRoot != nil {
|
||||
return db.genesisBlockRoot(ctx)
|
||||
}
|
||||
return [32]byte{}, errEmptyMockDBMethod
|
||||
}
|
||||
|
||||
func (db *mockBackfillDB) OriginCheckpointBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
if db.originCheckpointBlockRoot != nil {
|
||||
return db.originCheckpointBlockRoot(ctx)
|
||||
}
|
||||
return [32]byte{}, errEmptyMockDBMethod
|
||||
}
|
||||
|
||||
func (db *mockBackfillDB) BackfillBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
if db.backfillBlockRoot != nil {
|
||||
return db.backfillBlockRoot(ctx)
|
||||
}
|
||||
return [32]byte{}, errEmptyMockDBMethod
|
||||
}
|
||||
|
||||
func (db *mockBackfillDB) Block(ctx context.Context, blockRoot [32]byte) (block.SignedBeaconBlock, error) {
|
||||
if db.block != nil {
|
||||
return db.block(ctx, blockRoot)
|
||||
}
|
||||
return nil, errEmptyMockDBMethod
|
||||
}
|
||||
|
||||
func TestSlotCovered(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
slot types.Slot
|
||||
status *Status
|
||||
result bool
|
||||
}{
|
||||
{
|
||||
name: "below start true",
|
||||
status: &Status{start: 1},
|
||||
slot: 0,
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
name: "above end true",
|
||||
status: &Status{end: 1},
|
||||
slot: 2,
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
name: "equal end true",
|
||||
status: &Status{end: 1},
|
||||
slot: 1,
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
name: "equal start true",
|
||||
status: &Status{start: 2},
|
||||
slot: 2,
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
name: "between false",
|
||||
status: &Status{start: 1, end: 3},
|
||||
slot: 2,
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
name: "genesisSync always true",
|
||||
status: &Status{genesisSync: true},
|
||||
slot: 100,
|
||||
result: true,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
result := c.status.SlotCovered(c.slot)
|
||||
require.Equal(t, c.result, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdvance(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
saveBackfillBuf := make([][32]byte, 0)
|
||||
mdb := &mockBackfillDB{
|
||||
saveBackfillBlockRoot: func(ctx context.Context, root [32]byte) error {
|
||||
saveBackfillBuf = append(saveBackfillBuf, root)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
s := &Status{end: 100, store: mdb}
|
||||
var root [32]byte
|
||||
copy(root[:], []byte{0x23, 0x23})
|
||||
require.NoError(t, s.Advance(ctx, 90, root))
|
||||
require.Equal(t, root, saveBackfillBuf[0])
|
||||
not := s.SlotCovered(95)
|
||||
require.Equal(t, false, not)
|
||||
|
||||
// this should still be len 1 after failing to advance
|
||||
require.Equal(t, 1, len(saveBackfillBuf))
|
||||
require.ErrorIs(t, s.Advance(ctx, s.end+1, root), ErrAdvancePastOrigin)
|
||||
// this has an element in it from the previous test, there shouldn't be an additional one
|
||||
require.Equal(t, 1, len(saveBackfillBuf))
|
||||
}
|
||||
|
||||
func goodBlockRoot(root [32]byte) func(ctx context.Context) ([32]byte, error) {
|
||||
return func(ctx context.Context) ([32]byte, error) {
|
||||
return root, nil
|
||||
}
|
||||
}
|
||||
|
||||
func setupTestBlock(slot types.Slot) (block.SignedBeaconBlock, error) {
|
||||
bRaw := util.NewBeaconBlock()
|
||||
b, err := wrapper.WrappedSignedBeaconBlock(bRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, wrapper.SetBlockSlot(b, slot)
|
||||
}
|
||||
|
||||
func TestReload(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
derp := errors.New("derp")
|
||||
|
||||
originSlot := types.Slot(100)
|
||||
var originRoot [32]byte
|
||||
copy(originRoot[:], []byte{0x01})
|
||||
originBlock, err := setupTestBlock(originSlot)
|
||||
require.NoError(t, err)
|
||||
|
||||
backfillSlot := types.Slot(50)
|
||||
var backfillRoot [32]byte
|
||||
copy(originRoot[:], []byte{0x02})
|
||||
backfillBlock, err := setupTestBlock(backfillSlot)
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
db BackfillDB
|
||||
err error
|
||||
expected *Status
|
||||
}{
|
||||
{
|
||||
name: "origin not found, implying genesis sync ",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: func(ctx context.Context) ([32]byte, error) {
|
||||
return [32]byte{}, db.ErrNotFoundOriginBlockRoot
|
||||
}},
|
||||
expected: &Status{genesisSync: true},
|
||||
},
|
||||
{
|
||||
name: "genesis not found error",
|
||||
err: db.ErrNotFoundGenesisBlockRoot,
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: func(ctx context.Context) ([32]byte, error) {
|
||||
return [32]byte{}, db.ErrNotFoundGenesisBlockRoot
|
||||
},
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
switch root {
|
||||
case originRoot:
|
||||
return originBlock, nil
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "other genesis error",
|
||||
err: derp,
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: func(ctx context.Context) ([32]byte, error) {
|
||||
return [32]byte{}, derp
|
||||
},
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
switch root {
|
||||
case originRoot:
|
||||
return originBlock, nil
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "origin other error",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: func(ctx context.Context) ([32]byte, error) {
|
||||
return [32]byte{}, derp
|
||||
}},
|
||||
err: derp,
|
||||
},
|
||||
{
|
||||
name: "origin root found, block missing",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
err: helpers.ErrNilSignedBeaconBlock,
|
||||
},
|
||||
{
|
||||
name: "origin root found, block error",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
return nil, derp
|
||||
},
|
||||
},
|
||||
err: derp,
|
||||
},
|
||||
{
|
||||
name: "origin root found, block found, backfill root not found",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
return originBlock, nil
|
||||
},
|
||||
backfillBlockRoot: func(ctx context.Context) ([32]byte, error) {
|
||||
return [32]byte{}, db.ErrNotFoundBackfillBlockRoot
|
||||
},
|
||||
},
|
||||
err: db.ErrNotFoundBackfillBlockRoot,
|
||||
},
|
||||
{
|
||||
name: "origin root found, block found, random backfill root err",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
switch root {
|
||||
case originRoot:
|
||||
return originBlock, nil
|
||||
case backfillRoot:
|
||||
return nil, nil
|
||||
}
|
||||
return nil, derp
|
||||
},
|
||||
backfillBlockRoot: func(ctx context.Context) ([32]byte, error) {
|
||||
return [32]byte{}, derp
|
||||
},
|
||||
},
|
||||
err: derp,
|
||||
},
|
||||
{
|
||||
name: "origin root found, block found, backfill root found, backfill block not found",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
switch root {
|
||||
case originRoot:
|
||||
return originBlock, nil
|
||||
case backfillRoot:
|
||||
return nil, nil
|
||||
}
|
||||
return nil, derp
|
||||
},
|
||||
backfillBlockRoot: goodBlockRoot(backfillRoot),
|
||||
},
|
||||
err: helpers.ErrNilSignedBeaconBlock,
|
||||
},
|
||||
{
|
||||
name: "origin root found, block found, backfill root found, backfill block random err",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
switch root {
|
||||
case originRoot:
|
||||
return originBlock, nil
|
||||
case backfillRoot:
|
||||
return nil, derp
|
||||
}
|
||||
return nil, errors.New("not derp")
|
||||
},
|
||||
backfillBlockRoot: goodBlockRoot(backfillRoot),
|
||||
},
|
||||
err: derp,
|
||||
},
|
||||
{
|
||||
name: "complete happy path",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
switch root {
|
||||
case originRoot:
|
||||
return originBlock, nil
|
||||
case backfillRoot:
|
||||
return backfillBlock, nil
|
||||
}
|
||||
return nil, errors.New("not derp")
|
||||
},
|
||||
backfillBlockRoot: goodBlockRoot(backfillRoot),
|
||||
},
|
||||
err: derp,
|
||||
expected: &Status{genesisSync: false, start: backfillSlot, end: originSlot},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
s := &Status{
|
||||
store: c.db,
|
||||
}
|
||||
err := s.Reload(ctx)
|
||||
if err != nil {
|
||||
require.ErrorIs(t, err, c.err)
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err)
|
||||
if c.expected == nil {
|
||||
continue
|
||||
}
|
||||
require.Equal(t, c.expected.genesisSync, s.genesisSync)
|
||||
require.Equal(t, c.expected.start, s.start)
|
||||
require.Equal(t, c.expected.end, s.end)
|
||||
}
|
||||
}
|
17
beacon-chain/sync/checkpoint/BUILD.bazel
Normal file
17
beacon-chain/sync/checkpoint/BUILD.bazel
Normal file
@ -0,0 +1,17 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"api.go",
|
||||
"file.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/sync/checkpoint",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api/client/beacon:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
],
|
||||
)
|
35
beacon-chain/sync/checkpoint/api.go
Normal file
35
beacon-chain/sync/checkpoint/api.go
Normal file
@ -0,0 +1,35 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/api/client/beacon"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
)
|
||||
|
||||
// APIInitializer manages initializing the beacon node using checkpoint sync, retrieving the checkpoint state and root
|
||||
// from the remote beacon node api.
|
||||
type APIInitializer struct {
|
||||
c *beacon.Client
|
||||
}
|
||||
|
||||
// NewAPIInitializer creates an APIInitializer, handling the set up of a beacon node api client
|
||||
// using the provided host string.
|
||||
func NewAPIInitializer(beaconNodeHost string) (*APIInitializer, error) {
|
||||
c, err := beacon.NewClient(beaconNodeHost)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to parse beacon node url or hostname - %s", beaconNodeHost)
|
||||
}
|
||||
return &APIInitializer{c: c}, nil
|
||||
}
|
||||
|
||||
// Initialize downloads origin state and block for checkpoint sync and initializes database records to
|
||||
// prepare the node to begin syncing from that point.
|
||||
func (dl *APIInitializer) Initialize(ctx context.Context, d db.Database) error {
|
||||
od, err := beacon.DownloadOriginData(ctx, dl.c)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error retrieving checkpoint origin state and block")
|
||||
}
|
||||
return d.SaveOrigin(ctx, od.StateBytes(), od.BlockBytes())
|
||||
}
|
67
beacon-chain/sync/checkpoint/file.go
Normal file
67
beacon-chain/sync/checkpoint/file.go
Normal file
@ -0,0 +1,67 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/io/file"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
)
|
||||
|
||||
// Initializer describes a type that is able to obtain the checkpoint sync data (BeaconState and SignedBeaconBlock)
|
||||
// in some way and perform database setup to prepare the beacon node for syncing from the given checkpoint.
|
||||
// See FileInitializer and APIInitializer.
|
||||
type Initializer interface {
|
||||
Initialize(ctx context.Context, d db.Database) error
|
||||
}
|
||||
|
||||
// NewFileInitializer validates the given path information and creates an Initializer which will
|
||||
// use the provided state and block files to prepare the node for checkpoint sync.
|
||||
func NewFileInitializer(blockPath string, statePath string) (*FileInitializer, error) {
|
||||
var err error
|
||||
if err = existsAndIsFile(blockPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = existsAndIsFile(statePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// stat just to make sure it actually exists and is a file
|
||||
return &FileInitializer{blockPath: blockPath, statePath: statePath}, nil
|
||||
}
|
||||
|
||||
// FileInitializer initializes a beacon-node database to use checkpoint sync,
|
||||
// using ssz-encoded block and state data stored in files on the local filesystem.
|
||||
type FileInitializer struct {
|
||||
blockPath string
|
||||
statePath string
|
||||
}
|
||||
|
||||
// Initialize is called in the BeaconNode db startup code if an Initializer is present.
|
||||
// Initialize does what is needed to prepare the beacon node database for syncing from the weak subjectivity checkpoint.
|
||||
func (fi *FileInitializer) Initialize(ctx context.Context, d db.Database) error {
|
||||
serBlock, err := file.ReadFileAsBytes(fi.blockPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error reading block file %s for checkpoint sync init", fi.blockPath)
|
||||
}
|
||||
serState, err := file.ReadFileAsBytes(fi.statePath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error reading state file %s for checkpoint sync init", fi.blockPath)
|
||||
}
|
||||
return d.SaveOrigin(ctx, serState, serBlock)
|
||||
}
|
||||
|
||||
var _ Initializer = &FileInitializer{}
|
||||
|
||||
func existsAndIsFile(path string) error {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error checking existence of ssz-encoded file %s for checkpoint sync init", path)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fmt.Errorf("%s is a directory, please specify full path to file", path)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -21,6 +21,7 @@ go_library(
|
||||
"//cmd/beacon-chain/db:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//cmd/beacon-chain/powchain:go_default_library",
|
||||
"//cmd/beacon-chain/sync/checkpoint:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//io/logs:go_default_library",
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
|
||||
// FlagOptions for blockchain service flag configurations.
|
||||
func FlagOptions(c *cli.Context) ([]blockchain.Option, error) {
|
||||
wsp := c.String(flags.WeakSubjectivityCheckpt.Name)
|
||||
wsp := c.String(flags.WeakSubjectivityCheckpoint.Name)
|
||||
wsCheckpt, err := helpers.ParseWeakSubjectivityInputString(wsp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -185,13 +185,6 @@ var (
|
||||
Name: "network-id",
|
||||
Usage: "Sets the network id of the beacon chain.",
|
||||
}
|
||||
// WeakSubjectivityCheckpt defines the weak subjectivity checkpoint the node must sync through to defend against long range attacks.
|
||||
WeakSubjectivityCheckpt = &cli.StringFlag{
|
||||
Name: "weak-subjectivity-checkpoint",
|
||||
Usage: "Input in `block_root:epoch_number` format. This guarantees that syncing leads to the given Weak Subjectivity Checkpoint along the canonical chain. " +
|
||||
"If such a sync is not possible, the node will treat it a critical and irrecoverable failure",
|
||||
Value: "",
|
||||
}
|
||||
// Eth1HeaderReqLimit defines a flag to set the maximum number of headers that a deposit log query can fetch. If none is set, 1000 will be the limit.
|
||||
Eth1HeaderReqLimit = &cli.Uint64Flag{
|
||||
Name: "eth1-header-req-limit",
|
||||
@ -204,6 +197,14 @@ var (
|
||||
Usage: "Load a genesis state from ssz file. Testnet genesis files can be found in the " +
|
||||
"eth2-clients/eth2-testnets repository on github.",
|
||||
}
|
||||
// WeakSubjectivityCheckpoint defines the weak subjectivity checkpoint the node must sync through to defend against long range attacks.
|
||||
WeakSubjectivityCheckpoint = &cli.StringFlag{
|
||||
Name: "weak-subjectivity-checkpoint",
|
||||
Usage: "Input in `block_root:epoch_number` format." +
|
||||
" This guarantees that syncing leads to the given Weak Subjectivity Checkpoint along the canonical chain. " +
|
||||
"If such a sync is not possible, the node will treat it as a critical and irrecoverable failure",
|
||||
Value: "",
|
||||
}
|
||||
// MinPeersPerSubnet defines a flag to set the minimum number of peers that a node will attempt to peer with for a subnet.
|
||||
MinPeersPerSubnet = &cli.Uint64Flag{
|
||||
Name: "minimum-peers-per-subnet",
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
dbcommands "github.com/prysmaticlabs/prysm/cmd/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags"
|
||||
powchaincmd "github.com/prysmaticlabs/prysm/cmd/beacon-chain/powchain"
|
||||
"github.com/prysmaticlabs/prysm/cmd/beacon-chain/sync/checkpoint"
|
||||
"github.com/prysmaticlabs/prysm/config/features"
|
||||
"github.com/prysmaticlabs/prysm/io/file"
|
||||
"github.com/prysmaticlabs/prysm/io/logs"
|
||||
@ -62,7 +63,7 @@ var appFlags = []cli.Flag{
|
||||
flags.HistoricalSlasherNode,
|
||||
flags.ChainID,
|
||||
flags.NetworkID,
|
||||
flags.WeakSubjectivityCheckpt,
|
||||
flags.WeakSubjectivityCheckpoint,
|
||||
flags.Eth1HeaderReqLimit,
|
||||
flags.GenesisStatePath,
|
||||
flags.MinPeersPerSubnet,
|
||||
@ -118,6 +119,9 @@ var appFlags = []cli.Flag{
|
||||
cmd.BoltMMapInitialSizeFlag,
|
||||
cmd.ValidatorMonitorIndicesFlag,
|
||||
cmd.ApiTimeoutFlag,
|
||||
checkpoint.BlockPath,
|
||||
checkpoint.StatePath,
|
||||
checkpoint.RemoteURL,
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -242,6 +246,13 @@ func startNode(ctx *cli.Context) error {
|
||||
node.WithBlockchainFlagOptions(blockchainFlagOpts),
|
||||
node.WithPowchainFlagOptions(powchainFlagOpts),
|
||||
}
|
||||
cptOpts, err := checkpoint.BeaconNodeOptions(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cptOpts != nil {
|
||||
opts = append(opts, cptOpts)
|
||||
}
|
||||
beacon, err := node.New(ctx, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
|
14
cmd/beacon-chain/sync/checkpoint/BUILD.bazel
Normal file
14
cmd/beacon-chain/sync/checkpoint/BUILD.bazel
Normal file
@ -0,0 +1,14 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["options.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/cmd/beacon-chain/sync/checkpoint",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/node:go_default_library",
|
||||
"//beacon-chain/sync/checkpoint:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
68
cmd/beacon-chain/sync/checkpoint/options.go
Normal file
68
cmd/beacon-chain/sync/checkpoint/options.go
Normal file
@ -0,0 +1,68 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/node"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/sync/checkpoint"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
// StatePath defines a flag to start the beacon chain from a give genesis state file.
|
||||
StatePath = &cli.PathFlag{
|
||||
Name: "checkpoint-state",
|
||||
Usage: "Rather than syncing from genesis, you can start processing from a ssz-serialized BeaconState+Block." +
|
||||
" This flag allows you to specify a local file containing the checkpoint BeaconState to load.",
|
||||
}
|
||||
// BlockPath is required when using StatePath to also provide the latest integrated block.
|
||||
BlockPath = &cli.PathFlag{
|
||||
Name: "checkpoint-block",
|
||||
Usage: "Rather than syncing from genesis, you can start processing from a ssz-serialized BeaconState+Block." +
|
||||
" This flag allows you to specify a local file containing the checkpoint Block to load.",
|
||||
}
|
||||
RemoteURL = &cli.StringFlag{
|
||||
Name: "checkpoint-sync-url",
|
||||
Usage: "URL of a synced beacon node to trust in obtaining checkpoint sync data. " +
|
||||
"As an additional safety measure, it is strongly recommended to only use this option in conjunction with " +
|
||||
"--weak-subjectivity-checkpoint flag",
|
||||
}
|
||||
)
|
||||
|
||||
// BeaconNodeOptions is responsible for determining if the checkpoint sync options have been used, and if so,
|
||||
// reading the block and state ssz-serialized values from the filesystem locations specified and preparing a
|
||||
// checkpoint.Initializer, which uses the provided io.ReadClosers to initialize the beacon node database.
|
||||
func BeaconNodeOptions(c *cli.Context) (node.Option, error) {
|
||||
blockPath := c.Path(BlockPath.Name)
|
||||
statePath := c.Path(StatePath.Name)
|
||||
remoteURL := c.String(RemoteURL.Name)
|
||||
if remoteURL != "" {
|
||||
return func(node *node.BeaconNode) error {
|
||||
var err error
|
||||
node.CheckpointInitializer, err = checkpoint.NewAPIInitializer(remoteURL)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error while constructing beacon node api client for checkpoint sync")
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
if blockPath == "" && statePath == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if blockPath != "" && statePath == "" {
|
||||
return nil, fmt.Errorf("--checkpoint-block specified, but not --checkpoint-state. both are required")
|
||||
}
|
||||
if blockPath == "" && statePath != "" {
|
||||
return nil, fmt.Errorf("--checkpoint-state specified, but not --checkpoint-block. both are required")
|
||||
}
|
||||
|
||||
return func(node *node.BeaconNode) (err error) {
|
||||
node.CheckpointInitializer, err = checkpoint.NewFileInitializer(blockPath, statePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error preparing to initialize checkpoint from local ssz files")
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/prysmaticlabs/prysm/cmd"
|
||||
"github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags"
|
||||
"github.com/prysmaticlabs/prysm/cmd/beacon-chain/sync/checkpoint"
|
||||
"github.com/prysmaticlabs/prysm/config/features"
|
||||
"github.com/prysmaticlabs/prysm/runtime/debug"
|
||||
"github.com/urfave/cli/v2"
|
||||
@ -75,6 +76,9 @@ var appHelpFlagGroups = []flagGroup{
|
||||
cmd.BoltMMapInitialSizeFlag,
|
||||
cmd.ValidatorMonitorIndicesFlag,
|
||||
cmd.ApiTimeoutFlag,
|
||||
checkpoint.BlockPath,
|
||||
checkpoint.StatePath,
|
||||
checkpoint.RemoteURL,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -121,7 +125,7 @@ var appHelpFlagGroups = []flagGroup{
|
||||
flags.HistoricalSlasherNode,
|
||||
flags.ChainID,
|
||||
flags.NetworkID,
|
||||
flags.WeakSubjectivityCheckpt,
|
||||
flags.WeakSubjectivityCheckpoint,
|
||||
flags.Eth1HeaderReqLimit,
|
||||
flags.GenesisStatePath,
|
||||
flags.MinPeersPerSubnet,
|
||||
|
@ -30,6 +30,8 @@ func WrapFlags(flags []cli.Flag) []cli.Flag {
|
||||
f = altsrc.NewUint64Flag(t)
|
||||
case *cli.UintFlag:
|
||||
f = altsrc.NewUintFlag(t)
|
||||
case *cli.PathFlag:
|
||||
f = altsrc.NewPathFlag(t)
|
||||
case *cli.Int64Flag:
|
||||
// Int64Flag does not work. See https://github.com/prysmaticlabs/prysm/issues/6478
|
||||
panic(fmt.Sprintf("unsupported flag type type %T", f))
|
||||
|
Loading…
Reference in New Issue
Block a user