diff --git a/beacon-chain/rpc/beacon/assignments.go b/beacon-chain/rpc/beacon/assignments.go index 144d93e351bc..e98e18295bbc 100644 --- a/beacon-chain/rpc/beacon/assignments.go +++ b/beacon-chain/rpc/beacon/assignments.go @@ -10,6 +10,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/flags" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" "github.com/prysmaticlabs/prysm/shared/bytesutil" + "github.com/prysmaticlabs/prysm/shared/featureconfig" "github.com/prysmaticlabs/prysm/shared/hashutil" "github.com/prysmaticlabs/prysm/shared/pagination" "github.com/prysmaticlabs/prysm/shared/params" @@ -31,6 +32,112 @@ func (bs *Server) ListValidatorAssignments( ) } + if featureconfig.Get().DisableNewStateMgmt { + return bs.listValidatorAssignmentsUsingOldArchival(ctx, req) + } + + var res []*ethpb.ValidatorAssignments_CommitteeAssignment + filtered := map[uint64]bool{} // track filtered validators to prevent duplication in the response. + filteredIndices := make([]uint64, 0) + var requestedEpoch uint64 + switch q := req.QueryFilter.(type) { + case *ethpb.ListValidatorAssignmentsRequest_Genesis: + if q.Genesis { + requestedEpoch = 0 + } + case *ethpb.ListValidatorAssignmentsRequest_Epoch: + requestedEpoch = q.Epoch + } + + currentEpoch := helpers.SlotToEpoch(bs.GenesisTimeFetcher.CurrentSlot()) + if requestedEpoch > currentEpoch { + return nil, status.Errorf( + codes.InvalidArgument, + "Cannot retrieve information about an epoch in the future, current epoch %d, requesting %d", + currentEpoch, + requestedEpoch, + ) + } + + requestedState, err := bs.StateGen.StateBySlot(ctx, helpers.StartSlot(requestedEpoch)) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not retrieve archived state for epoch %d: %v", requestedEpoch, err) + } + + // Filter out assignments by public keys. + for _, pubKey := range req.PublicKeys { + index, ok := requestedState.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey)) + if !ok { + return nil, status.Errorf(codes.NotFound, "Could not find validator index for public key %#x", pubKey) + } + filtered[index] = true + filteredIndices = append(filteredIndices, index) + } + + // Filter out assignments by validator indices. + for _, index := range req.Indices { + if !filtered[index] { + filteredIndices = append(filteredIndices, index) + } + } + + activeIndices, err := helpers.ActiveValidatorIndices(requestedState, requestedEpoch) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not retrieve active validator indices: %v", err) + } + if len(filteredIndices) == 0 { + if len(activeIndices) == 0 { + return ðpb.ValidatorAssignments{ + Assignments: make([]*ethpb.ValidatorAssignments_CommitteeAssignment, 0), + TotalSize: int32(0), + NextPageToken: strconv.Itoa(0), + }, nil + } + // If no filter was specified, return assignments from active validator indices with pagination. + filteredIndices = activeIndices + } + + start, end, nextPageToken, err := pagination.StartAndEndPage(req.PageToken, int(req.PageSize), len(filteredIndices)) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not paginate results: %v", err) + } + + // Initialize all committee related data. + committeeAssignments := map[uint64]*helpers.CommitteeAssignmentContainer{} + proposerIndexToSlots := make(map[uint64][]uint64) + committeeAssignments, proposerIndexToSlots, err = helpers.CommitteeAssignments(requestedState, requestedEpoch) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not compute committee assignments: %v", err) + } + + for _, index := range filteredIndices[start:end] { + if int(index) >= requestedState.NumValidators() { + return nil, status.Errorf(codes.OutOfRange, "Validator index %d >= validator count %d", + index, requestedState.NumValidators()) + } + comAssignment := committeeAssignments[index] + pubkey := requestedState.PubkeyAtIndex(index) + assign := ðpb.ValidatorAssignments_CommitteeAssignment{ + BeaconCommittees: comAssignment.Committee, + CommitteeIndex: comAssignment.CommitteeIndex, + AttesterSlot: comAssignment.AttesterSlot, + ProposerSlots: proposerIndexToSlots[index], + PublicKey: pubkey[:], + } + res = append(res, assign) + } + + return ðpb.ValidatorAssignments{ + Epoch: requestedEpoch, + Assignments: res, + NextPageToken: nextPageToken, + TotalSize: int32(len(filteredIndices)), + }, nil +} + +func (bs *Server) listValidatorAssignmentsUsingOldArchival( + ctx context.Context, req *ethpb.ListValidatorAssignmentsRequest, +) (*ethpb.ValidatorAssignments, error) { var res []*ethpb.ValidatorAssignments_CommitteeAssignment headState, err := bs.HeadFetcher.HeadState(ctx) if err != nil { diff --git a/beacon-chain/rpc/beacon/assignments_test.go b/beacon-chain/rpc/beacon/assignments_test.go index 11bb7e19f1c9..24c262cc8e67 100644 --- a/beacon-chain/rpc/beacon/assignments_test.go +++ b/beacon-chain/rpc/beacon/assignments_test.go @@ -13,11 +13,11 @@ import ( ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" "github.com/prysmaticlabs/go-ssz" mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing" + "github.com/prysmaticlabs/prysm/beacon-chain/cache" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" dbTest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing" "github.com/prysmaticlabs/prysm/beacon-chain/flags" - stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state" - pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stategen" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/testutil" ) @@ -27,17 +27,9 @@ func TestServer_ListAssignments_CannotRequestFutureEpoch(t *testing.T) { defer dbTest.TeardownDB(t, db) ctx := context.Background() - st, err := stateTrie.InitializeFromProto(&pbp2p.BeaconState{ - Slot: 0, - }) - if err != nil { - t.Fatal(err) - } bs := &Server{ - BeaconDB: db, - HeadFetcher: &mock.ChainService{ - State: st, - }, + BeaconDB: db, + GenesisTimeFetcher: &mock.ChainService{}, } wanted := "Cannot retrieve information about an epoch in the future" @@ -45,7 +37,7 @@ func TestServer_ListAssignments_CannotRequestFutureEpoch(t *testing.T) { ctx, ðpb.ListValidatorAssignmentsRequest{ QueryFilter: ðpb.ListValidatorAssignmentsRequest_Epoch{ - Epoch: 1, + Epoch: helpers.SlotToEpoch(bs.GenesisTimeFetcher.CurrentSlot()) + 1, }, }, ); err != nil && !strings.Contains(err.Error(), wanted) { @@ -58,19 +50,27 @@ func TestServer_ListAssignments_NoResults(t *testing.T) { defer dbTest.TeardownDB(t, db) ctx := context.Background() - st, err := stateTrie.InitializeFromProto(&pbp2p.BeaconState{ - Slot: 0, - RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), - Validators: []*ethpb.Validator{}, - }) + st := testutil.NewBeaconState() + + b := ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{}} + if err := db.SaveBlock(ctx, b); err != nil { + t.Fatal(err) + } + gRoot, err := ssz.HashTreeRoot(b.Block) if err != nil { t.Fatal(err) } + if err := db.SaveGenesisBlockRoot(ctx, gRoot); err != nil { + t.Fatal(err) + } + if err := db.SaveState(ctx, st, gRoot); err != nil { + t.Fatal(err) + } + bs := &Server{ - BeaconDB: db, - HeadFetcher: &mock.ChainService{ - State: st, - }, + BeaconDB: db, + GenesisTimeFetcher: &mock.ChainService{}, + StateGen: stategen.New(db, cache.NewStateSummaryCache()), } wanted := ðpb.ValidatorAssignments{ Assignments: make([]*ethpb.ValidatorAssignments_CommitteeAssignment, 0), @@ -103,11 +103,26 @@ func TestServer_ListAssignments_Pagination_InputOutOfRange(t *testing.T) { if err != nil { t.Fatal(err) } + + b := ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{}} + if err := db.SaveBlock(ctx, b); err != nil { + t.Fatal(err) + } + gRoot, err := ssz.HashTreeRoot(b.Block) + if err != nil { + t.Fatal(err) + } + if err := db.SaveGenesisBlockRoot(ctx, gRoot); err != nil { + t.Fatal(err) + } + if err := db.SaveState(ctx, headState, gRoot); err != nil { + t.Fatal(err) + } + bs := &Server{ - BeaconDB: db, - HeadFetcher: &mock.ChainService{ - State: headState, - }, + BeaconDB: db, + GenesisTimeFetcher: &mock.ChainService{}, + StateGen: stategen.New(db, cache.NewStateSummaryCache()), } wanted := fmt.Sprintf("page start %d >= list %d", 0, 0) @@ -179,7 +194,7 @@ func TestServer_ListAssignments_Pagination_DefaultPageSize_NoArchive(t *testing. if err := db.SaveState(ctx, s, blockRoot); err != nil { t.Fatal(err) } - if err := db.SaveHeadBlockRoot(ctx, blockRoot); err != nil { + if err := db.SaveGenesisBlockRoot(ctx, blockRoot); err != nil { t.Fatal(err) } @@ -193,6 +208,8 @@ func TestServer_ListAssignments_Pagination_DefaultPageSize_NoArchive(t *testing. Epoch: 0, }, }, + GenesisTimeFetcher: &mock.ChainService{}, + StateGen: stategen.New(db, cache.NewStateSummaryCache()), } res, err := bs.ListValidatorAssignments(context.Background(), ðpb.ListValidatorAssignmentsRequest{ @@ -277,7 +294,7 @@ func TestServer_ListAssignments_Pagination_DefaultPageSize_FromArchive(t *testin if err := db.SaveState(ctx, s, blockRoot); err != nil { t.Fatal(err) } - if err := db.SaveHeadBlockRoot(ctx, blockRoot); err != nil { + if err := db.SaveGenesisBlockRoot(ctx, blockRoot); err != nil { t.Fatal(err) } @@ -285,35 +302,13 @@ func TestServer_ListAssignments_Pagination_DefaultPageSize_FromArchive(t *testin // we request assignments for epoch 0, it looks within the archived data. bs := &Server{ BeaconDB: db, - HeadFetcher: &mock.ChainService{ - State: s, - }, FinalizationFetcher: &mock.ChainService{ FinalizedCheckPoint: ðpb.Checkpoint{ Epoch: 10, }, }, - } - - // We then store archived data into the DB. - currentEpoch := helpers.CurrentEpoch(s) - proposerSeed, err := helpers.Seed(s, currentEpoch, params.BeaconConfig().DomainBeaconProposer) - if err != nil { - t.Fatal(err) - } - attesterSeed, err := helpers.Seed(s, currentEpoch, params.BeaconConfig().DomainBeaconAttester) - if err != nil { - t.Fatal(err) - } - if err := db.SaveArchivedCommitteeInfo(context.Background(), 0, &pbp2p.ArchivedCommitteeInfo{ - ProposerSeed: proposerSeed[:], - AttesterSeed: attesterSeed[:], - }); err != nil { - t.Fatal(err) - } - - if err := db.SaveArchivedBalances(context.Background(), 0, balances); err != nil { - t.Fatal(err) + GenesisTimeFetcher: &mock.ChainService{}, + StateGen: stategen.New(db, cache.NewStateSummaryCache()), } // Construct the wanted assignments. @@ -379,20 +374,19 @@ func TestServer_ListAssignments_FilterPubkeysIndices_NoPagination(t *testing.T) if err := db.SaveState(ctx, s, blockRoot); err != nil { t.Fatal(err) } - if err := db.SaveHeadBlockRoot(ctx, blockRoot); err != nil { + if err := db.SaveGenesisBlockRoot(ctx, blockRoot); err != nil { t.Fatal(err) } bs := &Server{ BeaconDB: db, - HeadFetcher: &mock.ChainService{ - State: s, - }, FinalizationFetcher: &mock.ChainService{ FinalizedCheckPoint: ðpb.Checkpoint{ Epoch: 0, }, }, + GenesisTimeFetcher: &mock.ChainService{}, + StateGen: stategen.New(db, cache.NewStateSummaryCache()), } pubKey1 := make([]byte, params.BeaconConfig().BLSPubkeyLength) @@ -462,20 +456,19 @@ func TestServer_ListAssignments_CanFilterPubkeysIndices_WithPagination(t *testin if err := db.SaveState(ctx, s, blockRoot); err != nil { t.Fatal(err) } - if err := db.SaveHeadBlockRoot(ctx, blockRoot); err != nil { + if err := db.SaveGenesisBlockRoot(ctx, blockRoot); err != nil { t.Fatal(err) } bs := &Server{ BeaconDB: db, - HeadFetcher: &mock.ChainService{ - State: s, - }, FinalizationFetcher: &mock.ChainService{ FinalizedCheckPoint: ðpb.Checkpoint{ Epoch: 0, }, }, + GenesisTimeFetcher: &mock.ChainService{}, + StateGen: stategen.New(db, cache.NewStateSummaryCache()), } req := ðpb.ListValidatorAssignmentsRequest{Indices: []uint64{1, 2, 3, 4, 5, 6}, PageSize: 2, PageToken: "1"} diff --git a/beacon-chain/rpc/service.go b/beacon-chain/rpc/service.go index e499f86d11cc..cd9bf531469b 100644 --- a/beacon-chain/rpc/service.go +++ b/beacon-chain/rpc/service.go @@ -259,9 +259,9 @@ func (s *Service) Start() { BlockNotifier: s.blockNotifier, AttestationNotifier: s.operationNotifier, Broadcaster: s.p2p, + StateGen: s.stateGen, ReceivedAttestationsBuffer: make(chan *ethpb.Attestation, 100), CollectedAttestationsBuffer: make(chan []*ethpb.Attestation, 100), - StateGen: s.stateGen, } ethpb.RegisterNodeServer(s.grpcServer, nodeServer) ethpb.RegisterBeaconChainServer(s.grpcServer, beaconChainServer)