diff --git a/api/server/structs/endpoints_beacon.go b/api/server/structs/endpoints_beacon.go index f7a3116572cf..0ee92bc0d5ab 100644 --- a/api/server/structs/endpoints_beacon.go +++ b/api/server/structs/endpoints_beacon.go @@ -196,3 +196,32 @@ type DepositSnapshot struct { ExecutionBlockHash string `json:"execution_block_hash"` ExecutionBlockHeight string `json:"execution_block_height"` } + +type GetIndividualVotesRequest struct { + Epoch string `json:"epoch"` + PublicKeys []string `json:"public_keys,omitempty"` + Indices []string `json:"indices,omitempty"` +} + +type GetIndividualVotesResponse struct { + IndividualVotes []*IndividualVote `json:"individual_votes"` +} + +type IndividualVote struct { + Epoch string `json:"epoch"` + PublicKey string `json:"public_keys,omitempty"` + ValidatorIndex string `json:"validator_index"` + IsSlashed bool `json:"is_slashed"` + IsWithdrawableInCurrentEpoch bool `json:"is_withdrawable_in_current_epoch"` + IsActiveInCurrentEpoch bool `json:"is_active_in_current_epoch"` + IsActiveInPreviousEpoch bool `json:"is_active_in_previous_epoch"` + IsCurrentEpochAttester bool `json:"is_current_epoch_attester"` + IsCurrentEpochTargetAttester bool `json:"is_current_epoch_target_attester"` + IsPreviousEpochAttester bool `json:"is_previous_epoch_attester"` + IsPreviousEpochTargetAttester bool `json:"is_previous_epoch_target_attester"` + IsPreviousEpochHeadAttester bool `json:"is_previous_epoch_head_attester"` + CurrentEpochEffectiveBalanceGwei string `json:"current_epoch_effective_balance_gwei"` + InclusionSlot string `json:"inclusion_slot"` + InclusionDistance string `json:"inclusion_distance"` + InactivityScore string `json:"inactivity_score"` +} diff --git a/beacon-chain/rpc/core/service.go b/beacon-chain/rpc/core/service.go index ad37804886c9..d407c881f4cd 100644 --- a/beacon-chain/rpc/core/service.go +++ b/beacon-chain/rpc/core/service.go @@ -21,5 +21,6 @@ type Service struct { AttestationCache *cache.AttestationCache StateGen stategen.StateManager P2P p2p.Broadcaster + ReplayerBuilder stategen.ReplayerBuilder OptimisticModeFetcher blockchain.OptimisticModeFetcher } diff --git a/beacon-chain/rpc/core/validator.go b/beacon-chain/rpc/core/validator.go index 44c534f83776..251e29ca68e1 100644 --- a/beacon-chain/rpc/core/validator.go +++ b/beacon-chain/rpc/core/validator.go @@ -211,6 +211,131 @@ func (s *Service) ComputeValidatorPerformance( }, nil } +// IndividualVotes retrieves individual voting status of validators. +func (s *Service) IndividualVotes( + ctx context.Context, + req *ethpb.IndividualVotesRequest, +) (*ethpb.IndividualVotesRespond, *RpcError) { + currentEpoch := slots.ToEpoch(s.GenesisTimeFetcher.CurrentSlot()) + if req.Epoch > currentEpoch { + return nil, &RpcError{ + Err: fmt.Errorf("cannot retrieve information about an epoch in the future, current epoch %d, requesting %d\n", currentEpoch, req.Epoch), + Reason: BadRequest, + } + } + + slot, err := slots.EpochEnd(req.Epoch) + if err != nil { + return nil, &RpcError{Err: err, Reason: Internal} + } + st, err := s.ReplayerBuilder.ReplayerForSlot(slot).ReplayBlocks(ctx) + if err != nil { + return nil, &RpcError{ + Err: errors.Wrapf(err, "failed to replay blocks for state at epoch %d", req.Epoch), + Reason: Internal, + } + } + // Track filtered validators to prevent duplication in the response. + filtered := map[primitives.ValidatorIndex]bool{} + filteredIndices := make([]primitives.ValidatorIndex, 0) + 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 := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey)) + if !ok { + votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{PublicKey: pubKey, ValidatorIndex: primitives.ValidatorIndex(^uint64(0))}) + continue + } + 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) + } + } + sort.Slice(filteredIndices, func(i, j int) bool { + return filteredIndices[i] < filteredIndices[j] + }) + + var v []*precompute.Validator + var bal *precompute.Balance + if st.Version() == version.Phase0 { + v, bal, err = precompute.New(ctx, st) + if err != nil { + return nil, &RpcError{ + Err: errors.Wrapf(err, "could not set up pre compute instance"), + Reason: Internal, + } + } + v, _, err = precompute.ProcessAttestations(ctx, st, v, bal) + if err != nil { + return nil, &RpcError{ + Err: errors.Wrapf(err, "could not pre compute attestations"), + Reason: Internal, + } + } + } else if st.Version() >= version.Altair { + v, bal, err = altair.InitializePrecomputeValidators(ctx, st) + if err != nil { + return nil, &RpcError{ + Err: errors.Wrapf(err, "could not set up altair pre compute instance"), + Reason: Internal, + } + } + v, _, err = altair.ProcessEpochParticipation(ctx, st, bal, v) + if err != nil { + return nil, &RpcError{ + Err: errors.Wrapf(err, "could not pre compute attestations"), + Reason: Internal, + } + } + } else { + return nil, &RpcError{ + Err: errors.Wrapf(err, "invalid state type retrieved with a version of %d", st.Version()), + Reason: Internal, + } + } + + for _, index := range filteredIndices { + if uint64(index) >= uint64(len(v)) { + votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{ValidatorIndex: index}) + continue + } + val, err := st.ValidatorAtIndexReadOnly(index) + if err != nil { + return nil, &RpcError{ + Err: errors.Wrapf(err, "could not retrieve validator"), + Reason: Internal, + } + } + pb := val.PublicKey() + votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{ + Epoch: req.Epoch, + PublicKey: pb[:], + ValidatorIndex: index, + IsSlashed: v[index].IsSlashed, + IsWithdrawableInCurrentEpoch: v[index].IsWithdrawableCurrentEpoch, + IsActiveInCurrentEpoch: v[index].IsActiveCurrentEpoch, + IsActiveInPreviousEpoch: v[index].IsActivePrevEpoch, + IsCurrentEpochAttester: v[index].IsCurrentEpochAttester, + IsCurrentEpochTargetAttester: v[index].IsCurrentEpochTargetAttester, + IsPreviousEpochAttester: v[index].IsPrevEpochAttester, + IsPreviousEpochTargetAttester: v[index].IsPrevEpochTargetAttester, + IsPreviousEpochHeadAttester: v[index].IsPrevEpochHeadAttester, + CurrentEpochEffectiveBalanceGwei: v[index].CurrentEpochEffectiveBalance, + InclusionSlot: v[index].InclusionSlot, + InclusionDistance: v[index].InclusionDistance, + InactivityScore: v[index].InactivityScore, + }) + } + + return ðpb.IndividualVotesRespond{ + IndividualVotes: votes, + }, nil +} + // SubmitSignedContributionAndProof is called by a sync committee aggregator // to submit signed contribution and proof object. func (s *Service) SubmitSignedContributionAndProof( diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index f603f75e45d4..c8a15a3b4e09 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -68,7 +68,7 @@ func (s *Service) endpoints( endpoints = append(endpoints, s.configEndpoints()...) endpoints = append(endpoints, s.lightClientEndpoints(blocker, stater)...) endpoints = append(endpoints, s.eventsEndpoints()...) - endpoints = append(endpoints, s.prysmBeaconEndpoints(ch, stater)...) + endpoints = append(endpoints, s.prysmBeaconEndpoints(ch, stater, coreService)...) endpoints = append(endpoints, s.prysmNodeEndpoints()...) endpoints = append(endpoints, s.prysmValidatorEndpoints(coreService)...) if enableDebug { @@ -926,8 +926,11 @@ func (s *Service) eventsEndpoints() []endpoint { } // Prysm custom endpoints - -func (s *Service) prysmBeaconEndpoints(ch *stategen.CanonicalHistory, stater lookup.Stater) []endpoint { +func (s *Service) prysmBeaconEndpoints( + ch *stategen.CanonicalHistory, + stater lookup.Stater, + coreService *core.Service, +) []endpoint { server := &beaconprysm.Server{ SyncChecker: s.cfg.SyncService, HeadFetcher: s.cfg.HeadFetcher, @@ -938,6 +941,7 @@ func (s *Service) prysmBeaconEndpoints(ch *stategen.CanonicalHistory, stater loo Stater: stater, ChainInfoFetcher: s.cfg.ChainInfoFetcher, FinalizationFetcher: s.cfg.FinalizationFetcher, + CoreService: coreService, } const namespace = "prysm.beacon" @@ -969,6 +973,16 @@ func (s *Service) prysmBeaconEndpoints(ch *stategen.CanonicalHistory, stater loo handler: server.GetValidatorCount, methods: []string{http.MethodGet}, }, + { + template: "/prysm/v1/beacon/individual_votes", + name: namespace + ".GetIndividualVotes", + middleware: []mux.MiddlewareFunc{ + middleware.ContentTypeHandler([]string{api.JsonMediaType}), + middleware.AcceptHeaderHandler([]string{api.JsonMediaType}), + }, + handler: server.GetIndividualVotes, + methods: []string{http.MethodPost}, + }, } } diff --git a/beacon-chain/rpc/endpoints_test.go b/beacon-chain/rpc/endpoints_test.go index aa9b0f069f6d..e3d8d753b114 100644 --- a/beacon-chain/rpc/endpoints_test.go +++ b/beacon-chain/rpc/endpoints_test.go @@ -44,6 +44,7 @@ func Test_endpoints(t *testing.T) { "/eth/v1/beacon/pool/sync_committees": {http.MethodPost}, "/eth/v1/beacon/pool/voluntary_exits": {http.MethodGet, http.MethodPost}, "/eth/v1/beacon/pool/bls_to_execution_changes": {http.MethodGet, http.MethodPost}, + "/prysm/v1/beacon/individual_votes": {http.MethodPost}, } lightClientRoutes := map[string][]string{ diff --git a/beacon-chain/rpc/prysm/beacon/BUILD.bazel b/beacon-chain/rpc/prysm/beacon/BUILD.bazel index 54c4a6b9223c..0dd081b0a8f8 100644 --- a/beacon-chain/rpc/prysm/beacon/BUILD.bazel +++ b/beacon-chain/rpc/prysm/beacon/BUILD.bazel @@ -14,6 +14,7 @@ go_library( "//beacon-chain/blockchain:go_default_library", "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/db:go_default_library", + "//beacon-chain/rpc/core:go_default_library", "//beacon-chain/rpc/eth/helpers:go_default_library", "//beacon-chain/rpc/eth/shared:go_default_library", "//beacon-chain/rpc/lookup:go_default_library", @@ -29,26 +30,40 @@ go_library( "//time/slots:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_gorilla_mux//:go_default_library", + "@com_github_pkg_errors//:go_default_library", "@io_opencensus_go//trace:go_default_library", ], ) go_test( name = "go_default_test", - srcs = ["validator_count_test.go"], + srcs = [ + "handlers_test.go", + "validator_count_test.go", + ], embed = [":go_default_library"], deps = [ "//api/server/structs:go_default_library", "//beacon-chain/blockchain/testing:go_default_library", + "//beacon-chain/core/helpers:go_default_library", + "//beacon-chain/db/testing:go_default_library", + "//beacon-chain/forkchoice/doubly-linked-tree:go_default_library", + "//beacon-chain/rpc/core:go_default_library", "//beacon-chain/rpc/lookup:go_default_library", "//beacon-chain/rpc/testutil: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/params:go_default_library", "//consensus-types/primitives:go_default_library", "//network/httputil:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//testing/assert:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", + "//time/slots:go_default_library", + "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_gorilla_mux//:go_default_library", + "@com_github_prysmaticlabs_go_bitfield//:go_default_library", ], ) diff --git a/beacon-chain/rpc/prysm/beacon/handlers.go b/beacon-chain/rpc/prysm/beacon/handlers.go index dc60043c7d6f..eca22946a95f 100644 --- a/beacon-chain/rpc/prysm/beacon/handlers.go +++ b/beacon-chain/rpc/prysm/beacon/handlers.go @@ -1,17 +1,23 @@ package beacon import ( + "encoding/json" "fmt" + "io" "log" "net/http" "strconv" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/api/server/structs" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared" "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/network/httputil" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/time/slots" "go.opencensus.io/trace" ) @@ -69,3 +75,82 @@ func (s *Server) GetWeakSubjectivity(w http.ResponseWriter, r *http.Request) { } httputil.WriteJson(w, resp) } + +// GetIndividualVotes returns a list of validators individual vote status of a given epoch. +func (s *Server) GetIndividualVotes(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "validator.GetIndividualVotes") + defer span.End() + + var req structs.GetIndividualVotesRequest + err := json.NewDecoder(r.Body).Decode(&req) + switch { + case errors.Is(err, io.EOF): + httputil.HandleError(w, "No data submitted", http.StatusBadRequest) + return + case err != nil: + httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest) + return + } + + publicKeyBytes := make([][]byte, len(req.PublicKeys)) + for i, s := range req.PublicKeys { + bs, err := hexutil.Decode(s) + if err != nil { + httputil.HandleError(w, "could not decode public keys: "+err.Error(), http.StatusBadRequest) + return + } + publicKeyBytes[i] = bs + } + epoch, err := strconv.ParseUint(req.Epoch, 10, 64) + if err != nil { + httputil.HandleError(w, "invalid epoch: "+err.Error(), http.StatusBadRequest) + return + } + var indices []primitives.ValidatorIndex + for _, i := range req.Indices { + u, err := strconv.ParseUint(i, 10, 64) + if err != nil { + httputil.HandleError(w, "invalid indices: "+err.Error(), http.StatusBadRequest) + return + } + indices = append(indices, primitives.ValidatorIndex(u)) + } + votes, rpcError := s.CoreService.IndividualVotes( + ctx, + ðpb.IndividualVotesRequest{ + Epoch: primitives.Epoch(epoch), + PublicKeys: publicKeyBytes, + Indices: indices, + }, + ) + + if rpcError != nil { + httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason)) + return + } + v := make([]*structs.IndividualVote, 0, len(votes.IndividualVotes)) + for _, vote := range votes.IndividualVotes { + v = append(v, &structs.IndividualVote{ + Epoch: fmt.Sprintf("%d", vote.Epoch), + PublicKey: hexutil.Encode(vote.PublicKey), + ValidatorIndex: fmt.Sprintf("%d", vote.ValidatorIndex), + IsSlashed: vote.IsSlashed, + IsWithdrawableInCurrentEpoch: vote.IsWithdrawableInCurrentEpoch, + IsActiveInCurrentEpoch: vote.IsActiveInCurrentEpoch, + IsActiveInPreviousEpoch: vote.IsActiveInPreviousEpoch, + IsCurrentEpochAttester: vote.IsCurrentEpochAttester, + IsCurrentEpochTargetAttester: vote.IsCurrentEpochTargetAttester, + IsPreviousEpochAttester: vote.IsPreviousEpochAttester, + IsPreviousEpochTargetAttester: vote.IsPreviousEpochTargetAttester, + IsPreviousEpochHeadAttester: vote.IsPreviousEpochHeadAttester, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", vote.CurrentEpochEffectiveBalanceGwei), + InclusionSlot: fmt.Sprintf("%d", vote.InclusionSlot), + InclusionDistance: fmt.Sprintf("%d", vote.InclusionDistance), + InactivityScore: fmt.Sprintf("%d", vote.InactivityScore), + }) + } + response := &structs.GetIndividualVotesResponse{ + IndividualVotes: v, + } + httputil.WriteJson(w, response) +} diff --git a/beacon-chain/rpc/prysm/beacon/handlers_test.go b/beacon-chain/rpc/prysm/beacon/handlers_test.go new file mode 100644 index 000000000000..521126548fce --- /dev/null +++ b/beacon-chain/rpc/prysm/beacon/handlers_test.go @@ -0,0 +1,660 @@ +package beacon + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "math" + "net/http" + "net/http/httptest" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/prysmaticlabs/go-bitfield" + "github.com/prysmaticlabs/prysm/v5/api/server/structs" + chainMock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + dbTest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing" + doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen" + mockstategen "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen/mock" + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/assert" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/util" + "github.com/prysmaticlabs/prysm/v5/time/slots" +) + +func individualVotesHelper(t *testing.T, request *structs.GetIndividualVotesRequest, s *Server) (string, *structs.GetIndividualVotesResponse) { + var buf bytes.Buffer + err := json.NewEncoder(&buf).Encode(request) + require.NoError(t, err) + + srv := httptest.NewServer(http.HandlerFunc(s.GetIndividualVotes)) + defer srv.Close() + req := httptest.NewRequest( + http.MethodGet, + "http://example.com/eth/v1/beacon/individual_votes", + &buf, + ) + client := &http.Client{} + rawResp, err := client.Post(srv.URL, "application/json", req.Body) + require.NoError(t, err) + defer func() { + if err := rawResp.Body.Close(); err != nil { + t.Fatal(err) + } + }() + body, err := io.ReadAll(rawResp.Body) + require.NoError(t, err) + type ErrorResponse struct { + Message string `json:"message"` + } + if rawResp.StatusCode != 200 { + var errorResponse ErrorResponse + err = json.Unmarshal(body, &errorResponse) + require.NoError(t, err) + return errorResponse.Message, &structs.GetIndividualVotesResponse{} + } + var votes *structs.GetIndividualVotesResponse + err = json.Unmarshal(body, &votes) + require.NoError(t, err) + return "", votes +} + +func TestServer_GetIndividualVotes_RequestFutureSlot(t *testing.T) { + s := &Server{ + CoreService: &core.Service{ + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + request := &structs.GetIndividualVotesRequest{ + Epoch: fmt.Sprintf("%d", slots.ToEpoch(s.CoreService.GenesisTimeFetcher.CurrentSlot())+1), + } + errorResp, _ := individualVotesHelper(t, request, s) + require.StringContains(t, "cannot retrieve information about an epoch in the future", errorResp) +} + +func addDefaultReplayerBuilder(s *Server, h stategen.HistoryAccessor) { + cc := &mockstategen.CanonicalChecker{Is: true, Err: nil} + cs := &mockstategen.CurrentSlotter{Slot: math.MaxUint64 - 1} + s.CoreService.ReplayerBuilder = stategen.NewCanonicalHistory(h, cc, cs) +} + +func TestServer_GetIndividualVotes_ValidatorsDontExist(t *testing.T) { + beaconDB := dbTest.SetupDB(t) + ctx := context.Background() + + var slot primitives.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(slot)) + + b := util.NewBeaconBlock() + b.Block.Slot = slot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err := b.Block.HashTreeRoot() + require.NoError(t, err) + gen := stategen.New(beaconDB, doublylinkedtree.New()) + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) + s := &Server{ + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + addDefaultReplayerBuilder(s, beaconDB) + + // Test non exist public key. + request := &structs.GetIndividualVotesRequest{ + PublicKeys: []string{"0xaa"}, + Epoch: "0", + } + errStr, resp := individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want := &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "0", + PublicKey: "0xaa", + ValidatorIndex: fmt.Sprintf("%d", ^uint64(0)), + CurrentEpochEffectiveBalanceGwei: "0", + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") + + // Test non-existent validator index. + request = &structs.GetIndividualVotesRequest{ + Indices: []string{"100"}, + Epoch: "0", + } + errStr, resp = individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want = &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "0", + PublicKey: "0x", + ValidatorIndex: "100", + CurrentEpochEffectiveBalanceGwei: "0", + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") + + // Test both. + request = &structs.GetIndividualVotesRequest{ + PublicKeys: []string{"0xaa", "0xbb"}, + Indices: []string{"100", "101"}, + Epoch: "0", + } + errStr, resp = individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want = &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + {Epoch: "0", PublicKey: "0xaa", ValidatorIndex: fmt.Sprintf("%d", ^uint64(0)), CurrentEpochEffectiveBalanceGwei: "0", InclusionSlot: "0", InclusionDistance: "0", InactivityScore: "0"}, + { + Epoch: "0", + PublicKey: "0xbb", + ValidatorIndex: fmt.Sprintf("%d", ^uint64(0)), + CurrentEpochEffectiveBalanceGwei: "0", + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + { + Epoch: "0", + PublicKey: "0x", + ValidatorIndex: "100", + CurrentEpochEffectiveBalanceGwei: "0", + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + { + Epoch: "0", + PublicKey: "0x", + ValidatorIndex: "101", + CurrentEpochEffectiveBalanceGwei: "0", + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") +} + +func TestServer_GetIndividualVotes_Working(t *testing.T) { + helpers.ClearCache() + beaconDB := dbTest.SetupDB(t) + ctx := context.Background() + + validators := uint64(32) + stateWithValidators, _ := util.DeterministicGenesisState(t, validators) + beaconState, err := util.NewBeaconState() + require.NoError(t, err) + require.NoError(t, beaconState.SetValidators(stateWithValidators.Validators())) + + bf := bitfield.NewBitlist(validators / uint64(params.BeaconConfig().SlotsPerEpoch)) + att1 := util.NewAttestation() + att1.AggregationBits = bf + att2 := util.NewAttestation() + att2.AggregationBits = bf + rt := [32]byte{'A'} + att1.Data.Target.Root = rt[:] + att1.Data.BeaconBlockRoot = rt[:] + br := beaconState.BlockRoots() + newRt := [32]byte{'B'} + br[0] = newRt[:] + require.NoError(t, beaconState.SetBlockRoots(br)) + att2.Data.Target.Root = rt[:] + att2.Data.BeaconBlockRoot = newRt[:] + err = beaconState.AppendPreviousEpochAttestations(ð.PendingAttestation{ + Data: att1.Data, AggregationBits: bf, InclusionDelay: 1, + }) + require.NoError(t, err) + err = beaconState.AppendCurrentEpochAttestations(ð.PendingAttestation{ + Data: att2.Data, AggregationBits: bf, InclusionDelay: 1, + }) + require.NoError(t, err) + + b := util.NewBeaconBlock() + b.Block.Slot = 0 + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err := b.Block.HashTreeRoot() + require.NoError(t, err) + gen := stategen.New(beaconDB, doublylinkedtree.New()) + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) + s := &Server{ + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + addDefaultReplayerBuilder(s, beaconDB) + + request := &structs.GetIndividualVotesRequest{ + Indices: []string{"0", "1"}, + Epoch: "0", + } + errStr, resp := individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want := &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "0", + ValidatorIndex: "0", + PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: fmt.Sprintf("%d", params.BeaconConfig().FarFutureSlot), + InclusionDistance: fmt.Sprintf("%d", params.BeaconConfig().FarFutureSlot), + InactivityScore: "0", + }, + { + Epoch: "0", + ValidatorIndex: "1", + PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: fmt.Sprintf("%d", params.BeaconConfig().FarFutureSlot), + InclusionDistance: fmt.Sprintf("%d", params.BeaconConfig().FarFutureSlot), + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") +} + +func TestServer_GetIndividualVotes_WorkingAltair(t *testing.T) { + helpers.ClearCache() + beaconDB := dbTest.SetupDB(t) + ctx := context.Background() + + var slot primitives.Slot = 0 + validators := uint64(32) + beaconState, _ := util.DeterministicGenesisStateAltair(t, validators) + require.NoError(t, beaconState.SetSlot(slot)) + + pb, err := beaconState.CurrentEpochParticipation() + require.NoError(t, err) + for i := range pb { + pb[i] = 0xff + } + require.NoError(t, beaconState.SetCurrentParticipationBits(pb)) + require.NoError(t, beaconState.SetPreviousParticipationBits(pb)) + + b := util.NewBeaconBlock() + b.Block.Slot = slot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err := b.Block.HashTreeRoot() + require.NoError(t, err) + gen := stategen.New(beaconDB, doublylinkedtree.New()) + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) + s := &Server{ + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + addDefaultReplayerBuilder(s, beaconDB) + + request := &structs.GetIndividualVotesRequest{ + Indices: []string{"0", "1"}, + Epoch: "0", + } + errStr, resp := individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want := &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "0", + ValidatorIndex: "0", + PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + { + Epoch: "0", + ValidatorIndex: "1", + PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") +} + +func TestServer_GetIndividualVotes_AltairEndOfEpoch(t *testing.T) { + helpers.ClearCache() + params.SetupTestConfigCleanup(t) + params.OverrideBeaconConfig(params.BeaconConfig()) + beaconDB := dbTest.SetupDB(t) + ctx := context.Background() + + validators := uint64(32) + beaconState, _ := util.DeterministicGenesisStateAltair(t, validators) + startSlot, err := slots.EpochStart(1) + assert.NoError(t, err) + require.NoError(t, beaconState.SetSlot(startSlot)) + + b := util.NewBeaconBlock() + b.Block.Slot = startSlot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err := b.Block.HashTreeRoot() + require.NoError(t, err) + gen := stategen.New(beaconDB, doublylinkedtree.New()) + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) + // Save State at the end of the epoch: + endSlot, err := slots.EpochEnd(1) + assert.NoError(t, err) + + beaconState, _ = util.DeterministicGenesisStateAltair(t, validators) + require.NoError(t, beaconState.SetSlot(endSlot)) + + pb, err := beaconState.CurrentEpochParticipation() + require.NoError(t, err) + for i := range pb { + pb[i] = 0xff + } + require.NoError(t, beaconState.SetCurrentParticipationBits(pb)) + require.NoError(t, beaconState.SetPreviousParticipationBits(pb)) + + b.Block.Slot = endSlot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err = b.Block.HashTreeRoot() + require.NoError(t, err) + + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + s := &Server{ + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + addDefaultReplayerBuilder(s, beaconDB) + + request := &structs.GetIndividualVotesRequest{ + Indices: []string{"0", "1"}, + Epoch: "1", + } + errStr, resp := individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want := &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "1", + ValidatorIndex: "0", + PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + { + Epoch: "1", + ValidatorIndex: "1", + PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") +} + +func TestServer_GetIndividualVotes_BellatrixEndOfEpoch(t *testing.T) { + helpers.ClearCache() + params.SetupTestConfigCleanup(t) + params.OverrideBeaconConfig(params.BeaconConfig()) + beaconDB := dbTest.SetupDB(t) + ctx := context.Background() + + validators := uint64(32) + beaconState, _ := util.DeterministicGenesisStateBellatrix(t, validators) + startSlot, err := slots.EpochStart(1) + assert.NoError(t, err) + require.NoError(t, beaconState.SetSlot(startSlot)) + + b := util.NewBeaconBlock() + b.Block.Slot = startSlot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err := b.Block.HashTreeRoot() + require.NoError(t, err) + gen := stategen.New(beaconDB, doublylinkedtree.New()) + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) + // Save State at the end of the epoch: + endSlot, err := slots.EpochEnd(1) + assert.NoError(t, err) + + beaconState, _ = util.DeterministicGenesisStateBellatrix(t, validators) + require.NoError(t, beaconState.SetSlot(endSlot)) + + pb, err := beaconState.CurrentEpochParticipation() + require.NoError(t, err) + for i := range pb { + pb[i] = 0xff + } + require.NoError(t, beaconState.SetCurrentParticipationBits(pb)) + require.NoError(t, beaconState.SetPreviousParticipationBits(pb)) + + b.Block.Slot = endSlot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err = b.Block.HashTreeRoot() + require.NoError(t, err) + + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + s := &Server{ + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + addDefaultReplayerBuilder(s, beaconDB) + + request := &structs.GetIndividualVotesRequest{ + Indices: []string{"0", "1"}, + Epoch: "1", + } + errStr, resp := individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want := &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "1", + ValidatorIndex: "0", + PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + { + Epoch: "1", + ValidatorIndex: "1", + PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") +} + +func TestServer_GetIndividualVotes_CapellaEndOfEpoch(t *testing.T) { + helpers.ClearCache() + params.SetupTestConfigCleanup(t) + params.OverrideBeaconConfig(params.BeaconConfig()) + beaconDB := dbTest.SetupDB(t) + ctx := context.Background() + + validators := uint64(32) + beaconState, _ := util.DeterministicGenesisStateCapella(t, validators) + startSlot, err := slots.EpochStart(1) + assert.NoError(t, err) + require.NoError(t, beaconState.SetSlot(startSlot)) + + b := util.NewBeaconBlock() + b.Block.Slot = startSlot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err := b.Block.HashTreeRoot() + require.NoError(t, err) + gen := stategen.New(beaconDB, doublylinkedtree.New()) + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) + // Save State at the end of the epoch: + endSlot, err := slots.EpochEnd(1) + assert.NoError(t, err) + + beaconState, _ = util.DeterministicGenesisStateCapella(t, validators) + require.NoError(t, beaconState.SetSlot(endSlot)) + + pb, err := beaconState.CurrentEpochParticipation() + require.NoError(t, err) + for i := range pb { + pb[i] = 0xff + } + require.NoError(t, beaconState.SetCurrentParticipationBits(pb)) + require.NoError(t, beaconState.SetPreviousParticipationBits(pb)) + + b.Block.Slot = endSlot + util.SaveBlock(t, ctx, beaconDB, b) + gRoot, err = b.Block.HashTreeRoot() + require.NoError(t, err) + + require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) + require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) + s := &Server{ + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &chainMock.ChainService{}, + }, + } + addDefaultReplayerBuilder(s, beaconDB) + + request := &structs.GetIndividualVotesRequest{ + Indices: []string{"0", "1"}, + Epoch: "1", + } + errStr, resp := individualVotesHelper(t, request, s) + require.Equal(t, "", errStr) + want := &structs.GetIndividualVotesResponse{ + IndividualVotes: []*structs.IndividualVote{ + { + Epoch: "1", + ValidatorIndex: "0", + PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + { + Epoch: "1", + ValidatorIndex: "1", + PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey), + IsActiveInCurrentEpoch: true, + IsActiveInPreviousEpoch: true, + IsCurrentEpochTargetAttester: true, + IsCurrentEpochAttester: true, + IsPreviousEpochAttester: true, + IsPreviousEpochHeadAttester: true, + IsPreviousEpochTargetAttester: true, + CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance), + InclusionSlot: "0", + InclusionDistance: "0", + InactivityScore: "0", + }, + }, + } + assert.DeepEqual(t, want, resp, "Unexpected response") +} diff --git a/beacon-chain/rpc/prysm/beacon/server.go b/beacon-chain/rpc/prysm/beacon/server.go index a7f4616ee089..5af654712f69 100644 --- a/beacon-chain/rpc/prysm/beacon/server.go +++ b/beacon-chain/rpc/prysm/beacon/server.go @@ -3,6 +3,7 @@ package beacon import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain" beacondb "github.com/prysmaticlabs/prysm/v5/beacon-chain/db" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/lookup" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen" "github.com/prysmaticlabs/prysm/v5/beacon-chain/sync" @@ -18,4 +19,5 @@ type Server struct { Stater lookup.Stater ChainInfoFetcher blockchain.ChainInfoFetcher FinalizationFetcher blockchain.FinalizationFetcher + CoreService *core.Service } diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments.go index 26ff052b4458..ed1965710557 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments.go @@ -16,7 +16,7 @@ import ( "google.golang.org/grpc/status" ) -const errEpoch = "Cannot retrieve information about an epoch in the future, current epoch %d, requesting %d" +const errEpoch = "cannot retrieve information about an epoch in the future, current epoch %d, requesting %d" // ListValidatorAssignments retrieves the validator assignments for a given epoch, // optional validator indices or public keys may be included to filter validator assignments. diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/committees_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/committees_test.go index 7d2fa10c1dd5..be1beb965eda 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/committees_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/committees_test.go @@ -79,6 +79,9 @@ func addDefaultReplayerBuilder(s *Server, h stategen.HistoryAccessor) { cc := &mockstategen.CanonicalChecker{Is: true, Err: nil} cs := &mockstategen.CurrentSlotter{Slot: math.MaxUint64 - 1} s.ReplayerBuilder = stategen.NewCanonicalHistory(h, cc, cs) + if s.CoreService != nil { + s.CoreService.ReplayerBuilder = stategen.NewCanonicalHistory(h, cc, cs) + } } func TestServer_ListBeaconCommittees_PreviousEpoch(t *testing.T) { diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/validators.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/validators.go index efad2fa89da6..4b4e59b04802 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/validators.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/validators.go @@ -672,105 +672,11 @@ func (bs *Server) GetIndividualVotes( ctx context.Context, req *ethpb.IndividualVotesRequest, ) (*ethpb.IndividualVotesRespond, error) { - currentEpoch := slots.ToEpoch(bs.GenesisTimeFetcher.CurrentSlot()) - if req.Epoch > currentEpoch { - return nil, status.Errorf( - codes.InvalidArgument, - errEpoch, - currentEpoch, - req.Epoch, - ) - } - - s, err := slots.EpochEnd(req.Epoch) - if err != nil { - return nil, err - } - st, err := bs.ReplayerBuilder.ReplayerForSlot(s).ReplayBlocks(ctx) + response, err := bs.CoreService.IndividualVotes(ctx, req) if err != nil { - 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[primitives.ValidatorIndex]bool{} - filteredIndices := make([]primitives.ValidatorIndex, 0) - 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 := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey)) - if !ok { - votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{PublicKey: pubKey, ValidatorIndex: primitives.ValidatorIndex(^uint64(0))}) - continue - } - 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) - } - } - sort.Slice(filteredIndices, func(i, j int) bool { - return filteredIndices[i] < filteredIndices[j] - }) - - var v []*precompute.Validator - var bal *precompute.Balance - if st.Version() == version.Phase0 { - 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, st, v, bal) - if err != nil { - return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err) - } - } else if st.Version() >= version.Altair { - 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, st, bal, v) - if err != nil { - return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err) - } - } else { - return nil, status.Errorf(codes.Internal, "Invalid state type retrieved with a version of %d", st.Version()) - } - - for _, index := range filteredIndices { - if uint64(index) >= uint64(len(v)) { - votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{ValidatorIndex: index}) - continue - } - val, err := st.ValidatorAtIndexReadOnly(index) - if err != nil { - return nil, status.Errorf(codes.Internal, "Could not retrieve validator: %v", err) - } - pb := val.PublicKey() - votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{ - Epoch: req.Epoch, - PublicKey: pb[:], - ValidatorIndex: index, - IsSlashed: v[index].IsSlashed, - IsWithdrawableInCurrentEpoch: v[index].IsWithdrawableCurrentEpoch, - IsActiveInCurrentEpoch: v[index].IsActiveCurrentEpoch, - IsActiveInPreviousEpoch: v[index].IsActivePrevEpoch, - IsCurrentEpochAttester: v[index].IsCurrentEpochAttester, - IsCurrentEpochTargetAttester: v[index].IsCurrentEpochTargetAttester, - IsPreviousEpochAttester: v[index].IsPrevEpochAttester, - IsPreviousEpochTargetAttester: v[index].IsPrevEpochTargetAttester, - IsPreviousEpochHeadAttester: v[index].IsPrevEpochHeadAttester, - CurrentEpochEffectiveBalanceGwei: v[index].CurrentEpochEffectiveBalance, - InclusionSlot: v[index].InclusionSlot, - InclusionDistance: v[index].InclusionDistance, - InactivityScore: v[index].InactivityScore, - }) + return nil, status.Errorf(core.ErrorReasonToGRPC(err.Reason), "Could not retrieve individual votes: %v", err.Err) } - - return ðpb.IndividualVotesRespond{ - IndividualVotes: votes, - }, nil + return response, nil } // Determines whether a validator has already exited. diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go index 56a6a3f3e516..83abeb5ae372 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go @@ -44,7 +44,7 @@ import ( ) const ( - errNoEpochInfoError = "Cannot retrieve information about an epoch in the future" + errNoEpochInfoError = "cannot retrieve information about an epoch in the future" ) func TestServer_GetValidatorActiveSetChanges_CannotRequestFutureEpoch(t *testing.T) { @@ -2258,12 +2258,17 @@ func setupValidators(t testing.TB, _ db.Database, count int) ([]*ethpb.Validator } func TestServer_GetIndividualVotes_RequestFutureSlot(t *testing.T) { - ds := &Server{GenesisTimeFetcher: &mock.ChainService{}} + bs := &Server{ + CoreService: &core.Service{ + GenesisTimeFetcher: &mock.ChainService{}, + }, + } + req := ðpb.IndividualVotesRequest{ - Epoch: slots.ToEpoch(ds.GenesisTimeFetcher.CurrentSlot()) + 1, + Epoch: slots.ToEpoch(bs.CoreService.GenesisTimeFetcher.CurrentSlot()) + 1, } wanted := errNoEpochInfoError - _, err := ds.GetIndividualVotes(context.Background(), req) + _, err := bs.GetIndividualVotes(context.Background(), req) assert.ErrorContains(t, wanted, err) } @@ -2292,8 +2297,10 @@ func TestServer_GetIndividualVotes_ValidatorsDontExist(t *testing.T) { require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) bs := &Server{ - StateGen: gen, - GenesisTimeFetcher: &mock.ChainService{}, + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &mock.ChainService{}, + }, } addDefaultReplayerBuilder(bs, beaconDB) @@ -2388,8 +2395,10 @@ func TestServer_GetIndividualVotes_Working(t *testing.T) { require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) bs := &Server{ - StateGen: gen, - GenesisTimeFetcher: &mock.ChainService{}, + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &mock.ChainService{}, + }, } addDefaultReplayerBuilder(bs, beaconDB) @@ -2451,8 +2460,10 @@ func TestServer_GetIndividualVotes_WorkingAltair(t *testing.T) { require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot)) bs := &Server{ - StateGen: gen, - GenesisTimeFetcher: &mock.ChainService{}, + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &mock.ChainService{}, + }, } addDefaultReplayerBuilder(bs, beaconDB) @@ -2537,8 +2548,10 @@ func TestServer_GetIndividualVotes_AltairEndOfEpoch(t *testing.T) { require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) bs := &Server{ - StateGen: gen, - GenesisTimeFetcher: &mock.ChainService{}, + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &mock.ChainService{}, + }, } addDefaultReplayerBuilder(bs, beaconDB) @@ -2625,8 +2638,10 @@ func TestServer_GetIndividualVotes_BellatrixEndOfEpoch(t *testing.T) { require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) bs := &Server{ - StateGen: gen, - GenesisTimeFetcher: &mock.ChainService{}, + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &mock.ChainService{}, + }, } addDefaultReplayerBuilder(bs, beaconDB) @@ -2713,8 +2728,10 @@ func TestServer_GetIndividualVotes_CapellaEndOfEpoch(t *testing.T) { require.NoError(t, gen.SaveState(ctx, gRoot, beaconState)) require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot)) bs := &Server{ - StateGen: gen, - GenesisTimeFetcher: &mock.ChainService{}, + CoreService: &core.Service{ + StateGen: gen, + GenesisTimeFetcher: &mock.ChainService{}, + }, } addDefaultReplayerBuilder(bs, beaconDB)