mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 21:38:05 -05:00
Compare commits
29 Commits
kiln-debug
...
init-clien
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd37678802 | ||
|
|
59b9519284 | ||
|
|
652134c1ff | ||
|
|
d4038fb752 | ||
|
|
32a0da89e8 | ||
|
|
56ab5872bb | ||
|
|
a5e73640de | ||
|
|
eb6d68a4b1 | ||
|
|
9526860ba5 | ||
|
|
7920528ede | ||
|
|
6cbaddd79c | ||
|
|
1af3c07ec5 | ||
|
|
b7ab6e4d9e | ||
|
|
7279349ae2 | ||
|
|
d43a32a99d | ||
|
|
2d60d04b57 | ||
|
|
4a4b34d43c | ||
|
|
ef8bc97d3e | ||
|
|
08156a4b72 | ||
|
|
4fe6c79ec9 | ||
|
|
526843ba27 | ||
|
|
bfdd154081 | ||
|
|
cd95c571f9 | ||
|
|
fac981b32a | ||
|
|
26476dcdd4 | ||
|
|
45e41eb8ea | ||
|
|
dce3d1e8ea | ||
|
|
bd255a5790 | ||
|
|
ad400fcf6e |
@@ -51,6 +51,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",
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
enginev1 "github.com/prysmaticlabs/prysm/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/prysmaticlabs/prysm/runtime/version"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
@@ -29,10 +28,7 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, headBlk block.Beac
|
||||
return nil, errors.New("nil head block")
|
||||
}
|
||||
// Must not call fork choice updated until the transition conditions are met on the Pow network.
|
||||
if isPreBellatrix(headBlk.Version()) {
|
||||
return nil, nil
|
||||
}
|
||||
isExecutionBlk, err := blocks.ExecutionBlock(headBlk.Body())
|
||||
isExecutionBlk, err := blocks.IsExecutionBlock(headBlk.Body())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not determine if block is execution block")
|
||||
}
|
||||
@@ -48,7 +44,7 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, headBlk block.Beac
|
||||
return nil, errors.Wrap(err, "could not get finalized block")
|
||||
}
|
||||
var finalizedHash []byte
|
||||
if isPreBellatrix(finalizedBlock.Block().Version()) {
|
||||
if blocks.IsPreBellatrixVersion(finalizedBlock.Block().Version()) {
|
||||
finalizedHash = params.BeaconConfig().ZeroHash[:]
|
||||
} else {
|
||||
payload, err := finalizedBlock.Block().Body().ExecutionPayload()
|
||||
@@ -93,7 +89,7 @@ func (s *Service) notifyNewPayload(ctx context.Context, preStateVersion, postSta
|
||||
|
||||
// Execution payload is only supported in Bellatrix and beyond. Pre
|
||||
// merge blocks are never optimistic
|
||||
if isPreBellatrix(postStateVersion) {
|
||||
if blocks.IsPreBellatrixVersion(postStateVersion) {
|
||||
return s.cfg.ForkChoiceStore.SetOptimisticToValid(ctx, root)
|
||||
}
|
||||
if err := helpers.BeaconBlockIsNil(blk); err != nil {
|
||||
@@ -130,12 +126,12 @@ func (s *Service) notifyNewPayload(ctx context.Context, preStateVersion, postSta
|
||||
}
|
||||
|
||||
// During the transition event, the transition block should be verified for sanity.
|
||||
if isPreBellatrix(preStateVersion) {
|
||||
if blocks.IsPreBellatrixVersion(preStateVersion) {
|
||||
// Handle case where pre-state is Altair but block contains payload.
|
||||
// To reach here, the block must have contained a valid payload.
|
||||
return s.validateMergeBlock(ctx, blk)
|
||||
}
|
||||
atTransition, err := blocks.IsMergeTransitionBlockUsingPayloadHeader(preStateHeader, body)
|
||||
atTransition, err := blocks.IsMergeTransitionBlockUsingPreStatePayloadHeader(preStateHeader, body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not check if merge block is terminal")
|
||||
}
|
||||
@@ -145,11 +141,6 @@ func (s *Service) notifyNewPayload(ctx context.Context, preStateVersion, postSta
|
||||
return s.validateMergeBlock(ctx, blk)
|
||||
}
|
||||
|
||||
// isPreBellatrix returns true if input version is before bellatrix fork.
|
||||
func isPreBellatrix(v int) bool {
|
||||
return v == version.Phase0 || v == version.Altair
|
||||
}
|
||||
|
||||
// optimisticCandidateBlock returns true if this block can be optimistically synced.
|
||||
//
|
||||
// Spec pseudocode definition:
|
||||
@@ -178,7 +169,7 @@ func (s *Service) optimisticCandidateBlock(ctx context.Context, blk block.Beacon
|
||||
return false, errNilParentInDB
|
||||
}
|
||||
|
||||
parentIsExecutionBlock, err := blocks.ExecutionBlock(parent.Block().Body())
|
||||
parentIsExecutionBlock, err := blocks.IsExecutionBlock(parent.Block().Body())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -194,5 +185,5 @@ func (s *Service) optimisticCandidateBlock(ctx context.Context, blk block.Beacon
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return blocks.ExecutionBlock(jBlock.Block().Body())
|
||||
return blocks.IsExecutionBlock(jBlock.Block().Body())
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
|
||||
testDB "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/protoarray"
|
||||
engine "github.com/prysmaticlabs/prysm/beacon-chain/powchain/engine-api-client/v1"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
@@ -589,3 +591,102 @@ func Test_IsOptimisticShallowExecutionParent(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, candidate)
|
||||
}
|
||||
|
||||
func Test_UpdateLastValidatedCheckpoint(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
params.OverrideBeaconConfig(params.MainnetConfig())
|
||||
|
||||
ctx := context.Background()
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
stateGen := stategen.New(beaconDB)
|
||||
fcs := protoarray.New(0, 0, [32]byte{})
|
||||
opts := []Option{
|
||||
WithDatabase(beaconDB),
|
||||
WithStateGen(stateGen),
|
||||
WithForkChoiceStore(fcs),
|
||||
}
|
||||
service, err := NewService(ctx, opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
genesisStateRoot := [32]byte{}
|
||||
genesisBlk := blocks.NewGenesisBlock(genesisStateRoot[:])
|
||||
wr, err := wrapper.WrappedSignedBeaconBlock(genesisBlk)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, beaconDB.SaveBlock(ctx, wr))
|
||||
genesisRoot, err := genesisBlk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, genesisRoot))
|
||||
require.NoError(t, fcs.InsertOptimisticBlock(ctx, 0, genesisRoot, params.BeaconConfig().ZeroHash,
|
||||
params.BeaconConfig().ZeroHash, 0, 0))
|
||||
genesisSummary := ðpb.StateSummary{
|
||||
Root: genesisStateRoot[:],
|
||||
Slot: 0,
|
||||
}
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, genesisSummary))
|
||||
|
||||
// Get last validated checkpoint
|
||||
origCheckpoint, err := service.cfg.BeaconDB.LastValidatedCheckpoint(ctx)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveLastValidatedCheckpoint(ctx, origCheckpoint))
|
||||
|
||||
// Optimistic finalized checkpoint
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.Slot = 320
|
||||
blk.Block.ParentRoot = genesisRoot[:]
|
||||
wr, err = wrapper.WrappedSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, wr))
|
||||
opRoot, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
opCheckpoint := ðpb.Checkpoint{
|
||||
Root: opRoot[:],
|
||||
Epoch: 10,
|
||||
}
|
||||
opStateSummary := ðpb.StateSummary{
|
||||
Root: opRoot[:],
|
||||
Slot: 320,
|
||||
}
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, opStateSummary))
|
||||
require.NoError(t, fcs.InsertOptimisticBlock(ctx, 320, opRoot, genesisRoot,
|
||||
params.BeaconConfig().ZeroHash, 10, 10))
|
||||
assert.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, opRoot))
|
||||
require.NoError(t, service.updateFinalized(ctx, opCheckpoint))
|
||||
cp, err := service.cfg.BeaconDB.LastValidatedCheckpoint(ctx)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, origCheckpoint.Root, cp.Root)
|
||||
require.Equal(t, origCheckpoint.Epoch, cp.Epoch)
|
||||
|
||||
// Validated finalized checkpoint
|
||||
blk = util.NewBeaconBlock()
|
||||
blk.Block.Slot = 640
|
||||
blk.Block.ParentRoot = opRoot[:]
|
||||
wr, err = wrapper.WrappedSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, wr))
|
||||
validRoot, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
validCheckpoint := ðpb.Checkpoint{
|
||||
Root: validRoot[:],
|
||||
Epoch: 20,
|
||||
}
|
||||
validSummary := ðpb.StateSummary{
|
||||
Root: validRoot[:],
|
||||
Slot: 640,
|
||||
}
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, validSummary))
|
||||
require.NoError(t, fcs.InsertOptimisticBlock(ctx, 640, validRoot, params.BeaconConfig().ZeroHash,
|
||||
params.BeaconConfig().ZeroHash, 20, 20))
|
||||
require.NoError(t, fcs.SetOptimisticToValid(ctx, validRoot))
|
||||
assert.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, validRoot))
|
||||
require.NoError(t, service.updateFinalized(ctx, validCheckpoint))
|
||||
cp, err = service.cfg.BeaconDB.LastValidatedCheckpoint(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
optimistic, err := service.IsOptimisticForRoot(ctx, validRoot)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, optimistic)
|
||||
require.DeepEqual(t, validCheckpoint.Root, cp.Root)
|
||||
require.Equal(t, validCheckpoint.Epoch, cp.Epoch)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
|
||||
statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
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"
|
||||
@@ -120,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
|
||||
}
|
||||
|
||||
@@ -535,7 +541,7 @@ func (s *Service) insertBlockToForkChoiceStore(ctx context.Context, blk block.Be
|
||||
|
||||
func getBlockPayloadHash(blk block.BeaconBlock) ([32]byte, error) {
|
||||
payloadHash := [32]byte{}
|
||||
if isPreBellatrix(blk.Version()) {
|
||||
if blocks.IsPreBellatrixVersion(blk.Version()) {
|
||||
return payloadHash, nil
|
||||
}
|
||||
payload, err := blk.Body().ExecutionPayload()
|
||||
|
||||
@@ -247,10 +247,19 @@ func (s *Service) updateFinalized(ctx context.Context, cp *ethpb.Checkpoint) err
|
||||
}
|
||||
|
||||
fRoot := bytesutil.ToBytes32(cp.Root)
|
||||
optimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(ctx, fRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !optimistic {
|
||||
err = s.cfg.BeaconDB.SaveLastValidatedCheckpoint(ctx, cp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := s.cfg.StateGen.MigrateToCold(ctx, fRoot); err != nil {
|
||||
return errors.Wrap(err, "could not migrate to cold")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -257,7 +257,7 @@ func (s *Service) StartFromSavedState(saved state.BeaconState) error {
|
||||
|
||||
func (s *Service) originRootFromSavedState(ctx context.Context) ([32]byte, error) {
|
||||
// first check if we have started from checkpoint sync and have a root
|
||||
originRoot, err := s.cfg.BeaconDB.OriginBlockRoot(ctx)
|
||||
originRoot, err := s.cfg.BeaconDB.OriginCheckpointBlockRoot(ctx)
|
||||
if err == nil {
|
||||
return originRoot, nil
|
||||
}
|
||||
@@ -265,7 +265,7 @@ func (s *Service) originRootFromSavedState(ctx context.Context) ([32]byte, error
|
||||
return originRoot, errors.Wrap(err, "could not retrieve checkpoint sync chain origin data from db")
|
||||
}
|
||||
|
||||
// we got here because OriginBlockRoot gave us an ErrNotFound. this means the node was started from a genesis state,
|
||||
// we got here because OriginCheckpointBlockRoot gave us an ErrNotFound. this means the node was started from a genesis state,
|
||||
// so we should have a value for GenesisBlock
|
||||
genesisBlock, err := s.cfg.BeaconDB.GenesisBlock(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -36,6 +36,7 @@ func NewWeakSubjectivityVerifier(wsc *ethpb.Checkpoint, db weakSubjectivityDB) (
|
||||
// per 7342, a nil checkpoint, zero-root or zero-epoch should all fail validation
|
||||
// and return an error instead of creating a WeakSubjectivityVerifier that permits any chain history.
|
||||
if wsc == nil || len(wsc.Root) == 0 || wsc.Epoch == 0 {
|
||||
log.Warn("No valid weak subjectivity checkpoint specified, running without weak subjectivity verification")
|
||||
return &WeakSubjectivityVerifier{
|
||||
enabled: false,
|
||||
}, nil
|
||||
@@ -79,15 +80,17 @@ func (v *WeakSubjectivityVerifier) VerifyWeakSubjectivity(ctx context.Context, f
|
||||
if !v.db.HasBlock(ctx, v.root) {
|
||||
return errors.Wrap(errWSBlockNotFound, fmt.Sprintf("missing root %#x", v.root))
|
||||
}
|
||||
filter := filters.NewFilter().SetStartSlot(v.slot).SetEndSlot(v.slot + params.BeaconConfig().SlotsPerEpoch)
|
||||
endSlot := v.slot + params.BeaconConfig().SlotsPerEpoch
|
||||
filter := filters.NewFilter().SetStartSlot(v.slot).SetEndSlot(endSlot)
|
||||
// A node should have the weak subjectivity block corresponds to the correct epoch in the DB.
|
||||
log.Infof("Searching block roots for weak subjectivity root=%#x, between slots %d-%d", v.root, v.slot, endSlot)
|
||||
roots, err := v.db.BlockRoots(ctx, filter)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error while retrieving block roots to verify weak subjectivity")
|
||||
}
|
||||
for _, root := range roots {
|
||||
if v.root == root {
|
||||
log.Info("Weak subjectivity check has passed")
|
||||
log.Info("Weak subjectivity check has passed!!")
|
||||
v.verified = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14,60 +14,65 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
)
|
||||
|
||||
func TestService_VerifyWeakSubjectivityRoot(t *testing.T) {
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = 32
|
||||
b.Block.Slot = 1792480
|
||||
wsb, err := wrapper.WrappedSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveBlock(context.Background(), wsb))
|
||||
r, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
blockEpoch := slots.ToEpoch(b.Block.Slot)
|
||||
tests := []struct {
|
||||
wsVerified bool
|
||||
disabled bool
|
||||
wantErr error
|
||||
checkpt *ethpb.Checkpoint
|
||||
finalizedEpoch types.Epoch
|
||||
name string
|
||||
}{
|
||||
{
|
||||
name: "nil root and epoch",
|
||||
},
|
||||
{
|
||||
name: "already verified",
|
||||
checkpt: ðpb.Checkpoint{Epoch: 2},
|
||||
finalizedEpoch: 2,
|
||||
wsVerified: true,
|
||||
name: "nil root and epoch",
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
name: "not yet to verify, ws epoch higher than finalized epoch",
|
||||
checkpt: ðpb.Checkpoint{Epoch: 2},
|
||||
finalizedEpoch: 1,
|
||||
checkpt: ðpb.Checkpoint{Root: bytesutil.PadTo([]byte{'a'}, 32), Epoch: blockEpoch},
|
||||
finalizedEpoch: blockEpoch - 1,
|
||||
},
|
||||
{
|
||||
name: "can't find the block in DB",
|
||||
checkpt: ðpb.Checkpoint{Root: bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength), Epoch: 1},
|
||||
finalizedEpoch: 3,
|
||||
finalizedEpoch: blockEpoch + 1,
|
||||
wantErr: errWSBlockNotFound,
|
||||
},
|
||||
{
|
||||
name: "can't find the block corresponds to ws epoch in DB",
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: 2}, // Root belongs in epoch 1.
|
||||
finalizedEpoch: 3,
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: blockEpoch - 2}, // Root belongs in epoch 1.
|
||||
finalizedEpoch: blockEpoch - 1,
|
||||
wantErr: errWSBlockNotFoundInEpoch,
|
||||
},
|
||||
{
|
||||
name: "can verify and pass",
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: 1},
|
||||
finalizedEpoch: 3,
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: blockEpoch},
|
||||
finalizedEpoch: blockEpoch + 1,
|
||||
},
|
||||
{
|
||||
name: "equal epoch",
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: blockEpoch},
|
||||
finalizedEpoch: blockEpoch,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
wv, err := NewWeakSubjectivityVerifier(tt.checkpt, beaconDB)
|
||||
require.Equal(t, !tt.disabled, wv.enabled)
|
||||
require.NoError(t, err)
|
||||
s := &Service{
|
||||
cfg: &config{BeaconDB: beaconDB, WeakSubjectivityCheckpt: tt.checkpt},
|
||||
|
||||
@@ -45,6 +45,7 @@ go_library(
|
||||
"//proto/prysm/v1alpha1/attestation:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"//proto/prysm/v1alpha1/slashings:go_default_library",
|
||||
"//proto/prysm/v1alpha1/wrapper:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
|
||||
@@ -2,7 +2,6 @@ package blocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
@@ -14,81 +13,75 @@ import (
|
||||
enginev1 "github.com/prysmaticlabs/prysm/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
)
|
||||
|
||||
// MergeTransitionComplete returns true if the transition to Bellatrix has completed.
|
||||
// IsMergeTransitionComplete returns true if the transition to Bellatrix has completed.
|
||||
// Meaning the payload header in beacon state is not `ExecutionPayloadHeader()` (i.e. not empty).
|
||||
//
|
||||
// Spec code:
|
||||
// def is_merge_transition_complete(state: BeaconState) -> bool:
|
||||
// return state.latest_execution_payload_header != ExecutionPayloadHeader()
|
||||
//
|
||||
// Deprecated: Use `IsMergeTransitionBlockUsingPayloadHeader` instead.
|
||||
func MergeTransitionComplete(st state.BeaconState) (bool, error) {
|
||||
func IsMergeTransitionComplete(st state.BeaconState) (bool, error) {
|
||||
if st == nil {
|
||||
return false, errors.New("nil state")
|
||||
}
|
||||
if IsPreBellatrixVersion(st.Version()) {
|
||||
return false, nil
|
||||
}
|
||||
h, err := st.LatestExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return !isEmptyHeader(h), nil
|
||||
}
|
||||
|
||||
// MergeTransitionBlock returns true if the input block is the terminal merge block.
|
||||
// Meaning the header in beacon state is `ExecutionPayloadHeader()` (i.e. empty).
|
||||
// And the input block has a non-empty header.
|
||||
//
|
||||
// Spec code:
|
||||
// def is_merge_transition_block(state: BeaconState, body: BeaconBlockBody) -> bool:
|
||||
// return not is_merge_transition_complete(state) and body.execution_payload != ExecutionPayload()
|
||||
func MergeTransitionBlock(st state.BeaconState, body block.BeaconBlockBody) (bool, error) {
|
||||
mergeComplete, err := MergeTransitionComplete(st)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if mergeComplete {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return ExecutionBlock(body)
|
||||
}
|
||||
|
||||
// IsMergeTransitionBlockUsingPayloadHeader returns true if the input block is the terminal merge block.
|
||||
// IsMergeTransitionBlockUsingPreStatePayloadHeader returns true if the input block is the terminal merge block.
|
||||
// Terminal merge block must be associated with an empty payload header.
|
||||
// This is an optimized version of MergeTransitionComplete where beacon state is not required as an argument.
|
||||
func IsMergeTransitionBlockUsingPayloadHeader(h *ethpb.ExecutionPayloadHeader, body block.BeaconBlockBody) (bool, error) {
|
||||
// This assumes the header `h` is referenced as the parent state for block body `body.
|
||||
func IsMergeTransitionBlockUsingPreStatePayloadHeader(h *ethpb.ExecutionPayloadHeader, body block.BeaconBlockBody) (bool, error) {
|
||||
if h == nil || body == nil {
|
||||
return false, errors.New("nil header or block body")
|
||||
}
|
||||
if !isEmptyHeader(h) {
|
||||
return false, nil
|
||||
}
|
||||
return ExecutionBlock(body)
|
||||
return IsExecutionBlock(body)
|
||||
}
|
||||
|
||||
// ExecutionBlock returns whether the block has a non-empty ExecutionPayload.
|
||||
// IsExecutionBlock returns whether the block has a non-empty ExecutionPayload.
|
||||
//
|
||||
// Spec code:
|
||||
// def is_execution_block(block: BeaconBlock) -> bool:
|
||||
// return block.body.execution_payload != ExecutionPayload()
|
||||
func ExecutionBlock(body block.BeaconBlockBody) (bool, error) {
|
||||
func IsExecutionBlock(body block.BeaconBlockBody) (bool, error) {
|
||||
if body == nil {
|
||||
return false, errors.New("nil block body")
|
||||
}
|
||||
payload, err := body.ExecutionPayload()
|
||||
if err != nil {
|
||||
if strings.HasPrefix(err.Error(), "ExecutionPayload is not supported in") {
|
||||
return false, nil
|
||||
}
|
||||
switch {
|
||||
case errors.Is(err, wrapper.ErrUnsupportedField):
|
||||
return false, nil
|
||||
case err != nil:
|
||||
return false, err
|
||||
default:
|
||||
}
|
||||
return !isEmptyPayload(payload), nil
|
||||
}
|
||||
|
||||
// ExecutionEnabled returns true if the beacon chain can begin executing.
|
||||
// IsExecutionEnabled returns true if the beacon chain can begin executing.
|
||||
// Meaning the payload header is beacon state is non-empty or the payload in block body is non-empty.
|
||||
//
|
||||
// Spec code:
|
||||
// def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool:
|
||||
// return is_merge_block(state, body) or is_merge_complete(state)
|
||||
// Deprecated: Use `IsExecutionEnabledUsingHeader` instead.
|
||||
func ExecutionEnabled(st state.BeaconState, body block.BeaconBlockBody) (bool, error) {
|
||||
if st.Version() == version.Phase0 || st.Version() == version.Altair {
|
||||
func IsExecutionEnabled(st state.BeaconState, body block.BeaconBlockBody) (bool, error) {
|
||||
if st == nil || body == nil {
|
||||
return false, errors.New("nil state or block body")
|
||||
}
|
||||
if IsPreBellatrixVersion(st.Version()) {
|
||||
return false, nil
|
||||
}
|
||||
header, err := st.LatestExecutionPayloadHeader()
|
||||
@@ -99,12 +92,17 @@ func ExecutionEnabled(st state.BeaconState, body block.BeaconBlockBody) (bool, e
|
||||
}
|
||||
|
||||
// IsExecutionEnabledUsingHeader returns true if the execution is enabled using post processed payload header and block body.
|
||||
// This is an optimized version of ExecutionEnabled where beacon state is not required as an argument.
|
||||
// This is an optimized version of IsExecutionEnabled where beacon state is not required as an argument.
|
||||
func IsExecutionEnabledUsingHeader(header *ethpb.ExecutionPayloadHeader, body block.BeaconBlockBody) (bool, error) {
|
||||
if !isEmptyHeader(header) {
|
||||
return true, nil
|
||||
}
|
||||
return ExecutionBlock(body)
|
||||
return IsExecutionBlock(body)
|
||||
}
|
||||
|
||||
// IsPreBellatrixVersion returns true if input version is before bellatrix fork.
|
||||
func IsPreBellatrixVersion(v int) bool {
|
||||
return v < version.Bellatrix
|
||||
}
|
||||
|
||||
// ValidatePayloadWhenMergeCompletes validates if payload is valid versus input beacon state.
|
||||
@@ -115,7 +113,7 @@ func IsExecutionEnabledUsingHeader(header *ethpb.ExecutionPayloadHeader, body bl
|
||||
// if is_merge_complete(state):
|
||||
// assert payload.parent_hash == state.latest_execution_payload_header.block_hash
|
||||
func ValidatePayloadWhenMergeCompletes(st state.BeaconState, payload *enginev1.ExecutionPayload) error {
|
||||
complete, err := MergeTransitionComplete(st)
|
||||
complete, err := IsMergeTransitionComplete(st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -237,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
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
)
|
||||
|
||||
func Test_MergeComplete(t *testing.T) {
|
||||
func Test_IsMergeComplete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload *ethpb.ExecutionPayloadHeader
|
||||
@@ -160,7 +160,7 @@ func Test_MergeComplete(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
||||
require.NoError(t, st.SetLatestExecutionPayloadHeader(tt.payload))
|
||||
got, err := blocks.MergeTransitionComplete(st)
|
||||
got, err := blocks.IsMergeTransitionComplete(st)
|
||||
require.NoError(t, err)
|
||||
if got != tt.want {
|
||||
t.Errorf("mergeComplete() got = %v, want %v", got, tt.want)
|
||||
@@ -169,187 +169,6 @@ func Test_MergeComplete(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MergeBlock(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload *enginev1.ExecutionPayload
|
||||
header *ethpb.ExecutionPayloadHeader
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty header, empty payload",
|
||||
payload: emptyPayload(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "non-empty header, empty payload",
|
||||
payload: emptyPayload(),
|
||||
header: func() *ethpb.ExecutionPayloadHeader {
|
||||
h := emptyPayloadHeader()
|
||||
h.ParentHash = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return h
|
||||
}(),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has parent hash",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.ParentHash = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has fee recipient",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.FeeRecipient = bytesutil.PadTo([]byte{'a'}, fieldparams.FeeRecipientLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has state root",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.StateRoot = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has receipt root",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.ReceiptsRoot = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has logs bloom",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.LogsBloom = bytesutil.PadTo([]byte{'a'}, fieldparams.LogsBloomLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has random",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.PrevRandao = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has base fee",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.BaseFeePerGas = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has block hash",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.BlockHash = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has tx",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.Transactions = [][]byte{{'a'}}
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has extra data",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.ExtraData = bytesutil.PadTo([]byte{'a'}, fieldparams.RootLength)
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has block number",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.BlockNumber = 1
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has gas limit",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.GasLimit = 1
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has gas used",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.GasUsed = 1
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty header, payload has timestamp",
|
||||
payload: func() *enginev1.ExecutionPayload {
|
||||
p := emptyPayload()
|
||||
p.Timestamp = 1
|
||||
return p
|
||||
}(),
|
||||
header: emptyPayloadHeader(),
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
||||
require.NoError(t, st.SetLatestExecutionPayloadHeader(tt.header))
|
||||
blk := util.NewBeaconBlockBellatrix()
|
||||
blk.Block.Body.ExecutionPayload = tt.payload
|
||||
body, err := wrapper.WrappedBellatrixBeaconBlockBody(blk.Block.Body)
|
||||
require.NoError(t, err)
|
||||
got, err := blocks.MergeTransitionBlock(st, body)
|
||||
require.NoError(t, err)
|
||||
if got != tt.want {
|
||||
t.Errorf("MergeTransitionBlock() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IsMergeTransitionBlockUsingPayloadHeader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -520,7 +339,7 @@ func Test_IsMergeTransitionBlockUsingPayloadHeader(t *testing.T) {
|
||||
blk.Block.Body.ExecutionPayload = tt.payload
|
||||
body, err := wrapper.WrappedBellatrixBeaconBlockBody(blk.Block.Body)
|
||||
require.NoError(t, err)
|
||||
got, err := blocks.IsMergeTransitionBlockUsingPayloadHeader(tt.header, body)
|
||||
got, err := blocks.IsMergeTransitionBlockUsingPreStatePayloadHeader(tt.header, body)
|
||||
require.NoError(t, err)
|
||||
if got != tt.want {
|
||||
t.Errorf("MergeTransitionBlock() got = %v, want %v", got, tt.want)
|
||||
@@ -556,14 +375,14 @@ func Test_IsExecutionBlock(t *testing.T) {
|
||||
blk.Block.Body.ExecutionPayload = tt.payload
|
||||
wrappedBlock, err := wrapper.WrappedBellatrixBeaconBlock(blk.Block)
|
||||
require.NoError(t, err)
|
||||
got, err := blocks.ExecutionBlock(wrappedBlock.Body())
|
||||
got, err := blocks.IsExecutionBlock(wrappedBlock.Body())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ExecutionEnabled(t *testing.T) {
|
||||
func Test_IsExecutionEnabled(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload *enginev1.ExecutionPayload
|
||||
@@ -630,10 +449,10 @@ func Test_ExecutionEnabled(t *testing.T) {
|
||||
if tt.useAltairSt {
|
||||
st, _ = util.DeterministicGenesisStateAltair(t, 1)
|
||||
}
|
||||
got, err := blocks.ExecutionEnabled(st, body)
|
||||
got, err := blocks.IsExecutionEnabled(st, body)
|
||||
require.NoError(t, err)
|
||||
if got != tt.want {
|
||||
t.Errorf("ExecutionEnabled() got = %v, want %v", got, tt.want)
|
||||
t.Errorf("IsExecutionEnabled() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -696,7 +515,7 @@ func Test_IsExecutionEnabledUsingHeader(t *testing.T) {
|
||||
got, err := blocks.IsExecutionEnabledUsingHeader(tt.header, body)
|
||||
require.NoError(t, err)
|
||||
if got != tt.want {
|
||||
t.Errorf("ExecutionEnabled() got = %v, want %v", got, tt.want)
|
||||
t.Errorf("IsExecutionEnabled() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -904,7 +723,7 @@ func BenchmarkBellatrixComplete(b *testing.B) {
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := blocks.MergeTransitionComplete(st)
|
||||
_, err := blocks.IsMergeTransitionComplete(st)
|
||||
require.NoError(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,20 +285,18 @@ func ProcessBlockForStateRoot(
|
||||
return nil, errors.Wrap(err, "could not process block header")
|
||||
}
|
||||
|
||||
if state.Version() == version.Bellatrix {
|
||||
enabled, err := b.ExecutionEnabled(state, blk.Body())
|
||||
enabled, err := b.IsExecutionEnabled(state, blk.Body())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not check if execution is enabled")
|
||||
}
|
||||
if enabled {
|
||||
payload, err := blk.Body().ExecutionPayload()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not check if execution is enabled")
|
||||
return nil, err
|
||||
}
|
||||
if enabled {
|
||||
payload, err := blk.Body().ExecutionPayload()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state, err = b.ProcessPayload(state, payload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process execution payload")
|
||||
}
|
||||
state, err = b.ProcessPayload(state, payload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process execution payload")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,3 +13,9 @@ var ErrNotFoundState = kv.ErrNotFoundState
|
||||
|
||||
// ErrNotFoundOriginBlockRoot wraps ErrNotFound for an error specific to the origin block root.
|
||||
var ErrNotFoundOriginBlockRoot = kv.ErrNotFoundOriginBlockRoot
|
||||
|
||||
// ErrNotFoundOriginBlockRoot wraps ErrNotFound for an error specific to the origin block root.
|
||||
var ErrNotFoundBackfillBlockRoot = kv.ErrNotFoundBackfillBlockRoot
|
||||
|
||||
// ErrNotFoundGenesisBlockRoot means no genesis block root was found, indicating the db was not initialized with genesis
|
||||
var ErrNotFoundGenesisBlockRoot = kv.ErrNotFoundGenesisBlockRoot
|
||||
|
||||
@@ -27,6 +27,7 @@ type ReadOnlyDatabase interface {
|
||||
BlockRootsBySlot(ctx context.Context, slot types.Slot) (bool, [][32]byte, error)
|
||||
HasBlock(ctx context.Context, blockRoot [32]byte) bool
|
||||
GenesisBlock(ctx context.Context) (block.SignedBeaconBlock, error)
|
||||
GenesisBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
IsFinalizedBlock(ctx context.Context, blockRoot [32]byte) bool
|
||||
FinalizedChildBlock(ctx context.Context, blockRoot [32]byte) (block.SignedBeaconBlock, error)
|
||||
HighestSlotBlocksBelow(ctx context.Context, slot types.Slot) ([]block.SignedBeaconBlock, error)
|
||||
@@ -53,7 +54,8 @@ type ReadOnlyDatabase interface {
|
||||
// Fee reicipients operations.
|
||||
FeeRecipientByValidatorID(ctx context.Context, id types.ValidatorIndex) (common.Address, error)
|
||||
// origin checkpoint sync support
|
||||
OriginBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
OriginCheckpointBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
BackfillBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
}
|
||||
|
||||
// NoHeadAccessDatabase defines a struct without access to chain head data.
|
||||
@@ -102,7 +104,8 @@ type HeadAccessDatabase interface {
|
||||
EnsureEmbeddedGenesis(ctx context.Context) error
|
||||
|
||||
// initialization method needed for origin checkpoint sync
|
||||
SaveOrigin(ctx context.Context, state io.Reader, block io.Reader) error
|
||||
SaveOrigin(ctx context.Context, serState, serBlock []byte) error
|
||||
SaveBackfillBlockRoot(ctx context.Context, blockRoot [32]byte) error
|
||||
}
|
||||
|
||||
// SlasherDatabase interface for persisting data related to detecting slashable offenses on Ethereum.
|
||||
|
||||
@@ -48,18 +48,18 @@ func (s *Store) Block(ctx context.Context, blockRoot [32]byte) (block.SignedBeac
|
||||
return blk, err
|
||||
}
|
||||
|
||||
// OriginBlockRoot returns the value written to the db in SaveOriginBlockRoot
|
||||
// OriginCheckpointBlockRoot returns the value written to the db in SaveOriginCheckpointBlockRoot
|
||||
// This is the root of a finalized block within the weak subjectivity period
|
||||
// at the time the chain was started, used to initialize the database and chain
|
||||
// without syncing from genesis.
|
||||
func (s *Store) OriginBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.OriginBlockRoot")
|
||||
func (s *Store) OriginCheckpointBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.OriginCheckpointBlockRoot")
|
||||
defer span.End()
|
||||
|
||||
var root [32]byte
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(blocksBucket)
|
||||
rootSlice := bkt.Get(originBlockRootKey)
|
||||
rootSlice := bkt.Get(originCheckpointBlockRootKey)
|
||||
if rootSlice == nil {
|
||||
return ErrNotFoundOriginBlockRoot
|
||||
}
|
||||
@@ -70,6 +70,25 @@ func (s *Store) OriginBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
return root, err
|
||||
}
|
||||
|
||||
// BackfillBlockRoot keeps track of the highest block available before the OriginCheckpointBlockRoot
|
||||
func (s *Store) BackfillBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.BackfillBlockRoot")
|
||||
defer span.End()
|
||||
|
||||
var root [32]byte
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(blocksBucket)
|
||||
rootSlice := bkt.Get(backfillBlockRootKey)
|
||||
if len(rootSlice) == 0 {
|
||||
return ErrNotFoundBackfillBlockRoot
|
||||
}
|
||||
root = bytesutil.ToBytes32(rootSlice)
|
||||
return nil
|
||||
})
|
||||
|
||||
return root, err
|
||||
}
|
||||
|
||||
// HeadBlock returns the latest canonical block in the Ethereum Beacon Chain.
|
||||
func (s *Store) HeadBlock(ctx context.Context) (block.SignedBeaconBlock, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "BeaconDB.HeadBlock")
|
||||
@@ -325,6 +344,22 @@ func (s *Store) GenesisBlock(ctx context.Context) (block.SignedBeaconBlock, erro
|
||||
return blk, err
|
||||
}
|
||||
|
||||
func (s *Store) GenesisBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "BeaconDB.GenesisBlockRoot")
|
||||
defer span.End()
|
||||
var root [32]byte
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(blocksBucket)
|
||||
r := bkt.Get(genesisBlockRootKey)
|
||||
if len(r) == 0 {
|
||||
return ErrNotFoundGenesisBlockRoot
|
||||
}
|
||||
root = bytesutil.ToBytes32(r)
|
||||
return nil
|
||||
})
|
||||
return root, err
|
||||
}
|
||||
|
||||
// SaveGenesisBlockRoot to the db.
|
||||
func (s *Store) SaveGenesisBlockRoot(ctx context.Context, blockRoot [32]byte) error {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.SaveGenesisBlockRoot")
|
||||
@@ -335,16 +370,27 @@ func (s *Store) SaveGenesisBlockRoot(ctx context.Context, blockRoot [32]byte) er
|
||||
})
|
||||
}
|
||||
|
||||
// SaveOriginBlockRoot is used to keep track of the block root used for origin sync.
|
||||
// SaveOriginCheckpointBlockRoot is used to keep track of the block root used for syncing from a checkpoint origin.
|
||||
// This should be a finalized block from within the current weak subjectivity period.
|
||||
// This value is used by a running beacon chain node to locate the state at the beginning
|
||||
// of the chain history, in places where genesis would typically be used.
|
||||
func (s *Store) SaveOriginBlockRoot(ctx context.Context, blockRoot [32]byte) error {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.SaveOriginBlockRoot")
|
||||
func (s *Store) SaveOriginCheckpointBlockRoot(ctx context.Context, blockRoot [32]byte) error {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.SaveOriginCheckpointBlockRoot")
|
||||
defer span.End()
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(blocksBucket)
|
||||
return bucket.Put(originBlockRootKey, blockRoot[:])
|
||||
return bucket.Put(originCheckpointBlockRootKey, blockRoot[:])
|
||||
})
|
||||
}
|
||||
|
||||
// SaveBackfillBlockRoot is used to keep track of the most recently backfilled block root when
|
||||
// the node was initialized via checkpoint sync.
|
||||
func (s *Store) SaveBackfillBlockRoot(ctx context.Context, blockRoot [32]byte) error {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.SaveBackfillBlockRoot")
|
||||
defer span.End()
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(blocksBucket)
|
||||
return bucket.Put(backfillBlockRootKey, blockRoot[:])
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,23 @@ var blockTests = []struct {
|
||||
},
|
||||
}
|
||||
|
||||
func TestStore_SaveBackfillBlockRoot(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := db.BackfillBlockRoot(ctx)
|
||||
require.ErrorIs(t, err, ErrNotFoundBackfillBlockRoot)
|
||||
|
||||
expected := [32]byte{}
|
||||
copy(expected[:], []byte{0x23})
|
||||
err = db.SaveBackfillBlockRoot(ctx, expected)
|
||||
require.NoError(t, err)
|
||||
actual, err := db.BackfillBlockRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, actual)
|
||||
|
||||
}
|
||||
|
||||
func TestStore_SaveBlock_NoDuplicates(t *testing.T) {
|
||||
BlockCacheSize = 1
|
||||
slot := types.Slot(20)
|
||||
|
||||
@@ -13,5 +13,11 @@ var ErrNotFoundState = errors.Wrap(ErrNotFound, "state not found")
|
||||
// ErrNotFoundOriginBlockRoot is an error specifically for the origin block root getter
|
||||
var ErrNotFoundOriginBlockRoot = errors.Wrap(ErrNotFound, "OriginBlockRoot")
|
||||
|
||||
// ErrNotFoundGenesisBlockRoot means no genesis block root was found, indicating the db was not initialized with genesis
|
||||
var ErrNotFoundGenesisBlockRoot = errors.Wrap(ErrNotFound, "OriginGenesisRoot")
|
||||
|
||||
// ErrNotFoundOriginBlockRoot is an error specifically for the origin block root getter
|
||||
var ErrNotFoundBackfillBlockRoot = errors.Wrap(ErrNotFound, "BackfillBlockRoot")
|
||||
|
||||
// ErrNotFoundFeeRecipient is a not found error specifically for the fee recipient getter
|
||||
var ErrNotFoundFeeRecipient = errors.Wrap(ErrNotFound, "fee recipient")
|
||||
|
||||
@@ -32,7 +32,7 @@ var containerFinalizedButNotCanonical = []byte("recent block needs reindexing to
|
||||
// - De-index all finalized beacon block roots from previous_finalized_epoch to
|
||||
// new_finalized_epoch. (I.e. delete these roots from the index, to be re-indexed.)
|
||||
// - Build the canonical finalized chain by walking up the ancestry chain from the finalized block
|
||||
// root until a parent is found in the index or the parent is genesis.
|
||||
// root until a parent is found in the index, or the parent is genesis or the origin checkpoint.
|
||||
// - Add all block roots in the database where epoch(block.slot) == checkpoint.epoch.
|
||||
//
|
||||
// This method ensures that all blocks from the current finalized epoch are considered "final" while
|
||||
@@ -46,6 +46,7 @@ func (s *Store) updateFinalizedBlockRoots(ctx context.Context, tx *bolt.Tx, chec
|
||||
root := checkpoint.Root
|
||||
var previousRoot []byte
|
||||
genesisRoot := tx.Bucket(blocksBucket).Get(genesisBlockRootKey)
|
||||
initCheckpointRoot := tx.Bucket(blocksBucket).Get(originCheckpointBlockRootKey)
|
||||
|
||||
// De-index recent finalized block roots, to be re-indexed.
|
||||
previousFinalizedCheckpoint := ðpb.Checkpoint{}
|
||||
@@ -74,7 +75,7 @@ func (s *Store) updateFinalizedBlockRoots(ctx context.Context, tx *bolt.Tx, chec
|
||||
// Walk up the ancestry chain until we reach a block root present in the finalized block roots
|
||||
// index bucket or genesis block root.
|
||||
for {
|
||||
if bytes.Equal(root, genesisRoot) {
|
||||
if bytes.Equal(root, genesisRoot) || bytes.Equal(root, initCheckpointRoot) {
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,9 @@ var (
|
||||
altairKey = []byte("altair")
|
||||
bellatrixKey = []byte("merge")
|
||||
// block root included in the beacon state used by weak subjectivity initial sync
|
||||
originBlockRootKey = []byte("origin-block-root")
|
||||
originCheckpointBlockRootKey = []byte("origin-checkpoint-block-root")
|
||||
// block root tracking the progress of backfill, or pointing at genesis if backfill has not been initiated
|
||||
backfillBlockRootKey = []byte("backfill-block-root")
|
||||
|
||||
// Deprecated: This index key was migrated in PR 6461. Do not use, except for migrations.
|
||||
lastArchivedIndexKey = []byte("last-archived")
|
||||
|
||||
@@ -2,69 +2,76 @@ package kv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/encoding/ssz/detect"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/runtime/version"
|
||||
)
|
||||
|
||||
// SaveOrigin loads an ssz serialized Block & BeaconState from an io.Reader
|
||||
// (ex: an open file) prepares the database so that the beacon node can begin
|
||||
// syncing, using the provided values as their point of origin. This is an alternative
|
||||
// to syncing from genesis, and should only be run on an empty database.
|
||||
func (s *Store) SaveOrigin(ctx context.Context, stateReader, blockReader io.Reader) error {
|
||||
sb, err := ioutil.ReadAll(stateReader)
|
||||
func (s *Store) SaveOrigin(ctx context.Context, serState, serBlock []byte) error {
|
||||
genesisRoot, err := s.GenesisBlockRoot(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read origin state bytes")
|
||||
if errors.Is(err, ErrNotFoundGenesisBlockRoot) {
|
||||
return errors.Wrap(err, "genesis block root not found: genesis must be provided for checkpoint sync")
|
||||
}
|
||||
return errors.Wrap(err, "genesis block root query error: checkpoint sync must verify genesis to proceed")
|
||||
}
|
||||
bb, err := ioutil.ReadAll(blockReader)
|
||||
err = s.SaveBackfillBlockRoot(ctx, genesisRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error reading block given to SaveOrigin")
|
||||
return errors.Wrap(err, "unable to save genesis root as initial backfill starting point for checkpoint sync")
|
||||
}
|
||||
|
||||
cf, err := detect.FromState(sb)
|
||||
cf, err := detect.FromState(serState)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to detect config and fork for origin state")
|
||||
return errors.Wrap(err, "could not sniff config+fork for origin state bytes")
|
||||
}
|
||||
bs, err := cf.UnmarshalBeaconState(sb)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not unmarshal origin state")
|
||||
}
|
||||
wblk, err := cf.UnmarshalBeaconBlock(bb)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to unmarshal origin SignedBeaconBlock")
|
||||
_, ok := params.BeaconConfig().ForkVersionSchedule[cf.Version]
|
||||
if !ok {
|
||||
return fmt.Errorf("config mismatch, beacon node configured to connect to %s, detected state is for %s", params.BeaconConfig().ConfigName, cf.Config.ConfigName)
|
||||
}
|
||||
|
||||
blockRoot, err := wblk.Block().HashTreeRoot()
|
||||
log.Infof("detected supported config for state & block version, config name=%s, fork name=%s", cf.Config.ConfigName, version.String(cf.Fork))
|
||||
state, err := cf.UnmarshalBeaconState(serState)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize origin state w/ bytes + config+fork")
|
||||
}
|
||||
|
||||
wblk, err := cf.UnmarshalBeaconBlock(serBlock)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize origin block w/ bytes + config+fork")
|
||||
}
|
||||
blk := wblk.Block()
|
||||
|
||||
// save block
|
||||
blockRoot, err := blk.HashTreeRoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not compute HashTreeRoot of checkpoint block")
|
||||
}
|
||||
// save block
|
||||
log.Infof("saving checkpoint block to db, w/ root=%#x", blockRoot)
|
||||
if err := s.SaveBlock(ctx, wblk); err != nil {
|
||||
return errors.Wrap(err, "could not save checkpoint block")
|
||||
}
|
||||
|
||||
// save state
|
||||
if err = s.SaveState(ctx, bs, blockRoot); err != nil {
|
||||
log.Infof("calling SaveState w/ blockRoot=%x", blockRoot)
|
||||
if err = s.SaveState(ctx, state, blockRoot); err != nil {
|
||||
return errors.Wrap(err, "could not save state")
|
||||
}
|
||||
if err = s.SaveStateSummary(ctx, ðpb.StateSummary{
|
||||
Slot: bs.Slot(),
|
||||
Slot: state.Slot(),
|
||||
Root: blockRoot[:],
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "could not save state summary")
|
||||
}
|
||||
|
||||
// save origin block root in special key, to be used when the canonical
|
||||
// origin (start of chain, ie alternative to genesis) block or state is needed
|
||||
if err = s.SaveOriginBlockRoot(ctx, blockRoot); err != nil {
|
||||
return errors.Wrap(err, "could not save origin block root")
|
||||
}
|
||||
|
||||
// mark block as head of chain, so that processing will pick up from this point
|
||||
if err = s.SaveHeadBlockRoot(ctx, blockRoot); err != nil {
|
||||
return errors.Wrap(err, "could not save head block root")
|
||||
@@ -87,5 +94,11 @@ func (s *Store) SaveOrigin(ctx context.Context, stateReader, blockReader io.Read
|
||||
return errors.Wrap(err, "could not mark checkpoint sync block as finalized")
|
||||
}
|
||||
|
||||
// save origin block root in a special key, to be used when the canonical
|
||||
// origin (start of chain, ie alternative to genesis) block or state is needed
|
||||
if err = s.SaveOriginCheckpointBlockRoot(ctx, blockRoot); err != nil {
|
||||
return errors.Wrap(err, "could not save origin block root")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
9
beacon-chain/forkchoice/types/BUILD.bazel
Normal file
9
beacon-chain/forkchoice/types/BUILD.bazel
Normal 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"],
|
||||
)
|
||||
13
beacon-chain/forkchoice/types/types.go
Normal file
13
beacon-chain/forkchoice/types/types.go
Normal 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
|
||||
}
|
||||
@@ -41,6 +41,8 @@ go_library(
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//beacon-chain/sync/backfill:go_default_library",
|
||||
"//beacon-chain/sync/checkpoint:go_default_library",
|
||||
"//beacon-chain/sync/initial-sync:go_default_library",
|
||||
"//cmd:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
|
||||
@@ -44,6 +44,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
regularsync "github.com/prysmaticlabs/prysm/beacon-chain/sync"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/sync/backfill"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/sync/checkpoint"
|
||||
initialsync "github.com/prysmaticlabs/prysm/beacon-chain/sync/initial-sync"
|
||||
"github.com/prysmaticlabs/prysm/cmd"
|
||||
"github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags"
|
||||
@@ -101,6 +103,8 @@ type BeaconNode struct {
|
||||
slasherAttestationsFeed *event.Feed
|
||||
finalizedStateAtStartUp state.BeaconState
|
||||
serviceFlagOpts *serviceFlagOpts
|
||||
blockchainFlagOpts []blockchain.Option
|
||||
CheckpointInitializer checkpoint.Initializer
|
||||
}
|
||||
|
||||
// New creates a new node instance, sets up configuration options, and registers
|
||||
@@ -162,13 +166,19 @@ func New(cliCtx *cli.Context, opts ...Option) (*BeaconNode, error) {
|
||||
if err := beacon.startDB(cliCtx, depositAddress); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugln("Starting Slashing DB")
|
||||
if err := beacon.startSlasherDB(cliCtx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bfs := backfill.NewStatus(beacon.db)
|
||||
if err := bfs.Reload(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "backfill status initialization error")
|
||||
}
|
||||
|
||||
log.Debugln("Starting State Gen")
|
||||
if err := beacon.startStateGen(); err != nil {
|
||||
if err := beacon.startStateGen(ctx, bfs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -239,7 +249,7 @@ func New(cliCtx *cli.Context, opts ...Option) (*BeaconNode, error) {
|
||||
|
||||
// db.DatabasePath is the path to the containing directory
|
||||
// db.NewDBFilename expands that to the canonical full path using
|
||||
// the same constuction as NewDB()
|
||||
// the same construction as NewDB()
|
||||
c, err := newBeaconNodePromCollector(db.NewDBFilename(beacon.db.DatabasePath()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -396,6 +406,13 @@ func (b *BeaconNode) startDB(cliCtx *cli.Context, depositAddress string) error {
|
||||
if err := b.db.EnsureEmbeddedGenesis(b.ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.CheckpointInitializer != nil {
|
||||
if err := b.CheckpointInitializer.Initialize(b.ctx, d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
knownContract, err := b.db.DepositContractAddress(b.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -463,10 +480,11 @@ func (b *BeaconNode) startSlasherDB(cliCtx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BeaconNode) startStateGen() error {
|
||||
b.stateGen = stategen.New(b.db)
|
||||
func (b *BeaconNode) startStateGen(ctx context.Context, bfs *backfill.Status) error {
|
||||
opts := []stategen.StateGenOption{stategen.WithBackfillStatus(bfs)}
|
||||
sg := stategen.New(b.db, opts...)
|
||||
|
||||
cp, err := b.db.FinalizedCheckpoint(b.ctx)
|
||||
cp, err := b.db.FinalizedCheckpoint(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -474,7 +492,7 @@ func (b *BeaconNode) startStateGen() error {
|
||||
r := bytesutil.ToBytes32(cp.Root)
|
||||
// Consider edge case where finalized root are zeros instead of genesis root hash.
|
||||
if r == params.BeaconConfig().ZeroHash {
|
||||
genesisBlock, err := b.db.GenesisBlock(b.ctx)
|
||||
genesisBlock, err := b.db.GenesisBlock(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -486,10 +504,12 @@ func (b *BeaconNode) startStateGen() error {
|
||||
}
|
||||
}
|
||||
|
||||
b.finalizedStateAtStartUp, err = b.stateGen.StateByRoot(b.ctx, r)
|
||||
b.finalizedStateAtStartUp, err = sg.StateByRoot(ctx, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.stateGen = sg
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(ðpb.SignedBeaconBlockBellatrix{
|
||||
Block: ðpb.BeaconBlockBellatrix{
|
||||
Body: ðpb.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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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))
|
||||
|
||||
30
beacon-chain/powchain/engine-api-client/v1/metrics.go
Normal file
30
beacon-chain/powchain/engine-api-client/v1/metrics.go
Normal 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},
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -212,6 +212,10 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.initializeEngineAPIClient(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.ensureValidPowchainData(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "unable to validate powchain data")
|
||||
}
|
||||
@@ -247,12 +251,8 @@ func (s *Service) Start() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.initializeEngineAPIClient(s.ctx); err != nil {
|
||||
log.WithError(err).Fatal("unable to initialize engine API client")
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -1056,8 +1056,12 @@ func (s *Service) ensureValidPowchainData(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initializes a connection to the engine API if an execution provider endpoint is set.
|
||||
// Initializes a client for the engine API if an execution provider endpoint is set.
|
||||
func (s *Service) initializeEngineAPIClient(ctx context.Context) error {
|
||||
// If Bellatrix fork epoch is not yet set, we exit early.
|
||||
//if params.BeaconConfig().BellatrixForkEpoch == math.MaxUint64 {
|
||||
// return nil
|
||||
//}
|
||||
opts := []engine.Option{
|
||||
engine.WithJWTSecret(s.cfg.executionEndpointJWTSecret),
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -38,7 +38,7 @@ func (vs *Server) getExecutionPayload(ctx context.Context, slot types.Slot, vIdx
|
||||
|
||||
var parentHash []byte
|
||||
var hasTerminalBlock bool
|
||||
mergeComplete, err := blocks.MergeTransitionComplete(st)
|
||||
mergeComplete, err := blocks.IsMergeTransitionComplete(st)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ go_library(
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/db/filters:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/sync/backfill:go_default_library",
|
||||
"//cache/lru:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
var ErrSlotBeforeOrigin = errors.New("cannot retrieve data for slots before sync origin")
|
||||
var ErrNoDataForSlot = errors.New("cannot retrieve data for slot")
|
||||
|
||||
// HasState returns true if the state exists in cache or in DB.
|
||||
func (s *State) HasState(ctx context.Context, blockRoot [32]byte) (bool, error) {
|
||||
@@ -230,16 +231,17 @@ func (s *State) LastAncestorState(ctx context.Context, root [32]byte) (state.Bea
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
// return an error if we have rewound to before the checkpoint sync slot
|
||||
if (b.Block().Slot() - 1) < s.minimumSlot() {
|
||||
return nil, errors.Wrapf(ErrSlotBeforeOrigin, "no blocks in db prior to slot %d", s.minimumSlot())
|
||||
}
|
||||
// Is the state a genesis state.
|
||||
parentRoot := bytesutil.ToBytes32(b.Block().ParentRoot())
|
||||
if parentRoot == params.BeaconConfig().ZeroHash {
|
||||
return s.beaconDB.GenesisState(ctx)
|
||||
}
|
||||
|
||||
// return an error if slot hasn't been covered by checkpoint sync backfill
|
||||
ps := b.Block().Slot() - 1
|
||||
if !s.slotAvailable(ps) {
|
||||
return nil, errors.Wrapf(ErrNoDataForSlot, "slot %d not in db due to checkpoint sync", ps)
|
||||
}
|
||||
// Does the state exist in the hot state cache.
|
||||
if s.hotStateCache.has(parentRoot) {
|
||||
return s.hotStateCache.get(parentRoot), nil
|
||||
@@ -283,3 +285,11 @@ func (s *State) CombinedCache() *CombinedCache {
|
||||
}
|
||||
return &CombinedCache{getters: getters}
|
||||
}
|
||||
|
||||
func (s *State) slotAvailable(slot types.Slot) bool {
|
||||
// default to assuming node was initialized from genesis - backfill only needs to be specified for checkpoint sync
|
||||
if s.backfillStatus == nil {
|
||||
return true
|
||||
}
|
||||
return s.backfillStatus.SlotCovered(slot)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/sync/backfill"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
@@ -48,8 +49,7 @@ type State struct {
|
||||
finalizedInfo *finalizedInfo
|
||||
epochBoundaryStateCache *epochBoundaryState
|
||||
saveHotStateDB *saveHotStateDbConfig
|
||||
minSlot types.Slot
|
||||
beaconDBInitType BeaconDBInitType
|
||||
backfillStatus *backfill.Status
|
||||
}
|
||||
|
||||
// This tracks the config in the event of long non-finality,
|
||||
@@ -71,28 +71,15 @@ type finalizedInfo struct {
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func WithMinimumSlot(min types.Slot) StateGenOption {
|
||||
return func(s *State) {
|
||||
s.minSlot = min
|
||||
}
|
||||
}
|
||||
|
||||
type BeaconDBInitType uint
|
||||
|
||||
const (
|
||||
BeaconDBInitTypeGenesisState = iota
|
||||
BeaconDBInitTypeCheckpoint
|
||||
)
|
||||
|
||||
func WithInitType(t BeaconDBInitType) StateGenOption {
|
||||
return func(s *State) {
|
||||
s.beaconDBInitType = t
|
||||
}
|
||||
}
|
||||
|
||||
// StateGenOption is a functional option for controlling the initialization of a *State value
|
||||
type StateGenOption func(*State)
|
||||
|
||||
func WithBackfillStatus(bfs *backfill.Status) StateGenOption {
|
||||
return func(sg *State) {
|
||||
sg.backfillStatus = bfs
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new state management object.
|
||||
func New(beaconDB db.NoHeadAccessDatabase, opts ...StateGenOption) *State {
|
||||
s := &State{
|
||||
@@ -104,12 +91,11 @@ func New(beaconDB db.NoHeadAccessDatabase, opts ...StateGenOption) *State {
|
||||
saveHotStateDB: &saveHotStateDbConfig{
|
||||
duration: defaultHotStateDBInterval,
|
||||
},
|
||||
// defaults to minimumSlot of zero (genesis), overridden by checkpoint sync
|
||||
minSlot: params.BeaconConfig().GenesisSlot,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(s)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -167,7 +153,3 @@ func (s *State) finalizedState() state.BeaconState {
|
||||
defer s.finalizedInfo.lock.RUnlock()
|
||||
return s.finalizedInfo.state.Copy()
|
||||
}
|
||||
|
||||
func (s *State) minimumSlot() types.Slot {
|
||||
return s.minSlot
|
||||
}
|
||||
|
||||
32
beacon-chain/sync/backfill/BUILD.bazel
Normal file
32
beacon-chain/sync/backfill/BUILD.bazel
Normal file
@@ -0,0 +1,32 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["status.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/sync/backfill",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["status_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"//proto/prysm/v1alpha1/wrapper:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
],
|
||||
)
|
||||
122
beacon-chain/sync/backfill/status.go
Normal file
122
beacon-chain/sync/backfill/status.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package backfill
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
)
|
||||
|
||||
// NewStatus correctly initializes a Status value with the required database value.
|
||||
func NewStatus(store BackfillDB) *Status {
|
||||
return &Status{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
// Status provides a way to update and query the status of a backfill process that may be necessary to track when
|
||||
// a node was initialized via checkpoint sync. With checkpoint sync, there will be a gap in node history from genesis
|
||||
// until the checkpoint sync origin block. Status provides the means to update the value keeping track of the lower
|
||||
// end of the missing block range via the Advance() method, to check whether a Slot is missing from the database
|
||||
// via the SlotCovered() method, and to see the current StartGap() and EndGap().
|
||||
type Status struct {
|
||||
start types.Slot
|
||||
end types.Slot
|
||||
store BackfillDB
|
||||
genesisSync bool
|
||||
}
|
||||
|
||||
// SlotCovered uses StartGap() and EndGap() to determine if the given slot is covered by the current chain history.
|
||||
// If the slot is <= StartGap(), or >= EndGap(), the result is true.
|
||||
// If the slot is between StartGap() and EndGap(), the result is false.
|
||||
func (s *Status) SlotCovered(sl types.Slot) bool {
|
||||
// short circuit if the node was synced from genesis
|
||||
if s.genesisSync {
|
||||
return true
|
||||
}
|
||||
if s.StartGap() < sl && sl < s.EndGap() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// StartGap returns the slot at the beginning of the range that needs to be backfilled.
|
||||
func (s *Status) StartGap() types.Slot {
|
||||
return s.start
|
||||
}
|
||||
|
||||
// EndGap returns the slot at the end of the range that needs to be backfilled.
|
||||
func (s *Status) EndGap() types.Slot {
|
||||
return s.end
|
||||
}
|
||||
|
||||
var ErrAdvancePastOrigin = errors.New("cannot advance backfill Status beyond the origin checkpoint slot")
|
||||
|
||||
// Advance advances the backfill position to the given slot & root.
|
||||
// It updates the backfill block root entry in the database,
|
||||
// and also updates the Status value's copy of the backfill position slot.
|
||||
func (s *Status) Advance(ctx context.Context, upTo types.Slot, root [32]byte) error {
|
||||
if upTo > s.end {
|
||||
return errors.Wrapf(ErrAdvancePastOrigin, "advance slot=%d, origin slot=%d", upTo, s.end)
|
||||
}
|
||||
s.start = upTo
|
||||
return s.store.SaveBackfillBlockRoot(ctx, root)
|
||||
}
|
||||
|
||||
// Reload queries the database for backfill status, initializing the internal data and validating the database state.
|
||||
func (s *Status) Reload(ctx context.Context) error {
|
||||
cpRoot, err := s.store.OriginCheckpointBlockRoot(ctx)
|
||||
if err != nil {
|
||||
// mark genesis sync and short circuit further lookups
|
||||
if errors.Is(err, db.ErrNotFoundOriginBlockRoot) {
|
||||
s.genesisSync = true
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
cpBlock, err := s.store.Block(ctx, cpRoot)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving block for origin checkpoint root=%#x", cpRoot)
|
||||
}
|
||||
if err := helpers.BeaconBlockIsNil(cpBlock); err != nil {
|
||||
return err
|
||||
}
|
||||
s.end = cpBlock.Block().Slot()
|
||||
|
||||
_, err = s.store.GenesisBlockRoot(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNotFoundGenesisBlockRoot) {
|
||||
return errors.Wrap(err, "genesis block root required for checkpoint sync")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
bfRoot, err := s.store.BackfillBlockRoot(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNotFoundBackfillBlockRoot) {
|
||||
return errors.Wrap(err, "found origin checkpoint block root, but no backfill block root")
|
||||
}
|
||||
return err
|
||||
}
|
||||
bfBlock, err := s.store.Block(ctx, bfRoot)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error retrieving block for backfill root=%#x", bfRoot)
|
||||
}
|
||||
if err := helpers.BeaconBlockIsNil(bfBlock); err != nil {
|
||||
return err
|
||||
}
|
||||
s.start = bfBlock.Block().Slot()
|
||||
return nil
|
||||
}
|
||||
|
||||
// BackfillDB describes the set of DB methods that the Status type needs to function.
|
||||
type BackfillDB interface {
|
||||
SaveBackfillBlockRoot(ctx context.Context, blockRoot [32]byte) error
|
||||
GenesisBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
OriginCheckpointBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
BackfillBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
Block(ctx context.Context, blockRoot [32]byte) (block.SignedBeaconBlock, error)
|
||||
}
|
||||
359
beacon-chain/sync/backfill/status_test.go
Normal file
359
beacon-chain/sync/backfill/status_test.go
Normal file
@@ -0,0 +1,359 @@
|
||||
package backfill
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
)
|
||||
|
||||
var errEmptyMockDBMethod = errors.New("uninitialized mock db method called")
|
||||
|
||||
type mockBackfillDB struct {
|
||||
saveBackfillBlockRoot func(ctx context.Context, blockRoot [32]byte) error
|
||||
genesisBlockRoot func(ctx context.Context) ([32]byte, error)
|
||||
originCheckpointBlockRoot func(ctx context.Context) ([32]byte, error)
|
||||
backfillBlockRoot func(ctx context.Context) ([32]byte, error)
|
||||
block func(ctx context.Context, blockRoot [32]byte) (block.SignedBeaconBlock, error)
|
||||
}
|
||||
|
||||
var _ BackfillDB = &mockBackfillDB{}
|
||||
|
||||
func (db *mockBackfillDB) SaveBackfillBlockRoot(ctx context.Context, blockRoot [32]byte) error {
|
||||
if db.saveBackfillBlockRoot != nil {
|
||||
return db.saveBackfillBlockRoot(ctx, blockRoot)
|
||||
}
|
||||
return errEmptyMockDBMethod
|
||||
}
|
||||
|
||||
func (db *mockBackfillDB) GenesisBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
if db.genesisBlockRoot != nil {
|
||||
return db.genesisBlockRoot(ctx)
|
||||
}
|
||||
return [32]byte{}, errEmptyMockDBMethod
|
||||
}
|
||||
|
||||
func (db *mockBackfillDB) OriginCheckpointBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
if db.originCheckpointBlockRoot != nil {
|
||||
return db.originCheckpointBlockRoot(ctx)
|
||||
}
|
||||
return [32]byte{}, errEmptyMockDBMethod
|
||||
}
|
||||
|
||||
func (db *mockBackfillDB) BackfillBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
if db.backfillBlockRoot != nil {
|
||||
return db.backfillBlockRoot(ctx)
|
||||
}
|
||||
return [32]byte{}, errEmptyMockDBMethod
|
||||
}
|
||||
|
||||
func (db *mockBackfillDB) Block(ctx context.Context, blockRoot [32]byte) (block.SignedBeaconBlock, error) {
|
||||
if db.block != nil {
|
||||
return db.block(ctx, blockRoot)
|
||||
}
|
||||
return nil, errEmptyMockDBMethod
|
||||
}
|
||||
|
||||
func TestSlotCovered(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
slot types.Slot
|
||||
status *Status
|
||||
result bool
|
||||
}{
|
||||
{
|
||||
name: "below start true",
|
||||
status: &Status{start: 1},
|
||||
slot: 0,
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
name: "above end true",
|
||||
status: &Status{end: 1},
|
||||
slot: 2,
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
name: "equal end true",
|
||||
status: &Status{end: 1},
|
||||
slot: 1,
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
name: "equal start true",
|
||||
status: &Status{start: 2},
|
||||
slot: 2,
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
name: "between false",
|
||||
status: &Status{start: 1, end: 3},
|
||||
slot: 2,
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
name: "genesisSync always true",
|
||||
status: &Status{genesisSync: true},
|
||||
slot: 100,
|
||||
result: true,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
result := c.status.SlotCovered(c.slot)
|
||||
require.Equal(t, c.result, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdvance(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
saveBackfillBuf := make([][32]byte, 0)
|
||||
mdb := &mockBackfillDB{
|
||||
saveBackfillBlockRoot: func(ctx context.Context, root [32]byte) error {
|
||||
saveBackfillBuf = append(saveBackfillBuf, root)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
s := &Status{end: 100, store: mdb}
|
||||
var root [32]byte
|
||||
copy(root[:], []byte{0x23, 0x23})
|
||||
require.NoError(t, s.Advance(ctx, 90, root))
|
||||
require.Equal(t, root, saveBackfillBuf[0])
|
||||
not := s.SlotCovered(95)
|
||||
require.Equal(t, false, not)
|
||||
|
||||
// this should still be len 1 after failing to advance
|
||||
require.Equal(t, 1, len(saveBackfillBuf))
|
||||
require.ErrorIs(t, s.Advance(ctx, s.end+1, root), ErrAdvancePastOrigin)
|
||||
// this has an element in it from the previous test, there shouldn't be an additional one
|
||||
require.Equal(t, 1, len(saveBackfillBuf))
|
||||
}
|
||||
|
||||
func goodBlockRoot(root [32]byte) func(ctx context.Context) ([32]byte, error) {
|
||||
return func(ctx context.Context) ([32]byte, error) {
|
||||
return root, nil
|
||||
}
|
||||
}
|
||||
|
||||
func setupTestBlock(slot types.Slot) (block.SignedBeaconBlock, error) {
|
||||
bRaw := util.NewBeaconBlock()
|
||||
b, err := wrapper.WrappedSignedBeaconBlock(bRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, wrapper.SetBlockSlot(b, slot)
|
||||
}
|
||||
|
||||
func TestReload(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
derp := errors.New("derp")
|
||||
|
||||
originSlot := types.Slot(100)
|
||||
var originRoot [32]byte
|
||||
copy(originRoot[:], []byte{0x01})
|
||||
originBlock, err := setupTestBlock(originSlot)
|
||||
require.NoError(t, err)
|
||||
|
||||
backfillSlot := types.Slot(50)
|
||||
var backfillRoot [32]byte
|
||||
copy(originRoot[:], []byte{0x02})
|
||||
backfillBlock, err := setupTestBlock(backfillSlot)
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
db BackfillDB
|
||||
err error
|
||||
expected *Status
|
||||
}{
|
||||
{
|
||||
name: "origin not found, implying genesis sync ",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: func(ctx context.Context) ([32]byte, error) {
|
||||
return [32]byte{}, db.ErrNotFoundOriginBlockRoot
|
||||
}},
|
||||
expected: &Status{genesisSync: true},
|
||||
},
|
||||
{
|
||||
name: "genesis not found error",
|
||||
err: db.ErrNotFoundGenesisBlockRoot,
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: func(ctx context.Context) ([32]byte, error) {
|
||||
return [32]byte{}, db.ErrNotFoundGenesisBlockRoot
|
||||
},
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
switch root {
|
||||
case originRoot:
|
||||
return originBlock, nil
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "other genesis error",
|
||||
err: derp,
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: func(ctx context.Context) ([32]byte, error) {
|
||||
return [32]byte{}, derp
|
||||
},
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
switch root {
|
||||
case originRoot:
|
||||
return originBlock, nil
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "origin other error",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: func(ctx context.Context) ([32]byte, error) {
|
||||
return [32]byte{}, derp
|
||||
}},
|
||||
err: derp,
|
||||
},
|
||||
{
|
||||
name: "origin root found, block missing",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
err: helpers.ErrNilSignedBeaconBlock,
|
||||
},
|
||||
{
|
||||
name: "origin root found, block error",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
return nil, derp
|
||||
},
|
||||
},
|
||||
err: derp,
|
||||
},
|
||||
{
|
||||
name: "origin root found, block found, backfill root not found",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
return originBlock, nil
|
||||
},
|
||||
backfillBlockRoot: func(ctx context.Context) ([32]byte, error) {
|
||||
return [32]byte{}, db.ErrNotFoundBackfillBlockRoot
|
||||
},
|
||||
},
|
||||
err: db.ErrNotFoundBackfillBlockRoot,
|
||||
},
|
||||
{
|
||||
name: "origin root found, block found, random backfill root err",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
switch root {
|
||||
case originRoot:
|
||||
return originBlock, nil
|
||||
case backfillRoot:
|
||||
return nil, nil
|
||||
}
|
||||
return nil, derp
|
||||
},
|
||||
backfillBlockRoot: func(ctx context.Context) ([32]byte, error) {
|
||||
return [32]byte{}, derp
|
||||
},
|
||||
},
|
||||
err: derp,
|
||||
},
|
||||
{
|
||||
name: "origin root found, block found, backfill root found, backfill block not found",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
switch root {
|
||||
case originRoot:
|
||||
return originBlock, nil
|
||||
case backfillRoot:
|
||||
return nil, nil
|
||||
}
|
||||
return nil, derp
|
||||
},
|
||||
backfillBlockRoot: goodBlockRoot(backfillRoot),
|
||||
},
|
||||
err: helpers.ErrNilSignedBeaconBlock,
|
||||
},
|
||||
{
|
||||
name: "origin root found, block found, backfill root found, backfill block random err",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
switch root {
|
||||
case originRoot:
|
||||
return originBlock, nil
|
||||
case backfillRoot:
|
||||
return nil, derp
|
||||
}
|
||||
return nil, errors.New("not derp")
|
||||
},
|
||||
backfillBlockRoot: goodBlockRoot(backfillRoot),
|
||||
},
|
||||
err: derp,
|
||||
},
|
||||
{
|
||||
name: "complete happy path",
|
||||
db: &mockBackfillDB{
|
||||
genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash),
|
||||
originCheckpointBlockRoot: goodBlockRoot(originRoot),
|
||||
block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) {
|
||||
switch root {
|
||||
case originRoot:
|
||||
return originBlock, nil
|
||||
case backfillRoot:
|
||||
return backfillBlock, nil
|
||||
}
|
||||
return nil, errors.New("not derp")
|
||||
},
|
||||
backfillBlockRoot: goodBlockRoot(backfillRoot),
|
||||
},
|
||||
err: derp,
|
||||
expected: &Status{genesisSync: false, start: backfillSlot, end: originSlot},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
s := &Status{
|
||||
store: c.db,
|
||||
}
|
||||
err := s.Reload(ctx)
|
||||
if err != nil {
|
||||
require.ErrorIs(t, err, c.err)
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err)
|
||||
if c.expected == nil {
|
||||
continue
|
||||
}
|
||||
require.Equal(t, c.expected.genesisSync, s.genesisSync)
|
||||
require.Equal(t, c.expected.start, s.start)
|
||||
require.Equal(t, c.expected.end, s.end)
|
||||
}
|
||||
}
|
||||
17
beacon-chain/sync/checkpoint/BUILD.bazel
Normal file
17
beacon-chain/sync/checkpoint/BUILD.bazel
Normal file
@@ -0,0 +1,17 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"api.go",
|
||||
"file.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/sync/checkpoint",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api/client/beacon:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
],
|
||||
)
|
||||
35
beacon-chain/sync/checkpoint/api.go
Normal file
35
beacon-chain/sync/checkpoint/api.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/api/client/beacon"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
)
|
||||
|
||||
// APIInitializer manages initializing the beacon node using checkpoint sync, retrieving the checkpoint state and root
|
||||
// from the remote beacon node api.
|
||||
type APIInitializer struct {
|
||||
c *beacon.Client
|
||||
}
|
||||
|
||||
// NewAPIInitializer creates an APIInitializer, handling the set up of a beacon node api client
|
||||
// using the provided host string.
|
||||
func NewAPIInitializer(beaconNodeHost string) (*APIInitializer, error) {
|
||||
c, err := beacon.NewClient(beaconNodeHost)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to parse beacon node url or hostname - %s", beaconNodeHost)
|
||||
}
|
||||
return &APIInitializer{c: c}, nil
|
||||
}
|
||||
|
||||
// Initialize downloads origin state and block for checkpoint sync and initializes database records to
|
||||
// prepare the node to begin syncing from that point.
|
||||
func (dl *APIInitializer) Initialize(ctx context.Context, d db.Database) error {
|
||||
od, err := beacon.DownloadOriginData(ctx, dl.c)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error retrieving checkpoint origin state and block")
|
||||
}
|
||||
return d.SaveOrigin(ctx, od.StateBytes(), od.BlockBytes())
|
||||
}
|
||||
67
beacon-chain/sync/checkpoint/file.go
Normal file
67
beacon-chain/sync/checkpoint/file.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/io/file"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
)
|
||||
|
||||
// Initializer describes a type that is able to obtain the checkpoint sync data (BeaconState and SignedBeaconBlock)
|
||||
// in some way and perform database setup to prepare the beacon node for syncing from the given checkpoint.
|
||||
// See FileInitializer and APIInitializer.
|
||||
type Initializer interface {
|
||||
Initialize(ctx context.Context, d db.Database) error
|
||||
}
|
||||
|
||||
// NewFileInitializer validates the given path information and creates an Initializer which will
|
||||
// use the provided state and block files to prepare the node for checkpoint sync.
|
||||
func NewFileInitializer(blockPath string, statePath string) (*FileInitializer, error) {
|
||||
var err error
|
||||
if err = existsAndIsFile(blockPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = existsAndIsFile(statePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// stat just to make sure it actually exists and is a file
|
||||
return &FileInitializer{blockPath: blockPath, statePath: statePath}, nil
|
||||
}
|
||||
|
||||
// FileInitializer initializes a beacon-node database to use checkpoint sync,
|
||||
// using ssz-encoded block and state data stored in files on the local filesystem.
|
||||
type FileInitializer struct {
|
||||
blockPath string
|
||||
statePath string
|
||||
}
|
||||
|
||||
// Initialize is called in the BeaconNode db startup code if an Initializer is present.
|
||||
// Initialize does what is needed to prepare the beacon node database for syncing from the weak subjectivity checkpoint.
|
||||
func (fi *FileInitializer) Initialize(ctx context.Context, d db.Database) error {
|
||||
serBlock, err := file.ReadFileAsBytes(fi.blockPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error reading block file %s for checkpoint sync init", fi.blockPath)
|
||||
}
|
||||
serState, err := file.ReadFileAsBytes(fi.statePath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error reading state file %s for checkpoint sync init", fi.blockPath)
|
||||
}
|
||||
return d.SaveOrigin(ctx, serState, serBlock)
|
||||
}
|
||||
|
||||
var _ Initializer = &FileInitializer{}
|
||||
|
||||
func existsAndIsFile(path string) error {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error checking existence of ssz-encoded file %s for checkpoint sync init", path)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fmt.Errorf("%s is a directory, please specify full path to file", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/monitoring/tracing"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/prysmaticlabs/prysm/runtime/version"
|
||||
prysmTime "github.com/prysmaticlabs/prysm/time"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -258,12 +257,9 @@ func (s *Service) validateBellatrixBeaconBlock(ctx context.Context, parentState
|
||||
if parentState.Version() != blk.Version() {
|
||||
return errors.New("block and state are not the same version")
|
||||
}
|
||||
if parentState.Version() != version.Bellatrix || blk.Version() != version.Bellatrix {
|
||||
return nil
|
||||
}
|
||||
|
||||
body := blk.Body()
|
||||
executionEnabled, err := blocks.ExecutionEnabled(parentState, body)
|
||||
executionEnabled, err := blocks.IsExecutionEnabled(parentState, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ go_library(
|
||||
"//cmd/beacon-chain/db:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//cmd/beacon-chain/powchain:go_default_library",
|
||||
"//cmd/beacon-chain/sync/checkpoint:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//io/logs:go_default_library",
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
// FlagOptions for blockchain service flag configurations.
|
||||
func FlagOptions(c *cli.Context) ([]blockchain.Option, error) {
|
||||
wsp := c.String(flags.WeakSubjectivityCheckpt.Name)
|
||||
wsp := c.String(flags.WeakSubjectivityCheckpoint.Name)
|
||||
wsCheckpt, err := helpers.ParseWeakSubjectivityInputString(wsp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -185,13 +185,6 @@ var (
|
||||
Name: "network-id",
|
||||
Usage: "Sets the network id of the beacon chain.",
|
||||
}
|
||||
// WeakSubjectivityCheckpt defines the weak subjectivity checkpoint the node must sync through to defend against long range attacks.
|
||||
WeakSubjectivityCheckpt = &cli.StringFlag{
|
||||
Name: "weak-subjectivity-checkpoint",
|
||||
Usage: "Input in `block_root:epoch_number` format. This guarantees that syncing leads to the given Weak Subjectivity Checkpoint along the canonical chain. " +
|
||||
"If such a sync is not possible, the node will treat it a critical and irrecoverable failure",
|
||||
Value: "",
|
||||
}
|
||||
// Eth1HeaderReqLimit defines a flag to set the maximum number of headers that a deposit log query can fetch. If none is set, 1000 will be the limit.
|
||||
Eth1HeaderReqLimit = &cli.Uint64Flag{
|
||||
Name: "eth1-header-req-limit",
|
||||
@@ -204,6 +197,14 @@ var (
|
||||
Usage: "Load a genesis state from ssz file. Testnet genesis files can be found in the " +
|
||||
"eth2-clients/eth2-testnets repository on github.",
|
||||
}
|
||||
// WeakSubjectivityCheckpoint defines the weak subjectivity checkpoint the node must sync through to defend against long range attacks.
|
||||
WeakSubjectivityCheckpoint = &cli.StringFlag{
|
||||
Name: "weak-subjectivity-checkpoint",
|
||||
Usage: "Input in `block_root:epoch_number` format." +
|
||||
" This guarantees that syncing leads to the given Weak Subjectivity Checkpoint along the canonical chain. " +
|
||||
"If such a sync is not possible, the node will treat it as a critical and irrecoverable failure",
|
||||
Value: "",
|
||||
}
|
||||
// MinPeersPerSubnet defines a flag to set the minimum number of peers that a node will attempt to peer with for a subnet.
|
||||
MinPeersPerSubnet = &cli.Uint64Flag{
|
||||
Name: "minimum-peers-per-subnet",
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
dbcommands "github.com/prysmaticlabs/prysm/cmd/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags"
|
||||
powchaincmd "github.com/prysmaticlabs/prysm/cmd/beacon-chain/powchain"
|
||||
"github.com/prysmaticlabs/prysm/cmd/beacon-chain/sync/checkpoint"
|
||||
"github.com/prysmaticlabs/prysm/config/features"
|
||||
"github.com/prysmaticlabs/prysm/io/file"
|
||||
"github.com/prysmaticlabs/prysm/io/logs"
|
||||
@@ -62,7 +63,7 @@ var appFlags = []cli.Flag{
|
||||
flags.HistoricalSlasherNode,
|
||||
flags.ChainID,
|
||||
flags.NetworkID,
|
||||
flags.WeakSubjectivityCheckpt,
|
||||
flags.WeakSubjectivityCheckpoint,
|
||||
flags.Eth1HeaderReqLimit,
|
||||
flags.GenesisStatePath,
|
||||
flags.MinPeersPerSubnet,
|
||||
@@ -118,6 +119,9 @@ var appFlags = []cli.Flag{
|
||||
cmd.BoltMMapInitialSizeFlag,
|
||||
cmd.ValidatorMonitorIndicesFlag,
|
||||
cmd.ApiTimeoutFlag,
|
||||
checkpoint.BlockPath,
|
||||
checkpoint.StatePath,
|
||||
checkpoint.RemoteURL,
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -242,6 +246,13 @@ func startNode(ctx *cli.Context) error {
|
||||
node.WithBlockchainFlagOptions(blockchainFlagOpts),
|
||||
node.WithPowchainFlagOptions(powchainFlagOpts),
|
||||
}
|
||||
cptOpts, err := checkpoint.BeaconNodeOptions(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cptOpts != nil {
|
||||
opts = append(opts, cptOpts)
|
||||
}
|
||||
beacon, err := node.New(ctx, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
14
cmd/beacon-chain/sync/checkpoint/BUILD.bazel
Normal file
14
cmd/beacon-chain/sync/checkpoint/BUILD.bazel
Normal file
@@ -0,0 +1,14 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["options.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/cmd/beacon-chain/sync/checkpoint",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/node:go_default_library",
|
||||
"//beacon-chain/sync/checkpoint:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
68
cmd/beacon-chain/sync/checkpoint/options.go
Normal file
68
cmd/beacon-chain/sync/checkpoint/options.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/node"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/sync/checkpoint"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
// StatePath defines a flag to start the beacon chain from a give genesis state file.
|
||||
StatePath = &cli.PathFlag{
|
||||
Name: "checkpoint-state",
|
||||
Usage: "Rather than syncing from genesis, you can start processing from a ssz-serialized BeaconState+Block." +
|
||||
" This flag allows you to specify a local file containing the checkpoint BeaconState to load.",
|
||||
}
|
||||
// BlockPath is required when using StatePath to also provide the latest integrated block.
|
||||
BlockPath = &cli.PathFlag{
|
||||
Name: "checkpoint-block",
|
||||
Usage: "Rather than syncing from genesis, you can start processing from a ssz-serialized BeaconState+Block." +
|
||||
" This flag allows you to specify a local file containing the checkpoint Block to load.",
|
||||
}
|
||||
RemoteURL = &cli.StringFlag{
|
||||
Name: "checkpoint-sync-url",
|
||||
Usage: "URL of a synced beacon node to trust in obtaining checkpoint sync data. " +
|
||||
"As an additional safety measure, it is strongly recommended to only use this option in conjunction with " +
|
||||
"--weak-subjectivity-checkpoint flag",
|
||||
}
|
||||
)
|
||||
|
||||
// BeaconNodeOptions is responsible for determining if the checkpoint sync options have been used, and if so,
|
||||
// reading the block and state ssz-serialized values from the filesystem locations specified and preparing a
|
||||
// checkpoint.Initializer, which uses the provided io.ReadClosers to initialize the beacon node database.
|
||||
func BeaconNodeOptions(c *cli.Context) (node.Option, error) {
|
||||
blockPath := c.Path(BlockPath.Name)
|
||||
statePath := c.Path(StatePath.Name)
|
||||
remoteURL := c.String(RemoteURL.Name)
|
||||
if remoteURL != "" {
|
||||
return func(node *node.BeaconNode) error {
|
||||
var err error
|
||||
node.CheckpointInitializer, err = checkpoint.NewAPIInitializer(remoteURL)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error while constructing beacon node api client for checkpoint sync")
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
if blockPath == "" && statePath == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if blockPath != "" && statePath == "" {
|
||||
return nil, fmt.Errorf("--checkpoint-block specified, but not --checkpoint-state. both are required")
|
||||
}
|
||||
if blockPath == "" && statePath != "" {
|
||||
return nil, fmt.Errorf("--checkpoint-state specified, but not --checkpoint-block. both are required")
|
||||
}
|
||||
|
||||
return func(node *node.BeaconNode) (err error) {
|
||||
node.CheckpointInitializer, err = checkpoint.NewFileInitializer(blockPath, statePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error preparing to initialize checkpoint from local ssz files")
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/prysmaticlabs/prysm/cmd"
|
||||
"github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags"
|
||||
"github.com/prysmaticlabs/prysm/cmd/beacon-chain/sync/checkpoint"
|
||||
"github.com/prysmaticlabs/prysm/config/features"
|
||||
"github.com/prysmaticlabs/prysm/runtime/debug"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -75,6 +76,9 @@ var appHelpFlagGroups = []flagGroup{
|
||||
cmd.BoltMMapInitialSizeFlag,
|
||||
cmd.ValidatorMonitorIndicesFlag,
|
||||
cmd.ApiTimeoutFlag,
|
||||
checkpoint.BlockPath,
|
||||
checkpoint.StatePath,
|
||||
checkpoint.RemoteURL,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -121,7 +125,7 @@ var appHelpFlagGroups = []flagGroup{
|
||||
flags.HistoricalSlasherNode,
|
||||
flags.ChainID,
|
||||
flags.NetworkID,
|
||||
flags.WeakSubjectivityCheckpt,
|
||||
flags.WeakSubjectivityCheckpoint,
|
||||
flags.Eth1HeaderReqLimit,
|
||||
flags.GenesisStatePath,
|
||||
flags.MinPeersPerSubnet,
|
||||
|
||||
@@ -30,6 +30,8 @@ func WrapFlags(flags []cli.Flag) []cli.Flag {
|
||||
f = altsrc.NewUint64Flag(t)
|
||||
case *cli.UintFlag:
|
||||
f = altsrc.NewUintFlag(t)
|
||||
case *cli.PathFlag:
|
||||
f = altsrc.NewPathFlag(t)
|
||||
case *cli.Int64Flag:
|
||||
// Int64Flag does not work. See https://github.com/prysmaticlabs/prysm/issues/6478
|
||||
panic(fmt.Sprintf("unsupported flag type type %T", f))
|
||||
|
||||
@@ -125,6 +125,7 @@ go_library(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
"//cache/lru:go_default_library",
|
||||
"@com_github_dgraph_io_ristretto//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_supranational_blst//:go_default_library",
|
||||
@@ -227,7 +228,7 @@ go_test(
|
||||
deps = [
|
||||
":go_default_library",
|
||||
"//crypto/bls/common:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//crypto/hash:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -7,19 +7,16 @@ package blst
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/dgraph-io/ristretto"
|
||||
"github.com/pkg/errors"
|
||||
lruwrpr "github.com/prysmaticlabs/prysm/cache/lru"
|
||||
"github.com/prysmaticlabs/prysm/config/features"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/crypto/bls/common"
|
||||
)
|
||||
|
||||
var maxKeys = int64(1000000)
|
||||
var pubkeyCache, _ = ristretto.NewCache(&ristretto.Config{
|
||||
NumCounters: maxKeys,
|
||||
MaxCost: 1 << 26, // ~64mb is cache max size
|
||||
BufferItems: 64,
|
||||
})
|
||||
var maxKeys = 1000000
|
||||
var pubkeyCache = lruwrpr.New(maxKeys)
|
||||
|
||||
// PublicKey used in the BLS signature scheme.
|
||||
type PublicKey struct {
|
||||
@@ -34,7 +31,8 @@ func PublicKeyFromBytes(pubKey []byte) (common.PublicKey, error) {
|
||||
if len(pubKey) != params.BeaconConfig().BLSPubkeyLength {
|
||||
return nil, fmt.Errorf("public key must be %d bytes", params.BeaconConfig().BLSPubkeyLength)
|
||||
}
|
||||
if cv, ok := pubkeyCache.Get(string(pubKey)); ok {
|
||||
newKey := (*[fieldparams.BLSPubkeyLength]byte)(pubKey)
|
||||
if cv, ok := pubkeyCache.Get(*newKey); ok {
|
||||
return cv.(*PublicKey).Copy(), nil
|
||||
}
|
||||
// Subgroup check NOT done when decompressing pubkey.
|
||||
@@ -49,7 +47,8 @@ func PublicKeyFromBytes(pubKey []byte) (common.PublicKey, error) {
|
||||
}
|
||||
pubKeyObj := &PublicKey{p: p}
|
||||
copiedKey := pubKeyObj.Copy()
|
||||
pubkeyCache.Set(string(pubKey), copiedKey, 48)
|
||||
cacheKey := *newKey
|
||||
pubkeyCache.Add(cacheKey, copiedKey)
|
||||
return pubKeyObj, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnsupportedField is returned when a field is not supported by a specific beacon block type.
|
||||
// This allows us to create a generic beacon block interface that is implemented by different
|
||||
// fork versions of beacon blocks.
|
||||
ErrUnsupportedField = errors.New("unsupported field for block type")
|
||||
// ErrUnsupportedSignedBeaconBlock is returned when the struct type is not a supported signed
|
||||
// beacon block type.
|
||||
ErrUnsupportedSignedBeaconBlock = errors.New("unsupported signed beacon block")
|
||||
|
||||
@@ -305,6 +305,6 @@ func (w altairBeaconBlockBody) Proto() proto.Message {
|
||||
}
|
||||
|
||||
// ExecutionPayload is a stub.
|
||||
func (altairBeaconBlockBody) ExecutionPayload() (*enginev1.ExecutionPayload, error) {
|
||||
return nil, errors.New("ExecutionPayload is not supported in altair block body")
|
||||
func (w altairBeaconBlockBody) ExecutionPayload() (*enginev1.ExecutionPayload, error) {
|
||||
return nil, errors.Wrapf(ErrUnsupportedField, "ExecutionPayload for %T", w)
|
||||
}
|
||||
|
||||
@@ -293,6 +293,6 @@ func (w Phase0BeaconBlockBody) Proto() proto.Message {
|
||||
}
|
||||
|
||||
// ExecutionPayload is a stub.
|
||||
func (Phase0BeaconBlockBody) ExecutionPayload() (*enginev1.ExecutionPayload, error) {
|
||||
return nil, errors.New("ExecutionPayload is not supported in phase 0 block body")
|
||||
func (w Phase0BeaconBlockBody) ExecutionPayload() (*enginev1.ExecutionPayload, error) {
|
||||
return nil, errors.Wrapf(ErrUnsupportedField, "ExecutionPayload for %T", w)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ go_library(
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/db/testing: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/state:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
@@ -34,6 +35,7 @@ go_library(
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/spectest/utils:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_golang_snappy//:go_default_library",
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/golang/snappy"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
forkchoicetypes "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1"
|
||||
v2 "github.com/prysmaticlabs/prysm/beacon-chain/state/v2"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/testing/spectest/utils"
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
)
|
||||
|
||||
// Run executes "forkchoice" test.
|
||||
@@ -103,6 +105,14 @@ func Run(t *testing.T, config string, fork int) {
|
||||
}
|
||||
r, err := beaconBlock.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
slotsSinceGenesis := slots.SinceGenesis(service.GenesisTime())
|
||||
args := &forkchoicetypes.ProposerBoostRootArgs{
|
||||
BlockRoot: r,
|
||||
BlockSlot: beaconBlock.Block().Slot(),
|
||||
CurrentSlot: slotsSinceGenesis,
|
||||
SecondsIntoSlot: uint64(lastTick) % params.BeaconConfig().SecondsPerSlot,
|
||||
}
|
||||
require.NoError(t, service.ForkChoicer().BoostProposerRoot(ctx, args))
|
||||
if step.Valid != nil && !*step.Valid {
|
||||
require.Equal(t, true, service.ReceiveBlock(ctx, beaconBlock, r) != nil)
|
||||
} else {
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user