prysm-pulse/beacon-chain/rpc/prysm/validator/validator_count.go
2024-02-03 11:57:01 +00:00

167 lines
5.3 KiB
Go

package validator
import (
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"github.com/gorilla/mux"
"github.com/prysmaticlabs/prysm/v4/api/server/structs"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
statenative "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v4/network/httputil"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/time/slots"
"go.opencensus.io/trace"
)
// GetValidatorCount is a HTTP handler that serves the GET /eth/v1/beacon/states/{state_id}/validator_count endpoint.
// It returns the total validator count according to the given statuses provided as a query parameter.
//
// The state ID is expected to be a valid Beacon Chain state identifier.
// The status query parameter can be an array of strings with the following values: pending_initialized, pending_queued, active_ongoing,
// active_exiting, active_slashed, exited_unslashed, exited_slashed, withdrawal_possible, withdrawal_done, active, pending, exited, withdrawal.
// The response is a JSON object containing the total validator count for the specified statuses.
//
// Example usage:
//
// GET /eth/v1/beacon/states/12345/validator_count?status=active&status=pending
//
// The above request will return a JSON response like:
//
// {
// "execution_optimistic": "false",
// "finalized": "true",
// "data": [
// {
// "status": "active",
// "count": "13"
// },
// {
// "status": "pending",
// "count": "6"
// }
// ]
// }
func (s *Server) GetValidatorCount(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "validator.GetValidatorCount")
defer span.End()
stateID := mux.Vars(r)["state_id"]
isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateID), s.OptimisticModeFetcher, s.Stater, s.ChainInfoFetcher, s.BeaconDB)
if err != nil {
errJson := &httputil.DefaultJsonError{
Message: fmt.Sprintf("could not check if slot's block is optimistic: %v", err),
Code: http.StatusInternalServerError,
}
httputil.WriteError(w, errJson)
return
}
st, err := s.Stater.State(ctx, []byte(stateID))
if err != nil {
shared.WriteStateFetchError(w, err)
return
}
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
if err != nil {
errJson := &httputil.DefaultJsonError{
Message: fmt.Sprintf("could not calculate root of latest block header: %v", err),
Code: http.StatusInternalServerError,
}
httputil.WriteError(w, errJson)
return
}
isFinalized := s.FinalizationFetcher.IsFinalized(ctx, blockRoot)
var statusVals []validator.Status
for _, status := range r.URL.Query()["status"] {
statusVal, ok := ethpb.ValidatorStatus_value[strings.ToUpper(status)]
if !ok {
errJson := &httputil.DefaultJsonError{
Message: fmt.Sprintf("invalid status query parameter: %v", status),
Code: http.StatusBadRequest,
}
httputil.WriteError(w, errJson)
return
}
statusVals = append(statusVals, validator.Status(statusVal))
}
// If no status was provided then consider all the statuses to return validator count for each status.
if len(statusVals) == 0 {
for _, val := range ethpb.ValidatorStatus_value {
statusVals = append(statusVals, validator.Status(val))
}
}
epoch := slots.ToEpoch(st.Slot())
valCount, err := validatorCountByStatus(st.Validators(), statusVals, epoch)
if err != nil {
errJson := &httputil.DefaultJsonError{
Message: fmt.Sprintf("could not get validator count: %v", err),
Code: http.StatusInternalServerError,
}
httputil.WriteError(w, errJson)
return
}
valCountResponse := &structs.GetValidatorCountResponse{
ExecutionOptimistic: strconv.FormatBool(isOptimistic),
Finalized: strconv.FormatBool(isFinalized),
Data: valCount,
}
httputil.WriteJson(w, valCountResponse)
}
// validatorCountByStatus returns a slice of validator count for each status in the given epoch.
func validatorCountByStatus(validators []*eth.Validator, statuses []validator.Status, epoch primitives.Epoch) ([]*structs.ValidatorCount, error) {
countByStatus := make(map[validator.Status]uint64)
for _, val := range validators {
readOnlyVal, err := statenative.NewValidator(val)
if err != nil {
return nil, fmt.Errorf("could not convert validator: %v", err)
}
valStatus, err := helpers.ValidatorStatus(readOnlyVal, epoch)
if err != nil {
return nil, fmt.Errorf("could not get validator status: %v", err)
}
valSubStatus, err := helpers.ValidatorSubStatus(readOnlyVal, epoch)
if err != nil {
return nil, fmt.Errorf("could not get validator sub status: %v", err)
}
for _, status := range statuses {
if valStatus == status || valSubStatus == status {
countByStatus[status]++
}
}
}
var resp []*structs.ValidatorCount
for status, count := range countByStatus {
resp = append(resp, &structs.ValidatorCount{
Status: status.String(),
Count: strconv.FormatUint(count, 10),
})
}
// Sort the response slice according to status strings for deterministic ordering of validator count response.
sort.Slice(resp, func(i, j int) bool {
return resp[i].Status < resp[j].Status
})
return resp, nil
}