prysm-pulse/beacon-chain/rpc/prysm/beacon/validator_count_test.go
Radosław Kapka f09fe4f038
Reorganize Prysm custom endpoints (#13621)
* Reorganize Prysm custom endpoints

* add deprecated paths

* review
2024-02-15 17:26:59 +00:00

493 lines
12 KiB
Go

package beacon
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
neturl "net/url"
"strconv"
"strings"
"testing"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/lookup"
"github.com/prysmaticlabs/prysm/v5/network/httputil"
"github.com/gorilla/mux"
chainMock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/testutil"
"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/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
)
func TestGetValidatorCountInvalidRequest(t *testing.T) {
st, _ := util.DeterministicGenesisState(t, 10)
stateIdCheckerStateFunc := func(_ context.Context, stateId []byte) (state.BeaconState, error) {
stateIdString := strings.ToLower(string(stateId))
switch stateIdString {
case "head", "genesis", "finalized", "justified":
return st, nil
default:
if len(stateId) == 32 {
return nil, nil
} else {
_, parseErr := strconv.ParseUint(stateIdString, 10, 64)
if parseErr != nil {
// ID format does not match any valid options.
e := lookup.NewStateIdParseError(parseErr)
return nil, &e
}
return st, nil
}
}
}
tests := []struct {
name string
stater lookup.Stater
status string
stateID string
expectedErrorMessage string
statusCode int
}{
{
name: "invalid status",
stater: &testutil.MockStater{
BeaconState: st,
},
status: "helloworld",
stateID: "head",
expectedErrorMessage: "invalid status query parameter",
statusCode: http.StatusBadRequest,
},
{
name: "invalid state ID",
stater: &testutil.MockStater{StateProviderFunc: stateIdCheckerStateFunc},
stateID: "helloworld",
expectedErrorMessage: "Invalid state ID",
statusCode: http.StatusBadRequest,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
chainService := &chainMock.ChainService{Optimistic: false, FinalizedRoots: make(map[[32]byte]bool)}
server := &Server{
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
Stater: test.stater,
}
testRouter := mux.NewRouter()
testRouter.HandleFunc("/eth/v1/beacon/states/{state_id}/validator_count", server.GetValidatorCount)
s := httptest.NewServer(testRouter)
defer s.Close()
queryParams := neturl.Values{}
queryParams.Add("status", test.status)
resp, err := http.Get(s.URL + fmt.Sprintf("/eth/v1/beacon/states/%s/validator_count?%s",
test.stateID, queryParams.Encode()))
require.NoError(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
var errJson httputil.DefaultJsonError
err = json.Unmarshal(body, &errJson)
require.NoError(t, err)
require.Equal(t, test.statusCode, errJson.Code)
require.StringContains(t, test.expectedErrorMessage, errJson.Message)
})
}
}
func TestGetValidatorCount(t *testing.T) {
st, _ := util.DeterministicGenesisState(t, 10)
farFutureEpoch := params.BeaconConfig().FarFutureEpoch
validators := []*eth.Validator{
// Pending initialized.
{
ActivationEpoch: farFutureEpoch,
ActivationEligibilityEpoch: farFutureEpoch,
ExitEpoch: farFutureEpoch,
WithdrawableEpoch: farFutureEpoch,
},
// Pending queued.
{
ActivationEpoch: 10,
ActivationEligibilityEpoch: 4,
ExitEpoch: farFutureEpoch,
WithdrawableEpoch: farFutureEpoch,
},
// Active ongoing.
{
ActivationEpoch: 0,
ExitEpoch: farFutureEpoch,
},
// Active slashed.
{
ActivationEpoch: 0,
ExitEpoch: 30,
Slashed: true,
WithdrawableEpoch: 50,
},
// Active exiting.
{
ActivationEpoch: 0,
ExitEpoch: 30,
Slashed: false,
WithdrawableEpoch: 50,
},
// Exit slashed (at epoch 35).
{
ActivationEpoch: 3,
ExitEpoch: 30,
WithdrawableEpoch: 50,
Slashed: true,
},
// Exit unslashed (at epoch 35).
{
ActivationEpoch: 3,
ExitEpoch: 30,
WithdrawableEpoch: 50,
Slashed: false,
},
// Withdrawable (at epoch 45).
{
ActivationEpoch: 3,
ExitEpoch: 30,
WithdrawableEpoch: 40,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
Slashed: false,
},
// Withdrawal done (at epoch 45).
{
ActivationEpoch: 3,
ExitEpoch: 30,
WithdrawableEpoch: 40,
EffectiveBalance: 0,
Slashed: false,
},
}
for _, validator := range validators {
require.NoError(t, st.AppendValidator(validator))
require.NoError(t, st.AppendBalance(params.BeaconConfig().MaxEffectiveBalance))
}
tests := []struct {
name string
stateID string
statuses []string
currentEpoch int
expectedResponse structs.GetValidatorCountResponse
}{
{
name: "Head count active validators",
stateID: "head",
statuses: []string{"active"},
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "active",
Count: "13",
},
},
},
},
{
name: "Head count active ongoing validators",
stateID: "head",
statuses: []string{"active_ongoing"},
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "active_ongoing",
Count: "11",
},
},
},
},
{
name: "Head count active exiting validators",
stateID: "head",
statuses: []string{"active_exiting"},
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "active_exiting",
Count: "1",
},
},
},
},
{
name: "Head count active slashed validators",
stateID: "head",
statuses: []string{"active_slashed"},
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "active_slashed",
Count: "1",
},
},
},
},
{
name: "Head count pending validators",
stateID: "head",
statuses: []string{"pending"},
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "pending",
Count: "6",
},
},
},
},
{
name: "Head count pending initialized validators",
stateID: "head",
statuses: []string{"pending_initialized"},
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "pending_initialized",
Count: "1",
},
},
},
},
{
name: "Head count pending queued validators",
stateID: "head",
statuses: []string{"pending_queued"},
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "pending_queued",
Count: "5",
},
},
},
},
{
name: "Head count exited validators",
stateID: "head",
statuses: []string{"exited"},
currentEpoch: 35,
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "exited",
Count: "6",
},
},
},
},
{
name: "Head count exited slashed validators",
stateID: "head",
statuses: []string{"exited_slashed"},
currentEpoch: 35,
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "exited_slashed",
Count: "2",
},
},
},
},
{
name: "Head count exited unslashed validators",
stateID: "head",
statuses: []string{"exited_unslashed"},
currentEpoch: 35,
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "exited_unslashed",
Count: "4",
},
},
},
},
{
name: "Head count withdrawal validators",
stateID: "head",
statuses: []string{"withdrawal"},
currentEpoch: 45,
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "withdrawal",
Count: "2",
},
},
},
},
{
name: "Head count withdrawal possible validators",
stateID: "head",
statuses: []string{"withdrawal_possible"},
currentEpoch: 45,
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "withdrawal_possible",
Count: "1",
},
},
},
},
{
name: "Head count withdrawal done validators",
stateID: "head",
statuses: []string{"withdrawal_done"},
currentEpoch: 45,
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "withdrawal_done",
Count: "1",
},
},
},
},
{
name: "Head count active and pending validators",
stateID: "head",
statuses: []string{"active", "pending"},
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "active",
Count: "13",
},
{
Status: "pending",
Count: "6",
},
},
},
},
{
name: "Head count of ALL validators",
stateID: "head",
expectedResponse: structs.GetValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*structs.ValidatorCount{
{
Status: "active",
Count: "13",
},
{
Status: "active_exiting",
Count: "1",
},
{
Status: "active_ongoing",
Count: "11",
},
{
Status: "active_slashed",
Count: "1",
},
{
Status: "pending",
Count: "6",
},
{
Status: "pending_initialized",
Count: "1",
},
{
Status: "pending_queued",
Count: "5",
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
chainService := &chainMock.ChainService{Optimistic: false, FinalizedRoots: make(map[[32]byte]bool)}
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
require.NoError(t, err)
chainService.FinalizedRoots[blockRoot] = true
require.NoError(t, st.SetSlot(params.BeaconConfig().SlotsPerEpoch*primitives.Slot(test.currentEpoch)))
server := &Server{
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
Stater: &testutil.MockStater{
BeaconState: st,
},
}
testRouter := mux.NewRouter()
testRouter.HandleFunc("/eth/v1/beacon/states/{state_id}/validator_count", server.GetValidatorCount)
s := httptest.NewServer(testRouter)
defer s.Close()
queryParams := neturl.Values{}
for _, status := range test.statuses {
queryParams.Add("status", status)
}
resp, err := http.Get(s.URL + fmt.Sprintf("/eth/v1/beacon/states/%s/validator_count?%s",
test.stateID, queryParams.Encode()))
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
var count structs.GetValidatorCountResponse
err = json.Unmarshal(body, &count)
require.NoError(t, err)
require.DeepEqual(t, test.expectedResponse, count)
})
}
}