Compare commits

...

9 Commits

Author SHA1 Message Date
terence tsao
fd9471667b Merge branch 'develop' into kiln-payload-cache 2022-03-29 11:20:16 -07:00
terence tsao
107f1f23af Update BUILD.bazel 2022-03-29 11:20:03 -07:00
terence tsao
39686510f0 Build 2022-03-29 11:14:53 -07:00
terence tsao
040b4006e0 Update BUILD.bazel 2022-03-29 11:10:24 -07:00
Nishant Das
d4038fb752 add rlock (#10444) 2022-03-29 06:55:41 -07:00
Raul Jordan
56ab5872bb Add Histogram Response Buckets to Engine API Methods (#10414)
* gaz

* defer payload func

* amend

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2022-03-28 22:17:31 +00:00
Raul Jordan
eb6d68a4b1 Deterministic Proposer Root Boosting in Forkchoice (#10427)
* begin refactor

* fix doubly linked tree tests

* pass

* fix up

* gaz

* buidl

* buidl more

* comment

* args check

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2022-03-28 21:34:41 +00:00
kasey
7920528ede 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>
2022-03-28 21:01:55 +00:00
Raul Jordan
1af3c07ec5 Stop Checking Transition Configuration Post-Merge (#10413)
* no longer check transition config after the merge

* nil check

* better payload check

* stop checking post merge based on received blocks

* add proper test

* gaz

* fix test

* lint

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2022-03-28 17:14:02 +00:00
56 changed files with 1327 additions and 225 deletions

View File

@@ -52,6 +52,7 @@ go_library(
"//beacon-chain/forkchoice:go_default_library",
"//beacon-chain/forkchoice/doubly-linked-tree:go_default_library",
"//beacon-chain/forkchoice/protoarray:go_default_library",
"//beacon-chain/forkchoice/types:go_default_library",
"//beacon-chain/operations/attestations:go_default_library",
"//beacon-chain/operations/slashings:go_default_library",
"//beacon-chain/operations/voluntaryexits:go_default_library",

View File

@@ -12,6 +12,7 @@ import (
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
coreTime "github.com/prysmaticlabs/prysm/beacon-chain/core/time"
"github.com/prysmaticlabs/prysm/beacon-chain/core/transition"
forkchoicetypes "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/types"
"github.com/prysmaticlabs/prysm/beacon-chain/state"
"github.com/prysmaticlabs/prysm/config/features"
"github.com/prysmaticlabs/prysm/config/params"
@@ -121,9 +122,13 @@ func (s *Service) onBlock(ctx context.Context, signed block.SignedBeaconBlock, b
// We add a proposer score boost to fork choice for the block root if applicable, right after
// running a successful state transition for the block.
if err := s.cfg.ForkChoiceStore.BoostProposerRoot(
ctx, signed.Block().Slot(), blockRoot, s.genesisTime,
); err != nil {
secondsIntoSlot := uint64(time.Since(s.genesisTime).Seconds()) % params.BeaconConfig().SecondsPerSlot
if err := s.cfg.ForkChoiceStore.BoostProposerRoot(ctx, &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: blockRoot,
BlockSlot: signed.Block().Slot(),
CurrentSlot: slots.SinceGenesis(s.genesisTime),
SecondsIntoSlot: secondsIntoSlot,
}); err != nil {
return err
}

View File

@@ -18,6 +18,7 @@ import (
testDB "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
doublylinkedtree "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/doubly-linked-tree"
"github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/protoarray"
forkchoicetypes "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/types"
"github.com/prysmaticlabs/prysm/beacon-chain/state"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1"
@@ -253,7 +254,13 @@ func TestStore_OnBlock_ProposerBoostEarly(t *testing.T) {
service, err := NewService(ctx, opts...)
require.NoError(t, err)
require.NoError(t, service.cfg.ForkChoiceStore.BoostProposerRoot(ctx, 0, [32]byte{'A'}, time.Now()))
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: [32]byte{'A'},
BlockSlot: types.Slot(0),
CurrentSlot: 0,
SecondsIntoSlot: 0,
}
require.NoError(t, service.cfg.ForkChoiceStore.BoostProposerRoot(ctx, args))
_, err = service.cfg.ForkChoiceStore.Head(ctx, 0,
params.BeaconConfig().ZeroHash, []uint64{}, 0)
require.ErrorContains(t, "could not apply proposer boost score: invalid proposer boost root", err)

View File

@@ -258,7 +258,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
}
@@ -266,7 +266,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 {

View File

@@ -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
}

View File

@@ -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: &ethpb.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: &ethpb.Checkpoint{Epoch: 2},
finalizedEpoch: 1,
checkpt: &ethpb.Checkpoint{Root: bytesutil.PadTo([]byte{'a'}, 32), Epoch: blockEpoch},
finalizedEpoch: blockEpoch - 1,
},
{
name: "can't find the block in DB",
checkpt: &ethpb.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: &ethpb.Checkpoint{Root: r[:], Epoch: 2}, // Root belongs in epoch 1.
finalizedEpoch: 3,
checkpt: &ethpb.Checkpoint{Root: r[:], Epoch: blockEpoch - 2}, // Root belongs in epoch 1.
finalizedEpoch: blockEpoch - 1,
wantErr: errWSBlockNotFoundInEpoch,
},
{
name: "can verify and pass",
checkpt: &ethpb.Checkpoint{Root: r[:], Epoch: 1},
finalizedEpoch: 3,
checkpt: &ethpb.Checkpoint{Root: r[:], Epoch: blockEpoch},
finalizedEpoch: blockEpoch + 1,
},
{
name: "equal epoch",
checkpt: &ethpb.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},

View File

@@ -27,6 +27,7 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/cache",
visibility = [
"//beacon-chain:__subpackages__",
"//testing/spectest:__subpackages__",
"//tools:__subpackages__",
],
deps = [

View File

@@ -235,6 +235,9 @@ func PayloadToHeader(payload *enginev1.ExecutionPayload) (*ethpb.ExecutionPayloa
}
func isEmptyPayload(p *enginev1.ExecutionPayload) bool {
if p == nil {
return true
}
if !bytes.Equal(p.ParentHash, make([]byte, fieldparams.RootLength)) {
return false
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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[:])
})
}

View File

@@ -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)

View File

@@ -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")

View File

@@ -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 := &ethpb.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
}

View File

@@ -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")

View File

@@ -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, &ethpb.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
}

View File

@@ -12,6 +12,7 @@ go_library(
"//testing/spectest:__subpackages__",
],
deps = [
"//beacon-chain/forkchoice/types:go_default_library",
"//config/fieldparams:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"@com_github_prysmaticlabs_eth2_types//:go_default_library",

View File

@@ -19,10 +19,10 @@ go_library(
"//testing/spectest:__subpackages__",
],
deps = [
"//beacon-chain/forkchoice/types:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//time/slots:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
@@ -45,6 +45,7 @@ go_test(
],
embed = [":go_default_library"],
deps = [
"//beacon-chain/forkchoice/types:go_default_library",
"//config/params:go_default_library",
"//crypto/hash:go_default_library",
"//encoding/bytesutil:go_default_library",

View File

@@ -2,12 +2,10 @@ package doublylinkedtree
import (
"context"
"time"
"github.com/pkg/errors"
types "github.com/prysmaticlabs/eth2-types"
forkchoicetypes "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/types"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/time/slots"
)
// BoostProposerRoot sets the block root which should be boosted during
@@ -19,17 +17,18 @@ import (
// is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT
// if get_current_slot(store) == block.slot and is_before_attesting_interval:
// store.proposer_boost_root = hash_tree_root(block)
func (f *ForkChoice) BoostProposerRoot(_ context.Context, blockSlot types.Slot, blockRoot [32]byte, genesisTime time.Time) error {
func (f *ForkChoice) BoostProposerRoot(_ context.Context, args *forkchoicetypes.ProposerBoostRootArgs) error {
if args == nil {
return errors.New("nil function args provided to BoostProposerRoot")
}
secondsPerSlot := params.BeaconConfig().SecondsPerSlot
timeIntoSlot := uint64(time.Since(genesisTime).Seconds()) % secondsPerSlot
isBeforeAttestingInterval := timeIntoSlot < secondsPerSlot/params.BeaconConfig().IntervalsPerSlot
currentSlot := slots.SinceGenesis(genesisTime)
isBeforeAttestingInterval := args.SecondsIntoSlot < secondsPerSlot/params.BeaconConfig().IntervalsPerSlot
// Only update the boosted proposer root to the incoming block root
// if the block is for the current, clock-based slot and the block was timely.
if currentSlot == blockSlot && isBeforeAttestingInterval {
if args.CurrentSlot == args.BlockSlot && isBeforeAttestingInterval {
f.store.proposerBoostLock.Lock()
f.store.proposerBoostRoot = blockRoot
f.store.proposerBoostRoot = args.BlockRoot
f.store.proposerBoostLock.Unlock()
}
return nil

View File

@@ -2,11 +2,11 @@ package doublylinkedtree
import (
"context"
"fmt"
"testing"
"time"
types "github.com/prysmaticlabs/eth2-types"
forkchoicetypes "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/types"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/testing/assert"
"github.com/prysmaticlabs/prysm/testing/require"
@@ -26,6 +26,11 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
balances[i] = 10
}
jEpoch, fEpoch := types.Epoch(0), types.Epoch(0)
t.Run("nil args check", func(t *testing.T) {
f := setup(jEpoch, fEpoch)
err := f.BoostProposerRoot(ctx, nil)
require.ErrorContains(t, "nil function args", err)
})
t.Run("back-propagates boost score to ancestors after proposer boosting", func(t *testing.T) {
f := setup(jEpoch, fEpoch)
@@ -128,9 +133,14 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
),
)
f.ProcessAttestation(ctx, []uint64{3}, newRoot, fEpoch)
threeSlots := 3 * params.BeaconConfig().SecondsPerSlot
genesisTime := time.Now().Add(-time.Second * time.Duration(threeSlots))
require.NoError(t, f.BoostProposerRoot(ctx, slot, newRoot, genesisTime))
clockSlot := types.Slot(3)
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: newRoot,
BlockSlot: slot,
CurrentSlot: clockSlot,
SecondsIntoSlot: 0,
}
require.NoError(t, f.BoostProposerRoot(ctx, args))
headRoot, err = f.Head(ctx, jEpoch, zeroHash, balances, fEpoch)
require.NoError(t, err)
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 3")
@@ -221,9 +231,13 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2")
// We boost the honest proposal at slot 2.
secondsPerSlot := time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot)
genesis := time.Now().Add(-2 * secondsPerSlot)
require.NoError(t, f.BoostProposerRoot(ctx, honestBlockSlot, honestBlock, genesis))
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: honestBlock,
BlockSlot: honestBlockSlot,
CurrentSlot: types.Slot(2),
SecondsIntoSlot: 0,
}
require.NoError(t, f.BoostProposerRoot(ctx, args))
// The maliciously withheld block has one vote.
votes := []uint64{1}
@@ -289,9 +303,13 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2")
// We boost the honest proposal at slot 2.
secondsPerSlot := time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot)
genesis := time.Now().Add(-2 * secondsPerSlot)
require.NoError(t, f.BoostProposerRoot(ctx, honestBlockSlot, honestBlock, genesis))
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: honestBlock,
BlockSlot: honestBlockSlot,
CurrentSlot: types.Slot(2),
SecondsIntoSlot: 0,
}
require.NoError(t, f.BoostProposerRoot(ctx, args))
// An attestation is received for B that has more voting power than C with the proposer boost,
// allowing B to then become the head if their attestation has enough adversarial votes.
@@ -345,9 +363,13 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
assert.Equal(t, c, r, "Incorrect head for justified epoch at slot 2")
// We boost C.
secondsPerSlot := time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot)
genesis := time.Now().Add(-2 * secondsPerSlot)
require.NoError(t, f.BoostProposerRoot(ctx, cSlot /* slot */, c, genesis))
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: c,
BlockSlot: cSlot,
CurrentSlot: types.Slot(2),
SecondsIntoSlot: 0,
}
require.NoError(t, f.BoostProposerRoot(ctx, args))
bSlot := types.Slot(1)
b := indexToHash(1)
@@ -393,8 +415,13 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
assert.Equal(t, c, r, "Expected C to remain the head")
// Block D receives the boost.
genesis = time.Now().Add(-3 * secondsPerSlot)
require.NoError(t, f.BoostProposerRoot(ctx, dSlot /* slot */, d, genesis))
args = &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: d,
BlockSlot: dSlot,
CurrentSlot: types.Slot(3),
SecondsIntoSlot: 0,
}
require.NoError(t, f.BoostProposerRoot(ctx, args))
// Ensure D becomes the head thanks to boosting.
r, err = f.Head(ctx, jEpoch, zeroHash, balances, fEpoch)
@@ -415,12 +442,15 @@ func TestForkChoice_BoostProposerRoot(t *testing.T) {
f := &ForkChoice{
store: &Store{},
}
// Genesis set to 1 slot ago.
genesis := time.Now().Add(-time.Duration(cfg.SecondsPerSlot) * time.Second)
blockRoot := [32]byte{'A'}
// Trying to boost a block from slot 0 should not work.
err := f.BoostProposerRoot(ctx, types.Slot(0), blockRoot, genesis)
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: blockRoot,
BlockSlot: types.Slot(0),
CurrentSlot: types.Slot(1),
SecondsIntoSlot: 0,
}
err := f.BoostProposerRoot(ctx, args)
require.NoError(t, err)
require.DeepEqual(t, [32]byte{}, f.store.proposerBoostRoot)
})
@@ -429,14 +459,18 @@ func TestForkChoice_BoostProposerRoot(t *testing.T) {
store: &Store{},
}
// Genesis set to 1 slot ago + X where X > attesting interval.
genesis := time.Now().Add(-time.Duration(cfg.SecondsPerSlot) * time.Second)
attestingInterval := time.Duration(cfg.SecondsPerSlot / cfg.IntervalsPerSlot)
greaterThanAttestingInterval := attestingInterval + 100*time.Millisecond
genesis = genesis.Add(-greaterThanAttestingInterval * time.Second)
blockRoot := [32]byte{'A'}
attestingInterval := time.Duration(cfg.SecondsPerSlot/cfg.IntervalsPerSlot) * time.Second
greaterThanAttestingInterval := attestingInterval + time.Second
// Trying to boost a block from slot 1 that is untimely should not work.
err := f.BoostProposerRoot(ctx, types.Slot(1), blockRoot, genesis)
blockRoot := [32]byte{'A'}
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: blockRoot,
BlockSlot: types.Slot(1),
CurrentSlot: 1,
SecondsIntoSlot: uint64(greaterThanAttestingInterval.Seconds()),
}
err := f.BoostProposerRoot(ctx, args)
require.NoError(t, err)
require.DeepEqual(t, [32]byte{}, f.store.proposerBoostRoot)
})
@@ -445,11 +479,15 @@ func TestForkChoice_BoostProposerRoot(t *testing.T) {
store: &Store{},
}
// Genesis set to 1 slot ago + 0 seconds into the attesting interval.
genesis := time.Now().Add(-time.Duration(cfg.SecondsPerSlot) * time.Second)
fmt.Println(genesis)
blockRoot := [32]byte{'A'}
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: blockRoot,
BlockSlot: types.Slot(1),
CurrentSlot: types.Slot(1),
SecondsIntoSlot: 0,
}
err := f.BoostProposerRoot(ctx, types.Slot(1), blockRoot, genesis)
err := f.BoostProposerRoot(ctx, args)
require.NoError(t, err)
require.DeepEqual(t, [32]byte{'A'}, f.store.proposerBoostRoot)
})
@@ -457,13 +495,16 @@ func TestForkChoice_BoostProposerRoot(t *testing.T) {
f := &ForkChoice{
store: &Store{},
}
// Genesis set to 1 slot ago + (attesting interval / 2).
genesis := time.Now().Add(-time.Duration(cfg.SecondsPerSlot) * time.Second)
blockRoot := [32]byte{'A'}
halfAttestingInterval := time.Second
genesis = genesis.Add(-halfAttestingInterval)
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: blockRoot,
BlockSlot: types.Slot(1),
CurrentSlot: types.Slot(1),
SecondsIntoSlot: uint64(halfAttestingInterval.Seconds()),
}
err := f.BoostProposerRoot(ctx, types.Slot(1), blockRoot, genesis)
err := f.BoostProposerRoot(ctx, args)
require.NoError(t, err)
require.DeepEqual(t, [32]byte{'A'}, f.store.proposerBoostRoot)
})

View File

@@ -2,9 +2,9 @@ package forkchoice
import (
"context"
"time"
types "github.com/prysmaticlabs/eth2-types"
forkchoicetypes "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/types"
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
pbrpc "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
)
@@ -51,7 +51,7 @@ type Pruner interface {
// ProposerBooster is able to boost the proposer's root score during fork choice.
type ProposerBooster interface {
BoostProposerRoot(ctx context.Context, blockSlot types.Slot, blockRoot [32]byte, genesisTime time.Time) error
BoostProposerRoot(ctx context.Context, args *forkchoicetypes.ProposerBoostRootArgs) error
ResetBoostedProposerRoot(ctx context.Context) error
}

View File

@@ -19,11 +19,11 @@ go_library(
"//testing/spectest:__subpackages__",
],
deps = [
"//beacon-chain/forkchoice/types:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//math:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//time/slots:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
@@ -46,6 +46,7 @@ go_test(
],
embed = [":go_default_library"],
deps = [
"//beacon-chain/forkchoice/types:go_default_library",
"//config/params:go_default_library",
"//crypto/hash:go_default_library",
"//encoding/bytesutil:go_default_library",

View File

@@ -2,12 +2,10 @@ package protoarray
import (
"context"
"time"
"github.com/pkg/errors"
types "github.com/prysmaticlabs/eth2-types"
"github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/types"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/time/slots"
)
// BoostProposerRoot sets the block root which should be boosted during
@@ -19,17 +17,18 @@ import (
// is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT
// if get_current_slot(store) == block.slot and is_before_attesting_interval:
// store.proposer_boost_root = hash_tree_root(block)
func (f *ForkChoice) BoostProposerRoot(_ context.Context, blockSlot types.Slot, blockRoot [32]byte, genesisTime time.Time) error {
func (f *ForkChoice) BoostProposerRoot(_ context.Context, args *types.ProposerBoostRootArgs) error {
if args == nil {
return errors.New("nil function args provided to BoostProposerRoot")
}
secondsPerSlot := params.BeaconConfig().SecondsPerSlot
timeIntoSlot := uint64(time.Since(genesisTime).Seconds()) % secondsPerSlot
isBeforeAttestingInterval := timeIntoSlot < secondsPerSlot/params.BeaconConfig().IntervalsPerSlot
currentSlot := slots.SinceGenesis(genesisTime)
isBeforeAttestingInterval := args.SecondsIntoSlot < secondsPerSlot/params.BeaconConfig().IntervalsPerSlot
// Only update the boosted proposer root to the incoming block root
// if the block is for the current, clock-based slot and the block was timely.
if currentSlot == blockSlot && isBeforeAttestingInterval {
if args.CurrentSlot == args.BlockSlot && isBeforeAttestingInterval {
f.store.proposerBoostLock.Lock()
f.store.proposerBoostRoot = blockRoot
f.store.proposerBoostRoot = args.BlockRoot
f.store.proposerBoostLock.Unlock()
}
return nil

View File

@@ -2,11 +2,11 @@ package protoarray
import (
"context"
"fmt"
"testing"
"time"
types "github.com/prysmaticlabs/eth2-types"
forkchoicetypes "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/types"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/testing/assert"
"github.com/prysmaticlabs/prysm/testing/require"
@@ -20,12 +20,17 @@ import (
// If the honest proposal is boosted at slot n+2, it will win against this attacker.
func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
ctx := context.Background()
jEpoch, fEpoch := types.Epoch(0), types.Epoch(0)
zeroHash := params.BeaconConfig().ZeroHash
balances := make([]uint64, 64) // 64 active validators.
for i := 0; i < len(balances); i++ {
balances[i] = 10
}
jEpoch, fEpoch := types.Epoch(0), types.Epoch(0)
t.Run("nil args check", func(t *testing.T) {
f := setup(jEpoch, fEpoch)
err := f.BoostProposerRoot(ctx, nil)
require.ErrorContains(t, "nil function args", err)
})
t.Run("back-propagates boost score to ancestors after proposer boosting", func(t *testing.T) {
f := setup(jEpoch, fEpoch)
@@ -88,8 +93,8 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
// 2
// |
// 3 <- HEAD
slot = types.Slot(2)
newRoot = indexToHash(2)
slot = types.Slot(3)
newRoot = indexToHash(3)
require.NoError(t,
f.InsertOptimisticBlock(
ctx,
@@ -128,15 +133,20 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
),
)
f.ProcessAttestation(ctx, []uint64{3}, newRoot, fEpoch)
threeSlots := 3 * params.BeaconConfig().SecondsPerSlot
genesisTime := time.Now().Add(-time.Second * time.Duration(threeSlots))
require.NoError(t, f.BoostProposerRoot(ctx, slot, newRoot, genesisTime))
clockSlot := types.Slot(3)
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: newRoot,
BlockSlot: slot,
CurrentSlot: clockSlot,
SecondsIntoSlot: 0,
}
require.NoError(t, f.BoostProposerRoot(ctx, args))
headRoot, err = f.Head(ctx, jEpoch, zeroHash, balances, fEpoch)
require.NoError(t, err)
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 3")
// Check the ancestor scores from the store.
require.Equal(t, 4, len(f.store.nodes))
require.Equal(t, 5, len(f.store.nodes))
// Expect nodes to have a boosted, back-propagated score.
// Ancestors have the added weights of their children. Genesis is a special exception at 0 weight,
@@ -163,7 +173,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
// middle instead of the normal progression of (44 -> 34 -> 24).
require.Equal(t, f.store.nodes[1].weight, uint64(54))
require.Equal(t, f.store.nodes[2].weight, uint64(44))
require.Equal(t, f.store.nodes[3].weight, uint64(24))
require.Equal(t, f.store.nodes[3].weight, uint64(34))
})
t.Run("vanilla ex ante attack", func(t *testing.T) {
f := setup(jEpoch, fEpoch)
@@ -189,7 +199,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
honestBlockSlot,
honestBlock,
zeroHash,
params.BeaconConfig().ZeroHash,
zeroHash,
jEpoch,
fEpoch,
),
@@ -206,7 +216,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
maliciouslyWithheldBlockSlot,
maliciouslyWithheldBlock,
zeroHash,
params.BeaconConfig().ZeroHash,
zeroHash,
jEpoch,
fEpoch,
),
@@ -218,9 +228,13 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2")
// We boost the honest proposal at slot 2.
secondsPerSlot := time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot)
genesis := time.Now().Add(-2 * secondsPerSlot)
require.NoError(t, f.BoostProposerRoot(ctx, honestBlockSlot, honestBlock, genesis))
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: honestBlock,
BlockSlot: honestBlockSlot,
CurrentSlot: types.Slot(2),
SecondsIntoSlot: 0,
}
require.NoError(t, f.BoostProposerRoot(ctx, args))
// The maliciously withheld block has one vote.
votes := []uint64{1}
@@ -255,7 +269,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
honestBlockSlot,
honestBlock,
zeroHash,
params.BeaconConfig().ZeroHash,
zeroHash,
jEpoch,
fEpoch,
),
@@ -274,7 +288,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
maliciouslyWithheldBlockSlot,
maliciouslyWithheldBlock,
zeroHash,
params.BeaconConfig().ZeroHash,
zeroHash,
jEpoch,
fEpoch,
),
@@ -286,9 +300,13 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2")
// We boost the honest proposal at slot 2.
secondsPerSlot := time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot)
genesis := time.Now().Add(-2 * secondsPerSlot)
require.NoError(t, f.BoostProposerRoot(ctx, honestBlockSlot, honestBlock, genesis))
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: honestBlock,
BlockSlot: honestBlockSlot,
CurrentSlot: types.Slot(2),
SecondsIntoSlot: 0,
}
require.NoError(t, f.BoostProposerRoot(ctx, args))
// An attestation is received for B that has more voting power than C with the proposer boost,
// allowing B to then become the head if their attestation has enough adversarial votes.
@@ -330,7 +348,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
cSlot,
c,
a, // parent
params.BeaconConfig().ZeroHash,
zeroHash,
jEpoch,
fEpoch,
),
@@ -342,9 +360,13 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
assert.Equal(t, c, r, "Incorrect head for justified epoch at slot 2")
// We boost C.
secondsPerSlot := time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot)
genesis := time.Now().Add(-2 * secondsPerSlot)
require.NoError(t, f.BoostProposerRoot(ctx, cSlot /* slot */, c, genesis))
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: c,
BlockSlot: cSlot,
CurrentSlot: types.Slot(2),
SecondsIntoSlot: 0,
}
require.NoError(t, f.BoostProposerRoot(ctx, args))
bSlot := types.Slot(1)
b := indexToHash(1)
@@ -354,7 +376,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
bSlot,
b,
a, // parent
params.BeaconConfig().ZeroHash,
zeroHash,
jEpoch,
fEpoch,
),
@@ -378,7 +400,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
dSlot,
d,
b, // parent
params.BeaconConfig().ZeroHash,
zeroHash,
jEpoch,
fEpoch,
),
@@ -390,8 +412,13 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
assert.Equal(t, c, r, "Expected C to remain the head")
// Block D receives the boost.
genesis = time.Now().Add(-3 * secondsPerSlot)
require.NoError(t, f.BoostProposerRoot(ctx, dSlot /* slot */, d, genesis))
args = &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: d,
BlockSlot: dSlot,
CurrentSlot: types.Slot(3),
SecondsIntoSlot: 0,
}
require.NoError(t, f.BoostProposerRoot(ctx, args))
// Ensure D becomes the head thanks to boosting.
r, err = f.Head(ctx, jEpoch, zeroHash, balances, fEpoch)
@@ -412,12 +439,15 @@ func TestForkChoice_BoostProposerRoot(t *testing.T) {
f := &ForkChoice{
store: &Store{},
}
// Genesis set to 1 slot ago.
genesis := time.Now().Add(-time.Duration(cfg.SecondsPerSlot) * time.Second)
blockRoot := [32]byte{'A'}
// Trying to boost a block from slot 0 should not work.
err := f.BoostProposerRoot(ctx, types.Slot(0), blockRoot, genesis)
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: blockRoot,
BlockSlot: types.Slot(0),
CurrentSlot: types.Slot(1),
SecondsIntoSlot: 0,
}
err := f.BoostProposerRoot(ctx, args)
require.NoError(t, err)
require.DeepEqual(t, [32]byte{}, f.store.proposerBoostRoot)
})
@@ -426,14 +456,18 @@ func TestForkChoice_BoostProposerRoot(t *testing.T) {
store: &Store{},
}
// Genesis set to 1 slot ago + X where X > attesting interval.
genesis := time.Now().Add(-time.Duration(cfg.SecondsPerSlot) * time.Second)
attestingInterval := time.Duration(cfg.SecondsPerSlot / cfg.IntervalsPerSlot)
greaterThanAttestingInterval := attestingInterval + 100*time.Millisecond
genesis = genesis.Add(-greaterThanAttestingInterval * time.Second)
blockRoot := [32]byte{'A'}
attestingInterval := time.Duration(cfg.SecondsPerSlot/cfg.IntervalsPerSlot) * time.Second
greaterThanAttestingInterval := attestingInterval + time.Second
// Trying to boost a block from slot 1 that is untimely should not work.
err := f.BoostProposerRoot(ctx, types.Slot(1), blockRoot, genesis)
blockRoot := [32]byte{'A'}
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: blockRoot,
BlockSlot: types.Slot(1),
CurrentSlot: 1,
SecondsIntoSlot: uint64(greaterThanAttestingInterval.Seconds()),
}
err := f.BoostProposerRoot(ctx, args)
require.NoError(t, err)
require.DeepEqual(t, [32]byte{}, f.store.proposerBoostRoot)
})
@@ -442,11 +476,15 @@ func TestForkChoice_BoostProposerRoot(t *testing.T) {
store: &Store{},
}
// Genesis set to 1 slot ago + 0 seconds into the attesting interval.
genesis := time.Now().Add(-time.Duration(cfg.SecondsPerSlot) * time.Second)
fmt.Println(genesis)
blockRoot := [32]byte{'A'}
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: blockRoot,
BlockSlot: types.Slot(1),
CurrentSlot: types.Slot(1),
SecondsIntoSlot: 0,
}
err := f.BoostProposerRoot(ctx, types.Slot(1), blockRoot, genesis)
err := f.BoostProposerRoot(ctx, args)
require.NoError(t, err)
require.DeepEqual(t, [32]byte{'A'}, f.store.proposerBoostRoot)
})
@@ -454,13 +492,16 @@ func TestForkChoice_BoostProposerRoot(t *testing.T) {
f := &ForkChoice{
store: &Store{},
}
// Genesis set to 1 slot ago + (attesting interval / 2).
genesis := time.Now().Add(-time.Duration(cfg.SecondsPerSlot) * time.Second)
blockRoot := [32]byte{'A'}
halfAttestingInterval := time.Second
genesis = genesis.Add(-halfAttestingInterval)
args := &forkchoicetypes.ProposerBoostRootArgs{
BlockRoot: blockRoot,
BlockSlot: types.Slot(1),
CurrentSlot: types.Slot(1),
SecondsIntoSlot: uint64(halfAttestingInterval.Seconds()),
}
err := f.BoostProposerRoot(ctx, types.Slot(1), blockRoot, genesis)
err := f.BoostProposerRoot(ctx, args)
require.NoError(t, err)
require.DeepEqual(t, [32]byte{'A'}, f.store.proposerBoostRoot)
})

View File

@@ -0,0 +1,9 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["types.go"],
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/types",
visibility = ["//visibility:public"],
deps = ["@com_github_prysmaticlabs_eth2_types//:go_default_library"],
)

View File

@@ -0,0 +1,13 @@
package types
import (
types "github.com/prysmaticlabs/eth2-types"
)
// ProposerBoostRootArgs to call the BoostProposerRoot function.
type ProposerBoostRootArgs struct {
BlockRoot [32]byte
BlockSlot types.Slot
CurrentSlot types.Slot
SecondsIntoSlot uint64
}

View File

@@ -42,6 +42,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",

View File

@@ -45,6 +45,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"
@@ -103,6 +105,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
@@ -165,13 +169,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
}
@@ -242,7 +252,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
@@ -399,6 +409,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
@@ -466,10 +483,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
}
@@ -477,7 +495,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
}
@@ -489,10 +507,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
}

View File

@@ -84,6 +84,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//async/event:go_default_library",
"//beacon-chain/blockchain/testing:go_default_library",
"//beacon-chain/cache/depositcache:go_default_library",
"//beacon-chain/core/feed:go_default_library",
"//beacon-chain/core/feed/state:go_default_library",
@@ -106,7 +107,9 @@ go_test(
"//monitoring/clientstats:go_default_library",
"//network:go_default_library",
"//network/authorization:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/wrapper:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",

View File

@@ -8,6 +8,8 @@ import (
"time"
"github.com/holiman/uint256"
"github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
v1 "github.com/prysmaticlabs/prysm/beacon-chain/powchain/engine-api-client/v1"
"github.com/prysmaticlabs/prysm/config/params"
pb "github.com/prysmaticlabs/prysm/proto/engine/v1"
@@ -24,11 +26,14 @@ var (
// there are no differences in terminal block difficulty and block hash.
// If there are any discrepancies, we must log errors to ensure users can resolve
//the problem and be ready for the merge transition.
func (s *Service) checkTransitionConfiguration(ctx context.Context) {
func (s *Service) checkTransitionConfiguration(
ctx context.Context, blockNotifications chan *statefeed.BlockProcessedData,
) {
// If Bellatrix fork epoch is not set, we do not run this check.
if params.BeaconConfig().BellatrixForkEpoch == math.MaxUint64 {
return
}
// If no engine API, then also avoid running this check.
if s.engineAPIClient == nil {
return
}
@@ -54,10 +59,24 @@ func (s *Service) checkTransitionConfiguration(ctx context.Context) {
// Bellatrix hard-fork transition.
ticker := time.NewTicker(checkTransitionPollingInterval)
defer ticker.Stop()
sub := s.cfg.stateNotifier.StateFeed().Subscribe(blockNotifications)
defer sub.Unsubscribe()
for {
select {
case <-ctx.Done():
return
case <-sub.Err():
return
case ev := <-blockNotifications:
isExecutionBlock, err := blocks.IsExecutionBlock(ev.SignedBlock.Block().Body())
if err != nil {
log.WithError(err).Debug("Could not check whether signed block is execution block")
continue
}
if isExecutionBlock {
log.Debug("PoS transition is complete, no longer checking for configuration changes")
return
}
case <-ticker.C:
err = s.engineAPIClient.ExchangeTransitionConfiguration(ctx, cfg)
s.handleExchangeConfigurationError(err)

View File

@@ -6,9 +6,15 @@ import (
"testing"
"time"
mockChain2 "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
v1 "github.com/prysmaticlabs/prysm/beacon-chain/powchain/engine-api-client/v1"
"github.com/prysmaticlabs/prysm/beacon-chain/powchain/engine-api-client/v1/mocks"
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
"github.com/prysmaticlabs/prysm/config/params"
enginev1 "github.com/prysmaticlabs/prysm/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
"github.com/prysmaticlabs/prysm/testing/require"
logTest "github.com/sirupsen/logrus/hooks/test"
)
@@ -18,21 +24,61 @@ func Test_checkTransitionConfiguration(t *testing.T) {
cfg := params.BeaconConfig().Copy()
cfg.BellatrixForkEpoch = 0
params.OverrideBeaconConfig(cfg)
ctx := context.Background()
hook := logTest.NewGlobal()
m := &mocks.EngineClient{}
m.Err = errors.New("something went wrong")
t.Run("context canceled", func(t *testing.T) {
ctx := context.Background()
m := &mocks.EngineClient{}
m.Err = errors.New("something went wrong")
srv := &Service{}
srv.engineAPIClient = m
checkTransitionPollingInterval = time.Millisecond
ctx, cancel := context.WithCancel(ctx)
go srv.checkTransitionConfiguration(ctx)
<-time.After(100 * time.Millisecond)
cancel()
require.LogsContain(t, hook, "Could not check configuration values")
mockChain := &mockChain2.MockStateNotifier{}
srv := &Service{
cfg: &config{stateNotifier: mockChain},
}
srv.engineAPIClient = m
checkTransitionPollingInterval = time.Millisecond
ctx, cancel := context.WithCancel(ctx)
go srv.checkTransitionConfiguration(ctx, make(chan *statefeed.BlockProcessedData, 1))
<-time.After(100 * time.Millisecond)
cancel()
require.LogsContain(t, hook, "Could not check configuration values")
})
t.Run("block containing execution payload exits routine", func(t *testing.T) {
ctx := context.Background()
m := &mocks.EngineClient{}
m.Err = errors.New("something went wrong")
mockChain := &mockChain2.MockStateNotifier{}
srv := &Service{
cfg: &config{stateNotifier: mockChain},
}
srv.engineAPIClient = m
checkTransitionPollingInterval = time.Millisecond
ctx, cancel := context.WithCancel(ctx)
exit := make(chan bool)
notification := make(chan *statefeed.BlockProcessedData)
go func() {
srv.checkTransitionConfiguration(ctx, notification)
exit <- true
}()
payload := emptyPayload()
payload.GasUsed = 21000
wrappedBlock, err := wrapper.WrappedSignedBeaconBlock(&ethpb.SignedBeaconBlockBellatrix{
Block: &ethpb.BeaconBlockBellatrix{
Body: &ethpb.BeaconBlockBodyBellatrix{
ExecutionPayload: payload,
},
}},
)
require.NoError(t, err)
notification <- &statefeed.BlockProcessedData{
SignedBlock: wrappedBlock,
}
<-exit
cancel()
require.LogsContain(t, hook, "PoS transition is complete, no longer checking")
})
}
func TestService_handleExchangeConfigurationError(t *testing.T) {
@@ -63,3 +109,17 @@ func TestService_handleExchangeConfigurationError(t *testing.T) {
require.LogsContain(t, hook, "Could not check configuration values")
})
}
func emptyPayload() *enginev1.ExecutionPayload {
return &enginev1.ExecutionPayload{
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: make([]byte, fieldparams.RootLength),
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
Transactions: make([][]byte, 0),
ExtraData: make([]byte, 0),
}
}

View File

@@ -6,6 +6,7 @@ go_library(
"auth.go",
"client.go",
"errors.go",
"metrics.go",
"options.go",
],
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/powchain/engine-api-client/v1",
@@ -18,6 +19,8 @@ go_library(
"@com_github_ethereum_go_ethereum//rpc:go_default_library",
"@com_github_golang_jwt_jwt_v4//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@io_opencensus_go//trace:go_default_library",
],
)

View File

@@ -99,6 +99,10 @@ func New(ctx context.Context, endpoint string, opts ...Option) (*Client, error)
func (c *Client) NewPayload(ctx context.Context, payload *pb.ExecutionPayload) ([]byte, error) {
ctx, span := trace.StartSpan(ctx, "powchain.engine-api-client.NewPayload")
defer span.End()
start := time.Now()
defer func() {
newPayloadLatency.Observe(float64(time.Since(start).Milliseconds()))
}()
result := &pb.PayloadStatus{}
err := c.rpc.CallContext(ctx, result, NewPayloadMethod, payload)
@@ -128,6 +132,10 @@ func (c *Client) ForkchoiceUpdated(
) (*pb.PayloadIDBytes, []byte, error) {
ctx, span := trace.StartSpan(ctx, "powchain.engine-api-client.ForkchoiceUpdated")
defer span.End()
start := time.Now()
defer func() {
forkchoiceUpdatedLatency.Observe(float64(time.Since(start).Milliseconds()))
}()
result := &ForkchoiceUpdatedResponse{}
err := c.rpc.CallContext(ctx, result, ForkchoiceUpdatedMethod, state, attrs)
@@ -157,6 +165,10 @@ func (c *Client) ForkchoiceUpdated(
func (c *Client) GetPayload(ctx context.Context, payloadId [8]byte) (*pb.ExecutionPayload, error) {
ctx, span := trace.StartSpan(ctx, "powchain.engine-api-client.GetPayload")
defer span.End()
start := time.Now()
defer func() {
getPayloadLatency.Observe(float64(time.Since(start).Milliseconds()))
}()
result := &pb.ExecutionPayload{}
err := c.rpc.CallContext(ctx, result, GetPayloadMethod, pb.PayloadIDBytes(payloadId))

View File

@@ -0,0 +1,30 @@
package v1
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
newPayloadLatency = promauto.NewHistogram(
prometheus.HistogramOpts{
Name: "new_payload_v1_latency_milliseconds",
Help: "Captures RPC latency for newPayloadV1 in milliseconds",
Buckets: []float64{1, 2, 5, 10, 20, 50, 100, 200, 500, 1000},
},
)
getPayloadLatency = promauto.NewHistogram(
prometheus.HistogramOpts{
Name: "get_payload_v1_latency_milliseconds",
Help: "Captures RPC latency for newPayloadV1 in milliseconds",
Buckets: []float64{1, 2, 5, 10, 20, 50, 100, 200, 500, 1000},
},
)
forkchoiceUpdatedLatency = promauto.NewHistogram(
prometheus.HistogramOpts{
Name: "forkchoice_updated_v1_latency_milliseconds",
Help: "Captures RPC latency for newPayloadV1 in milliseconds",
Buckets: []float64{1, 2, 5, 10, 20, 50, 100, 200, 500, 1000},
},
)
)

View File

@@ -252,7 +252,7 @@ func (s *Service) Start() {
}
// Check transition configuration for the engine API client in the background.
go s.checkTransitionConfiguration(s.ctx)
go s.checkTransitionConfiguration(s.ctx, make(chan *statefeed.BlockProcessedData, 1))
go func() {
s.isRunning = true

View File

@@ -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 {

View File

@@ -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",

View File

@@ -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)
}

View File

@@ -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
}

View 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",
],
)

View 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)
}

View 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)
}
}

View 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",
],
)

View 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())
}

View 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
}

View File

@@ -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",

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View 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",
],
)

View 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
}

View File

@@ -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,

View File

@@ -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))

View File

@@ -13,6 +13,7 @@ go_library(
deps = [
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/blockchain/testing:go_default_library",
"//beacon-chain/cache:go_default_library",
"//beacon-chain/cache/depositcache:go_default_library",
"//beacon-chain/core/time:go_default_library",
"//beacon-chain/db/testing:go_default_library",

View File

@@ -58,7 +58,7 @@ func startChainService(t *testing.T, st state.BeaconState, block block.SignedBea
blockchain.WithStateNotifier(&mock.MockStateNotifier{}),
blockchain.WithAttestationPool(attestations.NewPool()),
blockchain.WithDepositCache(depositCache),
blockchain.WithProposerIdsCache(cache.NewProposerPayloadIDsCache())),
blockchain.WithProposerIdsCache(cache.NewProposerPayloadIDsCache()),
)
service, err := blockchain.NewService(context.Background(), opts...)
require.NoError(t, err)

View File

@@ -447,10 +447,12 @@ func (v *validator) UpdateLogAggregateStats(resp *ethpb.ValidatorPerformanceResp
log.WithFields(epochSummaryFields).Info("Previous epoch aggregated voting summary")
var totalStartBal, totalPrevBal uint64
v.prevBalanceLock.RLock()
for i, val := range v.startBalances {
totalStartBal += val
totalPrevBal += v.prevBalance[i]
}
v.prevBalanceLock.RUnlock()
if totalStartBal == 0 || summary.totalAttestedCount == 0 {
log.Error("Failed to print launch summary: one or more divisors is 0")