mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 13:28:01 -05:00
Compare commits
47 Commits
c6c9414d8b
...
new-block-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30ca6360d5 | ||
|
|
85c0e0cecf | ||
|
|
7c871f3123 | ||
|
|
1d3dea325f | ||
|
|
9c91e35367 | ||
|
|
7f2ec23827 | ||
|
|
94b147d340 | ||
|
|
e4037b3d23 | ||
|
|
3bf3ea3ab1 | ||
|
|
ea0b9c66ff | ||
|
|
1e946e4c3d | ||
|
|
1e922633ae | ||
|
|
e65f7b6974 | ||
|
|
a726224397 | ||
|
|
92d66dfc49 | ||
|
|
703161a1ac | ||
|
|
5aed95bbb5 | ||
|
|
2c800d2853 | ||
|
|
95acd101d3 | ||
|
|
4f554369c5 | ||
|
|
267a0c8e1e | ||
|
|
b66be8c3a6 | ||
|
|
39265ddc99 | ||
|
|
f692b041bc | ||
|
|
1383161ced | ||
|
|
238c5eb70c | ||
|
|
1317e6e1e0 | ||
|
|
497e1f4c4a | ||
|
|
ffa772f221 | ||
|
|
3518d0c61d | ||
|
|
ba4e255b02 | ||
|
|
00017fb367 | ||
|
|
b36bd645cf | ||
|
|
4c70c7a5e1 | ||
|
|
9501d627d5 | ||
|
|
a617eb8844 | ||
|
|
85bf11e879 | ||
|
|
f7ef135cdc | ||
|
|
3bc76457e9 | ||
|
|
5195f665d7 | ||
|
|
4ca1046025 | ||
|
|
f9f1894be1 | ||
|
|
324cffe1ad | ||
|
|
e7f071e719 | ||
|
|
e70c60e4ee | ||
|
|
fe91399dbc | ||
|
|
f8c43aa29f |
@@ -11,18 +11,22 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
)
|
||||
|
||||
var ErrNilSignedBeaconBlock = errors.New("signed beacon block can't be nil")
|
||||
var ErrNilBeaconBlock = errors.New("beacon block can't be nil")
|
||||
var ErrNilBeaconBlockBody = errors.New("beacon block body can't be nil")
|
||||
|
||||
// BeaconBlockIsNil checks if any composite field of input signed beacon block is nil.
|
||||
// Access to these nil fields will result in run time panic,
|
||||
// it is recommended to run these checks as first line of defense.
|
||||
func BeaconBlockIsNil(b block.SignedBeaconBlock) error {
|
||||
if b == nil || b.IsNil() {
|
||||
return errors.New("signed beacon block can't be nil")
|
||||
return ErrNilSignedBeaconBlock
|
||||
}
|
||||
if b.Block().IsNil() {
|
||||
return errors.New("beacon block can't be nil")
|
||||
return ErrNilBeaconBlock
|
||||
}
|
||||
if b.Block().Body().IsNil() {
|
||||
return errors.New("beacon block body can't be nil")
|
||||
return ErrNilBeaconBlockBody
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -137,16 +137,6 @@ func CalculateStateRoot(
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "could not process block")
|
||||
}
|
||||
if signed.Version() == version.Altair || signed.Version() == version.Bellatrix {
|
||||
sa, err := signed.Block().Body().SyncAggregate()
|
||||
if err != nil {
|
||||
return [32]byte{}, err
|
||||
}
|
||||
state, err = altair.ProcessSyncAggregate(ctx, state, sa)
|
||||
if err != nil {
|
||||
return [32]byte{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return state.HashTreeRoot(ctx)
|
||||
}
|
||||
@@ -183,16 +173,6 @@ func ProcessBlockNoVerifyAnySig(
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if signed.Version() == version.Altair || signed.Version() == version.Bellatrix {
|
||||
sa, err := signed.Block().Body().SyncAggregate()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
state, err = altair.ProcessSyncAggregate(ctx, state, sa)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
bSet, err := b.BlockSignatureBatch(state, blk.ProposerIndex(), signed.Signature(), blk.HashTreeRoot)
|
||||
if err != nil {
|
||||
@@ -340,6 +320,19 @@ func ProcessBlockForStateRoot(
|
||||
return nil, errors.Wrap(err, "could not process block operation")
|
||||
}
|
||||
|
||||
if signed.Block().Version() == version.Phase0 {
|
||||
return state, nil
|
||||
}
|
||||
|
||||
sa, err := signed.Block().Body().SyncAggregate()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get sync aggregate from block")
|
||||
}
|
||||
state, err = altair.ProcessSyncAggregate(ctx, state, sa)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "process_sync_aggregate failed")
|
||||
}
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,3 +7,9 @@ import "github.com/prysmaticlabs/prysm/beacon-chain/db/kv"
|
||||
// i/o error. This variable copies the value in the kv package to the same scope as the Database interfaces,
|
||||
// so that it is available to code paths that do not interact directly with the kv package.
|
||||
var ErrNotFound = kv.ErrNotFound
|
||||
|
||||
// ErrNotFoundState wraps ErrNotFound for an error specific to a state not being found in the database.
|
||||
var ErrNotFoundState = kv.ErrNotFoundState
|
||||
|
||||
// ErrNotFoundOriginBlockRoot wraps ErrNotFound for an error specific to the origin block root.
|
||||
var ErrNotFoundOriginBlockRoot = kv.ErrNotFoundOriginBlockRoot
|
||||
|
||||
@@ -33,6 +33,7 @@ type ReadOnlyDatabase interface {
|
||||
ValidatedTips(ctx context.Context) (map[[32]byte]types.Slot, error)
|
||||
// State related methods.
|
||||
State(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error)
|
||||
StateOrError(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error)
|
||||
GenesisState(ctx context.Context) (state.BeaconState, error)
|
||||
HasState(ctx context.Context, blockRoot [32]byte) bool
|
||||
StateSummary(ctx context.Context, blockRoot [32]byte) (*ethpb.StateSummary, error)
|
||||
|
||||
@@ -81,7 +81,6 @@ go_test(
|
||||
"checkpoint_test.go",
|
||||
"deposit_contract_test.go",
|
||||
"encoding_test.go",
|
||||
"error_test.go",
|
||||
"finalized_block_roots_test.go",
|
||||
"genesis_test.go",
|
||||
"init_test.go",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package kv
|
||||
|
||||
import "errors"
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
// errDeleteFinalized is raised when we attempt to delete a finalized block/state
|
||||
var errDeleteFinalized = errors.New("cannot delete finalized block or state")
|
||||
@@ -8,42 +8,7 @@ var errDeleteFinalized = errors.New("cannot delete finalized block or state")
|
||||
// ErrNotFound can be used directly, or as a wrapped DBError, whenever a db method needs to
|
||||
// indicate that a value couldn't be found.
|
||||
var ErrNotFound = errors.New("not found in db")
|
||||
var ErrNotFoundState = errors.Wrap(ErrNotFound, "state not found")
|
||||
|
||||
// ErrNotFoundOriginBlockRoot is an error specifically for the origin block root getter
|
||||
var ErrNotFoundOriginBlockRoot = WrapDBError(ErrNotFound, "OriginBlockRoot")
|
||||
|
||||
// WrapDBError wraps an error in a DBError. See commentary on DBError for more context.
|
||||
func WrapDBError(e error, outer string) error {
|
||||
return DBError{
|
||||
Wraps: e,
|
||||
Outer: errors.New(outer),
|
||||
}
|
||||
}
|
||||
|
||||
// DBError implements the Error method so that it can be asserted as an error.
|
||||
// The Unwrap method supports error wrapping, enabling it to be used with errors.Is/As.
|
||||
// The primary use case is to make it simple for database methods to return errors
|
||||
// that wrap ErrNotFound, allowing calling code to check for "not found" errors
|
||||
// like: `error.Is(err, ErrNotFound)`. This is intended to improve error handling
|
||||
// in db lookup methods that need to differentiate between a missing value and some
|
||||
// other database error. for more background see:
|
||||
// https://go.dev/blog/go1.13-errors
|
||||
type DBError struct {
|
||||
Wraps error
|
||||
Outer error
|
||||
}
|
||||
|
||||
// Error satisfies the error interface, so that DBErrors can be used anywhere that
|
||||
// expects an `error`.
|
||||
func (e DBError) Error() string {
|
||||
es := e.Outer.Error()
|
||||
if e.Wraps != nil {
|
||||
es += ": " + e.Wraps.Error()
|
||||
}
|
||||
return es
|
||||
}
|
||||
|
||||
// Unwrap is used by the errors package Is and As methods.
|
||||
func (e DBError) Unwrap() error {
|
||||
return e.Wraps
|
||||
}
|
||||
var ErrNotFoundOriginBlockRoot = errors.Wrap(ErrNotFound, "OriginBlockRoot")
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package kv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWrappedSentinelError(t *testing.T) {
|
||||
e := ErrNotFoundOriginBlockRoot
|
||||
if !errors.Is(e, ErrNotFoundOriginBlockRoot) {
|
||||
t.Error("expected that a copy of ErrNotFoundOriginBlockRoot would have an is-a relationship")
|
||||
}
|
||||
|
||||
outer := errors.New("wrapped error")
|
||||
e2 := DBError{Wraps: ErrNotFoundOriginBlockRoot, Outer: outer}
|
||||
if !errors.Is(e2, ErrNotFoundOriginBlockRoot) {
|
||||
t.Error("expected that errors.Is would know DBError wraps ErrNotFoundOriginBlockRoot")
|
||||
}
|
||||
|
||||
// test that the innermost not found error is detected
|
||||
if !errors.Is(e2, ErrNotFound) {
|
||||
t.Error("expected that errors.Is would know ErrNotFoundOriginBlockRoot wraps ErrNotFound")
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package kv
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/snappy"
|
||||
"github.com/pkg/errors"
|
||||
@@ -46,6 +47,19 @@ func (s *Store) State(ctx context.Context, blockRoot [32]byte) (state.BeaconStat
|
||||
return s.unmarshalState(ctx, enc, valEntries)
|
||||
}
|
||||
|
||||
// StateOrError is just like State(), except it only returns a non-error response
|
||||
// if the requested state is found in the database.
|
||||
func (s *Store) StateOrError(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error) {
|
||||
st, err := s.State(ctx, blockRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if st == nil || st.IsNil() {
|
||||
return nil, errors.Wrap(ErrNotFoundState, fmt.Sprintf("no state with blockroot=%#x", blockRoot))
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// GenesisState returns the genesis state in beacon chain.
|
||||
func (s *Store) GenesisState(ctx context.Context) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "BeaconDB.GenesisState")
|
||||
|
||||
@@ -21,6 +21,12 @@ import (
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
func TestStateNil(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
_, err := db.StateOrError(context.Background(), [32]byte{})
|
||||
require.ErrorIs(t, err, ErrNotFoundState)
|
||||
}
|
||||
|
||||
func TestState_CanSaveRetrieve(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
|
||||
|
||||
@@ -212,6 +212,10 @@ func (m *futureSyncMockFetcher) StateRoot(context.Context, []byte) ([]byte, erro
|
||||
return m.BeaconStateRoot, nil
|
||||
}
|
||||
|
||||
func (m *futureSyncMockFetcher) StateBySlot(context.Context, types.Slot) (state.BeaconState, error) {
|
||||
return m.BeaconState, nil
|
||||
}
|
||||
|
||||
func TestListSyncCommitteesFuture(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
|
||||
|
||||
@@ -91,6 +91,7 @@ go_test(
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/core/transition:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/db/testing:go_default_library",
|
||||
"//beacon-chain/operations/attestations:go_default_library",
|
||||
|
||||
@@ -2,6 +2,7 @@ package beacon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
@@ -58,9 +59,11 @@ func (bs *Server) ListValidatorAssignments(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestedState, err := bs.StateGen.StateBySlot(ctx, startSlot)
|
||||
requestedState, err := bs.ReplayerBuilder.ForSlot(startSlot).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not retrieve archived state for epoch %d: %v", requestedEpoch, err)
|
||||
msg := fmt.Sprintf("could not replay all blocks from the closest stored state (at slot %d) "+
|
||||
"to the requested epoch (%d) - %v", startSlot, requestedEpoch, err)
|
||||
return nil, status.Error(codes.Internal, msg)
|
||||
}
|
||||
|
||||
// Filter out assignments by public keys.
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
dbTest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
mockstategen "github.com/prysmaticlabs/prysm/beacon-chain/state/stategen/mock"
|
||||
"github.com/prysmaticlabs/prysm/cmd"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
@@ -24,13 +25,13 @@ import (
|
||||
)
|
||||
|
||||
func TestServer_ListAssignments_CannotRequestFutureEpoch(t *testing.T) {
|
||||
|
||||
db := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
bs := &Server{
|
||||
BeaconDB: db,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, db)
|
||||
|
||||
wanted := errNoEpochInfoError
|
||||
_, err := bs.ListValidatorAssignments(
|
||||
@@ -45,7 +46,6 @@ func TestServer_ListAssignments_CannotRequestFutureEpoch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_ListAssignments_NoResults(t *testing.T) {
|
||||
|
||||
db := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
st, err := util.NewBeaconState()
|
||||
@@ -62,6 +62,7 @@ func TestServer_ListAssignments_NoResults(t *testing.T) {
|
||||
BeaconDB: db,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
StateGen: stategen.New(db),
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st)),
|
||||
}
|
||||
wanted := ðpb.ValidatorAssignments{
|
||||
Assignments: make([]*ethpb.ValidatorAssignments_CommitteeAssignment, 0),
|
||||
@@ -101,8 +102,8 @@ func TestServer_ListAssignments_Pagination_InputOutOfRange(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
blk := util.NewBeaconBlock().Block
|
||||
blockRoot, err := blk.HashTreeRoot()
|
||||
blk := util.NewBeaconBlock()
|
||||
blockRoot, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
s, err := util.NewBeaconState()
|
||||
@@ -123,6 +124,7 @@ func TestServer_ListAssignments_Pagination_InputOutOfRange(t *testing.T) {
|
||||
},
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
StateGen: stategen.New(db),
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(s)),
|
||||
}
|
||||
|
||||
wanted := fmt.Sprintf("page start %d >= list %d", 500, count)
|
||||
@@ -176,8 +178,8 @@ func TestServer_ListAssignments_Pagination_DefaultPageSize_NoArchive(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
blk := util.NewBeaconBlock().Block
|
||||
blockRoot, err := blk.HashTreeRoot()
|
||||
b := util.NewBeaconBlock()
|
||||
blockRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
s, err := util.NewBeaconState()
|
||||
@@ -198,6 +200,7 @@ func TestServer_ListAssignments_Pagination_DefaultPageSize_NoArchive(t *testing.
|
||||
},
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
StateGen: stategen.New(db),
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(s)),
|
||||
}
|
||||
|
||||
res, err := bs.ListValidatorAssignments(context.Background(), ðpb.ListValidatorAssignmentsRequest{
|
||||
@@ -246,8 +249,8 @@ func TestServer_ListAssignments_FilterPubkeysIndices_NoPagination(t *testing.T)
|
||||
validators = append(validators, val)
|
||||
}
|
||||
|
||||
blk := util.NewBeaconBlock().Block
|
||||
blockRoot, err := blk.HashTreeRoot()
|
||||
b := util.NewBeaconBlock()
|
||||
blockRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
s, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
@@ -264,6 +267,7 @@ func TestServer_ListAssignments_FilterPubkeysIndices_NoPagination(t *testing.T)
|
||||
},
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
StateGen: stategen.New(db),
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(s)),
|
||||
}
|
||||
|
||||
pubKey1 := make([]byte, params.BeaconConfig().BLSPubkeyLength)
|
||||
@@ -315,13 +319,16 @@ func TestServer_ListAssignments_CanFilterPubkeysIndices_WithPagination(t *testin
|
||||
validators = append(validators, val)
|
||||
}
|
||||
|
||||
blk := util.NewBeaconBlock().Block
|
||||
blockRoot, err := blk.HashTreeRoot()
|
||||
b := util.NewBeaconBlock()
|
||||
blockRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
s, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
w, err := wrapper.WrappedSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.SetValidators(validators))
|
||||
require.NoError(t, db.SaveState(ctx, s, blockRoot))
|
||||
require.NoError(t, db.SaveBlock(ctx, w))
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, blockRoot))
|
||||
|
||||
bs := &Server{
|
||||
@@ -335,6 +342,8 @@ func TestServer_ListAssignments_CanFilterPubkeysIndices_WithPagination(t *testin
|
||||
StateGen: stategen.New(db),
|
||||
}
|
||||
|
||||
addDefaultReplayerBuilder(bs, db)
|
||||
|
||||
req := ðpb.ListValidatorAssignmentsRequest{Indices: []types.ValidatorIndex{1, 2, 3, 4, 5, 6}, PageSize: 2, PageToken: "1"}
|
||||
res, err := bs.ListValidatorAssignments(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -2,6 +2,7 @@ package beacon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -478,10 +479,12 @@ func (bs *Server) GetWeakSubjectivityCheckpoint(ctx context.Context, _ *emptypb.
|
||||
return nil, status.Error(codes.Internal, "Could not get weak subjectivity slot")
|
||||
}
|
||||
|
||||
wsState, err := bs.StateGen.StateBySlot(ctx, wsSlot)
|
||||
wsState, err := bs.ReplayerBuilder.ForSlot(wsSlot).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not get weak subjectivity state")
|
||||
msg := fmt.Sprintf("error replaying blocks for state at slot %d: %v", wsSlot, err)
|
||||
return nil, status.Error(codes.Internal, msg)
|
||||
}
|
||||
|
||||
stateRoot, err := wsState.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not get weak subjectivity state root")
|
||||
|
||||
@@ -12,9 +12,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
|
||||
blockfeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/block"
|
||||
statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
dbTest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1"
|
||||
"github.com/prysmaticlabs/prysm/cmd"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
|
||||
@@ -728,77 +726,6 @@ func TestServer_StreamBlocksVerified_OnHeadUpdated(t *testing.T) {
|
||||
<-exitRoutine
|
||||
}
|
||||
|
||||
func TestServer_GetWeakSubjectivityCheckpoint(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
params.OverrideBeaconConfig(params.MainnetConfig())
|
||||
|
||||
db := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// Beacon state.
|
||||
beaconState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconState.SetSlot(10))
|
||||
|
||||
// Active validator set is used for computing the weak subjectivity period.
|
||||
numVals := 256 // Works with params.BeaconConfig().MinGenesisActiveValidatorCount as well, but takes longer.
|
||||
validators := make([]*ethpb.Validator, numVals)
|
||||
balances := make([]uint64, len(validators))
|
||||
for i := 0; i < len(validators); i++ {
|
||||
validators[i] = ðpb.Validator{
|
||||
PublicKey: make([]byte, params.BeaconConfig().BLSPubkeyLength),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
EffectiveBalance: 28 * 1e9,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
}
|
||||
balances[i] = validators[i].EffectiveBalance
|
||||
}
|
||||
require.NoError(t, beaconState.SetValidators(validators))
|
||||
require.NoError(t, beaconState.SetBalances(balances))
|
||||
|
||||
// Genesis block.
|
||||
genesisBlock := util.NewBeaconBlock()
|
||||
genesisBlockRoot, err := genesisBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(genesisBlock)))
|
||||
require.NoError(t, db.SaveState(ctx, beaconState, genesisBlockRoot))
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, genesisBlockRoot))
|
||||
|
||||
// Finalized checkpoint.
|
||||
finalizedEpoch := types.Epoch(1020)
|
||||
require.NoError(t, beaconState.SetSlot(types.Slot(finalizedEpoch.Mul(uint64(params.BeaconConfig().SlotsPerEpoch)))))
|
||||
require.NoError(t, beaconState.SetCurrentJustifiedCheckpoint(ðpb.Checkpoint{
|
||||
Epoch: finalizedEpoch - 1,
|
||||
Root: bytesutil.PadTo([]byte{'A'}, fieldparams.RootLength),
|
||||
}))
|
||||
require.NoError(t, beaconState.SetFinalizedCheckpoint(ðpb.Checkpoint{
|
||||
Epoch: finalizedEpoch,
|
||||
Root: bytesutil.PadTo([]byte{'B'}, fieldparams.RootLength),
|
||||
}))
|
||||
|
||||
chainService := &chainMock.ChainService{State: beaconState}
|
||||
server := &Server{
|
||||
Ctx: ctx,
|
||||
BlockNotifier: chainService.BlockNotifier(),
|
||||
HeadFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
StateGen: stategen.New(db),
|
||||
}
|
||||
|
||||
wsEpoch, err := helpers.ComputeWeakSubjectivityPeriod(context.Background(), beaconState)
|
||||
require.NoError(t, err)
|
||||
|
||||
c, err := server.GetWeakSubjectivityCheckpoint(ctx, &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
e := finalizedEpoch - (finalizedEpoch % wsEpoch)
|
||||
require.Equal(t, e, c.Epoch)
|
||||
wsState, err := server.StateGen.StateBySlot(ctx, params.BeaconConfig().SlotsPerEpoch.Mul(uint64(e)))
|
||||
require.NoError(t, err)
|
||||
sRoot, err := wsState.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, sRoot[:], c.StateRoot)
|
||||
}
|
||||
|
||||
func TestServer_ListBeaconBlocks_NoResults(t *testing.T) {
|
||||
db := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -74,9 +74,9 @@ func (bs *Server) retrieveCommitteesForEpoch(
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
requestedState, err := bs.StateGen.StateBySlot(ctx, startSlot)
|
||||
requestedState, err := bs.ReplayerBuilder.ForSlot(startSlot).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
|
||||
return nil, nil, status.Errorf(codes.Internal, "error replaying blocks for state at slot %d: %v", startSlot, err)
|
||||
}
|
||||
seed, err := helpers.Seed(requestedState, epoch, params.BeaconConfig().DomainBeaconAttester)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package beacon
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
dbTest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
mockstategen "github.com/prysmaticlabs/prysm/beacon-chain/state/stategen/mock"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
@@ -48,6 +50,8 @@ func TestServer_ListBeaconCommittees_CurrentEpoch(t *testing.T) {
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
require.NoError(t, db.SaveState(ctx, headState, gRoot))
|
||||
|
||||
bs.ReplayerBuilder = mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(headState))
|
||||
|
||||
activeIndices, err := helpers.ActiveValidatorIndices(ctx, headState, 0)
|
||||
require.NoError(t, err)
|
||||
attesterSeed, err := helpers.Seed(headState, 0, params.BeaconConfig().DomainBeaconAttester)
|
||||
@@ -69,6 +73,13 @@ func TestServer_ListBeaconCommittees_CurrentEpoch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func addDefaultReplayerBuilder(s *Server, h stategen.HistoryAccessor) {
|
||||
cc := &mockstategen.MockCanonicalChecker{Is: true, Err: nil}
|
||||
cs := &mockstategen.MockCurrentSlotter{Slot: math.MaxUint64 - 1}
|
||||
s.ReplayerBuilder = stategen.NewCanonicalBuilder(h, cc, cs)
|
||||
}
|
||||
|
||||
// TODO: test failure
|
||||
func TestServer_ListBeaconCommittees_PreviousEpoch(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
params.OverrideBeaconConfig(params.MainnetConfig())
|
||||
@@ -87,12 +98,13 @@ func TestServer_ListBeaconCommittees_PreviousEpoch(t *testing.T) {
|
||||
require.NoError(t, headState.SetRandaoMixes(mixes))
|
||||
require.NoError(t, headState.SetSlot(params.BeaconConfig().SlotsPerEpoch))
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
require.NoError(t, db.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(b)))
|
||||
gRoot, err := b.Block.HashTreeRoot()
|
||||
b, err := wrapper.WrappedSignedBeaconBlock(util.NewBeaconBlock())
|
||||
require.NoError(t, wrapper.SetBlockSlot(b, headState.Slot()))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.SaveBlock(ctx, b))
|
||||
gRoot, err := b.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.SaveState(ctx, headState, gRoot))
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
|
||||
offset := int64(headState.Slot().Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
m := &mock.ChainService{
|
||||
@@ -104,6 +116,7 @@ func TestServer_ListBeaconCommittees_PreviousEpoch(t *testing.T) {
|
||||
GenesisTimeFetcher: m,
|
||||
StateGen: stategen.New(db),
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, db)
|
||||
|
||||
activeIndices, err := helpers.ActiveValidatorIndices(ctx, headState, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -45,4 +45,5 @@ type Server struct {
|
||||
CollectedAttestationsBuffer chan []*ethpb.Attestation
|
||||
StateGen stategen.StateManager
|
||||
SyncChecker sync.Checker
|
||||
ReplayerBuilder stategen.ReplayerBuilder
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package beacon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
@@ -64,9 +65,9 @@ func (bs *Server) ListValidatorBalances(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestedState, err := bs.StateGen.StateBySlot(ctx, startSlot)
|
||||
requestedState, err := bs.ReplayerBuilder.ForSlot(startSlot).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
|
||||
return nil, status.Error(codes.Internal, fmt.Sprintf("error replaying blocks for state at slot %d: %v", startSlot, err))
|
||||
}
|
||||
|
||||
vals := requestedState.Validators()
|
||||
@@ -219,7 +220,10 @@ func (bs *Server) ListValidators(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqState, err = bs.StateGen.StateBySlot(ctx, s)
|
||||
reqState, err = bs.ReplayerBuilder.ForSlot(s).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, fmt.Sprintf("error replaying blocks for state at slot %d: %v", s, err))
|
||||
}
|
||||
} else {
|
||||
reqState, err = bs.HeadFetcher.HeadState(ctx)
|
||||
}
|
||||
@@ -411,9 +415,9 @@ func (bs *Server) GetValidatorActiveSetChanges(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestedState, err := bs.StateGen.StateBySlot(ctx, s)
|
||||
requestedState, err := bs.ReplayerBuilder.ForSlot(s).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
|
||||
return nil, status.Error(codes.Internal, fmt.Sprintf("error replaying blocks for state at slot %d: %v", s, err))
|
||||
}
|
||||
|
||||
activeValidatorCount, err := helpers.ActiveValidatorCount(ctx, requestedState, coreTime.CurrentEpoch(requestedState))
|
||||
@@ -502,22 +506,11 @@ func (bs *Server) GetValidatorParticipation(
|
||||
// Use the last slot of requested epoch to obtain current and previous epoch attestations.
|
||||
// This ensures that we don't miss previous attestations when input requested epochs.
|
||||
startSlot += params.BeaconConfig().SlotsPerEpoch - 1
|
||||
// The start slot should be a canonical slot.
|
||||
canonical, err := bs.isSlotCanonical(ctx, startSlot)
|
||||
|
||||
// ReplayerBuilder insures that a canonical chain is followed to the slot
|
||||
beaconState, err := bs.ReplayerBuilder.ForSlot(startSlot).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Keep looking back until there's a canonical slot.
|
||||
for i := int(startSlot - 1); !canonical && i >= 0; i-- {
|
||||
canonical, err = bs.isSlotCanonical(ctx, types.Slot(i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
startSlot = types.Slot(i)
|
||||
}
|
||||
beaconState, err := bs.StateGen.StateBySlot(ctx, startSlot)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
|
||||
return nil, status.Error(codes.Internal, fmt.Sprintf("error replaying blocks for state at slot %d: %v", startSlot, err))
|
||||
}
|
||||
var v []*precompute.Validator
|
||||
var b *precompute.Balance
|
||||
@@ -834,9 +827,9 @@ func (bs *Server) GetIndividualVotes(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestedState, err := bs.StateGen.StateBySlot(ctx, s)
|
||||
st, err := bs.ReplayerBuilder.ForSlot(s).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not retrieve archived state for epoch %d: %v", req.Epoch, err)
|
||||
return nil, status.Errorf(codes.Internal, "failed to replay blocks for state at epoch %d: %v", req.Epoch, err)
|
||||
}
|
||||
// Track filtered validators to prevent duplication in the response.
|
||||
filtered := map[types.ValidatorIndex]bool{}
|
||||
@@ -844,7 +837,7 @@ func (bs *Server) GetIndividualVotes(
|
||||
votes := make([]*ethpb.IndividualVotesRespond_IndividualVote, 0, len(req.Indices)+len(req.PublicKeys))
|
||||
// Filter out assignments by public keys.
|
||||
for _, pubKey := range req.PublicKeys {
|
||||
index, ok := requestedState.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey))
|
||||
index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey))
|
||||
if !ok {
|
||||
votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{PublicKey: pubKey, ValidatorIndex: types.ValidatorIndex(^uint64(0))})
|
||||
continue
|
||||
@@ -864,27 +857,27 @@ func (bs *Server) GetIndividualVotes(
|
||||
|
||||
var v []*precompute.Validator
|
||||
var bal *precompute.Balance
|
||||
switch requestedState.Version() {
|
||||
switch st.Version() {
|
||||
case version.Phase0:
|
||||
v, bal, err = precompute.New(ctx, requestedState)
|
||||
v, bal, err = precompute.New(ctx, st)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not set up pre compute instance: %v", err)
|
||||
}
|
||||
v, _, err = precompute.ProcessAttestations(ctx, requestedState, v, bal)
|
||||
v, _, err = precompute.ProcessAttestations(ctx, st, v, bal)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err)
|
||||
}
|
||||
case version.Altair:
|
||||
v, bal, err = altair.InitializePrecomputeValidators(ctx, requestedState)
|
||||
v, bal, err = altair.InitializePrecomputeValidators(ctx, st)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not set up altair pre compute instance: %v", err)
|
||||
}
|
||||
v, _, err = altair.ProcessEpochParticipation(ctx, requestedState, bal, v)
|
||||
v, _, err = altair.ProcessEpochParticipation(ctx, st, bal, v)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err)
|
||||
}
|
||||
default:
|
||||
return nil, status.Errorf(codes.Internal, "Invalid state type retrieved with a version of %d", requestedState.Version())
|
||||
return nil, status.Errorf(codes.Internal, "Invalid state type retrieved with a version of %d", st.Version())
|
||||
}
|
||||
|
||||
for _, index := range filteredIndices {
|
||||
@@ -892,7 +885,7 @@ func (bs *Server) GetIndividualVotes(
|
||||
votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{ValidatorIndex: index})
|
||||
continue
|
||||
}
|
||||
val, err := requestedState.ValidatorAtIndexReadOnly(index)
|
||||
val, err := st.ValidatorAtIndexReadOnly(index)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not retrieve validator: %v", err)
|
||||
|
||||
@@ -923,37 +916,6 @@ func (bs *Server) GetIndividualVotes(
|
||||
}, nil
|
||||
}
|
||||
|
||||
// isSlotCanonical returns true if the input slot has a canonical block in the chain,
|
||||
// if the input slot has a skip block, false is returned,
|
||||
// if the input slot has more than one block, an error is returned.
|
||||
func (bs *Server) isSlotCanonical(ctx context.Context, slot types.Slot) (bool, error) {
|
||||
if slot == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
hasBlockRoots, roots, err := bs.BeaconDB.BlockRootsBySlot(ctx, slot)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !hasBlockRoots {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Loop through all roots in slot, and
|
||||
// check which one is canonical.
|
||||
for _, rt := range roots {
|
||||
canonical, err := bs.CanonicalFetcher.IsCanonical(ctx, rt)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if canonical {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Determines whether a validator has already exited.
|
||||
func validatorHasExited(validator *ethpb.Validator, currentEpoch types.Epoch) bool {
|
||||
farFutureEpoch := params.BeaconConfig().FarFutureEpoch
|
||||
|
||||
@@ -12,9 +12,11 @@ import (
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/altair"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/epoch/precompute"
|
||||
"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"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
dbTest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
@@ -115,6 +117,8 @@ func TestServer_ListValidatorBalances_NoResults(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, gRoot))
|
||||
|
||||
bs.ReplayerBuilder = mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(headState))
|
||||
|
||||
wanted := ðpb.ValidatorBalances{
|
||||
Balances: make([]*ethpb.ValidatorBalances_Balance, 0),
|
||||
TotalSize: int32(0),
|
||||
@@ -172,6 +176,7 @@ func TestServer_ListValidatorBalances_DefaultResponse_NoArchive(t *testing.T) {
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: st,
|
||||
},
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st)),
|
||||
}
|
||||
res, err := bs.ListValidatorBalances(
|
||||
ctx,
|
||||
@@ -200,6 +205,7 @@ func TestServer_ListValidatorBalances_PaginationOutOfRange(t *testing.T) {
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: headState,
|
||||
},
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(headState)),
|
||||
}
|
||||
|
||||
wanted := fmt.Sprintf("page start %d >= list %d", 200, len(headState.Balances()))
|
||||
@@ -248,6 +254,7 @@ func TestServer_ListValidatorBalances_Pagination_Default(t *testing.T) {
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: headState,
|
||||
},
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(headState)),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
@@ -331,6 +338,7 @@ func TestServer_ListValidatorBalances_Pagination_CustomPageSizes(t *testing.T) {
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: headState,
|
||||
},
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(headState)),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
@@ -398,6 +406,7 @@ func TestServer_ListValidatorBalances_OutOfRange(t *testing.T) {
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: headState,
|
||||
},
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(headState)),
|
||||
}
|
||||
|
||||
req := ðpb.ListValidatorBalancesRequest{Indices: []types.ValidatorIndex{types.ValidatorIndex(1)}, QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}}
|
||||
@@ -660,6 +669,7 @@ func TestServer_ListValidatorBalances_UnknownValidatorInResponse(t *testing.T) {
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: headState,
|
||||
},
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(headState)),
|
||||
}
|
||||
|
||||
nonExistentPubKey := [32]byte{8}
|
||||
@@ -1009,47 +1019,41 @@ func TestServer_ListValidators_DefaultPageSize(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_ListValidators_FromOldEpoch(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
params.OverrideBeaconConfig(params.MainnetConfig())
|
||||
transition.SkipSlotCache.Disable()
|
||||
|
||||
ctx := context.Background()
|
||||
slot := types.Slot(0)
|
||||
epochs := 10
|
||||
numVals := uint64(10)
|
||||
|
||||
numEpochs := types.Epoch(30)
|
||||
validators := make([]*ethpb.Validator, numEpochs)
|
||||
for i := types.Epoch(0); i < numEpochs; i++ {
|
||||
validators[i] = ðpb.Validator{
|
||||
ActivationEpoch: i,
|
||||
PublicKey: make([]byte, 48),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
}
|
||||
}
|
||||
want := make([]*ethpb.Validators_ValidatorContainer, len(validators))
|
||||
for i := 0; i < len(validators); i++ {
|
||||
want[i] = ðpb.Validators_ValidatorContainer{
|
||||
Index: types.ValidatorIndex(i),
|
||||
Validator: validators[i],
|
||||
}
|
||||
}
|
||||
|
||||
st, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, st.SetSlot(20*params.BeaconConfig().SlotsPerEpoch))
|
||||
require.NoError(t, st.SetValidators(validators))
|
||||
b := util.NewBeaconBlock()
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(b)))
|
||||
gRoot, err := b.Block.HashTreeRoot()
|
||||
b.Block.Slot = slot
|
||||
sb, err := wrapper.WrappedSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveState(ctx, st, gRoot))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
|
||||
r, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
st, _ := util.DeterministicGenesisState(t, numVals)
|
||||
require.NoError(t, st.SetSlot(slot))
|
||||
require.Equal(t, int(numVals), len(st.Validators()))
|
||||
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, sb))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, st, r))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, r))
|
||||
|
||||
secondsPerEpoch := params.BeaconConfig().SecondsPerSlot * uint64(params.BeaconConfig().SlotsPerEpoch)
|
||||
bs := &Server{
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: st,
|
||||
},
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
// We are in epoch 30
|
||||
Genesis: time.Now().Add(time.Duration(-1*int64(30*secondsPerEpoch)) * time.Second),
|
||||
Genesis: time.Now().Add(time.Duration(-1*int64(uint64(epochs)*secondsPerEpoch)) * time.Second),
|
||||
},
|
||||
StateGen: stategen.New(beaconDB),
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
req := ðpb.ListValidatorsRequest{
|
||||
QueryFilter: ðpb.ListValidatorsRequest_Genesis{
|
||||
@@ -1058,16 +1062,26 @@ func TestServer_ListValidators_FromOldEpoch(t *testing.T) {
|
||||
}
|
||||
res, err := bs.ListValidators(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 30, len(res.ValidatorList))
|
||||
assert.Equal(t, epochs, len(res.ValidatorList))
|
||||
|
||||
vals := st.Validators()
|
||||
want := make([]*ethpb.Validators_ValidatorContainer, 0)
|
||||
for i, v := range vals {
|
||||
want = append(want, ðpb.Validators_ValidatorContainer{
|
||||
Index: types.ValidatorIndex(i),
|
||||
Validator: v,
|
||||
})
|
||||
}
|
||||
req = ðpb.ListValidatorsRequest{
|
||||
QueryFilter: ðpb.ListValidatorsRequest_Epoch{
|
||||
Epoch: 20,
|
||||
Epoch: 10,
|
||||
},
|
||||
}
|
||||
res, err = bs.ListValidators(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
assert.DeepSSZEqual(t, want, res.ValidatorList, "Incorrect number of validators")
|
||||
|
||||
require.Equal(t, len(want), len(res.ValidatorList), "incorrect number of validators")
|
||||
assert.DeepSSZEqual(t, want, res.ValidatorList, "mismatch in validator values")
|
||||
}
|
||||
|
||||
func TestServer_ListValidators_ProcessHeadStateSlots(t *testing.T) {
|
||||
@@ -1270,8 +1284,8 @@ func TestServer_GetValidatorActiveSetChanges(t *testing.T) {
|
||||
FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 0, Root: make([]byte, fieldparams.RootLength)},
|
||||
},
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
StateGen: stategen.New(beaconDB),
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
res, err := bs.GetValidatorActiveSetChanges(ctx, ðpb.GetValidatorActiveSetChangesRequest{
|
||||
QueryFilter: ðpb.GetValidatorActiveSetChangesRequest_Genesis{Genesis: true},
|
||||
})
|
||||
@@ -1489,43 +1503,6 @@ func TestServer_GetValidatorParticipation_CannotRequestFutureEpoch(t *testing.T)
|
||||
assert.ErrorContains(t, wanted, err)
|
||||
}
|
||||
|
||||
func TestServer_GetValidatorParticipation_UnknownState(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
headState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, headState.SetSlot(0))
|
||||
epoch := types.Epoch(50)
|
||||
slots := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(epoch))
|
||||
mockStateGen := &mockstategen.MockStateManager{
|
||||
StatesBySlot: map[types.Slot]state.BeaconState{
|
||||
0: (*v1.BeaconState)(nil),
|
||||
},
|
||||
}
|
||||
bs := &Server{
|
||||
BeaconDB: beaconDB,
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: headState,
|
||||
},
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
Genesis: time.Now().Add(time.Duration(-1*int64(slots)) * time.Second),
|
||||
},
|
||||
StateGen: mockStateGen,
|
||||
}
|
||||
|
||||
wanted := "Could not set up pre compute instance: failed to initialize precompute: nil inner state"
|
||||
_, err = bs.GetValidatorParticipation(
|
||||
ctx,
|
||||
ðpb.GetValidatorParticipationRequest{
|
||||
QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{
|
||||
Epoch: 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
assert.ErrorContains(t, wanted, err)
|
||||
}
|
||||
|
||||
func TestServer_GetValidatorParticipation_CurrentAndPrevEpoch(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
@@ -1552,7 +1529,7 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpoch(t *testing.T) {
|
||||
}}
|
||||
headState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, headState.SetSlot(2*params.BeaconConfig().SlotsPerEpoch-1))
|
||||
require.NoError(t, headState.SetSlot(16))
|
||||
require.NoError(t, headState.SetValidators(validators))
|
||||
require.NoError(t, headState.SetBalances(balances))
|
||||
require.NoError(t, headState.AppendCurrentEpochAttestations(atts[0]))
|
||||
@@ -1567,6 +1544,7 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpoch(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bRoot))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, bRoot))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, params.BeaconConfig().ZeroHash))
|
||||
|
||||
m := &mock.ChainService{State: headState}
|
||||
offset := int64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
@@ -1584,6 +1562,7 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpoch(t *testing.T) {
|
||||
},
|
||||
FinalizationFetcher: &mock.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 100}},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
res, err := bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{Epoch: 1}})
|
||||
require.NoError(t, err)
|
||||
@@ -1632,7 +1611,7 @@ func TestServer_GetValidatorParticipation_OrphanedUntilGenesis(t *testing.T) {
|
||||
}}
|
||||
headState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, headState.SetSlot(2*params.BeaconConfig().SlotsPerEpoch-1))
|
||||
require.NoError(t, headState.SetSlot(0))
|
||||
require.NoError(t, headState.SetValidators(validators))
|
||||
require.NoError(t, headState.SetBalances(balances))
|
||||
require.NoError(t, headState.AppendCurrentEpochAttestations(atts[0]))
|
||||
@@ -1641,11 +1620,10 @@ func TestServer_GetValidatorParticipation_OrphanedUntilGenesis(t *testing.T) {
|
||||
b := util.NewBeaconBlock()
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(b)))
|
||||
bRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: bRoot[:]}))
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: params.BeaconConfig().ZeroHash[:]}))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bRoot))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, bRoot))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, params.BeaconConfig().ZeroHash))
|
||||
|
||||
m := &mock.ChainService{State: headState}
|
||||
offset := int64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
@@ -1663,6 +1641,7 @@ func TestServer_GetValidatorParticipation_OrphanedUntilGenesis(t *testing.T) {
|
||||
},
|
||||
FinalizationFetcher: &mock.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 100}},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
res, err := bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{Epoch: 1}})
|
||||
require.NoError(t, err)
|
||||
@@ -1688,32 +1667,36 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpochAltair(t *testing.T
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
params.SetupTestConfigCleanup(t)
|
||||
params.OverrideBeaconConfig(params.MainnetConfig())
|
||||
transition.SkipSlotCache.Disable()
|
||||
|
||||
ctx := context.Background()
|
||||
validatorCount := uint64(32)
|
||||
|
||||
genState, _ := util.DeterministicGenesisStateAltair(t, validatorCount)
|
||||
c, err := altair.NextSyncCommittee(ctx, genState)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, genState.SetCurrentSyncCommittee(c))
|
||||
|
||||
bits := make([]byte, validatorCount)
|
||||
for i := range bits {
|
||||
bits[i] = 0xff
|
||||
}
|
||||
headState, _ := util.DeterministicGenesisStateAltair(t, validatorCount)
|
||||
require.NoError(t, headState.SetSlot(2*params.BeaconConfig().SlotsPerEpoch-1))
|
||||
require.NoError(t, headState.SetCurrentParticipationBits(bits))
|
||||
require.NoError(t, headState.SetPreviousParticipationBits(bits))
|
||||
require.NoError(t, genState.SetCurrentParticipationBits(bits))
|
||||
require.NoError(t, genState.SetPreviousParticipationBits(bits))
|
||||
|
||||
b := util.NewBeaconBlockAltair()
|
||||
b.Block.Slot = 16
|
||||
ab, err := wrapper.WrappedAltairSignedBeaconBlock(b)
|
||||
gsr, err := genState.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, ab))
|
||||
bRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: bRoot[:]}))
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: params.BeaconConfig().ZeroHash[:]}))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bRoot))
|
||||
gb, err := wrapper.WrappedSignedBeaconBlock(util.NewBeaconBlockAltair())
|
||||
require.NoError(t, wrapper.SetBlockStateRoot(gb, gsr))
|
||||
require.NoError(t, err)
|
||||
gRoot, err := gb.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, bRoot))
|
||||
|
||||
m := &mock.ChainService{State: headState}
|
||||
require.NoError(t, beaconDB.SaveState(ctx, genState, gRoot))
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, gb))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
|
||||
m := &mock.ChainService{State: genState}
|
||||
offset := int64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
bs := &Server{
|
||||
BeaconDB: beaconDB,
|
||||
@@ -1722,15 +1705,11 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpochAltair(t *testing.T
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
Genesis: prysmTime.Now().Add(time.Duration(-1*offset) * time.Second),
|
||||
},
|
||||
CanonicalFetcher: &mock.ChainService{
|
||||
CanonicalRoots: map[[32]byte]bool{
|
||||
bRoot: true,
|
||||
},
|
||||
},
|
||||
FinalizationFetcher: &mock.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 100}},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
res, err := bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{Epoch: 1}})
|
||||
res, err := bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{Epoch: 0}})
|
||||
require.NoError(t, err)
|
||||
|
||||
wanted := ðpb.ValidatorParticipation{
|
||||
@@ -1747,6 +1726,24 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpochAltair(t *testing.T
|
||||
}
|
||||
assert.DeepEqual(t, true, res.Finalized, "Incorrect validator participation respond")
|
||||
assert.DeepEqual(t, wanted, res.Participation, "Incorrect validator participation respond")
|
||||
|
||||
res, err = bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{Epoch: 1}})
|
||||
require.NoError(t, err)
|
||||
|
||||
wanted = ðpb.ValidatorParticipation{
|
||||
GlobalParticipationRate: 1,
|
||||
VotedEther: validatorCount * params.BeaconConfig().MaxEffectiveBalance,
|
||||
EligibleEther: validatorCount * params.BeaconConfig().MaxEffectiveBalance,
|
||||
CurrentEpochActiveGwei: validatorCount * params.BeaconConfig().MaxEffectiveBalance,
|
||||
CurrentEpochAttestingGwei: params.BeaconConfig().EffectiveBalanceIncrement, // Empty because after one epoch, current participation rotates to previous
|
||||
CurrentEpochTargetAttestingGwei: params.BeaconConfig().EffectiveBalanceIncrement,
|
||||
PreviousEpochActiveGwei: validatorCount * params.BeaconConfig().MaxEffectiveBalance,
|
||||
PreviousEpochAttestingGwei: validatorCount * params.BeaconConfig().MaxEffectiveBalance,
|
||||
PreviousEpochTargetAttestingGwei: validatorCount * params.BeaconConfig().MaxEffectiveBalance,
|
||||
PreviousEpochHeadAttestingGwei: validatorCount * params.BeaconConfig().MaxEffectiveBalance,
|
||||
}
|
||||
assert.DeepEqual(t, true, res.Finalized, "Incorrect validator participation respond")
|
||||
assert.DeepEqual(t, wanted, res.Participation, "Incorrect validator participation respond")
|
||||
}
|
||||
|
||||
func TestGetValidatorPerformance_Syncing(t *testing.T) {
|
||||
@@ -2070,6 +2067,7 @@ func BenchmarkListValidatorBalances(b *testing.B) {
|
||||
State: headState,
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
req := ðpb.ListValidatorBalancesRequest{PageSize: 100}
|
||||
b.StartTimer()
|
||||
@@ -2114,15 +2112,16 @@ func TestServer_GetIndividualVotes_ValidatorsDontExist(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
var slot types.Slot = 0
|
||||
validators := uint64(64)
|
||||
stateWithValidators, _ := util.DeterministicGenesisState(t, validators)
|
||||
beaconState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconState.SetValidators(stateWithValidators.Validators()))
|
||||
require.NoError(t, beaconState.SetSlot(params.BeaconConfig().SlotsPerEpoch))
|
||||
require.NoError(t, beaconState.SetSlot(slot))
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = params.BeaconConfig().SlotsPerEpoch
|
||||
b.Block.Slot = slot
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(b)))
|
||||
gRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
@@ -2134,6 +2133,7 @@ func TestServer_GetIndividualVotes_ValidatorsDontExist(t *testing.T) {
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
// Test non exist public key.
|
||||
res, err := bs.GetIndividualVotes(ctx, ðpb.IndividualVotesRequest{
|
||||
@@ -2230,6 +2230,7 @@ func TestServer_GetIndividualVotes_Working(t *testing.T) {
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
res, err := bs.GetIndividualVotes(ctx, ðpb.IndividualVotesRequest{
|
||||
Indices: []types.ValidatorIndex{0, 1},
|
||||
@@ -2268,9 +2269,10 @@ func TestServer_GetIndividualVotes_WorkingAltair(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
var slot types.Slot = 0
|
||||
validators := uint64(32)
|
||||
beaconState, _ := util.DeterministicGenesisStateAltair(t, validators)
|
||||
require.NoError(t, beaconState.SetSlot(params.BeaconConfig().SlotsPerEpoch))
|
||||
require.NoError(t, beaconState.SetSlot(slot))
|
||||
|
||||
pb, err := beaconState.CurrentEpochParticipation()
|
||||
require.NoError(t, err)
|
||||
@@ -2281,7 +2283,7 @@ func TestServer_GetIndividualVotes_WorkingAltair(t *testing.T) {
|
||||
require.NoError(t, beaconState.SetPreviousParticipationBits(pb))
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = params.BeaconConfig().SlotsPerEpoch
|
||||
b.Block.Slot = slot
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(b)))
|
||||
gRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
@@ -2293,6 +2295,7 @@ func TestServer_GetIndividualVotes_WorkingAltair(t *testing.T) {
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
res, err := bs.GetIndividualVotes(ctx, ðpb.IndividualVotesRequest{
|
||||
Indices: []types.ValidatorIndex{0, 1},
|
||||
@@ -2378,6 +2381,7 @@ func TestServer_GetIndividualVotes_AltairEndOfEpoch(t *testing.T) {
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
res, err := bs.GetIndividualVotes(ctx, ðpb.IndividualVotesRequest{
|
||||
Indices: []types.ValidatorIndex{0, 1},
|
||||
@@ -2499,88 +2503,3 @@ func Test_validatorStatus(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_isSlotCanonical(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
var roots [][32]byte
|
||||
cRoots := map[[32]byte]bool{}
|
||||
for i := 1; i < 100; i++ {
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = types.Slot(i)
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(b)))
|
||||
br, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
if i%2 == 0 {
|
||||
cRoots[br] = true
|
||||
}
|
||||
roots = append(roots, br)
|
||||
}
|
||||
|
||||
bs := &Server{
|
||||
BeaconDB: beaconDB,
|
||||
CanonicalFetcher: &mock.ChainService{
|
||||
CanonicalRoots: cRoots,
|
||||
},
|
||||
}
|
||||
|
||||
for i := range roots {
|
||||
slot := types.Slot(i + 1)
|
||||
c, err := bs.isSlotCanonical(ctx, slot)
|
||||
require.NoError(t, err)
|
||||
if slot%2 == 0 {
|
||||
require.Equal(t, true, c)
|
||||
} else {
|
||||
require.Equal(t, false, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_isSlotCanonical_MultipleBlocks(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
var roots [][32]byte
|
||||
cRoots := map[[32]byte]bool{}
|
||||
for i := 1; i < 100; i++ {
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = types.Slot(i)
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(b)))
|
||||
br, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
if i%2 == 0 {
|
||||
cRoots[br] = true
|
||||
// Save a block in the same slot
|
||||
b = util.NewBeaconBlock()
|
||||
b.Block.Slot = types.Slot(i)
|
||||
b.Block.ProposerIndex = 100
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(b)))
|
||||
}
|
||||
roots = append(roots, br)
|
||||
}
|
||||
|
||||
bs := &Server{
|
||||
BeaconDB: beaconDB,
|
||||
CanonicalFetcher: &mock.ChainService{
|
||||
CanonicalRoots: cRoots,
|
||||
},
|
||||
}
|
||||
|
||||
for i := range roots {
|
||||
slot := types.Slot(i + 1)
|
||||
c, err := bs.isSlotCanonical(ctx, slot)
|
||||
require.NoError(t, err)
|
||||
if slot%2 == 0 {
|
||||
require.Equal(t, true, c)
|
||||
} else {
|
||||
require.Equal(t, false, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_isSlotCanonicalForSlot0(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
bs := &Server{}
|
||||
c, err := bs.isSlotCanonical(ctx, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, c)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,9 @@ go_test(
|
||||
"//beacon-chain/db/testing:go_default_library",
|
||||
"//beacon-chain/forkchoice/protoarray:go_default_library",
|
||||
"//beacon-chain/p2p/testing:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/state/stategen/mock:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
|
||||
@@ -30,6 +30,7 @@ type Server struct {
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
PeerManager p2p.PeerManager
|
||||
PeersFetcher p2p.PeersProvider
|
||||
ReplayerBuilder stategen.ReplayerBuilder
|
||||
}
|
||||
|
||||
// SetLoggingLevel of a beacon node according to a request type,
|
||||
|
||||
@@ -2,6 +2,7 @@ package debug
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
pbrpc "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
@@ -28,10 +29,11 @@ func (ds *Server) GetBeaconState(
|
||||
)
|
||||
}
|
||||
|
||||
st, err := ds.StateGen.StateBySlot(ctx, q.Slot)
|
||||
st, err := ds.ReplayerBuilder.ForSlot(q.Slot).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not compute state by slot: %v", err)
|
||||
return nil, status.Error(codes.Internal, fmt.Sprintf("error replaying blocks for state at slot %d: %v", q.Slot, err))
|
||||
}
|
||||
|
||||
encoded, err := st.MarshalSSZ()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not ssz encode beacon state: %v", err)
|
||||
|
||||
@@ -2,12 +2,15 @@ package debug
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
|
||||
dbTest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
mockstategen "github.com/prysmaticlabs/prysm/beacon-chain/state/stategen/mock"
|
||||
pbrpc "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/testing/assert"
|
||||
@@ -15,6 +18,16 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
)
|
||||
|
||||
func addReplayerBuilder(s *Server, h stategen.HistoryAccessor, is bool, canonErr error, currSlot types.Slot) {
|
||||
cc := &mockstategen.MockCanonicalChecker{Is: is, Err: canonErr}
|
||||
cs := &mockstategen.MockCurrentSlotter{Slot: currSlot}
|
||||
s.ReplayerBuilder = stategen.NewCanonicalBuilder(h, cc, cs)
|
||||
}
|
||||
|
||||
func addDefaultReplayerBuilder(s *Server, h stategen.HistoryAccessor) {
|
||||
addReplayerBuilder(s, h, true, nil, math.MaxUint64-1)
|
||||
}
|
||||
|
||||
func TestServer_GetBeaconState(t *testing.T) {
|
||||
db := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
@@ -34,6 +47,7 @@ func TestServer_GetBeaconState(t *testing.T) {
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, db)
|
||||
_, err = bs.GetBeaconState(ctx, &pbrpc.BeaconStateRequest{})
|
||||
assert.ErrorContains(t, "Need to specify either a block root or slot to request state", err)
|
||||
req := &pbrpc.BeaconStateRequest{
|
||||
@@ -46,16 +60,44 @@ func TestServer_GetBeaconState(t *testing.T) {
|
||||
wanted, err := st.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, wanted, res.Encoded)
|
||||
|
||||
req = &pbrpc.BeaconStateRequest{
|
||||
QueryFilter: &pbrpc.BeaconStateRequest_Slot{
|
||||
Slot: st.Slot(),
|
||||
},
|
||||
}
|
||||
wanted, err = st.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
res, err = bs.GetBeaconState(ctx, req)
|
||||
require.NoError(t, err)
|
||||
resState := &pbrpc.BeaconState{}
|
||||
err = resState.UnmarshalSSZ(res.Encoded)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, resState.Slot, st.Slot())
|
||||
assert.DeepEqual(t, wanted, res.Encoded)
|
||||
|
||||
// request a slot after the state
|
||||
// note that if the current slot were <= slot+1, this would fail
|
||||
// but the mock stategen.CurrentSlotter gives a current slot far in the future
|
||||
// so this acts like requesting a state at a skipped slot
|
||||
req = &pbrpc.BeaconStateRequest{
|
||||
QueryFilter: &pbrpc.BeaconStateRequest_Slot{
|
||||
Slot: slot + 1,
|
||||
},
|
||||
}
|
||||
require.NoError(t, st.SetSlot(slot+1))
|
||||
wanted, err = st.MarshalSSZ()
|
||||
state := state.BeaconState(st)
|
||||
// since we are requesting a state at a skipped slot, use the same method as stategen
|
||||
// to advance to the pre-state for the subsequent slot
|
||||
state, err = stategen.ReplayProcessSlots(ctx, state, slot+1)
|
||||
require.NoError(t, err)
|
||||
wanted, err = state.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
res, err = bs.GetBeaconState(ctx, req)
|
||||
require.NoError(t, err)
|
||||
resState = &pbrpc.BeaconState{}
|
||||
err = resState.UnmarshalSSZ(res.Encoded)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, resState.Slot, state.Slot())
|
||||
assert.DeepEqual(t, wanted, res.Encoded)
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ type Server struct {
|
||||
PendingDepositsFetcher depositcache.PendingDepositsFetcher
|
||||
OperationNotifier opfeed.Notifier
|
||||
StateGen stategen.StateManager
|
||||
ReplayerBuilder stategen.ReplayerBuilder
|
||||
}
|
||||
|
||||
// WaitForActivation checks if a validator public key exists in the active validator registry of the current
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/signing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
@@ -338,11 +337,8 @@ func (vs *Server) retrieveAfterEpochTransition(ctx context.Context, epoch types.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
retState, err := vs.StateGen.StateBySlot(ctx, endSlot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transition.ProcessSlots(ctx, retState, retState.Slot()+1)
|
||||
// replay to first slot of following epoch
|
||||
return vs.ReplayerBuilder.ForSlot(endSlot).ReplayToSlot(ctx, endSlot+1)
|
||||
}
|
||||
|
||||
func checkValidatorsAreRecent(headEpoch types.Epoch, req *ethpb.DoppelGangerRequest) (bool, *ethpb.DoppelGangerResponse) {
|
||||
|
||||
@@ -937,8 +937,7 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
|
||||
name: "normal doppelganger request",
|
||||
wantErr: false,
|
||||
svSetup: func(t *testing.T) (*Server, *ethpb.DoppelGangerRequest, *ethpb.DoppelGangerResponse) {
|
||||
mockGen := mockstategen.NewMockService()
|
||||
hs, ps, os, keys := createStateSetup(t, 4, mockGen)
|
||||
hs, ps, os, keys, builder := createStateSetup(t, 4)
|
||||
// Previous Epoch State
|
||||
for i := 0; i < 3; i++ {
|
||||
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
|
||||
@@ -954,11 +953,11 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
|
||||
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+2000000000))
|
||||
}
|
||||
vs := &Server{
|
||||
StateGen: mockGen,
|
||||
HeadFetcher: &mockChain.ChainService{
|
||||
State: hs,
|
||||
},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
ReplayerBuilder: builder,
|
||||
}
|
||||
request := ðpb.DoppelGangerRequest{
|
||||
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
|
||||
@@ -982,9 +981,7 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
|
||||
name: "doppelganger exists current epoch",
|
||||
wantErr: false,
|
||||
svSetup: func(t *testing.T) (*Server, *ethpb.DoppelGangerRequest, *ethpb.DoppelGangerResponse) {
|
||||
mockGen := mockstategen.NewMockService()
|
||||
|
||||
hs, ps, os, keys := createStateSetup(t, 4, mockGen)
|
||||
hs, ps, os, keys, builder := createStateSetup(t, 4)
|
||||
// Previous Epoch State
|
||||
for i := 0; i < 2; i++ {
|
||||
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
|
||||
@@ -1010,11 +1007,11 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
|
||||
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-1000000000))
|
||||
|
||||
vs := &Server{
|
||||
StateGen: mockGen,
|
||||
HeadFetcher: &mockChain.ChainService{
|
||||
State: hs,
|
||||
},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
ReplayerBuilder: builder,
|
||||
}
|
||||
request := ðpb.DoppelGangerRequest{
|
||||
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
|
||||
@@ -1049,9 +1046,7 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
|
||||
name: "doppelganger exists previous epoch",
|
||||
wantErr: false,
|
||||
svSetup: func(t *testing.T) (*Server, *ethpb.DoppelGangerRequest, *ethpb.DoppelGangerResponse) {
|
||||
mockGen := mockstategen.NewMockService()
|
||||
|
||||
hs, ps, os, keys := createStateSetup(t, 4, mockGen)
|
||||
hs, ps, os, keys, builder := createStateSetup(t, 4)
|
||||
// Previous Epoch State
|
||||
for i := 0; i < 2; i++ {
|
||||
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
|
||||
@@ -1077,11 +1072,11 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
|
||||
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-2000000000))
|
||||
|
||||
vs := &Server{
|
||||
StateGen: mockGen,
|
||||
HeadFetcher: &mockChain.ChainService{
|
||||
State: hs,
|
||||
},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
ReplayerBuilder: builder,
|
||||
}
|
||||
request := ðpb.DoppelGangerRequest{
|
||||
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
|
||||
@@ -1116,9 +1111,7 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
|
||||
name: "multiple doppelganger exists",
|
||||
wantErr: false,
|
||||
svSetup: func(t *testing.T) (*Server, *ethpb.DoppelGangerRequest, *ethpb.DoppelGangerResponse) {
|
||||
mockGen := mockstategen.NewMockService()
|
||||
|
||||
hs, ps, os, keys := createStateSetup(t, 4, mockGen)
|
||||
hs, ps, os, keys, builder := createStateSetup(t, 4)
|
||||
// Previous Epoch State
|
||||
for i := 10; i < 15; i++ {
|
||||
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
|
||||
@@ -1136,11 +1129,11 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
|
||||
}
|
||||
|
||||
vs := &Server{
|
||||
StateGen: mockGen,
|
||||
HeadFetcher: &mockChain.ChainService{
|
||||
State: hs,
|
||||
},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
ReplayerBuilder: builder,
|
||||
}
|
||||
request := ðpb.DoppelGangerRequest{
|
||||
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
|
||||
@@ -1166,16 +1159,14 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
|
||||
name: "attesters are too recent",
|
||||
wantErr: false,
|
||||
svSetup: func(t *testing.T) (*Server, *ethpb.DoppelGangerRequest, *ethpb.DoppelGangerResponse) {
|
||||
mockGen := mockstategen.NewMockService()
|
||||
|
||||
hs, _, _, keys := createStateSetup(t, 4, mockGen)
|
||||
hs, _, _, keys, _ := createStateSetup(t, 4)
|
||||
|
||||
vs := &Server{
|
||||
StateGen: nil,
|
||||
HeadFetcher: &mockChain.ChainService{
|
||||
State: hs,
|
||||
},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
ReplayerBuilder: nil,
|
||||
}
|
||||
request := ðpb.DoppelGangerRequest{
|
||||
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
|
||||
@@ -1213,8 +1204,9 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func createStateSetup(t *testing.T, head types.Epoch, mockgen *mockstategen.MockStateManager) (state.BeaconState,
|
||||
state.BeaconState, state.BeaconState, []bls.SecretKey) {
|
||||
func createStateSetup(t *testing.T, head types.Epoch) (state.BeaconState,
|
||||
state.BeaconState, state.BeaconState, []bls.SecretKey, *mockstategen.MockReplayerBuilder) {
|
||||
rb := &mockstategen.MockReplayerBuilder{}
|
||||
gs, keys := util.DeterministicGenesisState(t, 64)
|
||||
hs := gs.Copy()
|
||||
// Head State
|
||||
@@ -1243,9 +1235,8 @@ func createStateSetup(t *testing.T, head types.Epoch, mockgen *mockstategen.Mock
|
||||
ProposerIndex: 10,
|
||||
}
|
||||
assert.NoError(t, hs.AppendCurrentEpochAttestations(pendingAtt))
|
||||
|
||||
}
|
||||
mockgen.StatesBySlot[headSlot] = hs
|
||||
rb.SetMockState(hs)
|
||||
|
||||
// Previous Epoch State
|
||||
prevEpoch := headEpoch - 1
|
||||
@@ -1275,9 +1266,8 @@ func createStateSetup(t *testing.T, head types.Epoch, mockgen *mockstategen.Mock
|
||||
ProposerIndex: 10,
|
||||
}
|
||||
assert.NoError(t, ps.AppendCurrentEpochAttestations(pendingAtt))
|
||||
|
||||
}
|
||||
mockgen.StatesBySlot[prevSlot] = ps
|
||||
rb.SetMockState(ps)
|
||||
|
||||
// Older Epoch State
|
||||
olderEpoch := prevEpoch - 1
|
||||
@@ -1311,8 +1301,7 @@ func createStateSetup(t *testing.T, head types.Epoch, mockgen *mockstategen.Mock
|
||||
ProposerIndex: 10,
|
||||
}
|
||||
assert.NoError(t, os.AppendCurrentEpochAttestations(pendingAtt))
|
||||
|
||||
}
|
||||
mockgen.StatesBySlot[olderSlot] = os
|
||||
return hs, ps, os, keys
|
||||
rb.SetMockState(os)
|
||||
return hs, ps, os, keys, rb
|
||||
}
|
||||
|
||||
@@ -123,6 +123,10 @@ func NewService(ctx context.Context, cfg *Config) *Service {
|
||||
}
|
||||
}
|
||||
|
||||
// paranoid build time check to ensure ChainInfoFetcher implements required interfaces
|
||||
var _ stategen.CanonicalChecker = blockchain.ChainInfoFetcher(nil)
|
||||
var _ stategen.CurrentSlotter = blockchain.ChainInfoFetcher(nil)
|
||||
|
||||
// Start the gRPC server.
|
||||
func (s *Service) Start() {
|
||||
address := fmt.Sprintf("%s:%s", s.cfg.Host, s.cfg.Port)
|
||||
@@ -167,6 +171,13 @@ func (s *Service) Start() {
|
||||
}
|
||||
s.grpcServer = grpc.NewServer(opts...)
|
||||
|
||||
var stateCache stategen.CachedGetter
|
||||
// s.cfg.StateGen is often nil in tests...
|
||||
if s.cfg.StateGen != nil {
|
||||
stateCache = s.cfg.StateGen.CombinedCache()
|
||||
}
|
||||
withCache := stategen.WithCache(stateCache)
|
||||
|
||||
validatorServer := &validatorv1alpha1.Server{
|
||||
Ctx: s.ctx,
|
||||
AttestationCache: cache.NewAttestationCache(),
|
||||
@@ -192,6 +203,7 @@ func (s *Service) Start() {
|
||||
SlashingsPool: s.cfg.SlashingsPool,
|
||||
StateGen: s.cfg.StateGen,
|
||||
SyncCommitteePool: s.cfg.SyncCommitteeObjectPool,
|
||||
ReplayerBuilder: stategen.NewCanonicalBuilder(s.cfg.BeaconDB, s.cfg.ChainInfoFetcher, s.cfg.ChainInfoFetcher, withCache),
|
||||
}
|
||||
validatorServerV1 := &validator.Server{
|
||||
HeadFetcher: s.cfg.HeadFetcher,
|
||||
@@ -206,6 +218,7 @@ func (s *Service) Start() {
|
||||
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
|
||||
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
|
||||
StateGenService: s.cfg.StateGen,
|
||||
ReplayerBuilder: stategen.NewCanonicalBuilder(s.cfg.BeaconDB, s.cfg.ChainInfoFetcher, s.cfg.ChainInfoFetcher, withCache),
|
||||
},
|
||||
SyncCommitteePool: s.cfg.SyncCommitteeObjectPool,
|
||||
}
|
||||
@@ -255,6 +268,7 @@ func (s *Service) Start() {
|
||||
SyncChecker: s.cfg.SyncService,
|
||||
ReceivedAttestationsBuffer: make(chan *ethpbv1alpha1.Attestation, attestationBufferSize),
|
||||
CollectedAttestationsBuffer: make(chan []*ethpbv1alpha1.Attestation, attestationBufferSize),
|
||||
ReplayerBuilder: stategen.NewCanonicalBuilder(s.cfg.BeaconDB, s.cfg.ChainInfoFetcher, s.cfg.ChainInfoFetcher, withCache),
|
||||
}
|
||||
beaconChainServerV1 := &beacon.Server{
|
||||
BeaconDB: s.cfg.BeaconDB,
|
||||
@@ -297,6 +311,7 @@ func (s *Service) Start() {
|
||||
HeadFetcher: s.cfg.HeadFetcher,
|
||||
PeerManager: s.cfg.PeerManager,
|
||||
PeersFetcher: s.cfg.PeersFetcher,
|
||||
ReplayerBuilder: stategen.NewCanonicalBuilder(s.cfg.BeaconDB, s.cfg.ChainInfoFetcher, s.cfg.ChainInfoFetcher, withCache),
|
||||
}
|
||||
debugServerV1 := &debug.Server{
|
||||
BeaconDB: s.cfg.BeaconDB,
|
||||
@@ -306,6 +321,7 @@ func (s *Service) Start() {
|
||||
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
|
||||
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
|
||||
StateGenService: s.cfg.StateGen,
|
||||
ReplayerBuilder: stategen.NewCanonicalBuilder(s.cfg.BeaconDB, s.cfg.ChainInfoFetcher, s.cfg.ChainInfoFetcher, withCache),
|
||||
},
|
||||
}
|
||||
ethpbv1alpha1.RegisterDebugServer(s.grpcServer, debugServer)
|
||||
|
||||
@@ -14,6 +14,7 @@ go_library(
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -24,6 +25,7 @@ go_test(
|
||||
deps = [
|
||||
"//beacon-chain/blockchain/testing:go_default_library",
|
||||
"//beacon-chain/db/testing:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/state/stategen/mock:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
|
||||
@@ -15,8 +15,13 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
var ErrNoAncestorForBlock = errors.New("could not find an ancestor state for block")
|
||||
var ErrNoCanonicalBlockForSlot = errors.New("none of the blocks found in the db slot index are canonical")
|
||||
var ErrInvalidDBBlock = errors.New("invalid block found in database")
|
||||
|
||||
// StateIdParseError represents an error scenario where a state ID could not be parsed.
|
||||
type StateIdParseError struct {
|
||||
message string
|
||||
@@ -72,6 +77,7 @@ func (e *StateRootNotFoundError) Error() string {
|
||||
type Fetcher interface {
|
||||
State(ctx context.Context, stateId []byte) (state.BeaconState, error)
|
||||
StateRoot(ctx context.Context, stateId []byte) ([]byte, error)
|
||||
StateBySlot(ctx context.Context, slot types.Slot) (state.BeaconState, error)
|
||||
}
|
||||
|
||||
// StateProvider is a real implementation of Fetcher.
|
||||
@@ -80,6 +86,7 @@ type StateProvider struct {
|
||||
ChainInfoFetcher blockchain.ChainInfoFetcher
|
||||
GenesisTimeFetcher blockchain.TimeFetcher
|
||||
StateGenService stategen.StateManager
|
||||
ReplayerBuilder stategen.ReplayerBuilder
|
||||
}
|
||||
|
||||
// State returns the BeaconState for a given identifier. The identifier can be one of:
|
||||
@@ -129,7 +136,7 @@ func (p *StateProvider) State(ctx context.Context, stateId []byte) (state.Beacon
|
||||
e := NewStateIdParseError(parseErr)
|
||||
return nil, &e
|
||||
}
|
||||
s, err = p.stateBySlot(ctx, types.Slot(slotNumber))
|
||||
s, err = p.StateBySlot(ctx, types.Slot(slotNumber))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,16 +194,21 @@ func (p *StateProvider) stateByHex(ctx context.Context, stateId []byte) (state.B
|
||||
return nil, &stateNotFoundErr
|
||||
}
|
||||
|
||||
func (p *StateProvider) stateBySlot(ctx context.Context, slot types.Slot) (state.BeaconState, error) {
|
||||
currentSlot := p.GenesisTimeFetcher.CurrentSlot()
|
||||
if slot > currentSlot {
|
||||
return nil, errors.New("slot cannot be in the future")
|
||||
}
|
||||
state, err := p.StateGenService.StateBySlot(ctx, slot)
|
||||
// StateBySlot returns the post-state for the requested slot. To generate the state, it uses the
|
||||
// most recent canonical state prior to the target slot, and all canonical blocks
|
||||
// between the found state's slot and the target slot.
|
||||
// process_blocks is applied for all canonical blocks, and process_slots is called for any skipped
|
||||
// slots, or slots following the most recent canonical block up to and including the target slot.
|
||||
func (p *StateProvider) StateBySlot(ctx context.Context, target types.Slot) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "statefetcher.StateBySlot")
|
||||
defer span.End()
|
||||
|
||||
st, err := p.ReplayerBuilder.ForSlot(target).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get state")
|
||||
msg := fmt.Sprintf("error while replaying history to slot=%d", target)
|
||||
return nil, errors.Wrap(err, msg)
|
||||
}
|
||||
return state, nil
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func (p *StateProvider) headStateRoot(ctx context.Context) ([]byte, error) {
|
||||
@@ -204,7 +216,7 @@ func (p *StateProvider) headStateRoot(ctx context.Context) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get head block")
|
||||
}
|
||||
if err := helpers.BeaconBlockIsNil(b); err != nil {
|
||||
if err = helpers.BeaconBlockIsNil(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.Block().StateRoot(), nil
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
chainMock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
|
||||
@@ -151,12 +153,14 @@ func TestGetState(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("slot", func(t *testing.T) {
|
||||
stateGen := mockstategen.NewMockService()
|
||||
stateGen.StatesBySlot[headSlot] = newBeaconState
|
||||
|
||||
p := StateProvider{
|
||||
GenesisTimeFetcher: &chainMock.ChainService{Slot: &headSlot},
|
||||
StateGenService: stateGen,
|
||||
ChainInfoFetcher: &chainMock.ChainService{
|
||||
CanonicalRoots: map[[32]byte]bool{
|
||||
bytesutil.ToBytes32(newBeaconState.LatestBlockHeader().ParentRoot): true,
|
||||
},
|
||||
},
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(newBeaconState)),
|
||||
}
|
||||
|
||||
s, err := p.State(ctx, []byte(strconv.FormatUint(uint64(headSlot), 10)))
|
||||
@@ -166,14 +170,16 @@ func TestGetState(t *testing.T) {
|
||||
assert.Equal(t, stateRoot, sRoot)
|
||||
})
|
||||
|
||||
rb := mockstategen.NewMockReplayerBuilder(mockstategen.WithStateError(1, stategen.ErrFutureSlotRequested))
|
||||
t.Run("slot_too_big", func(t *testing.T) {
|
||||
p := StateProvider{
|
||||
GenesisTimeFetcher: &chainMock.ChainService{
|
||||
Genesis: time.Now(),
|
||||
},
|
||||
ReplayerBuilder: rb,
|
||||
}
|
||||
_, err := p.State(ctx, []byte(strconv.FormatUint(1, 10)))
|
||||
assert.ErrorContains(t, "slot cannot be in the future", err)
|
||||
assert.ErrorContains(t, "cannot replay to future slots", err)
|
||||
})
|
||||
|
||||
t.Run("invalid_state", func(t *testing.T) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package testutil
|
||||
import (
|
||||
"context"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
)
|
||||
|
||||
@@ -21,3 +22,7 @@ func (m *MockFetcher) State(context.Context, []byte) (state.BeaconState, error)
|
||||
func (m *MockFetcher) StateRoot(context.Context, []byte) ([]byte, error) {
|
||||
return m.BeaconStateRoot, nil
|
||||
}
|
||||
|
||||
func (m *MockFetcher) StateBySlot(context.Context, types.Slot) (state.BeaconState, error) {
|
||||
return m.BeaconState, nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"cacher.go",
|
||||
"epoch_boundary_state_cache.go",
|
||||
"errors.go",
|
||||
"getter.go",
|
||||
@@ -11,16 +12,12 @@ go_library(
|
||||
"metrics.go",
|
||||
"migrate.go",
|
||||
"replay.go",
|
||||
"replayer.go",
|
||||
"service.go",
|
||||
"setter.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state/stategen",
|
||||
visibility = [
|
||||
"//beacon-chain:__subpackages__",
|
||||
"//testing/endtoend:__subpackages__",
|
||||
"//testing/slasher/simulator:__pkg__",
|
||||
"//testing/spectest:__subpackages__",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/core/execution:go_default_library",
|
||||
@@ -33,6 +30,7 @@ go_library(
|
||||
"//cache/lru:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//monitoring/tracing:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
@@ -56,13 +54,17 @@ go_test(
|
||||
"hot_state_cache_test.go",
|
||||
"init_test.go",
|
||||
"migrate_test.go",
|
||||
"mock_test.go",
|
||||
"replay_test.go",
|
||||
"replayer_test.go",
|
||||
"service_test.go",
|
||||
"setter_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/transition:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/db/testing:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
@@ -71,11 +73,13 @@ go_test(
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block/mock:go_default_library",
|
||||
"//proto/prysm/v1alpha1/wrapper:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//testing/assert: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",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
|
||||
32
beacon-chain/state/stategen/cacher.go
Normal file
32
beacon-chain/state/stategen/cacher.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package stategen
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
)
|
||||
|
||||
var ErrNotInCache = errors.New("state not found in cache")
|
||||
|
||||
type CachedGetter interface {
|
||||
ByRoot([32]byte) (state.BeaconState, error)
|
||||
}
|
||||
|
||||
type CombinedCache struct {
|
||||
getters []CachedGetter
|
||||
}
|
||||
|
||||
func (c CombinedCache) ByRoot(root [32]byte) (state.BeaconState, error) {
|
||||
for _, getter := range c.getters {
|
||||
st, err := getter.ByRoot(root)
|
||||
if err == nil {
|
||||
return st, nil
|
||||
}
|
||||
if errors.Is(err, ErrNotInCache) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return nil, ErrNotInCache
|
||||
}
|
||||
|
||||
var _ CachedGetter = &CombinedCache{}
|
||||
@@ -66,6 +66,18 @@ func newBoundaryStateCache() *epochBoundaryState {
|
||||
}
|
||||
}
|
||||
|
||||
// ByRoot satisfies the CachedGetter interface
|
||||
func (e *epochBoundaryState) ByRoot(r [32]byte) (state.BeaconState, error) {
|
||||
rsi, ok, err := e.getByRoot(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, ErrNotInCache
|
||||
}
|
||||
return rsi.state, nil
|
||||
}
|
||||
|
||||
// get epoch boundary state by its block root. Returns copied state in state info object if exists. Otherwise returns nil.
|
||||
func (e *epochBoundaryState) getByRoot(r [32]byte) (*rootStateInfo, bool, error) {
|
||||
e.lock.RLock()
|
||||
|
||||
@@ -84,7 +84,7 @@ func (s *State) StateByRootInitialSync(ctx context.Context, blockRoot [32]byte)
|
||||
return cachedInfo.state, nil
|
||||
}
|
||||
|
||||
startState, err := s.lastAncestorState(ctx, blockRoot)
|
||||
startState, err := s.LastAncestorState(ctx, blockRoot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get ancestor state")
|
||||
}
|
||||
@@ -185,7 +185,7 @@ func (s *State) loadStateByRoot(ctx context.Context, blockRoot [32]byte) (state.
|
||||
|
||||
// Since the requested state is not in caches, start replaying using the last available ancestor state which is
|
||||
// retrieved using input block's parent root.
|
||||
startState, err := s.lastAncestorState(ctx, blockRoot)
|
||||
startState, err := s.LastAncestorState(ctx, blockRoot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get ancestor state")
|
||||
}
|
||||
@@ -230,7 +230,7 @@ func (s *State) loadStateBySlot(ctx context.Context, slot types.Slot) (state.Bea
|
||||
}
|
||||
|
||||
if lastValidSlot < slot {
|
||||
replayStartState, err = processSlotsStateGen(ctx, replayStartState, slot)
|
||||
replayStartState, err = ReplayProcessSlots(ctx, replayStartState, slot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -246,8 +246,8 @@ func (s *State) loadStateBySlot(ctx context.Context, slot types.Slot) (state.Bea
|
||||
// 1.) block parent state is the last finalized state
|
||||
// 2.) block parent state is the epoch boundary state and exists in epoch boundary cache.
|
||||
// 3.) block parent state is in DB.
|
||||
func (s *State) lastAncestorState(ctx context.Context, root [32]byte) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "stateGen.lastAncestorState")
|
||||
func (s *State) LastAncestorState(ctx context.Context, root [32]byte) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "stateGen.LastAncestorState")
|
||||
defer span.End()
|
||||
|
||||
if s.isFinalizedRoot(root) && s.finalizedState() != nil {
|
||||
@@ -304,3 +304,14 @@ func (s *State) lastAncestorState(ctx context.Context, root [32]byte) (state.Bea
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) CombinedCache() *CombinedCache {
|
||||
getters := make([]CachedGetter, 0)
|
||||
if s.hotStateCache != nil {
|
||||
getters = append(getters, s.hotStateCache)
|
||||
}
|
||||
if s.epochBoundaryStateCache != nil {
|
||||
getters = append(getters, s.epochBoundaryStateCache)
|
||||
}
|
||||
return &CombinedCache{getters: getters}
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@ func TestLastAncestorState_CanGetUsingDB(t *testing.T) {
|
||||
require.NoError(t, service.beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(b3)))
|
||||
require.NoError(t, service.beaconDB.SaveState(ctx, b1State, r1))
|
||||
|
||||
lastState, err := service.lastAncestorState(ctx, r3)
|
||||
lastState, err := service.LastAncestorState(ctx, r3)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, b1State.Slot(), lastState.Slot(), "Did not get wanted state")
|
||||
}
|
||||
@@ -531,7 +531,7 @@ func TestLastAncestorState_CanGetUsingCache(t *testing.T) {
|
||||
require.NoError(t, service.beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(b3)))
|
||||
service.hotStateCache.put(r1, b1State)
|
||||
|
||||
lastState, err := service.lastAncestorState(ctx, r3)
|
||||
lastState, err := service.LastAncestorState(ctx, r3)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, b1State.Slot(), lastState.Slot(), "Did not get wanted state")
|
||||
}
|
||||
|
||||
@@ -52,6 +52,14 @@ func (c *hotStateCache) get(root [32]byte) state.BeaconState {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *hotStateCache) ByRoot(root [32]byte) (state.BeaconState, error) {
|
||||
st := c.get(root)
|
||||
if st == nil {
|
||||
return nil, ErrNotInCache
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// GetWithoutCopy returns a non-copied cached response via input block root.
|
||||
func (c *hotStateCache) getWithoutCopy(root [32]byte) state.BeaconState {
|
||||
c.lock.RLock()
|
||||
|
||||
@@ -3,11 +3,15 @@ load("@prysm//tools/go:def.bzl", "go_library")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
testonly = True,
|
||||
srcs = ["mock.go"],
|
||||
srcs = [
|
||||
"mock.go",
|
||||
"replayer.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state/stategen/mock",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
|
||||
91
beacon-chain/state/stategen/mock/replayer.go
Normal file
91
beacon-chain/state/stategen/mock/replayer.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
||||
)
|
||||
|
||||
func NewMockReplayerBuilder(opt ...MockReplayerBuilderOption) *MockReplayerBuilder {
|
||||
b := &MockReplayerBuilder{}
|
||||
for _, o := range opt {
|
||||
o(b)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
type MockReplayerBuilderOption func(*MockReplayerBuilder)
|
||||
|
||||
func WithMockState(s state.BeaconState) MockReplayerBuilderOption {
|
||||
return func(b *MockReplayerBuilder) {
|
||||
b.SetMockState(s)
|
||||
}
|
||||
}
|
||||
|
||||
func WithStateError(s types.Slot, e error) MockReplayerBuilderOption {
|
||||
return func(b *MockReplayerBuilder) {
|
||||
b.SetMockSlotError(s, e)
|
||||
}
|
||||
}
|
||||
|
||||
type MockReplayerBuilder struct {
|
||||
forSlot map[types.Slot]*MockReplayer
|
||||
}
|
||||
|
||||
func (b *MockReplayerBuilder) SetMockState(s state.BeaconState) {
|
||||
if b.forSlot == nil {
|
||||
b.forSlot = make(map[types.Slot]*MockReplayer)
|
||||
}
|
||||
b.forSlot[s.Slot()] = &MockReplayer{State: s}
|
||||
}
|
||||
|
||||
func (b *MockReplayerBuilder) SetMockSlotError(s types.Slot, e error) {
|
||||
if b.forSlot == nil {
|
||||
b.forSlot = make(map[types.Slot]*MockReplayer)
|
||||
}
|
||||
b.forSlot[s] = &MockReplayer{Err: e}
|
||||
}
|
||||
|
||||
func (b *MockReplayerBuilder) ForSlot(target types.Slot) stategen.Replayer {
|
||||
return b.forSlot[target]
|
||||
}
|
||||
|
||||
var _ stategen.ReplayerBuilder = &MockReplayerBuilder{}
|
||||
|
||||
type MockReplayer struct {
|
||||
State state.BeaconState
|
||||
Err error
|
||||
}
|
||||
|
||||
func (m *MockReplayer) ReplayBlocks(_ context.Context) (state.BeaconState, error) {
|
||||
return m.State, m.Err
|
||||
}
|
||||
|
||||
func (m *MockReplayer) ReplayToSlot(_ context.Context, _ types.Slot) (state.BeaconState, error) {
|
||||
return m.State, m.Err
|
||||
}
|
||||
|
||||
var _ stategen.Replayer = &MockReplayer{}
|
||||
|
||||
type MockCanonicalChecker struct {
|
||||
Is bool
|
||||
Err error
|
||||
}
|
||||
|
||||
func (m *MockCanonicalChecker) IsCanonical(_ context.Context, _ [32]byte) (bool, error) {
|
||||
return m.Is, m.Err
|
||||
}
|
||||
|
||||
var _ stategen.CanonicalChecker = &MockCanonicalChecker{}
|
||||
|
||||
type MockCurrentSlotter struct {
|
||||
Slot types.Slot
|
||||
}
|
||||
|
||||
func (c *MockCurrentSlotter) CurrentSlot() types.Slot {
|
||||
return c.Slot
|
||||
}
|
||||
|
||||
var _ stategen.CurrentSlotter = &MockCurrentSlotter{}
|
||||
282
beacon-chain/state/stategen/mock_test.go
Normal file
282
beacon-chain/state/stategen/mock_test.go
Normal file
@@ -0,0 +1,282 @@
|
||||
package stategen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestMockHistoryStates(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var begin, middle, end types.Slot = 100, 150, 155
|
||||
specs := []mockHistorySpec{
|
||||
{slot: begin},
|
||||
{slot: middle, savedState: true},
|
||||
{slot: end, canonicalBlock: true},
|
||||
}
|
||||
hist := newMockHistory(t, specs, end+1)
|
||||
// we should have 2 "saved" states, genesis and "middle" (savedState == true)
|
||||
require.Equal(t, 2, len(hist.states))
|
||||
genesisRoot := hist.slotMap[0]
|
||||
st, err := hist.StateOrError(ctx, genesisRoot)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, hist.states[genesisRoot], st)
|
||||
require.Equal(t, types.Slot(0), st.Slot())
|
||||
|
||||
shouldExist, err := hist.StateOrError(ctx, hist.slotMap[middle])
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, hist.states[hist.slotMap[middle]], shouldExist)
|
||||
require.Equal(t, middle, shouldExist.Slot())
|
||||
|
||||
cantExist, err := hist.StateOrError(ctx, hist.slotMap[end])
|
||||
require.ErrorIs(t, err, db.ErrNotFoundState)
|
||||
require.Equal(t, nil, cantExist)
|
||||
|
||||
cantExist, err = hist.StateOrError(ctx, hist.slotMap[begin])
|
||||
require.ErrorIs(t, err, db.ErrNotFoundState)
|
||||
require.Equal(t, nil, cantExist)
|
||||
}
|
||||
|
||||
func TestMockHistoryParentRoot(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var begin, middle, end types.Slot = 100, 150, 155
|
||||
specs := []mockHistorySpec{
|
||||
{slot: begin},
|
||||
{slot: middle, savedState: true},
|
||||
{slot: end, canonicalBlock: true},
|
||||
}
|
||||
hist := newMockHistory(t, specs, end+1)
|
||||
endRoot := hist.slotMap[end]
|
||||
endBlock, err := hist.Block(ctx, endRoot)
|
||||
require.NoError(t, err)
|
||||
// middle should be the parent of end, compare the middle root to endBlock's parent root
|
||||
require.Equal(t, hist.slotMap[middle], bytesutil.ToBytes32(endBlock.Block().ParentRoot()))
|
||||
}
|
||||
|
||||
type mockHistorySpec struct {
|
||||
slot types.Slot
|
||||
savedState bool
|
||||
canonicalBlock bool
|
||||
}
|
||||
|
||||
type mockHistory struct {
|
||||
blocks map[[32]byte]block.SignedBeaconBlock
|
||||
slotMap map[types.Slot][32]byte
|
||||
slotIndex slotList
|
||||
canonical map[[32]byte]bool
|
||||
states map[[32]byte]state.BeaconState
|
||||
hiddenStates map[[32]byte]state.BeaconState
|
||||
current types.Slot
|
||||
overrideHighestSlotBlocksBelow func(context.Context, types.Slot) ([]block.SignedBeaconBlock, error)
|
||||
}
|
||||
|
||||
type slotList []types.Slot
|
||||
|
||||
func (m slotList) Len() int {
|
||||
return len(m)
|
||||
}
|
||||
|
||||
func (m slotList) Less(i, j int) bool {
|
||||
return m[i] < m[j]
|
||||
}
|
||||
|
||||
func (m slotList) Swap(i, j int) {
|
||||
m[i], m[j] = m[j], m[i]
|
||||
}
|
||||
|
||||
var errFallThroughOverride = errors.New("override yielding control back to real HighestSlotBlocksBelow")
|
||||
|
||||
func (m *mockHistory) HighestSlotBlocksBelow(_ context.Context, slot types.Slot) ([]block.SignedBeaconBlock, error) {
|
||||
if m.overrideHighestSlotBlocksBelow != nil {
|
||||
s, err := m.overrideHighestSlotBlocksBelow(context.Background(), slot)
|
||||
if !errors.Is(err, errFallThroughOverride) {
|
||||
return s, err
|
||||
}
|
||||
}
|
||||
if len(m.slotIndex) == 0 && len(m.slotMap) > 0 {
|
||||
for k := range m.slotMap {
|
||||
m.slotIndex = append(m.slotIndex, k)
|
||||
}
|
||||
sort.Sort(sort.Reverse(m.slotIndex))
|
||||
}
|
||||
for _, s := range m.slotIndex {
|
||||
if s < slot {
|
||||
return []block.SignedBeaconBlock{m.blocks[m.slotMap[s]]}, nil
|
||||
}
|
||||
}
|
||||
return []block.SignedBeaconBlock{}, nil
|
||||
}
|
||||
|
||||
var errGenesisBlockNotFound = errors.New("canonical genesis block not found in db")
|
||||
|
||||
func (m *mockHistory) GenesisBlock(_ context.Context) (block.SignedBeaconBlock, error) {
|
||||
genesisRoot, ok := m.slotMap[0]
|
||||
if !ok {
|
||||
return nil, errGenesisBlockNotFound
|
||||
}
|
||||
return m.blocks[genesisRoot], nil
|
||||
}
|
||||
|
||||
func (m *mockHistory) Block(_ context.Context, blockRoot [32]byte) (block.SignedBeaconBlock, error) {
|
||||
if b, ok := m.blocks[blockRoot]; ok {
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockHistory) StateOrError(_ context.Context, blockRoot [32]byte) (state.BeaconState, error) {
|
||||
if s, ok := m.states[blockRoot]; ok {
|
||||
return s.Copy(), nil
|
||||
}
|
||||
return nil, db.ErrNotFoundState
|
||||
}
|
||||
|
||||
func (m *mockHistory) IsCanonical(_ context.Context, blockRoot [32]byte) (bool, error) {
|
||||
canon, ok := m.canonical[blockRoot]
|
||||
return ok && canon, nil
|
||||
}
|
||||
|
||||
func (m *mockHistory) CurrentSlot() types.Slot {
|
||||
return m.current
|
||||
}
|
||||
|
||||
func (h *mockHistory) addBlock(root [32]byte, b block.SignedBeaconBlock, canon bool) {
|
||||
h.blocks[root] = b
|
||||
h.slotMap[b.Block().Slot()] = root
|
||||
h.canonical[root] = canon
|
||||
}
|
||||
|
||||
func (h *mockHistory) addState(root [32]byte, s state.BeaconState) {
|
||||
h.states[root] = s
|
||||
}
|
||||
|
||||
func (h *mockHistory) hideState(root [32]byte, s state.BeaconState) {
|
||||
h.hiddenStates[root] = s
|
||||
}
|
||||
|
||||
func (h *mockHistory) validateRoots() error {
|
||||
uniqParentRoots := make(map[[32]byte]types.Slot)
|
||||
for s, root := range h.slotMap {
|
||||
b := h.blocks[root]
|
||||
htr, err := b.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("error computing htr for block at slot %d", s))
|
||||
}
|
||||
if htr != root {
|
||||
return fmt.Errorf("htr mismatch, expected=%#x, actual=%#x", root, htr)
|
||||
}
|
||||
if ps, ok := uniqParentRoots[htr]; ok {
|
||||
return fmt.Errorf("duplicate parent_root %#x seen at slots %d, %d", htr, ps, s)
|
||||
}
|
||||
uniqParentRoots[htr] = s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMockHistory(t *testing.T, hist []mockHistorySpec, current types.Slot) *mockHistory {
|
||||
ctx := context.Background()
|
||||
mh := &mockHistory{
|
||||
blocks: map[[32]byte]block.SignedBeaconBlock{},
|
||||
canonical: map[[32]byte]bool{},
|
||||
states: map[[32]byte]state.BeaconState{},
|
||||
hiddenStates: map[[32]byte]state.BeaconState{},
|
||||
slotMap: map[types.Slot][32]byte{},
|
||||
slotIndex: slotList{},
|
||||
current: current,
|
||||
}
|
||||
|
||||
// genesis state for history
|
||||
gs, _ := util.DeterministicGenesisState(t, 32)
|
||||
gsr, err := gs.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// generate new genesis block using the root of the deterministic state
|
||||
gb, err := wrapper.WrappedSignedBeaconBlock(blocks.NewGenesisBlock(gsr[:]))
|
||||
require.NoError(t, err)
|
||||
pr, err := gb.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
// add genesis block as canonical
|
||||
mh.addBlock(pr, gb, true)
|
||||
// add genesis state, indexed by unapplied genesis block - genesis block is never really processed...
|
||||
mh.addState(pr, gs.Copy())
|
||||
|
||||
ps := gs.Copy()
|
||||
for _, spec := range hist {
|
||||
// call process_slots and process_block separately, because process_slots updates values used in randao mix
|
||||
// which influences proposer_index.
|
||||
s, err := ReplayProcessSlots(ctx, ps, spec.slot)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create proposer block, setting values in the order seen in the validator.md spec
|
||||
b, err := wrapper.WrappedSignedBeaconBlock(util.NewBeaconBlock())
|
||||
require.NoError(t, err)
|
||||
|
||||
// set slot to mock history spec value
|
||||
require.NoError(t, wrapper.SetBlockSlot(b, spec.slot))
|
||||
|
||||
// set the correct proposer_index in the "proposal" block
|
||||
// so that it will pass validation in process_block. important that we do this
|
||||
// after process_slots!
|
||||
idx, err := helpers.BeaconProposerIndex(ctx, s)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, wrapper.SetProposerIndex(b, idx))
|
||||
|
||||
// set parent root
|
||||
require.NoError(t, wrapper.SetBlockParentRoot(b, pr))
|
||||
|
||||
// now do process_block
|
||||
s, err = transition.ProcessBlockForStateRoot(ctx, s, b)
|
||||
require.NoError(t, err)
|
||||
|
||||
sr, err := s.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
err = wrapper.SetBlockStateRoot(b, sr)
|
||||
require.NoError(t, err)
|
||||
|
||||
pr, err = b.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
if spec.savedState {
|
||||
mh.addState(pr, s)
|
||||
} else {
|
||||
mh.hideState(pr, s)
|
||||
}
|
||||
mh.addBlock(pr, b, spec.canonicalBlock)
|
||||
ps = s.Copy()
|
||||
}
|
||||
|
||||
require.NoError(t, mh.validateRoots())
|
||||
return mh
|
||||
}
|
||||
|
||||
var _ HistoryAccessor = &mockHistory{}
|
||||
var _ CanonicalChecker = &mockHistory{}
|
||||
var _ CurrentSlotter = &mockHistory{}
|
||||
|
||||
type mockCachedGetter struct {
|
||||
cache map[[32]byte]state.BeaconState
|
||||
}
|
||||
|
||||
func (m mockCachedGetter) ByRoot(root [32]byte) (state.BeaconState, error) {
|
||||
st, ok := m.cache[root]
|
||||
if !ok {
|
||||
return nil, ErrNotInCache
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
var _ CachedGetter = &mockCachedGetter{}
|
||||
@@ -10,11 +10,12 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/altair"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/execution"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
prysmTime "github.com/prysmaticlabs/prysm/beacon-chain/core/time"
|
||||
prysmtime "github.com/prysmaticlabs/prysm/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db/filters"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"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"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -60,7 +61,7 @@ func (_ *State) ReplayBlocks(
|
||||
|
||||
// If there is skip slots at the end.
|
||||
if targetSlot > state.Slot() {
|
||||
state, err = processSlotsStateGen(ctx, state, targetSlot)
|
||||
state, err = ReplayProcessSlots(ctx, state, targetSlot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -151,7 +152,7 @@ func executeStateTransitionStateGen(
|
||||
|
||||
// Execute per slots transition.
|
||||
// Given this is for state gen, a node uses the version process slots without skip slots cache.
|
||||
state, err = processSlotsStateGen(ctx, state, signed.Block().Slot())
|
||||
state, err = ReplayProcessSlots(ctx, state, signed.Block().Slot())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process slot")
|
||||
}
|
||||
@@ -163,22 +164,14 @@ func executeStateTransitionStateGen(
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process block")
|
||||
}
|
||||
if signed.Version() == version.Phase0 {
|
||||
return state, nil
|
||||
}
|
||||
|
||||
sa, err := signed.Block().Body().SyncAggregate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return altair.ProcessSyncAggregate(ctx, state, sa)
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// processSlotsStateGen to process old slots for state gen usages.
|
||||
// ReplayProcessSlots to process old slots for state gen usages.
|
||||
// There's no skip slot cache involved given state gen only works with already stored block and state in DB.
|
||||
// WARNING: This method should not be used for future slot.
|
||||
func processSlotsStateGen(ctx context.Context, state state.BeaconState, slot types.Slot) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "stategen.ProcessSlotsStateGen")
|
||||
func ReplayProcessSlots(ctx context.Context, state state.BeaconState, slot types.Slot) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "stategen.ReplayProcessSlots")
|
||||
defer span.End()
|
||||
if state == nil || state.IsNil() {
|
||||
return nil, errUnknownState
|
||||
@@ -199,35 +192,41 @@ func processSlotsStateGen(ctx context.Context, state state.BeaconState, slot typ
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process slot")
|
||||
}
|
||||
if prysmTime.CanProcessEpoch(state) {
|
||||
if prysmtime.CanProcessEpoch(state) {
|
||||
switch state.Version() {
|
||||
case version.Phase0:
|
||||
state, err = transition.ProcessEpochPrecompute(ctx, state)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, errors.Wrap(err, "could not process epoch with optimizations")
|
||||
}
|
||||
case version.Altair, version.Bellatrix:
|
||||
state, err = altair.ProcessEpoch(ctx, state)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process epoch with optimization")
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, errors.Wrap(err, "could not process epoch")
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("beacon state should have a version")
|
||||
}
|
||||
}
|
||||
if err := state.SetSlot(state.Slot() + 1); err != nil {
|
||||
return nil, err
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, errors.Wrap(err, "failed to increment state slot")
|
||||
}
|
||||
|
||||
if prysmTime.CanUpgradeToAltair(state.Slot()) {
|
||||
if prysmtime.CanUpgradeToAltair(state.Slot()) {
|
||||
state, err = altair.UpgradeToAltair(ctx, state)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if prysmTime.CanUpgradeToBellatrix(state.Slot()) {
|
||||
|
||||
if prysmtime.CanUpgradeToBellatrix(state.Slot()) {
|
||||
state, err = execution.UpgradeToBellatrix(ctx, state)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
387
beacon-chain/state/stategen/replayer.go
Normal file
387
beacon-chain/state/stategen/replayer.go
Normal file
@@ -0,0 +1,387 @@
|
||||
package stategen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"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/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
var ErrFutureSlotRequested = errors.New("cannot replay to future slots")
|
||||
var ErrNoCanonicalBlockForSlot = errors.New("none of the blocks found in the db slot index are canonical")
|
||||
var ErrNoBlocksBelowSlot = errors.New("no blocks found in db below slot")
|
||||
var ErrInvalidDBBlock = errors.New("invalid block found in database")
|
||||
|
||||
// HistoryAccessor describes the minimum set of database methods needed to support the ReplayerBuilder.
|
||||
type HistoryAccessor interface {
|
||||
HighestSlotBlocksBelow(ctx context.Context, slot types.Slot) ([]block.SignedBeaconBlock, error)
|
||||
GenesisBlock(ctx context.Context) (block.SignedBeaconBlock, error)
|
||||
Block(ctx context.Context, blockRoot [32]byte) (block.SignedBeaconBlock, error)
|
||||
StateOrError(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error)
|
||||
}
|
||||
|
||||
// CanonicalChecker determines whether the given block root is canonical.
|
||||
// In practice this should be satisfied by a type that uses the fork choice store.
|
||||
type CanonicalChecker interface {
|
||||
IsCanonical(ctx context.Context, blockRoot [32]byte) (bool, error)
|
||||
}
|
||||
|
||||
// CurrentSlotter provides the current Slot.
|
||||
type CurrentSlotter interface {
|
||||
CurrentSlot() types.Slot
|
||||
}
|
||||
|
||||
// Replayer encapsulates database query and replay logic. It can be constructed via a StateReplayerBuilder.
|
||||
type Replayer interface {
|
||||
// ReplayBlocks replays the blocks the Replayer knows about based on Builder params
|
||||
ReplayBlocks(ctx context.Context) (state.BeaconState, error)
|
||||
// ReplayToSlot invokes ReplayBlocks under the hood,
|
||||
// but then also runs process_slots to advance the state past the root or slot used in the builder.
|
||||
// For example, if you wanted the state to be at the target slot, but only integrating blocks up to
|
||||
// slot-1, you could request Builder.ForSlot(slot-1).ReplayToSlot(slot)
|
||||
ReplayToSlot(ctx context.Context, target types.Slot) (state.BeaconState, error)
|
||||
}
|
||||
|
||||
var _ Replayer = &stateReplayer{}
|
||||
|
||||
type stateReplayer struct {
|
||||
s state.BeaconState
|
||||
descendants []block.SignedBeaconBlock
|
||||
target types.Slot
|
||||
method retrievalMethod
|
||||
chainer chainer
|
||||
}
|
||||
|
||||
// ReplayBlocks applies all the blocks that were accumulated when building the Replayer.
|
||||
// This method relies on the correctness of the code that constructed the Replayer data.
|
||||
func (rs *stateReplayer) ReplayBlocks(ctx context.Context) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "stateGen.stateReplayer.ReplayBlocks")
|
||||
defer span.End()
|
||||
|
||||
var s state.BeaconState
|
||||
var descendants []block.SignedBeaconBlock
|
||||
var err error
|
||||
switch rs.method {
|
||||
case forSlot:
|
||||
s, descendants, err = rs.chainer.chainForSlot(ctx, rs.target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("Replayer initialized using unknown state retrieval method")
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
diff, err := rs.target.SafeSubSlot(s.Slot())
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("error subtracting state.slot %d from replay target slot %d", s.Slot(), rs.target)
|
||||
return nil, errors.Wrap(err, msg)
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"startSlot": s.Slot(),
|
||||
"endSlot": rs.target,
|
||||
"diff": diff,
|
||||
}).Debug("Replaying canonical blocks from most recent state")
|
||||
|
||||
for _, b := range descendants {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
s, err = executeStateTransitionStateGen(ctx, s, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if rs.target > s.Slot() {
|
||||
s, err = ReplayProcessSlots(ctx, s, rs.target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
duration := time.Since(start)
|
||||
log.WithFields(logrus.Fields{
|
||||
"duration": duration,
|
||||
}).Debug("Finished calling process_blocks on all blocks in ReplayBlocks")
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ReplayToSlot invokes ReplayBlocks under the hood,
|
||||
// but then also runs process_slots to advance the state past the root or slot used in the builder.
|
||||
// for example, if you wanted the state to be at the target slot, but only integrating blocks up to
|
||||
// slot-1, you could request Builder.ForSlot(slot-1).ReplayToSlot(slot)
|
||||
func (rs *stateReplayer) ReplayToSlot(ctx context.Context, replayTo types.Slot) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "stateGen.stateReplayer.ReplayToSlot")
|
||||
defer span.End()
|
||||
|
||||
s, err := rs.ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to ReplayBlocks")
|
||||
}
|
||||
|
||||
if replayTo > s.Slot() {
|
||||
start := time.Now()
|
||||
log.WithFields(logrus.Fields{
|
||||
"startSlot": s.Slot(),
|
||||
"endSlot": replayTo,
|
||||
"diff": replayTo - s.Slot(),
|
||||
}).Debug("calling process_slots on remaining slots")
|
||||
|
||||
if replayTo > s.Slot() {
|
||||
// err will be handled after the bookend log
|
||||
s, err = ReplayProcessSlots(ctx, s, replayTo)
|
||||
}
|
||||
|
||||
duration := time.Since(start)
|
||||
log.WithFields(logrus.Fields{
|
||||
"duration": duration,
|
||||
}).Debug("time spent in process_slots")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("ReplayToSlot failed to seek to slot %d after applying blocks", replayTo))
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ReplayerBuilder creates a Replayer that can be used to obtain a state at a specified slot or root
|
||||
// (only ForSlot implemented so far).
|
||||
// See documentation on Replayer for more on how to use this to obtain pre/post-block states
|
||||
type ReplayerBuilder interface {
|
||||
// ForSlot creates a builder that will create a state that includes blocks up to and including the requested slot
|
||||
// The resulting Replayer will always yield a state with .Slot=target; if there are skipped blocks
|
||||
// between the highest canonical block in the db and the target, the replayer will fast-forward past the intervening
|
||||
// slots via process_slots.
|
||||
ForSlot(target types.Slot) Replayer
|
||||
}
|
||||
|
||||
func WithCache(c CachedGetter) CanonicalBuilderOption {
|
||||
return func(b *CanonicalBuilder) {
|
||||
b.chainer.useCache(c)
|
||||
}
|
||||
}
|
||||
|
||||
type CanonicalBuilderOption func(*CanonicalBuilder)
|
||||
|
||||
// NewCanonicalBuilder handles initializing the default concrete ReplayerBuilder implementation.
|
||||
func NewCanonicalBuilder(h HistoryAccessor, c CanonicalChecker, cs CurrentSlotter, opts ...CanonicalBuilderOption) *CanonicalBuilder {
|
||||
b := &CanonicalBuilder{
|
||||
chainer: &canonicalChainer{
|
||||
h: h,
|
||||
c: c,
|
||||
cs: cs,
|
||||
},
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(b)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
type retrievalMethod int
|
||||
|
||||
const (
|
||||
forSlot retrievalMethod = iota
|
||||
)
|
||||
|
||||
// CanonicalBuilder builds a Replayer that uses a combination of database queries and a
|
||||
// CanonicalChecker (which should usually be a fork choice store implementing an IsCanonical method)
|
||||
// to determine the canonical chain and apply it to generate the desired state.
|
||||
type CanonicalBuilder struct {
|
||||
chainer chainer
|
||||
}
|
||||
|
||||
var _ ReplayerBuilder = &CanonicalBuilder{}
|
||||
|
||||
func (r *CanonicalBuilder) ForSlot(target types.Slot) Replayer {
|
||||
return &stateReplayer{chainer: r.chainer, method: forSlot, target: target}
|
||||
}
|
||||
|
||||
// chainer is responsible for supplying the chain components necessary to rebuild a state,
|
||||
// namely a starting BeaconState and all available blocks from the starting state up to and including the target slot
|
||||
type chainer interface {
|
||||
chainForSlot(ctx context.Context, target types.Slot) (state.BeaconState, []block.SignedBeaconBlock, error)
|
||||
useCache(c CachedGetter)
|
||||
}
|
||||
|
||||
type canonicalChainer struct {
|
||||
h HistoryAccessor
|
||||
c CanonicalChecker
|
||||
cs CurrentSlotter
|
||||
cache CachedGetter
|
||||
}
|
||||
|
||||
var _ chainer = &canonicalChainer{}
|
||||
|
||||
func (c *canonicalChainer) useCache(cache CachedGetter) {
|
||||
c.cache = cache
|
||||
}
|
||||
|
||||
// ChainForSlot creates a value that satisfies the Replayer interface via db queries
|
||||
// and the stategen transition helper methods. This implementation uses the following algorithm:
|
||||
// - find the highest canonical block <= the target slot
|
||||
// - starting with this block, recursively search backwards for a stored state, and accumulate intervening blocks
|
||||
func (c *canonicalChainer) chainForSlot(ctx context.Context, target types.Slot) (state.BeaconState, []block.SignedBeaconBlock, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "canonicalChainer.chainForSlot")
|
||||
defer span.End()
|
||||
currentSlot := c.cs.CurrentSlot()
|
||||
if target > currentSlot {
|
||||
return nil, nil, errors.Wrap(ErrFutureSlotRequested, fmt.Sprintf("requested=%d, current=%d", target, currentSlot))
|
||||
}
|
||||
_, b, err := c.canonicalBlockForSlot(ctx, target)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, fmt.Sprintf("unable to find replay data for slot=%d", target))
|
||||
}
|
||||
s, descendants, err := c.ancestorChain(ctx, b)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to query for ancestor and descendant blocks")
|
||||
}
|
||||
|
||||
return s, descendants, nil
|
||||
}
|
||||
|
||||
// canonicalBlockForSlot uses HighestSlotBlocksBelow(target+1) and the CanonicalChecker
|
||||
// to find the highest canonical block available to replay to the given slot.
|
||||
func (c *canonicalChainer) canonicalBlockForSlot(ctx context.Context, target types.Slot) ([32]byte, block.SignedBeaconBlock, error) {
|
||||
for target > 0 {
|
||||
if ctx.Err() != nil {
|
||||
return [32]byte{}, nil, errors.Wrap(ctx.Err(), "context canceled during canonicalBlockForSlot")
|
||||
}
|
||||
hbs, err := c.h.HighestSlotBlocksBelow(ctx, target+1)
|
||||
if err != nil {
|
||||
return [32]byte{}, nil, errors.Wrap(err, fmt.Sprintf("error finding highest block w/ slot <= %d", target))
|
||||
}
|
||||
if len(hbs) == 0 {
|
||||
return [32]byte{}, nil, errors.Wrap(ErrNoBlocksBelowSlot, fmt.Sprintf("slot=%d", target))
|
||||
}
|
||||
r, b, err := c.bestForSlot(ctx, hbs)
|
||||
if err == nil {
|
||||
// we found a valid, canonical block!
|
||||
return r, b, nil
|
||||
}
|
||||
|
||||
// we found a block, but it wasn't considered canonical - keep looking
|
||||
if errors.Is(err, ErrNoCanonicalBlockForSlot) {
|
||||
// break once we've seen slot 0 (and prevent underflow)
|
||||
if hbs[0].Block().Slot() == 0 {
|
||||
break
|
||||
}
|
||||
target = hbs[0].Block().Slot() - 1
|
||||
continue
|
||||
}
|
||||
return [32]byte{}, nil, err
|
||||
}
|
||||
b, err := c.h.GenesisBlock(ctx)
|
||||
if err != nil {
|
||||
return [32]byte{}, nil, errors.Wrap(err, "db error while retrieving genesis block")
|
||||
}
|
||||
root, _, err := c.bestForSlot(ctx, []block.SignedBeaconBlock{b})
|
||||
if err != nil {
|
||||
return [32]byte{}, nil, errors.Wrap(err, "problem retrieving genesis block")
|
||||
}
|
||||
return root, b, nil
|
||||
}
|
||||
|
||||
// bestForSlot encapsulates several messy realities of the underlying db code, looping through multiple blocks,
|
||||
// performing null/validity checks, and using CanonicalChecker to only pick canonical blocks.
|
||||
func (c *canonicalChainer) bestForSlot(ctx context.Context, hbs []block.SignedBeaconBlock) ([32]byte, block.SignedBeaconBlock, error) {
|
||||
for _, b := range hbs {
|
||||
if helpers.BeaconBlockIsNil(b) != nil {
|
||||
continue
|
||||
}
|
||||
root, err := b.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
// use this error message to wrap a sentinel error for error type matching
|
||||
wrapped := errors.Wrap(ErrInvalidDBBlock, err.Error())
|
||||
msg := fmt.Sprintf("could not compute hash_tree_root for block at slot=%d", b.Block().Slot())
|
||||
return [32]byte{}, nil, errors.Wrap(wrapped, msg)
|
||||
}
|
||||
canon, err := c.c.IsCanonical(ctx, root)
|
||||
if err != nil {
|
||||
return [32]byte{}, nil, errors.Wrap(err, "replayer could not check if block is canonical")
|
||||
}
|
||||
if canon {
|
||||
return root, b, nil
|
||||
}
|
||||
}
|
||||
return [32]byte{}, nil, errors.Wrap(ErrNoCanonicalBlockForSlot, "no good block for slot")
|
||||
}
|
||||
|
||||
func (c *canonicalChainer) getState(ctx context.Context, root [32]byte) (state.BeaconState, error) {
|
||||
if c.cache != nil {
|
||||
st, err := c.cache.ByRoot(root)
|
||||
if err == nil {
|
||||
return st, nil
|
||||
}
|
||||
if !errors.Is(err, ErrNotInCache) {
|
||||
return nil, errors.Wrap(err, "error reading from state cache during state replay")
|
||||
}
|
||||
}
|
||||
return c.h.StateOrError(ctx, root)
|
||||
}
|
||||
|
||||
// ancestorChain works backwards through the chain lineage, accumulating blocks and checking for a saved state.
|
||||
// If it finds a saved state that the tail block was descended from, it returns this state and
|
||||
// all blocks in the lineage, including the tail block. Blocks are returned in ascending order.
|
||||
// Note that this function assumes that the tail is a canonical block, and therefore assumes that
|
||||
// all ancestors are also canonical.
|
||||
func (c *canonicalChainer) ancestorChain(ctx context.Context, tail block.SignedBeaconBlock) (state.BeaconState, []block.SignedBeaconBlock, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "canonicalChainer.ancestorChain")
|
||||
defer span.End()
|
||||
chain := make([]block.SignedBeaconBlock, 0)
|
||||
for {
|
||||
if err := ctx.Err(); err != nil {
|
||||
msg := fmt.Sprintf("context canceled while finding ancestors of block at slot %d", tail.Block().Slot())
|
||||
return nil, nil, errors.Wrap(err, msg)
|
||||
}
|
||||
b := tail.Block()
|
||||
// compute hash_tree_root of current block and try to look up the corresponding state
|
||||
root, err := b.HashTreeRoot()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("could not compute htr for descendant block at slot=%d", b.Slot())
|
||||
return nil, nil, errors.Wrap(err, msg)
|
||||
}
|
||||
st, err := c.getState(ctx, root)
|
||||
// err == nil, we've got a real state - the job is done!
|
||||
// Note: in cases where there are skipped slots we could find a state that is a descendant
|
||||
// of the block we are searching for. We don't want to return a future block, so in this case
|
||||
// we keep working backwards.
|
||||
if err == nil && st.Slot() == b.Slot() {
|
||||
// we found the state by the root of the head, meaning it has already been applied.
|
||||
// we only want to return the blocks descended from it.
|
||||
reverseChain(chain)
|
||||
return st, chain, nil
|
||||
}
|
||||
// ErrNotFoundState errors are fine, but other errors mean something is wrong with the db
|
||||
if err != nil && !errors.Is(err, db.ErrNotFoundState) {
|
||||
return nil, nil, errors.Wrap(err, fmt.Sprintf("error querying database for state w/ block root = %#x", root))
|
||||
}
|
||||
parent, err := c.h.Block(ctx, bytesutil.ToBytes32(b.ParentRoot()))
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("db error when retrieving parent of block at slot=%d by root=%#x", b.Slot(), b.ParentRoot())
|
||||
return nil, nil, errors.Wrap(err, msg)
|
||||
}
|
||||
if helpers.BeaconBlockIsNil(parent) != nil {
|
||||
msg := fmt.Sprintf("unable to retrieve parent of block at slot=%d by root=%#x", b.Slot(), b.ParentRoot())
|
||||
return nil, nil, errors.Wrap(db.ErrNotFound, msg)
|
||||
}
|
||||
chain = append(chain, tail)
|
||||
tail = parent
|
||||
}
|
||||
}
|
||||
|
||||
func reverseChain(c []block.SignedBeaconBlock) {
|
||||
last := len(c) - 1
|
||||
swaps := (last + 1) / 2
|
||||
for i := 0; i < swaps; i++ {
|
||||
c[i], c[last-i] = c[last-i], c[i]
|
||||
}
|
||||
}
|
||||
716
beacon-chain/state/stategen/replayer_test.go
Normal file
716
beacon-chain/state/stategen/replayer_test.go
Normal file
@@ -0,0 +1,716 @@
|
||||
package stategen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block/mock"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
)
|
||||
|
||||
func headerFromBlock(b block.SignedBeaconBlock) (*ethpb.BeaconBlockHeader, error) {
|
||||
bodyRoot, err := b.Block().Body().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.BeaconBlockHeader{
|
||||
Slot: b.Block().Slot(),
|
||||
StateRoot: b.Block().StateRoot(),
|
||||
ProposerIndex: b.Block().ProposerIndex(),
|
||||
BodyRoot: bodyRoot[:],
|
||||
ParentRoot: b.Block().ParentRoot(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestReplayBlocks(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var zero, one, two, three, four, five types.Slot = 50, 51, 150, 151, 152, 200
|
||||
specs := []mockHistorySpec{
|
||||
{slot: zero},
|
||||
{slot: one, savedState: true},
|
||||
{slot: two},
|
||||
{slot: three},
|
||||
{slot: four},
|
||||
{slot: five, canonicalBlock: true},
|
||||
}
|
||||
|
||||
hist := newMockHistory(t, specs, five+1)
|
||||
bld := NewCanonicalBuilder(hist, hist, hist)
|
||||
st, err := bld.ForSlot(five).ReplayBlocks(ctx)
|
||||
require.NoError(t, err)
|
||||
expected := hist.hiddenStates[hist.slotMap[five]]
|
||||
expectedHTR, err := expected.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
actualHTR, err := st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
expectedLBH := expected.LatestBlockHeader()
|
||||
actualLBH := st.LatestBlockHeader()
|
||||
require.Equal(t, expectedLBH.Slot, actualLBH.Slot)
|
||||
require.Equal(t, bytesutil.ToBytes32(expectedLBH.ParentRoot), bytesutil.ToBytes32(actualLBH.ParentRoot))
|
||||
require.Equal(t, bytesutil.ToBytes32(expectedLBH.StateRoot), bytesutil.ToBytes32(actualLBH.StateRoot))
|
||||
require.Equal(t, expectedLBH.ProposerIndex, actualLBH.ProposerIndex)
|
||||
require.Equal(t, bytesutil.ToBytes32(expectedLBH.BodyRoot), bytesutil.ToBytes32(actualLBH.BodyRoot))
|
||||
require.Equal(t, expectedHTR, actualHTR)
|
||||
|
||||
st, err = bld.ForSlot(one).ReplayBlocks(ctx)
|
||||
require.NoError(t, err)
|
||||
expected = hist.states[hist.slotMap[one]]
|
||||
|
||||
// no canonical blocks in between, so latest block process_block_header will be for genesis
|
||||
expectedLBH, err = headerFromBlock(hist.blocks[hist.slotMap[0]])
|
||||
require.NoError(t, err)
|
||||
actualLBH = st.LatestBlockHeader()
|
||||
require.Equal(t, expectedLBH.Slot, actualLBH.Slot)
|
||||
require.Equal(t, bytesutil.ToBytes32(expectedLBH.ParentRoot), bytesutil.ToBytes32(actualLBH.ParentRoot))
|
||||
require.Equal(t, bytesutil.ToBytes32(expectedLBH.StateRoot), bytesutil.ToBytes32(actualLBH.StateRoot))
|
||||
require.Equal(t, expectedLBH.ProposerIndex, actualLBH.ProposerIndex)
|
||||
require.Equal(t, bytesutil.ToBytes32(expectedLBH.BodyRoot), bytesutil.ToBytes32(actualLBH.BodyRoot))
|
||||
|
||||
require.Equal(t, expected.Slot(), st.Slot())
|
||||
// NOTE: HTR is not compared, because process_block is not called for non-canonical blocks,
|
||||
// so there are multiple differences compared to the "db" state that applies all blocks
|
||||
}
|
||||
|
||||
func TestReplayToSlot(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var zero, one, two, three, four, five types.Slot = 50, 51, 150, 151, 152, 200
|
||||
specs := []mockHistorySpec{
|
||||
{slot: zero},
|
||||
{slot: one, savedState: true},
|
||||
{slot: two},
|
||||
{slot: three},
|
||||
{slot: four},
|
||||
{slot: five, canonicalBlock: true},
|
||||
}
|
||||
|
||||
// first case tests that ReplayToSlot is equivalent to ReplayBlocks
|
||||
hist := newMockHistory(t, specs, five+1)
|
||||
bld := NewCanonicalBuilder(hist, hist, hist)
|
||||
|
||||
st, err := bld.ForSlot(five).ReplayToSlot(ctx, five)
|
||||
require.NoError(t, err)
|
||||
expected := hist.hiddenStates[hist.slotMap[five]]
|
||||
expectedHTR, err := expected.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
actualHTR, err := st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
expectedLBH := expected.LatestBlockHeader()
|
||||
actualLBH := st.LatestBlockHeader()
|
||||
require.Equal(t, expectedLBH.Slot, actualLBH.Slot)
|
||||
require.Equal(t, bytesutil.ToBytes32(expectedLBH.ParentRoot), bytesutil.ToBytes32(actualLBH.ParentRoot))
|
||||
require.Equal(t, bytesutil.ToBytes32(expectedLBH.StateRoot), bytesutil.ToBytes32(actualLBH.StateRoot))
|
||||
require.Equal(t, expectedLBH.ProposerIndex, actualLBH.ProposerIndex)
|
||||
require.Equal(t, bytesutil.ToBytes32(expectedLBH.BodyRoot), bytesutil.ToBytes32(actualLBH.BodyRoot))
|
||||
require.Equal(t, expectedHTR, actualHTR)
|
||||
|
||||
st, err = bld.ForSlot(five).ReplayToSlot(ctx, five+100)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, five+100, st.Slot())
|
||||
expectedLBH, err = headerFromBlock(hist.blocks[hist.slotMap[five]])
|
||||
require.NoError(t, err)
|
||||
actualLBH = st.LatestBlockHeader()
|
||||
require.Equal(t, expectedLBH.Slot, actualLBH.Slot)
|
||||
require.Equal(t, bytesutil.ToBytes32(expectedLBH.ParentRoot), bytesutil.ToBytes32(actualLBH.ParentRoot))
|
||||
require.Equal(t, bytesutil.ToBytes32(expectedLBH.StateRoot), bytesutil.ToBytes32(actualLBH.StateRoot))
|
||||
require.Equal(t, expectedLBH.ProposerIndex, actualLBH.ProposerIndex)
|
||||
require.Equal(t, bytesutil.ToBytes32(expectedLBH.BodyRoot), bytesutil.ToBytes32(actualLBH.BodyRoot))
|
||||
}
|
||||
|
||||
// happy path tests
|
||||
func TestCanonicalBlockForSlotHappy(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var begin, middle, end types.Slot = 100, 150, 155
|
||||
specs := []mockHistorySpec{
|
||||
{slot: begin},
|
||||
{slot: middle, savedState: true},
|
||||
{slot: end, canonicalBlock: true},
|
||||
}
|
||||
hist := newMockHistory(t, specs, end+1)
|
||||
cc := canonicalChainer{h: hist, c: hist, cs: hist}
|
||||
|
||||
// since only the end block and genesis are canonical, once the slot drops below
|
||||
// end, we should always get genesis
|
||||
cases := []struct {
|
||||
slot types.Slot
|
||||
highest types.Slot
|
||||
canon types.Slot
|
||||
name string
|
||||
}{
|
||||
{slot: hist.current, highest: end, canon: end, name: "slot > end"},
|
||||
{slot: end, highest: end, canon: end, name: "slot == end"},
|
||||
{slot: end - 1, highest: middle, canon: 0, name: "middle < slot < end"},
|
||||
{slot: middle, highest: middle, canon: 0, name: "slot == middle"},
|
||||
{slot: middle - 1, highest: begin, canon: 0, name: "begin < slot < middle"},
|
||||
{slot: begin, highest: begin, canon: 0, name: "slot == begin"},
|
||||
{slot: begin - 1, highest: 0, canon: 0, name: "genesis < slot < begin"},
|
||||
{slot: 0, highest: 0, canon: 0, name: "slot == genesis"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
bs, err := hist.HighestSlotBlocksBelow(ctx, c.slot+1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(bs), 1)
|
||||
r, err := bs[0].Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hist.slotMap[c.highest], r)
|
||||
cr, _, err := cc.canonicalBlockForSlot(ctx, c.slot)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hist.slotMap[c.canon], cr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalBlockForSlotNonHappy(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var begin, middle, end types.Slot = 100, 150, 155
|
||||
specs := []mockHistorySpec{
|
||||
{slot: begin},
|
||||
{slot: middle, savedState: true},
|
||||
{slot: end, canonicalBlock: true},
|
||||
}
|
||||
hist := newMockHistory(t, specs, end+1)
|
||||
|
||||
slotOrderObserved := make([]types.Slot, 0)
|
||||
derp := errors.New("HighestSlotBlocksBelow don't work")
|
||||
// since only the end block and genesis are canonical, once the slot drops below
|
||||
// end, we should always get genesis
|
||||
cases := []struct {
|
||||
name string
|
||||
slot types.Slot
|
||||
canon CanonicalChecker
|
||||
overrideHighest func(context.Context, types.Slot) ([]block.SignedBeaconBlock, error)
|
||||
slotOrderExpected []types.Slot
|
||||
err error
|
||||
root [32]byte
|
||||
}{
|
||||
{
|
||||
name: "HigestSlotBlocksBelow not called for genesis",
|
||||
overrideHighest: func(_ context.Context, _ types.Slot) ([]block.SignedBeaconBlock, error) {
|
||||
return nil, derp
|
||||
},
|
||||
root: hist.slotMap[0],
|
||||
},
|
||||
{
|
||||
name: "wrapped error from HigestSlotBlocksBelow returned",
|
||||
err: derp,
|
||||
overrideHighest: func(_ context.Context, _ types.Slot) ([]block.SignedBeaconBlock, error) {
|
||||
return nil, derp
|
||||
},
|
||||
slot: end,
|
||||
},
|
||||
{
|
||||
name: "HigestSlotBlocksBelow empty list",
|
||||
err: ErrNoBlocksBelowSlot,
|
||||
overrideHighest: func(_ context.Context, _ types.Slot) ([]block.SignedBeaconBlock, error) {
|
||||
return []block.SignedBeaconBlock{}, nil
|
||||
},
|
||||
slot: end,
|
||||
},
|
||||
{
|
||||
name: "HigestSlotBlocksBelow no canonical",
|
||||
err: ErrNoCanonicalBlockForSlot,
|
||||
canon: &mockCanonicalChecker{is: false},
|
||||
slot: end,
|
||||
},
|
||||
{
|
||||
name: "slot ordering correct - only genesis canonical",
|
||||
canon: &mockCanonicalChecker{isCanon: func(root [32]byte) (bool, error) {
|
||||
if root == hist.slotMap[0] {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}},
|
||||
overrideHighest: func(_ context.Context, s types.Slot) ([]block.SignedBeaconBlock, error) {
|
||||
slotOrderObserved = append(slotOrderObserved, s)
|
||||
// this allows the mock HighestSlotBlocksBelow to continue to execute now that we've recorded
|
||||
// the slot in our channel
|
||||
return nil, errFallThroughOverride
|
||||
},
|
||||
slotOrderExpected: []types.Slot{156, 155, 150, 100},
|
||||
slot: end,
|
||||
root: hist.slotMap[0],
|
||||
},
|
||||
{
|
||||
name: "slot ordering correct - slot 100 canonical",
|
||||
canon: &mockCanonicalChecker{isCanon: func(root [32]byte) (bool, error) {
|
||||
if root == hist.slotMap[100] {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}},
|
||||
overrideHighest: func(_ context.Context, s types.Slot) ([]block.SignedBeaconBlock, error) {
|
||||
slotOrderObserved = append(slotOrderObserved, s)
|
||||
// this allows the mock HighestSlotBlocksBelow to continue to execute now that we've recorded
|
||||
// the slot in our channel
|
||||
return nil, errFallThroughOverride
|
||||
},
|
||||
slotOrderExpected: []types.Slot{156, 155, 150},
|
||||
slot: end,
|
||||
root: hist.slotMap[100],
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var canon CanonicalChecker = hist
|
||||
if c.canon != nil {
|
||||
canon = c.canon
|
||||
}
|
||||
cc := canonicalChainer{h: hist, c: canon, cs: hist}
|
||||
hist.overrideHighestSlotBlocksBelow = c.overrideHighest
|
||||
r, _, err := cc.canonicalBlockForSlot(ctx, c.slot)
|
||||
if c.err == nil {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorIs(t, err, c.err)
|
||||
}
|
||||
if len(c.slotOrderExpected) > 0 {
|
||||
require.Equal(t, len(c.slotOrderExpected), len(slotOrderObserved), "HighestSlotBlocksBelow not called the expected number of times")
|
||||
for i := range c.slotOrderExpected {
|
||||
require.Equal(t, c.slotOrderExpected[i], slotOrderObserved[i])
|
||||
}
|
||||
}
|
||||
if c.root != [32]byte{} {
|
||||
require.Equal(t, c.root, r)
|
||||
}
|
||||
slotOrderObserved = make([]types.Slot, 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockCurrentSlotter struct {
|
||||
Slot types.Slot
|
||||
}
|
||||
|
||||
func (c *mockCurrentSlotter) CurrentSlot() types.Slot {
|
||||
return c.Slot
|
||||
}
|
||||
|
||||
var _ CurrentSlotter = &mockCurrentSlotter{}
|
||||
|
||||
func TestCanonicalChainerFuture(t *testing.T) {
|
||||
r := &canonicalChainer{
|
||||
cs: &mockCurrentSlotter{Slot: 0},
|
||||
}
|
||||
_, _, err := r.chainForSlot(context.Background(), 1)
|
||||
require.ErrorIs(t, err, ErrFutureSlotRequested)
|
||||
}
|
||||
|
||||
func TestAncestorChainCache(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var begin, middle, end types.Slot = 100, 150, 155
|
||||
specs := []mockHistorySpec{
|
||||
{slot: begin, canonicalBlock: true},
|
||||
{slot: middle, canonicalBlock: true},
|
||||
{slot: end, canonicalBlock: true},
|
||||
}
|
||||
hist := newMockHistory(t, specs, end+1)
|
||||
cc := &canonicalChainer{h: hist, c: hist, cs: hist}
|
||||
|
||||
// should only contain the genesis block
|
||||
require.Equal(t, 1, len(hist.states))
|
||||
|
||||
endBlock := hist.blocks[hist.slotMap[end]]
|
||||
st, bs, err := cc.ancestorChain(ctx, endBlock)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(bs))
|
||||
expectedHTR, err := hist.states[hist.slotMap[0]].HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
actualHTR, err := st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedHTR, actualHTR)
|
||||
|
||||
// now populate the cache, we should get the cached state instead of genesis
|
||||
cc.cache = &mockCachedGetter{
|
||||
cache: map[[32]byte]state.BeaconState{
|
||||
hist.slotMap[end]: hist.hiddenStates[hist.slotMap[end]],
|
||||
},
|
||||
}
|
||||
st, bs, err = cc.ancestorChain(ctx, endBlock)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(bs))
|
||||
expectedHTR, err = hist.hiddenStates[hist.slotMap[end]].HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
actualHTR, err = st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedHTR, actualHTR)
|
||||
|
||||
// populate cache with a different state for good measure
|
||||
cc.cache = &mockCachedGetter{
|
||||
cache: map[[32]byte]state.BeaconState{
|
||||
hist.slotMap[begin]: hist.hiddenStates[hist.slotMap[begin]],
|
||||
},
|
||||
}
|
||||
st, bs, err = cc.ancestorChain(ctx, endBlock)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(bs))
|
||||
expectedHTR, err = hist.hiddenStates[hist.slotMap[begin]].HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
actualHTR, err = st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedHTR, actualHTR)
|
||||
|
||||
// rebuild history w/ last state saved, make sure we get that instead of cache
|
||||
specs[2].savedState = true
|
||||
hist = newMockHistory(t, specs, end+1)
|
||||
cc = &canonicalChainer{h: hist, c: hist, cs: hist}
|
||||
cc.cache = &mockCachedGetter{
|
||||
cache: map[[32]byte]state.BeaconState{
|
||||
hist.slotMap[begin]: hist.hiddenStates[hist.slotMap[begin]],
|
||||
},
|
||||
}
|
||||
st, bs, err = cc.ancestorChain(ctx, endBlock)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(bs))
|
||||
expectedHTR, err = hist.states[hist.slotMap[end]].HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
actualHTR, err = st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedHTR, actualHTR)
|
||||
}
|
||||
|
||||
func TestAncestorChainOK(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var begin, middle, end types.Slot = 100, 150, 155
|
||||
specs := []mockHistorySpec{
|
||||
{slot: begin},
|
||||
{slot: middle, savedState: true},
|
||||
{slot: end, canonicalBlock: true},
|
||||
}
|
||||
hist := newMockHistory(t, specs, end+1)
|
||||
cc := &canonicalChainer{h: hist, c: hist, cs: hist}
|
||||
|
||||
endBlock := hist.blocks[hist.slotMap[end]]
|
||||
st, bs, err := cc.ancestorChain(ctx, endBlock)
|
||||
require.NoError(t, err)
|
||||
|
||||
// middle is the most recent slot where savedState == true
|
||||
require.Equal(t, 1, len(bs))
|
||||
require.DeepEqual(t, endBlock, bs[0])
|
||||
expectedHTR, err := hist.states[hist.slotMap[middle]].HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
actualHTR, err := st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedHTR, actualHTR)
|
||||
|
||||
middleBlock := hist.blocks[hist.slotMap[middle]]
|
||||
st, bs, err = cc.ancestorChain(ctx, middleBlock)
|
||||
require.NoError(t, err)
|
||||
actualHTR, err = st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(bs))
|
||||
require.Equal(t, expectedHTR, actualHTR)
|
||||
}
|
||||
|
||||
func TestChainForSlot(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var zero, one, two, three types.Slot = 50, 51, 150, 151
|
||||
specs := []mockHistorySpec{
|
||||
{slot: zero, canonicalBlock: true, savedState: true},
|
||||
{slot: one, canonicalBlock: true},
|
||||
{slot: two},
|
||||
{slot: three, canonicalBlock: true},
|
||||
}
|
||||
hist := newMockHistory(t, specs, three+10)
|
||||
cc := &canonicalChainer{h: hist, c: hist, cs: hist}
|
||||
firstNonGenesisRoot := hist.slotMap[zero]
|
||||
nonGenesisStateRoot, err := hist.states[firstNonGenesisRoot].HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
slot types.Slot
|
||||
stateRoot [32]byte
|
||||
blockRoots [][32]byte
|
||||
}{
|
||||
{
|
||||
name: "above latest slot (but before current slot)",
|
||||
slot: three + 1,
|
||||
stateRoot: nonGenesisStateRoot,
|
||||
blockRoots: [][32]byte{hist.slotMap[one], hist.slotMap[two], hist.slotMap[three]},
|
||||
},
|
||||
{
|
||||
name: "last canonical slot - two treated as canonical because it is parent of three",
|
||||
slot: three,
|
||||
stateRoot: nonGenesisStateRoot,
|
||||
blockRoots: [][32]byte{hist.slotMap[one], hist.slotMap[two], hist.slotMap[three]},
|
||||
},
|
||||
{
|
||||
name: "non-canonical slot skipped",
|
||||
slot: two,
|
||||
stateRoot: nonGenesisStateRoot,
|
||||
blockRoots: [][32]byte{hist.slotMap[one]},
|
||||
},
|
||||
{
|
||||
name: "first canonical slot",
|
||||
slot: one,
|
||||
stateRoot: nonGenesisStateRoot,
|
||||
blockRoots: [][32]byte{hist.slotMap[one]},
|
||||
},
|
||||
{
|
||||
name: "slot at saved state",
|
||||
slot: zero,
|
||||
stateRoot: nonGenesisStateRoot,
|
||||
blockRoots: [][32]byte{},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
st, blocks, err := cc.chainForSlot(ctx, c.slot)
|
||||
require.NoError(t, err)
|
||||
actualStRoot, err := st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.stateRoot, actualStRoot)
|
||||
require.Equal(t, len(c.blockRoots), len(blocks))
|
||||
for i, b := range blocks {
|
||||
root, err := b.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.blockRoots[i], root)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAncestorChainOrdering(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var zero, one, two, three, four, five types.Slot = 50, 51, 150, 151, 152, 200
|
||||
specs := []mockHistorySpec{
|
||||
{slot: zero},
|
||||
{slot: one, savedState: true},
|
||||
{slot: two},
|
||||
{slot: three},
|
||||
{slot: four},
|
||||
{slot: five},
|
||||
}
|
||||
|
||||
hist := newMockHistory(t, specs, five+1)
|
||||
endRoot := hist.slotMap[specs[len(specs)-1].slot]
|
||||
endBlock := hist.blocks[endRoot]
|
||||
|
||||
cc := &canonicalChainer{h: hist, c: hist, cs: hist}
|
||||
st, bs, err := cc.ancestorChain(ctx, endBlock)
|
||||
require.NoError(t, err)
|
||||
expectedRoot, err := hist.states[hist.slotMap[one]].HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
actualRoot, err := st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedRoot, actualRoot)
|
||||
// we asked for the chain leading up to five
|
||||
// one has the savedState. one is applied to the savedState, so it should be omitted
|
||||
// that means we should get two, three, four, five (length of 4)
|
||||
require.Equal(t, 4, len(bs))
|
||||
for i, slot := range []types.Slot{two, three, four, five} {
|
||||
require.Equal(t, slot, bs[i].Block().Slot(), fmt.Sprintf("wrong value at index %d", i))
|
||||
}
|
||||
|
||||
// do the same query, but with the final state saved
|
||||
// we should just get the final state w/o block to apply
|
||||
specs[5].savedState = true
|
||||
hist = newMockHistory(t, specs, five+1)
|
||||
endRoot = hist.slotMap[specs[len(specs)-1].slot]
|
||||
endBlock = hist.blocks[endRoot]
|
||||
|
||||
cc = &canonicalChainer{h: hist, c: hist, cs: hist}
|
||||
st, bs, err = cc.ancestorChain(ctx, endBlock)
|
||||
require.NoError(t, err)
|
||||
expectedRoot, err = hist.states[endRoot].HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
actualRoot, err = st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedRoot, actualRoot)
|
||||
require.Equal(t, 0, len(bs))
|
||||
|
||||
// slice off the last element for an odd size list (to cover odd/even in the reverseChain func)
|
||||
specs = specs[:len(specs)-1]
|
||||
require.Equal(t, 5, len(specs))
|
||||
hist = newMockHistory(t, specs, five+1)
|
||||
|
||||
cc = &canonicalChainer{h: hist, c: hist, cs: hist}
|
||||
endRoot = hist.slotMap[specs[len(specs)-1].slot]
|
||||
endBlock = hist.blocks[endRoot]
|
||||
st, bs, err = cc.ancestorChain(ctx, endBlock)
|
||||
require.NoError(t, err)
|
||||
expectedRoot, err = hist.states[hist.slotMap[one]].HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
actualRoot, err = st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedRoot, actualRoot)
|
||||
require.Equal(t, 3, len(bs))
|
||||
for i, slot := range []types.Slot{two, three, four} {
|
||||
require.Equal(t, slot, bs[i].Block().Slot(), fmt.Sprintf("wrong value at index %d", i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBestForSlot(t *testing.T) {
|
||||
nilBlock, err := wrapper.WrappedSignedBeaconBlock(ðpb.SignedBeaconBlock{})
|
||||
require.NoError(t, err)
|
||||
nilBody, err := wrapper.WrappedSignedBeaconBlock(ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{}})
|
||||
require.NoError(t, err)
|
||||
derp := errors.New("fake hash tree root method no hash good")
|
||||
badHTR := &mock.SignedBeaconBlock{BeaconBlock: &mock.BeaconBlock{HtrErr: derp, BeaconBlockBody: &mock.BeaconBlockBody{}}}
|
||||
var goodHTR [32]byte
|
||||
copy(goodHTR[:], []byte{23})
|
||||
var betterHTR [32]byte
|
||||
copy(betterHTR[:], []byte{42})
|
||||
good := &mock.SignedBeaconBlock{BeaconBlock: &mock.BeaconBlock{BeaconBlockBody: &mock.BeaconBlockBody{}, Htr: goodHTR}}
|
||||
better := &mock.SignedBeaconBlock{BeaconBlock: &mock.BeaconBlock{BeaconBlockBody: &mock.BeaconBlockBody{}, Htr: betterHTR}}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
err error
|
||||
blocks []block.SignedBeaconBlock
|
||||
best block.SignedBeaconBlock
|
||||
root [32]byte
|
||||
cc CanonicalChecker
|
||||
}{
|
||||
{
|
||||
name: "empty list",
|
||||
err: ErrNoCanonicalBlockForSlot,
|
||||
blocks: []block.SignedBeaconBlock{},
|
||||
},
|
||||
{
|
||||
name: "empty SignedBeaconBlock",
|
||||
err: ErrNoCanonicalBlockForSlot,
|
||||
blocks: []block.SignedBeaconBlock{nil},
|
||||
},
|
||||
{
|
||||
name: "empty BeaconBlock",
|
||||
err: ErrNoCanonicalBlockForSlot,
|
||||
blocks: []block.SignedBeaconBlock{nilBlock},
|
||||
},
|
||||
{
|
||||
name: "empty BeaconBlockBody",
|
||||
err: ErrNoCanonicalBlockForSlot,
|
||||
blocks: []block.SignedBeaconBlock{nilBody},
|
||||
},
|
||||
{
|
||||
name: "bad HTR",
|
||||
err: ErrInvalidDBBlock,
|
||||
blocks: []block.SignedBeaconBlock{badHTR},
|
||||
},
|
||||
{
|
||||
name: "IsCanonical fail",
|
||||
blocks: []block.SignedBeaconBlock{good, better},
|
||||
cc: &mockCanonicalChecker{is: true, err: derp},
|
||||
err: derp,
|
||||
},
|
||||
{
|
||||
name: "all non-canonical",
|
||||
err: ErrNoCanonicalBlockForSlot,
|
||||
blocks: []block.SignedBeaconBlock{good, better},
|
||||
cc: &mockCanonicalChecker{is: false},
|
||||
},
|
||||
{
|
||||
name: "one canonical",
|
||||
blocks: []block.SignedBeaconBlock{good},
|
||||
cc: &mockCanonicalChecker{is: true},
|
||||
root: goodHTR,
|
||||
best: good,
|
||||
},
|
||||
{
|
||||
name: "all canonical",
|
||||
blocks: []block.SignedBeaconBlock{better, good},
|
||||
cc: &mockCanonicalChecker{is: true},
|
||||
root: betterHTR,
|
||||
best: better,
|
||||
},
|
||||
{
|
||||
name: "first wins",
|
||||
blocks: []block.SignedBeaconBlock{good, better},
|
||||
cc: &mockCanonicalChecker{is: true},
|
||||
root: goodHTR,
|
||||
best: good,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
chk := CanonicalChecker(&mockCanonicalChecker{is: true})
|
||||
if c.cc != nil {
|
||||
chk = c.cc
|
||||
}
|
||||
cc := &canonicalChainer{c: chk}
|
||||
r, b, err := cc.bestForSlot(context.Background(), c.blocks)
|
||||
if c.err == nil {
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, c.best, b)
|
||||
require.Equal(t, c.root, r)
|
||||
} else {
|
||||
require.ErrorIs(t, err, c.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockCanonicalChecker struct {
|
||||
isCanon func([32]byte) (bool, error)
|
||||
is bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *mockCanonicalChecker) IsCanonical(_ context.Context, root [32]byte) (bool, error) {
|
||||
if m.isCanon != nil {
|
||||
return m.isCanon(root)
|
||||
}
|
||||
return m.is, m.err
|
||||
}
|
||||
|
||||
func TestReverseChain(t *testing.T) {
|
||||
// test 0,1,2,3 elements to handle: zero case; single element; even number; odd number
|
||||
for i := 0; i < 4; i++ {
|
||||
t.Run(fmt.Sprintf("reverseChain with %d elements", i), func(t *testing.T) {
|
||||
actual := mockBlocks(i, incrFwd)
|
||||
expected := mockBlocks(i, incrBwd)
|
||||
reverseChain(actual)
|
||||
if len(actual) != len(expected) {
|
||||
t.Errorf("different list lengths")
|
||||
}
|
||||
for i := 0; i < len(actual); i++ {
|
||||
sblockA, ok := actual[i].(*mock.SignedBeaconBlock)
|
||||
require.Equal(t, true, ok)
|
||||
blockA, ok := sblockA.BeaconBlock.(*mock.BeaconBlock)
|
||||
require.Equal(t, true, ok)
|
||||
sblockE, ok := expected[i].(*mock.SignedBeaconBlock)
|
||||
require.Equal(t, true, ok)
|
||||
blockE, ok := sblockE.BeaconBlock.(*mock.BeaconBlock)
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, blockA.Htr, blockE.Htr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func incrBwd(n int, c chan uint32) {
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
c <- uint32(i)
|
||||
}
|
||||
close(c)
|
||||
}
|
||||
|
||||
func incrFwd(n int, c chan uint32) {
|
||||
for i := 0; i < n; i++ {
|
||||
c <- uint32(i)
|
||||
}
|
||||
close(c)
|
||||
}
|
||||
|
||||
func mockBlocks(n int, iter func(int, chan uint32)) []block.SignedBeaconBlock {
|
||||
bchan := make(chan uint32)
|
||||
go iter(n, bchan)
|
||||
mb := make([]block.SignedBeaconBlock, 0)
|
||||
for i := range bchan {
|
||||
h := [32]byte{}
|
||||
binary.LittleEndian.PutUint32(h[:], i)
|
||||
b := &mock.SignedBeaconBlock{BeaconBlock: &mock.BeaconBlock{BeaconBlockBody: &mock.BeaconBlockBody{}, Htr: h}}
|
||||
mb = append(mb, b)
|
||||
}
|
||||
return mb
|
||||
}
|
||||
16
proto/prysm/v1alpha1/block/mock/BUILD.bazel
Normal file
16
proto/prysm/v1alpha1/block/mock/BUILD.bazel
Normal file
@@ -0,0 +1,16 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["block.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block/mock",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"@com_github_ferranbt_fastssz//:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
],
|
||||
)
|
||||
191
proto/prysm/v1alpha1/block/mock/block.go
Normal file
191
proto/prysm/v1alpha1/block/mock/block.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
ssz "github.com/ferranbt/fastssz"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/proto/engine/v1"
|
||||
eth "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type SignedBeaconBlock struct {
|
||||
BeaconBlock block.BeaconBlock
|
||||
}
|
||||
|
||||
func (m SignedBeaconBlock) Block() block.BeaconBlock {
|
||||
return m.BeaconBlock
|
||||
}
|
||||
|
||||
func (SignedBeaconBlock) Signature() []byte {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (m SignedBeaconBlock) IsNil() bool {
|
||||
return m.BeaconBlock == nil || m.Block().IsNil()
|
||||
}
|
||||
|
||||
func (SignedBeaconBlock) Copy() block.SignedBeaconBlock {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (SignedBeaconBlock) Proto() proto.Message {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (SignedBeaconBlock) PbPhase0Block() (*eth.SignedBeaconBlock, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (SignedBeaconBlock) PbAltairBlock() (*eth.SignedBeaconBlockAltair, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (SignedBeaconBlock) PbBellatrixBlock() (*eth.SignedBeaconBlockBellatrix, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (SignedBeaconBlock) MarshalSSZTo(_ []byte) ([]byte, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (SignedBeaconBlock) MarshalSSZ() ([]byte, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (SignedBeaconBlock) SizeSSZ() int {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (SignedBeaconBlock) UnmarshalSSZ(_ []byte) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (SignedBeaconBlock) Version() int {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (SignedBeaconBlock) Header() (*eth.SignedBeaconBlockHeader, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
type BeaconBlock struct {
|
||||
Htr [32]byte
|
||||
HtrErr error
|
||||
BeaconBlockBody block.BeaconBlockBody
|
||||
BlockSlot types.Slot
|
||||
}
|
||||
|
||||
func (m BeaconBlock) HashTreeRoot() ([32]byte, error) {
|
||||
return m.Htr, m.HtrErr
|
||||
}
|
||||
|
||||
func (m BeaconBlock) Slot() types.Slot {
|
||||
return m.BlockSlot
|
||||
}
|
||||
|
||||
func (BeaconBlock) ProposerIndex() types.ValidatorIndex {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlock) ParentRoot() []byte {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlock) StateRoot() []byte {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (m BeaconBlock) Body() block.BeaconBlockBody {
|
||||
return m.BeaconBlockBody
|
||||
}
|
||||
|
||||
func (BeaconBlock) IsNil() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (BeaconBlock) Proto() proto.Message {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlock) MarshalSSZTo(_ []byte) ([]byte, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlock) MarshalSSZ() ([]byte, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlock) SizeSSZ() int {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlock) UnmarshalSSZ(_ []byte) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlock) HashTreeRootWith(_ *ssz.Hasher) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlock) Version() int {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
type BeaconBlockBody struct{}
|
||||
|
||||
func (BeaconBlockBody) RandaoReveal() []byte {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlockBody) Eth1Data() *eth.Eth1Data {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlockBody) Graffiti() []byte {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlockBody) ProposerSlashings() []*eth.ProposerSlashing {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlockBody) AttesterSlashings() []*eth.AttesterSlashing {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlockBody) Attestations() []*eth.Attestation {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlockBody) Deposits() []*eth.Deposit {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlockBody) VoluntaryExits() []*eth.SignedVoluntaryExit {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlockBody) SyncAggregate() (*eth.SyncAggregate, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlockBody) IsNil() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (BeaconBlockBody) HashTreeRoot() ([32]byte, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlockBody) Proto() proto.Message {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (BeaconBlockBody) ExecutionPayload() (*enginev1.ExecutionPayload, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
var _ block.SignedBeaconBlock = &SignedBeaconBlock{}
|
||||
var _ block.BeaconBlock = &BeaconBlock{}
|
||||
var _ block.BeaconBlockBody = &BeaconBlockBody{}
|
||||
@@ -8,6 +8,7 @@ go_library(
|
||||
"beacon_block_bellatrix.go",
|
||||
"beacon_block_phase0.go",
|
||||
"metadata.go",
|
||||
"mutator.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper",
|
||||
visibility = ["//visibility:public"],
|
||||
|
||||
77
proto/prysm/v1alpha1/wrapper/mutator.go
Normal file
77
proto/prysm/v1alpha1/wrapper/mutator.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package wrapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
eth "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/prysmaticlabs/prysm/runtime/version"
|
||||
)
|
||||
|
||||
type BlockMutator struct {
|
||||
Phase0 func(beaconBlock *eth.SignedBeaconBlock)
|
||||
Altair func(beaconBlock *eth.SignedBeaconBlockAltair)
|
||||
Bellatrix func(beaconBlock *eth.SignedBeaconBlockBellatrix)
|
||||
}
|
||||
|
||||
func (m BlockMutator) Apply(b block.SignedBeaconBlock) error {
|
||||
switch b.Version() {
|
||||
case version.Phase0:
|
||||
bb, err := b.PbPhase0Block()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Phase0(bb)
|
||||
return nil
|
||||
case version.Altair:
|
||||
bb, err := b.PbAltairBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Altair(bb)
|
||||
return nil
|
||||
case version.Bellatrix:
|
||||
bb, err := b.PbBellatrixBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Bellatrix(bb)
|
||||
return nil
|
||||
}
|
||||
msg := fmt.Sprintf("version %d = %s", b.Version(), version.String(b.Version()))
|
||||
return errors.Wrap(ErrUnsupportedSignedBeaconBlock, msg)
|
||||
}
|
||||
|
||||
func SetBlockStateRoot(b block.SignedBeaconBlock, sr [32]byte) error {
|
||||
return BlockMutator{
|
||||
Phase0: func(bb *eth.SignedBeaconBlock) { bb.Block.StateRoot = sr[:] },
|
||||
Altair: func(bb *eth.SignedBeaconBlockAltair) { bb.Block.StateRoot = sr[:] },
|
||||
Bellatrix: func(bb *eth.SignedBeaconBlockBellatrix) { bb.Block.StateRoot = sr[:] },
|
||||
}.Apply(b)
|
||||
}
|
||||
|
||||
func SetBlockParentRoot(b block.SignedBeaconBlock, pr [32]byte) error {
|
||||
return BlockMutator{
|
||||
Phase0: func(bb *eth.SignedBeaconBlock) { bb.Block.ParentRoot = pr[:] },
|
||||
Altair: func(bb *eth.SignedBeaconBlockAltair) { bb.Block.ParentRoot = pr[:] },
|
||||
Bellatrix: func(bb *eth.SignedBeaconBlockBellatrix) { bb.Block.ParentRoot = pr[:] },
|
||||
}.Apply(b)
|
||||
}
|
||||
|
||||
func SetBlockSlot(b block.SignedBeaconBlock, s types.Slot) error {
|
||||
return BlockMutator{
|
||||
Phase0: func(bb *eth.SignedBeaconBlock) { bb.Block.Slot = s },
|
||||
Altair: func(bb *eth.SignedBeaconBlockAltair) { bb.Block.Slot = s },
|
||||
Bellatrix: func(bb *eth.SignedBeaconBlockBellatrix) { bb.Block.Slot = s },
|
||||
}.Apply(b)
|
||||
}
|
||||
|
||||
func SetProposerIndex(b block.SignedBeaconBlock, idx types.ValidatorIndex) error {
|
||||
return BlockMutator{
|
||||
Phase0: func(bb *eth.SignedBeaconBlock) { bb.Block.ProposerIndex = idx },
|
||||
Altair: func(bb *eth.SignedBeaconBlockAltair) { bb.Block.ProposerIndex = idx },
|
||||
Bellatrix: func(bb *eth.SignedBeaconBlockBellatrix) { bb.Block.ProposerIndex = idx },
|
||||
}.Apply(b)
|
||||
}
|
||||
@@ -44,9 +44,11 @@ go_library(
|
||||
"//proto/eth/v1:go_default_library",
|
||||
"//proto/eth/v2:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"//proto/prysm/v1alpha1/wrapper:go_default_library",
|
||||
"//runtime/interop:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
|
||||
@@ -2,6 +2,10 @@ package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
@@ -36,8 +40,26 @@ func FillRootsNaturalOpt(state *ethpb.BeaconState) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func WithStateSlot(slot types.Slot) NewBeaconStateOption {
|
||||
return func(st *ethpb.BeaconState) error {
|
||||
st.Slot = slot
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithLatestHeaderFromBlock(t *testing.T, b block.SignedBeaconBlock) NewBeaconStateOption {
|
||||
return func(st *ethpb.BeaconState) error {
|
||||
sh, err := b.Header()
|
||||
require.NoError(t, err)
|
||||
st.LatestBlockHeader = sh.Header
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type NewBeaconStateOption func(state *ethpb.BeaconState) error
|
||||
|
||||
// NewBeaconState creates a beacon state with minimum marshalable fields.
|
||||
func NewBeaconState(options ...func(state *ethpb.BeaconState) error) (state.BeaconState, error) {
|
||||
func NewBeaconState(options ...NewBeaconStateOption) (state.BeaconState, error) {
|
||||
seed := ðpb.BeaconState{
|
||||
BlockRoots: filledByteSlice2D(uint64(params.MainnetConfig().SlotsPerHistoricalRoot), 32),
|
||||
StateRoots: filledByteSlice2D(uint64(params.MainnetConfig().SlotsPerHistoricalRoot), 32),
|
||||
|
||||
Reference in New Issue
Block a user