integrate validator count endpoint in validator client (#12912)

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
Dhruv Bodani 2023-10-11 18:53:02 +05:30 committed by GitHub
parent d7318ea485
commit 2806326155
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1225 additions and 276 deletions

View File

@ -162,7 +162,7 @@ func validatorCountByStatus(validators []*eth.Validator, statuses []validator.Va
var resp []*ValidatorCount
for status, count := range countByStatus {
resp = append(resp, &ValidatorCount{
Status: strings.ToLower(ethpb.ValidatorStatus_name[int32(status)]),
Status: status.String(),
Count: strconv.FormatUint(count, 10),
})
}

View File

@ -57,6 +57,7 @@ done
# --------------------------------------------------------
iface_mocks=(
"$iface_mock_path/beacon_chain_client_mock.go BeaconChainClient"
"$iface_mock_path/prysm_beacon_chain_client_mock.go PrysmBeaconChainClient"
"$iface_mock_path/node_client_mock.go NodeClient"
"$iface_mock_path/slasher_client_mock.go SlasherClient"
"$iface_mock_path/validator_client_mock.go ValidatorClient"

View File

@ -7,13 +7,16 @@ go_library(
srcs = [
"beacon_chain_client_mock.go",
"node_client_mock.go",
"prysm_beacon_chain_client_mock.go",
"validator_client_mock.go",
],
importpath = "github.com/prysmaticlabs/prysm/v4/testing/validator-mock",
visibility = ["//visibility:public"],
deps = [
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//validator/client/iface:go_default_library",
"@com_github_golang_mock//gomock:go_default_library",
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
],

View File

@ -0,0 +1,52 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/prysmaticlabs/prysm/v4/validator/client/iface (interfaces: PrysmBeaconChainClient)
// Package validator_mock is a generated GoMock package.
package validator_mock
import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
validator "github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
iface "github.com/prysmaticlabs/prysm/v4/validator/client/iface"
)
// MockPrysmBeaconChainClient is a mock of PrysmBeaconChainClient interface.
type MockPrysmBeaconChainClient struct {
ctrl *gomock.Controller
recorder *MockPrysmBeaconChainClientMockRecorder
}
// MockPrysmBeaconChainClientMockRecorder is the mock recorder for MockPrysmBeaconChainClient.
type MockPrysmBeaconChainClientMockRecorder struct {
mock *MockPrysmBeaconChainClient
}
// NewMockPrysmBeaconChainClient creates a new mock instance.
func NewMockPrysmBeaconChainClient(ctrl *gomock.Controller) *MockPrysmBeaconChainClient {
mock := &MockPrysmBeaconChainClient{ctrl: ctrl}
mock.recorder = &MockPrysmBeaconChainClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPrysmBeaconChainClient) EXPECT() *MockPrysmBeaconChainClientMockRecorder {
return m.recorder
}
// GetValidatorCount mocks base method.
func (m *MockPrysmBeaconChainClient) GetValidatorCount(arg0 context.Context, arg1 string, arg2 []validator.ValidatorStatus) ([]iface.ValidatorCount, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetValidatorCount", arg0, arg1, arg2)
ret0, _ := ret[0].([]iface.ValidatorCount)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetValidatorCount indicates an expected call of GetValidatorCount.
func (mr *MockPrysmBeaconChainClientMockRecorder) GetValidatorCount(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidatorCount", reflect.TypeOf((*MockPrysmBeaconChainClient)(nil).GetValidatorCount), arg0, arg1, arg2)
}

View File

@ -41,6 +41,7 @@ go_library(
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
"//crypto/bls:go_default_library",
"//crypto/hash:go_default_library",
"//crypto/rand:go_default_library",

View File

@ -24,6 +24,7 @@ go_library(
"propose_attestation.go",
"propose_beacon_block.go",
"propose_exit.go",
"prysm_beacon_chain_client.go",
"registration.go",
"state_validators.go",
"status.go",
@ -48,6 +49,7 @@ go_library(
"//beacon-chain/rpc/prysm/validator:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
"//encoding/bytesutil:go_default_library",
"//network/forks:go_default_library",
"//proto/engine/v1:go_default_library",
@ -105,6 +107,7 @@ go_test(
"submit_signed_contribution_and_proof_test.go",
"subscribe_committee_subnets_test.go",
"sync_committee_test.go",
"validator_count_test.go",
"wait_for_chain_start_test.go",
],
embed = [":go_default_library"],
@ -118,14 +121,17 @@ go_test(
"//beacon-chain/rpc/prysm/validator:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/validator-mock:go_default_library",
"//time/slots:go_default_library",
"//validator/client/beacon-api/mock:go_default_library",
"//validator/client/beacon-api/test-helpers:go_default_library",
"//validator/client/iface:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_golang_mock//gomock:go_default_library",
"@com_github_golang_protobuf//ptypes/empty",

View File

@ -20,6 +20,7 @@ type beaconApiValidatorClient struct {
stateValidatorsProvider stateValidatorsProvider
jsonRestHandler jsonRestHandler
beaconBlockConverter beaconBlockConverter
prysmBeaconChainCLient iface.PrysmBeaconChainClient
}
func NewBeaconApiValidatorClient(host string, timeout time.Duration) iface.ValidatorClient {
@ -34,6 +35,10 @@ func NewBeaconApiValidatorClient(host string, timeout time.Duration) iface.Valid
stateValidatorsProvider: beaconApiStateValidatorsProvider{jsonRestHandler: jsonRestHandler},
jsonRestHandler: jsonRestHandler,
beaconBlockConverter: beaconApiBeaconBlockConverter{},
prysmBeaconChainCLient: prysmBeaconChainClient{
nodeClient: &beaconApiNodeClient{jsonRestHandler: jsonRestHandler},
jsonRestHandler: jsonRestHandler,
},
}
}

View File

@ -9,6 +9,9 @@ import (
"strconv"
"testing"
validatormock "github.com/prysmaticlabs/prysm/v4/testing/validator-mock"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/mock/gomock"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/beacon"
@ -1259,10 +1262,21 @@ func TestGetDuties_Valid(t *testing.T) {
nil,
).MinTimes(1)
prysmBeaconChainClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
prysmBeaconChainClient.EXPECT().GetValidatorCount(
ctx,
gomock.Any(),
gomock.Any(),
).Return(
nil,
iface.ErrNotSupported,
).MinTimes(1)
// Make sure that our values are equal to what would be returned by calling getDutiesForEpoch individually
validatorClient := &beaconApiValidatorClient{
dutiesProvider: dutiesProvider,
stateValidatorsProvider: stateValidatorsProvider,
prysmBeaconChainCLient: prysmBeaconChainClient,
}
expectedCurrentEpochDuties, err := validatorClient.getDutiesForEpoch(
@ -1356,9 +1370,20 @@ func TestGetDuties_GetDutiesForEpochFailed(t *testing.T) {
errors.New("foo error"),
).Times(1)
prysmBeaconChainClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
prysmBeaconChainClient.EXPECT().GetValidatorCount(
ctx,
gomock.Any(),
gomock.Any(),
).Return(
nil,
iface.ErrNotSupported,
).MinTimes(1)
validatorClient := &beaconApiValidatorClient{
stateValidatorsProvider: stateValidatorsProvider,
dutiesProvider: dutiesProvider,
prysmBeaconChainCLient: prysmBeaconChainClient,
}
_, err := validatorClient.getDuties(ctx, &ethpb.DutiesRequest{

View File

@ -9,8 +9,8 @@ import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/validator"
shared "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
validator "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/validator"
primitives "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
)

View File

@ -10,7 +10,7 @@ import (
gomock "github.com/golang/mock/gomock"
apimiddleware "github.com/prysmaticlabs/prysm/v4/api/gateway/apimiddleware"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/beacon"
beacon "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/beacon"
)
// MockgenesisProvider is a mock of genesisProvider interface.

View File

@ -0,0 +1,81 @@
package beacon_api
import (
"context"
"fmt"
"net/http"
neturl "net/url"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/prysm/validator"
validator2 "github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
)
// NewPrysmBeaconChainClient returns implementation of iface.PrysmBeaconChainClient.
func NewPrysmBeaconChainClient(host string, timeout time.Duration, nodeClient iface.NodeClient) iface.PrysmBeaconChainClient {
jsonRestHandler := beaconApiJsonRestHandler{
httpClient: http.Client{Timeout: timeout},
host: host,
}
return prysmBeaconChainClient{
jsonRestHandler: jsonRestHandler,
nodeClient: nodeClient,
}
}
type prysmBeaconChainClient struct {
jsonRestHandler jsonRestHandler
nodeClient iface.NodeClient
}
func (c prysmBeaconChainClient) GetValidatorCount(ctx context.Context, stateID string, statuses []validator2.ValidatorStatus) ([]iface.ValidatorCount, error) {
// Check node version for prysm beacon node as it is a custom endpoint for prysm beacon node.
nodeVersion, err := c.nodeClient.GetVersion(ctx, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to get node version")
}
if !strings.Contains(strings.ToLower(nodeVersion.Version), "prysm") {
return nil, iface.ErrNotSupported
}
queryParams := neturl.Values{}
for _, status := range statuses {
queryParams.Add("status", status.String())
}
queryUrl := buildURL(fmt.Sprintf("/eth/v1/beacon/states/%s/validator_count", stateID), queryParams)
var validatorCountResponse validator.ValidatorCountResponse
if _, err := c.jsonRestHandler.GetRestJsonResponse(ctx, queryUrl, &validatorCountResponse); err != nil {
return nil, errors.Wrap(err, "failed to query GET REST endpoint")
}
if validatorCountResponse.Data == nil {
return nil, errors.New("validator count data is nil")
}
if len(statuses) != 0 && len(statuses) != len(validatorCountResponse.Data) {
return nil, errors.New("mismatch between validator count data and the number of statuses provided")
}
var resp []iface.ValidatorCount
for _, vc := range validatorCountResponse.Data {
count, err := strconv.ParseUint(vc.Count, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse validator count %s", vc.Count)
}
resp = append(resp, iface.ValidatorCount{
Status: vc.Status,
Count: count,
})
}
return resp, nil
}

View File

@ -4,6 +4,8 @@ import (
"context"
"strconv"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/config/params"
@ -73,12 +75,32 @@ func (c *beaconApiValidatorClient) getValidatorsStatusResponse(ctx context.Conte
return nil, nil, nil, errors.Wrap(err, "failed to get state validators")
}
isLastActivatedValidatorIndexRetrieved := false
var lastActivatedValidatorIndex uint64 = 0
validatorsCountResponse, err := c.prysmBeaconChainCLient.GetValidatorCount(ctx, "head", nil)
if err != nil && !errors.Is(err, iface.ErrNotSupported) {
return nil, nil, nil, errors.Wrap(err, "failed to get total validator count")
}
var total, pending uint64
for _, valCount := range validatorsCountResponse {
if valCount.Status == "pending" {
pending = valCount.Count
} else {
total += valCount.Count
}
}
// Calculate last activated validator's index, it will be -1 whenever all validators are pending.
lastActivatedValidatorIndex := int(total - pending - 1)
for i, validatorContainer := range stateValidatorsResponse.Data {
stringPubKey := validatorContainer.Validator.Pubkey
validatorIndex, err := strconv.ParseUint(validatorContainer.Index, 10, 64)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "failed to parse validator index %s", validatorContainer.Index)
}
outIndexes[i] = primitives.ValidatorIndex(validatorIndex)
stringPubKey := validatorContainer.Validator.Pubkey
stringRetrievedPubKeys[stringPubKey] = struct{}{}
pubKey, ok := stringTargetPubKeysToPubKeys[stringPubKey]
@ -90,14 +112,7 @@ func (c *beaconApiValidatorClient) getValidatorsStatusResponse(ctx context.Conte
}
}
validatorIndex, err := strconv.ParseUint(validatorContainer.Index, 10, 64)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "failed to parse validator index %s", validatorContainer.Index)
}
outPubKeys[i] = pubKey
outIndexes[i] = primitives.ValidatorIndex(validatorIndex)
validatorStatus := &ethpb.ValidatorStatusResponse{}
// Set Status
@ -119,28 +134,9 @@ func (c *beaconApiValidatorClient) getValidatorsStatusResponse(ctx context.Conte
// Set PositionInActivationQueue
switch status {
case ethpb.ValidatorStatus_PENDING, ethpb.ValidatorStatus_PARTIALLY_DEPOSITED, ethpb.ValidatorStatus_DEPOSITED:
if !isLastActivatedValidatorIndexRetrieved {
isLastActivatedValidatorIndexRetrieved = true
// TODO: double check this due to potential of PENDING STATE being active..
// edge case https://github.com/prysmaticlabs/prysm/blob/0669050ffabe925c3d6e5e5d535a86361ae8522b/validator/client/validator.go#L1068
activeStateValidators, err := c.stateValidatorsProvider.GetStateValidators(ctx, nil, nil, []string{"active"})
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to get state validators")
if lastActivatedValidatorIndex >= 0 {
validatorStatus.PositionInActivationQueue = validatorIndex - uint64(lastActivatedValidatorIndex)
}
data := activeStateValidators.Data
if nbActiveValidators := len(data); nbActiveValidators != 0 {
lastValidator := data[nbActiveValidators-1]
lastActivatedValidatorIndex, err = strconv.ParseUint(lastValidator.Index, 10, 64)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "failed to parse last validator index %s", lastValidator.Index)
}
}
}
validatorStatus.PositionInActivationQueue = validatorIndex - lastActivatedValidatorIndex
}
outValidatorsStatuses[i] = validatorStatus

View File

@ -6,6 +6,10 @@ import (
"fmt"
"testing"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware"
validator2 "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/prysm/validator"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/mock/gomock"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/beacon"
@ -50,7 +54,26 @@ func TestValidatorStatus_Nominal(t *testing.T) {
nil,
).Times(1)
validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider}
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
validatorClient := beaconApiValidatorClient{
stateValidatorsProvider: stateValidatorsProvider,
prysmBeaconChainCLient: prysmBeaconChainClient{
nodeClient: &beaconApiNodeClient{
jsonRestHandler: jsonRestHandler,
},
},
}
// Expect node version endpoint call.
var nodeVersionResponse apimiddleware.VersionResponseJson
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
"/eth/v1/node/version",
&nodeVersionResponse,
).Return(
nil,
iface.ErrNotSupported,
).Times(1)
actualValidatorStatusResponse, err := validatorClient.ValidatorStatus(
ctx,
@ -147,7 +170,27 @@ func TestMultipleValidatorStatus_Nominal(t *testing.T) {
nil,
).Times(1)
validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider}
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
// Expect node version endpoint call.
var nodeVersionResponse apimiddleware.VersionResponseJson
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
"/eth/v1/node/version",
&nodeVersionResponse,
).Return(
nil,
iface.ErrNotSupported,
).Times(1)
validatorClient := beaconApiValidatorClient{
stateValidatorsProvider: stateValidatorsProvider,
prysmBeaconChainCLient: prysmBeaconChainClient{
nodeClient: &beaconApiNodeClient{
jsonRestHandler: jsonRestHandler,
},
},
}
expectedValidatorStatusResponse := ethpb.MultipleValidatorStatusResponse{
PublicKeys: validatorsPubKey,
@ -289,33 +332,44 @@ func TestGetValidatorsStatusResponse_Nominal_SomeActiveValidators(t *testing.T)
nil,
).Times(1)
stateValidatorsProvider.EXPECT().GetStateValidators(
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
// Expect node version endpoint call.
var nodeVersionResponse apimiddleware.VersionResponseJson
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
nil,
nil,
[]string{"active"},
"/eth/v1/node/version",
&nodeVersionResponse,
).Return(
&beacon.GetValidatorsResponse{
Data: []*beacon.ValidatorContainer{
{
Index: "35000",
Status: "active_ongoing",
Validator: &beacon.Validator{
Pubkey: "0x8000ab56b051f9d8f31c687528c6e91c9b98e4c3a241e752f9ccfbea7c5a7fbbd272bdf2c0a7e52ce7e0b57693df364d",
ActivationEpoch: "56",
},
},
{
Index: "39000",
Status: "active_ongoing",
Validator: &beacon.Validator{
Pubkey: "0x8000ab56b051f9d8f31c687528c6e91c9b98e4c3a241e752f9ccfbea7c5a7fbbd272bdf2c0a7e52ce7e0b57693df364e",
ActivationEpoch: "56",
},
},
},
},
nil,
nil,
).SetArg(
2,
apimiddleware.VersionResponseJson{Data: &apimiddleware.VersionJson{Version: "prysm/v0.0.1"}},
).Times(1)
var validatorCountResponse validator2.ValidatorCountResponse
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
"/eth/v1/beacon/states/head/validator_count?",
&validatorCountResponse,
).Return(
nil,
nil,
).SetArg(
2,
validator2.ValidatorCountResponse{
Data: []*validator2.ValidatorCount{
{
Status: "active",
Count: "50001",
},
{
Status: "pending",
Count: "11000",
},
},
},
).Times(1)
wantedStringValidatorsPubkey := []string{
@ -379,7 +433,15 @@ func TestGetValidatorsStatusResponse_Nominal_SomeActiveValidators(t *testing.T)
},
}
validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider}
validatorClient := beaconApiValidatorClient{
stateValidatorsProvider: stateValidatorsProvider,
prysmBeaconChainCLient: prysmBeaconChainClient{
nodeClient: &beaconApiNodeClient{
jsonRestHandler: jsonRestHandler,
},
jsonRestHandler: jsonRestHandler,
},
}
actualValidatorsPubKey, actualValidatorsIndex, actualValidatorsStatusResponse, err := validatorClient.getValidatorsStatusResponse(ctx, validatorsPubKey, validatorsIndex)
require.NoError(t, err)
@ -420,16 +482,17 @@ func TestGetValidatorsStatusResponse_Nominal_NoActiveValidators(t *testing.T) {
nil,
).Times(1)
stateValidatorsProvider.EXPECT().GetStateValidators(
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
// Expect node version endpoint call.
var nodeVersionResponse apimiddleware.VersionResponseJson
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
nil,
nil,
[]string{"active"},
"/eth/v1/node/version",
&nodeVersionResponse,
).Return(
&beacon.GetValidatorsResponse{
Data: []*beacon.ValidatorContainer{},
},
nil,
iface.ErrNotSupported,
).Times(1)
wantedValidatorsPubKey := [][]byte{validatorPubKey}
@ -438,11 +501,18 @@ func TestGetValidatorsStatusResponse_Nominal_NoActiveValidators(t *testing.T) {
{
Status: ethpb.ValidatorStatus_PENDING,
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
PositionInActivationQueue: 40000,
},
}
validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider}
validatorClient := beaconApiValidatorClient{
stateValidatorsProvider: stateValidatorsProvider,
prysmBeaconChainCLient: prysmBeaconChainClient{
nodeClient: &beaconApiNodeClient{
jsonRestHandler: jsonRestHandler,
},
jsonRestHandler: jsonRestHandler,
},
}
actualValidatorsPubKey, actualValidatorsIndex, actualValidatorsStatusResponse, err := validatorClient.getValidatorsStatusResponse(ctx, wantedValidatorsPubKey, nil)
require.NoError(t, err)
@ -474,26 +544,21 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
// Inputs
inputPubKeys [][]byte
inputIndexes []int64
inputGetStateValidatorsInterfaces []getStateValidatorsInterface
inputGetStateValidatorsInterface getStateValidatorsInterface
validatorCountCalled int
// Outputs
outputErrMessage string
}{
{
name: "failed getStateValidators",
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputGetStateValidatorsInterface: getStateValidatorsInterface{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
outputStateValidatorsResponseJson: &beacon.GetValidatorsResponse{},
outputErr: errors.New("a specific error"),
},
},
outputErrMessage: "failed to get state validators",
},
{
@ -501,8 +566,7 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputGetStateValidatorsInterface: getStateValidatorsInterface{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
@ -510,6 +574,7 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
outputStateValidatorsResponseJson: &beacon.GetValidatorsResponse{
Data: []*beacon.ValidatorContainer{
{
Index: "0",
Validator: &beacon.Validator{
Pubkey: "NotAPublicKey",
},
@ -518,7 +583,7 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
},
outputErr: nil,
},
},
validatorCountCalled: 1,
outputErrMessage: "failed to parse validator public key",
},
{
@ -526,8 +591,8 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputGetStateValidatorsInterface: getStateValidatorsInterface{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
@ -544,7 +609,7 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
},
outputErr: nil,
},
},
validatorCountCalled: 1,
outputErrMessage: "failed to parse validator index",
},
{
@ -552,8 +617,7 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputGetStateValidatorsInterface: getStateValidatorsInterface{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
@ -571,7 +635,7 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
},
outputErr: nil,
},
},
validatorCountCalled: 1,
outputErrMessage: "invalid validator status NotAStatus",
},
{
@ -579,8 +643,7 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputGetStateValidatorsInterface: getStateValidatorsInterface{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
@ -599,7 +662,7 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
},
outputErr: nil,
},
},
validatorCountCalled: 1,
outputErrMessage: "failed to parse activation epoch NotAnEpoch",
},
{
@ -607,35 +670,14 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputGetStateValidatorsInterface: getStateValidatorsInterface{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
outputStateValidatorsResponseJson: &beacon.GetValidatorsResponse{
Data: []*beacon.ValidatorContainer{
{
Index: "12345",
Status: "pending_queued",
Validator: &beacon.Validator{
Pubkey: stringPubKey,
ActivationEpoch: "10",
},
},
},
},
outputErr: nil,
},
{
inputStringPubKeys: nil,
inputIndexes: nil,
inputStatuses: []string{"active"},
outputStateValidatorsResponseJson: &beacon.GetValidatorsResponse{},
outputStateValidatorsResponseJson: nil,
outputErr: errors.New("a specific error"),
},
},
outputErrMessage: "failed to get state validators",
},
{
@ -643,31 +685,11 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputGetStateValidatorsInterface: getStateValidatorsInterface{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
outputStateValidatorsResponseJson: &beacon.GetValidatorsResponse{
Data: []*beacon.ValidatorContainer{
{
Index: "12345",
Status: "pending_queued",
Validator: &beacon.Validator{
Pubkey: stringPubKey,
ActivationEpoch: "10",
},
},
},
},
outputErr: nil,
},
{
inputStringPubKeys: nil,
inputIndexes: nil,
inputStatuses: []string{"active"},
outputStateValidatorsResponseJson: &beacon.GetValidatorsResponse{
Data: []*beacon.ValidatorContainer{
{
@ -677,8 +699,8 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
},
outputErr: nil,
},
},
outputErrMessage: "failed to parse last validator index NotAnIndex",
validatorCountCalled: 1,
outputErrMessage: "failed to parse validator index NotAnIndex",
},
}
@ -690,20 +712,38 @@ func TestValidatorStatusResponse_InvalidData(t *testing.T) {
ctx := context.Background()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
for _, aa := range testCase.inputGetStateValidatorsInterfaces {
stateValidatorsProvider.EXPECT().GetStateValidators(
ctx,
aa.inputStringPubKeys,
aa.inputIndexes,
aa.inputStatuses,
testCase.inputGetStateValidatorsInterface.inputStringPubKeys,
testCase.inputGetStateValidatorsInterface.inputIndexes,
testCase.inputGetStateValidatorsInterface.inputStatuses,
).Return(
aa.outputStateValidatorsResponseJson,
aa.outputErr,
testCase.inputGetStateValidatorsInterface.outputStateValidatorsResponseJson,
testCase.inputGetStateValidatorsInterface.outputErr,
).Times(1)
}
validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider}
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
// Expect node version endpoint call.
var nodeVersionResponse apimiddleware.VersionResponseJson
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
"/eth/v1/node/version",
&nodeVersionResponse,
).Return(
nil,
iface.ErrNotSupported,
).Times(testCase.validatorCountCalled)
validatorClient := beaconApiValidatorClient{
stateValidatorsProvider: stateValidatorsProvider,
prysmBeaconChainCLient: prysmBeaconChainClient{
nodeClient: &beaconApiNodeClient{
jsonRestHandler: jsonRestHandler,
},
jsonRestHandler: jsonRestHandler,
},
}
_, _, _, err := validatorClient.getValidatorsStatusResponse(
ctx,

View File

@ -0,0 +1,163 @@
package beacon_api
import (
"context"
"errors"
"testing"
"github.com/golang/mock/gomock"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware"
validator2 "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/prysm/validator"
"github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/validator/client/beacon-api/mock"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
)
func TestGetValidatorCount(t *testing.T) {
const nodeVersion = "prysm/v0.0.1"
testCases := []struct {
name string
versionEndpointError error
validatorCountEndpointError error
versionResponse apimiddleware.VersionResponseJson
validatorCountResponse validator2.ValidatorCountResponse
validatorCountCalled int
expectedResponse []iface.ValidatorCount
expectedError string
}{
{
name: "success",
versionResponse: apimiddleware.VersionResponseJson{
Data: &apimiddleware.VersionJson{Version: nodeVersion},
},
validatorCountResponse: validator2.ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*validator2.ValidatorCount{
{
Status: "active",
Count: "10",
},
},
},
validatorCountCalled: 1,
expectedResponse: []iface.ValidatorCount{
{
Status: "active",
Count: 10,
},
},
},
{
name: "not supported beacon node",
versionResponse: apimiddleware.VersionResponseJson{
Data: &apimiddleware.VersionJson{Version: "lighthouse/v0.0.1"},
},
expectedError: "endpoint not supported",
},
{
name: "fails to get version",
versionEndpointError: errors.New("foo error"),
expectedError: "failed to get node version",
},
{
name: "fails to get validator count",
versionResponse: apimiddleware.VersionResponseJson{
Data: &apimiddleware.VersionJson{Version: nodeVersion},
},
validatorCountEndpointError: errors.New("foo error"),
validatorCountCalled: 1,
expectedError: "foo error",
},
{
name: "nil validator count data",
versionResponse: apimiddleware.VersionResponseJson{
Data: &apimiddleware.VersionJson{Version: nodeVersion},
},
validatorCountResponse: validator2.ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: nil,
},
validatorCountCalled: 1,
expectedError: "validator count data is nil",
},
{
name: "invalid validator count",
versionResponse: apimiddleware.VersionResponseJson{
Data: &apimiddleware.VersionJson{Version: nodeVersion},
},
validatorCountResponse: validator2.ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*validator2.ValidatorCount{
{
Status: "active",
Count: "10",
},
{
Status: "exited",
Count: "10",
},
},
},
validatorCountCalled: 1,
expectedError: "mismatch between validator count data and the number of statuses provided",
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
// Expect node version endpoint call.
var nodeVersionResponse apimiddleware.VersionResponseJson
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
"/eth/v1/node/version",
&nodeVersionResponse,
).Return(
nil,
test.versionEndpointError,
).SetArg(
2,
test.versionResponse,
)
var validatorCountResponse validator2.ValidatorCountResponse
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
"/eth/v1/beacon/states/head/validator_count?status=active",
&validatorCountResponse,
).Return(
nil,
test.validatorCountEndpointError,
).SetArg(
2,
test.validatorCountResponse,
).Times(test.validatorCountCalled)
// Type assertion.
var client iface.PrysmBeaconChainClient = &prysmBeaconChainClient{
nodeClient: &beaconApiNodeClient{jsonRestHandler: jsonRestHandler},
jsonRestHandler: jsonRestHandler,
}
countResponse, err := client.GetValidatorCount(ctx, "head", []validator.ValidatorStatus{validator.Active})
if len(test.expectedResponse) == 0 {
require.ErrorContains(t, test.expectedError, err)
} else {
require.NoError(t, err)
require.DeepEqual(t, test.expectedResponse, countResponse)
}
})
}
}

View File

@ -10,6 +10,7 @@ go_library(
"//validator/client/beacon-api:go_default_library",
"//validator/client/grpc-api:go_default_library",
"//validator/client/iface:go_default_library",
"//validator/client/node-client-factory:go_default_library",
"//validator/helpers:go_default_library",
],
)

View File

@ -5,6 +5,7 @@ import (
beaconApi "github.com/prysmaticlabs/prysm/v4/validator/client/beacon-api"
grpcApi "github.com/prysmaticlabs/prysm/v4/validator/client/grpc-api"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
nodeClientFactory "github.com/prysmaticlabs/prysm/v4/validator/client/node-client-factory"
validatorHelpers "github.com/prysmaticlabs/prysm/v4/validator/helpers"
)
@ -13,8 +14,26 @@ func NewBeaconChainClient(validatorConn validatorHelpers.NodeConnection) iface.B
featureFlags := features.Get()
if featureFlags.EnableBeaconRESTApi {
return beaconApi.NewBeaconApiBeaconChainClientWithFallback(validatorConn.GetBeaconApiUrl(), validatorConn.GetBeaconApiTimeout(), grpcClient)
return beaconApi.NewBeaconApiBeaconChainClientWithFallback(
validatorConn.GetBeaconApiUrl(),
validatorConn.GetBeaconApiTimeout(),
grpcClient,
)
} else {
return grpcClient
}
}
func NewPrysmBeaconClient(validatorConn validatorHelpers.NodeConnection) iface.PrysmBeaconChainClient {
featureFlags := features.Get()
if featureFlags.EnableBeaconRESTApi {
return beaconApi.NewPrysmBeaconChainClient(
validatorConn.GetBeaconApiUrl(),
validatorConn.GetBeaconApiTimeout(),
nodeClientFactory.NewNodeClient(validatorConn),
)
} else {
return grpcApi.NewGrpcPrysmBeaconChainClient(validatorConn.GetGrpcClientConn())
}
}

View File

@ -5,12 +5,17 @@ go_library(
srcs = [
"grpc_beacon_chain_client.go",
"grpc_node_client.go",
"grpc_prysm_beacon_chain_client.go",
"grpc_validator_client.go",
],
importpath = "github.com/prysmaticlabs/prysm/v4/validator/client/grpc-api",
visibility = ["//validator:__subpackages__"],
deps = [
"//beacon-chain/rpc/eth/helpers:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//validator/client/iface:go_default_library",
"@com_github_golang_protobuf//ptypes/empty",
@ -22,11 +27,22 @@ go_library(
go_test(
name = "go_default_test",
size = "small",
srcs = ["grpc_validator_client_test.go"],
srcs = [
"grpc_prysm_beacon_chain_client_test.go",
"grpc_validator_client_test.go",
],
embed = [":go_default_library"],
deps = [
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//testing/assert:go_default_library",
"//testing/mock:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//testing/validator-mock:go_default_library",
"//validator/client/iface:go_default_library",
"@com_github_golang_mock//gomock:go_default_library",
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
],

View File

@ -0,0 +1,97 @@
package grpc_api
import (
"context"
"fmt"
"sort"
"github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
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"
eth "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
"google.golang.org/grpc"
)
type grpcPrysmBeaconChainClient struct {
beaconChainClient iface.BeaconChainClient
}
func (g grpcPrysmBeaconChainClient) GetValidatorCount(ctx context.Context, _ string, statuses []validator.ValidatorStatus) ([]iface.ValidatorCount, error) {
resp, err := g.beaconChainClient.ListValidators(ctx, &ethpb.ListValidatorsRequest{PageSize: 0})
if err != nil {
return nil, errors.Wrap(err, "list validators failed")
}
var vals []*ethpb.Validator
for _, val := range resp.ValidatorList {
vals = append(vals, val.Validator)
}
head, err := g.beaconChainClient.GetChainHead(ctx, &empty.Empty{})
if err != nil {
return nil, errors.Wrap(err, "get chain head")
}
if len(statuses) == 0 {
for _, val := range eth.ValidatorStatus_value {
statuses = append(statuses, validator.ValidatorStatus(val))
}
}
valCount, err := validatorCountByStatus(vals, statuses, head.HeadEpoch)
if err != nil {
return nil, errors.Wrap(err, "validator count by status")
}
return valCount, nil
}
// validatorCountByStatus returns a slice of validator count for each status in the given epoch.
func validatorCountByStatus(validators []*ethpb.Validator, statuses []validator.ValidatorStatus, epoch primitives.Epoch) ([]iface.ValidatorCount, error) {
countByStatus := make(map[validator.ValidatorStatus]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 []iface.ValidatorCount
for status, count := range countByStatus {
resp = append(resp, iface.ValidatorCount{
Status: status.String(),
Count: count,
})
}
// 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
}
func NewGrpcPrysmBeaconChainClient(cc grpc.ClientConnInterface) iface.PrysmBeaconChainClient {
return &grpcPrysmBeaconChainClient{beaconChainClient: &grpcBeaconChainClient{ethpb.NewBeaconChainClient(cc)}}
}

View File

@ -0,0 +1,326 @@
package grpc_api
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/testing/util"
mock "github.com/prysmaticlabs/prysm/v4/testing/validator-mock"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
)
func TestGetValidatorCount(t *testing.T) {
st, _ := util.DeterministicGenesisState(t, 10)
farFutureEpoch := params.BeaconConfig().FarFutureEpoch
validators := []*ethpb.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
statuses []string
currentEpoch int
expectedResponse []iface.ValidatorCount
}{
{
name: "Head count active validators",
statuses: []string{"active"},
expectedResponse: []iface.ValidatorCount{
{
Status: "active",
Count: 13,
},
},
},
{
name: "Head count active ongoing validators",
statuses: []string{"active_ongoing"},
expectedResponse: []iface.ValidatorCount{
{
Status: "active_ongoing",
Count: 11,
},
},
},
{
name: "Head count active exiting validators",
statuses: []string{"active_exiting"},
expectedResponse: []iface.ValidatorCount{
{
Status: "active_exiting",
Count: 1,
},
},
},
{
name: "Head count active slashed validators",
statuses: []string{"active_slashed"},
expectedResponse: []iface.ValidatorCount{
{
Status: "active_slashed",
Count: 1,
},
},
},
{
name: "Head count pending validators",
statuses: []string{"pending"},
expectedResponse: []iface.ValidatorCount{
{
Status: "pending",
Count: 6,
},
},
},
{
name: "Head count pending initialized validators",
statuses: []string{"pending_initialized"},
expectedResponse: []iface.ValidatorCount{
{
Status: "pending_initialized",
Count: 1,
},
},
},
{
name: "Head count pending queued validators",
statuses: []string{"pending_queued"},
expectedResponse: []iface.ValidatorCount{
{
Status: "pending_queued",
Count: 5,
},
},
},
{
name: "Head count exited validators",
statuses: []string{"exited"},
currentEpoch: 35,
expectedResponse: []iface.ValidatorCount{
{
Status: "exited",
Count: 6,
},
},
},
{
name: "Head count exited slashed validators",
statuses: []string{"exited_slashed"},
currentEpoch: 35,
expectedResponse: []iface.ValidatorCount{
{
Status: "exited_slashed",
Count: 2,
},
},
},
{
name: "Head count exited unslashed validators",
statuses: []string{"exited_unslashed"},
currentEpoch: 35,
expectedResponse: []iface.ValidatorCount{
{
Status: "exited_unslashed",
Count: 4,
},
},
},
{
name: "Head count withdrawal validators",
statuses: []string{"withdrawal"},
currentEpoch: 45,
expectedResponse: []iface.ValidatorCount{
{
Status: "withdrawal",
Count: 2,
},
},
},
{
name: "Head count withdrawal possible validators",
statuses: []string{"withdrawal_possible"},
currentEpoch: 45,
expectedResponse: []iface.ValidatorCount{
{
Status: "withdrawal_possible",
Count: 1,
},
},
},
{
name: "Head count withdrawal done validators",
statuses: []string{"withdrawal_done"},
currentEpoch: 45,
expectedResponse: []iface.ValidatorCount{
{
Status: "withdrawal_done",
Count: 1,
},
},
},
{
name: "Head count active and pending validators",
statuses: []string{"active", "pending"},
expectedResponse: []iface.ValidatorCount{
{
Status: "active",
Count: 13,
},
{
Status: "pending",
Count: 6,
},
},
},
{
name: "Head count of ALL validators",
expectedResponse: []iface.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) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
listValidatorResp := &ethpb.Validators{}
for _, val := range st.Validators() {
listValidatorResp.ValidatorList = append(listValidatorResp.ValidatorList, &ethpb.Validators_ValidatorContainer{
Validator: val,
})
}
beaconChainClient := mock.NewMockBeaconChainClient(ctrl)
beaconChainClient.EXPECT().ListValidators(
gomock.Any(),
gomock.Any(),
).Return(
listValidatorResp,
nil,
)
beaconChainClient.EXPECT().GetChainHead(
gomock.Any(),
gomock.Any(),
).Return(
&ethpb.ChainHead{HeadEpoch: primitives.Epoch(test.currentEpoch)},
nil,
)
prysmBeaconChainClient := &grpcPrysmBeaconChainClient{
beaconChainClient: beaconChainClient,
}
var statuses []validator.ValidatorStatus
for _, status := range test.statuses {
ok, valStatus := validator.ValidatorStatusFromString(status)
require.Equal(t, true, ok)
statuses = append(statuses, valStatus)
}
vcCountResp, err := prysmBeaconChainClient.GetValidatorCount(context.Background(), "", statuses)
require.NoError(t, err)
require.DeepEqual(t, test.expectedResponse, vcCountResp)
})
}
}

View File

@ -5,19 +5,22 @@ go_library(
srcs = [
"beacon_chain_client.go",
"node_client.go",
"prysm_beacon_chain_client.go",
"validator.go",
"validator_client.go",
],
importpath = "github.com/prysmaticlabs/prysm/v4/validator/client/iface",
visibility = ["//validator:__subpackages__"],
visibility = ["//visibility:public"],
deps = [
"//config/fieldparams:go_default_library",
"//config/validator/service:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
"//crypto/bls:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//validator/keymanager:go_default_library",
"@com_github_golang_protobuf//ptypes/empty",
"@com_github_pkg_errors//:go_default_library",
],
)

View File

@ -0,0 +1,20 @@
package iface
import (
"context"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
)
var ErrNotSupported = errors.New("endpoint not supported")
type ValidatorCount struct {
Status string
Count uint64
}
// PrysmBeaconChainClient defines an interface required to implement all the prysm specific custom endpoints.
type PrysmBeaconChainClient interface {
GetValidatorCount(context.Context, string, []validator.ValidatorStatus) ([]ValidatorCount, error)
}

View File

@ -4,7 +4,10 @@ import (
"context"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
validator2 "github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"go.opencensus.io/trace"
)
@ -33,11 +36,19 @@ func (v *validator) HandleKeyReload(ctx context.Context, currentKeys [][fieldpar
index: resp.Indices[i],
}
}
vals, err := v.beaconClient.ListValidators(ctx, &eth.ListValidatorsRequest{Active: true, PageSize: 0})
if err != nil {
// "-1" indicates that validator count endpoint is not supported by the beacon node.
var valCount int64 = -1
valCounts, err := v.prysmBeaconClient.GetValidatorCount(ctx, "head", []validator2.ValidatorStatus{validator2.Active})
if err != nil && !errors.Is(err, iface.ErrNotSupported) {
return false, errors.Wrap(err, "could not get active validator count")
}
anyActive = v.checkAndLogValidatorStatus(statuses, uint64(vals.TotalSize))
if len(valCounts) > 0 {
valCount = int64(valCounts[0].Count)
}
anyActive = v.checkAndLogValidatorStatus(statuses, valCount)
if anyActive {
logActiveValidatorStatus(statuses)
}

View File

@ -4,6 +4,9 @@ import (
"context"
"testing"
validator2 "github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
"github.com/golang/mock/gomock"
"github.com/pkg/errors"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
@ -27,11 +30,13 @@ func TestValidator_HandleKeyReload(t *testing.T) {
client := validatormock.NewMockValidatorClient(ctrl)
beaconClient := validatormock.NewMockBeaconChainClient(ctrl)
prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
v := validator{
validatorClient: client,
keyManager: newMockKeymanager(t, inactive),
genesisTime: 1,
beaconClient: beaconClient,
prysmBeaconClient: prysmBeaconClient,
}
resp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactive.pub[:], active.pub[:]})
@ -43,7 +48,11 @@ func TestValidator_HandleKeyReload(t *testing.T) {
PublicKeys: [][]byte{inactive.pub[:], active.pub[:]},
},
).Return(resp, nil)
beaconClient.EXPECT().ListValidators(gomock.Any(), gomock.Any()).Return(&ethpb.Validators{}, nil)
prysmBeaconClient.EXPECT().GetValidatorCount(
gomock.Any(),
"head",
[]validator2.ValidatorStatus{validator2.Active},
).Return([]iface.ValidatorCount{}, nil)
anyActive, err := v.HandleKeyReload(context.Background(), [][fieldparams.BLSPubkeyLength]byte{inactive.pub, active.pub})
require.NoError(t, err)
@ -57,12 +66,14 @@ func TestValidator_HandleKeyReload(t *testing.T) {
client := validatormock.NewMockValidatorClient(ctrl)
beaconClient := validatormock.NewMockBeaconChainClient(ctrl)
prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
kp := randKeypair(t)
v := validator{
validatorClient: client,
keyManager: newMockKeymanager(t, kp),
genesisTime: 1,
beaconClient: beaconClient,
prysmBeaconClient: prysmBeaconClient,
}
resp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{kp.pub[:]})
@ -73,7 +84,11 @@ func TestValidator_HandleKeyReload(t *testing.T) {
PublicKeys: [][]byte{kp.pub[:]},
},
).Return(resp, nil)
beaconClient.EXPECT().ListValidators(gomock.Any(), gomock.Any()).Return(&ethpb.Validators{}, nil)
prysmBeaconClient.EXPECT().GetValidatorCount(
gomock.Any(),
"head",
[]validator2.ValidatorStatus{validator2.Active},
).Return([]iface.ValidatorCount{}, nil)
anyActive, err := v.HandleKeyReload(context.Background(), [][fieldparams.BLSPubkeyLength]byte{kp.pub})
require.NoError(t, err)

View File

@ -189,6 +189,7 @@ func (v *ValidatorService) Start() {
validatorClient := validatorClientFactory.NewValidatorClient(v.conn)
beaconClient := beaconChainClientFactory.NewBeaconChainClient(v.conn)
prysmBeaconClient := beaconChainClientFactory.NewPrysmBeaconClient(v.conn)
valStruct := &validator{
db: v.db,
@ -218,6 +219,7 @@ func (v *ValidatorService) Start() {
Web3SignerConfig: v.Web3SignerConfig,
proposerSettings: v.proposerSettings,
walletInitializedChannel: make(chan *wallet.Wallet, 1),
prysmBeaconClient: prysmBeaconClient,
}
// To resolve a race condition at startup due to the interface

View File

@ -104,6 +104,7 @@ type validator struct {
Web3SignerConfig *remoteweb3signer.SetupConfig
proposerSettings *validatorserviceconfig.ProposerSettings
walletInitializedChannel chan *wallet.Wallet
prysmBeaconClient iface.PrysmBeaconChainClient
}
type validatorStatus struct {
@ -369,10 +370,7 @@ func (v *validator) ReceiveBlocks(ctx context.Context, connectionErrorChannel ch
}
}
func (v *validator) checkAndLogValidatorStatus(statuses []*validatorStatus, activeValCount uint64) bool {
activationsPerEpoch :=
uint64(math.Max(float64(params.BeaconConfig().MinPerEpochChurnLimit), float64(activeValCount/params.BeaconConfig().ChurnLimitQuotient)))
func (v *validator) checkAndLogValidatorStatus(statuses []*validatorStatus, activeValCount int64) bool {
nonexistentIndex := primitives.ValidatorIndex(^uint64(0))
var validatorActivated bool
for _, status := range statuses {
@ -398,15 +396,17 @@ func (v *validator) checkAndLogValidatorStatus(statuses []*validatorStatus, acti
).Info("Deposit processed, entering activation queue after finalization")
}
case ethpb.ValidatorStatus_PENDING:
if activeValCount >= 0 && status.status.ActivationEpoch == params.BeaconConfig().FarFutureEpoch {
activationsPerEpoch :=
uint64(math.Max(float64(params.BeaconConfig().MinPerEpochChurnLimit), float64(uint64(activeValCount)/params.BeaconConfig().ChurnLimitQuotient)))
secondsPerEpoch := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
expectedWaitingTime :=
time.Duration((status.status.PositionInActivationQueue+activationsPerEpoch)/activationsPerEpoch*secondsPerEpoch) * time.Second
if status.status.ActivationEpoch == params.BeaconConfig().FarFutureEpoch {
log.WithFields(logrus.Fields{
"positionInActivationQueue": status.status.PositionInActivationQueue,
"expectedWaitingTime": expectedWaitingTime.String(),
}).Info("Waiting to be assigned activation epoch")
} else {
} else if status.status.ActivationEpoch != params.BeaconConfig().FarFutureEpoch {
log.WithFields(logrus.Fields{
"activationEpoch": status.status.ActivationEpoch,
}).Info("Waiting for activation")

View File

@ -358,12 +358,14 @@ func TestWaitMultipleActivation_LogsActivationEpochOK(t *testing.T) {
defer ctrl.Finish()
validatorClient := validatormock.NewMockValidatorClient(ctrl)
beaconClient := validatormock.NewMockBeaconChainClient(ctrl)
prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
kp := randKeypair(t)
v := validator{
validatorClient: validatorClient,
keyManager: newMockKeymanager(t, kp),
beaconClient: beaconClient,
prysmBeaconClient: prysmBeaconClient,
}
resp := generateMockStatusResponse([][]byte{kp.pub[:]})
@ -379,7 +381,11 @@ func TestWaitMultipleActivation_LogsActivationEpochOK(t *testing.T) {
resp,
nil,
)
beaconClient.EXPECT().ListValidators(gomock.Any(), gomock.Any()).Return(&ethpb.Validators{}, nil)
prysmBeaconClient.EXPECT().GetValidatorCount(
gomock.Any(),
"head",
[]validatorType.ValidatorStatus{validatorType.Active},
).Return([]iface.ValidatorCount{}, nil)
require.NoError(t, v.WaitForActivation(ctx, nil), "Could not wait for activation")
require.LogsContain(t, hook, "Validator activated")
}
@ -389,12 +395,14 @@ func TestWaitActivation_NotAllValidatorsActivatedOK(t *testing.T) {
defer ctrl.Finish()
validatorClient := validatormock.NewMockValidatorClient(ctrl)
beaconClient := validatormock.NewMockBeaconChainClient(ctrl)
prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
kp := randKeypair(t)
v := validator{
validatorClient: validatorClient,
keyManager: newMockKeymanager(t, kp),
beaconClient: beaconClient,
prysmBeaconClient: prysmBeaconClient,
}
resp := generateMockStatusResponse([][]byte{kp.pub[:]})
resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE
@ -403,7 +411,11 @@ func TestWaitActivation_NotAllValidatorsActivatedOK(t *testing.T) {
gomock.Any(),
gomock.Any(),
).Return(clientStream, nil)
beaconClient.EXPECT().ListValidators(gomock.Any(), gomock.Any()).Return(&ethpb.Validators{}, nil).Times(2)
prysmBeaconClient.EXPECT().GetValidatorCount(
gomock.Any(),
"head",
[]validatorType.ValidatorStatus{validatorType.Active},
).Return([]iface.ValidatorCount{}, nil).Times(2)
clientStream.EXPECT().Recv().Return(
&ethpb.ValidatorActivationResponse{},
nil,

View File

@ -5,6 +5,9 @@ import (
"io"
"time"
validator2 "github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
"github.com/pkg/errors"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
@ -136,12 +139,18 @@ func (v *validator) handleAccountsChanged(ctx context.Context, accountsChangedCh
}
}
vals, err := v.beaconClient.ListValidators(ctx, &ethpb.ListValidatorsRequest{Active: true, PageSize: 0})
if err != nil {
// "-1" indicates that validator count endpoint is not supported by the beacon node.
var valCount int64 = -1
valCounts, err := v.prysmBeaconClient.GetValidatorCount(ctx, "head", []validator2.ValidatorStatus{validator2.Active})
if err != nil && !errors.Is(err, iface.ErrNotSupported) {
return errors.Wrap(err, "could not get active validator count")
}
valActivated := v.checkAndLogValidatorStatus(statuses, uint64(vals.TotalSize))
if len(valCounts) > 0 {
valCount = int64(valCounts[0].Count)
}
valActivated := v.checkAndLogValidatorStatus(statuses, valCount)
if valActivated {
logActiveValidatorStatus(statuses)
} else {

View File

@ -7,6 +7,9 @@ import (
"testing"
"time"
validatorType "github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
"github.com/golang/mock/gomock"
"github.com/pkg/errors"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
@ -57,11 +60,13 @@ func TestWaitActivation_StreamSetupFails_AttemptsToReconnect(t *testing.T) {
defer ctrl.Finish()
validatorClient := validatormock.NewMockValidatorClient(ctrl)
beaconClient := validatormock.NewMockBeaconChainClient(ctrl)
prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
kp := randKeypair(t)
v := validator{
validatorClient: validatorClient,
keyManager: newMockKeymanager(t, kp),
beaconClient: beaconClient,
prysmBeaconClient: prysmBeaconClient,
}
clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
validatorClient.EXPECT().WaitForActivation(
@ -70,7 +75,11 @@ func TestWaitActivation_StreamSetupFails_AttemptsToReconnect(t *testing.T) {
PublicKeys: [][]byte{kp.pub[:]},
},
).Return(clientStream, errors.New("failed stream")).Return(clientStream, nil)
beaconClient.EXPECT().ListValidators(gomock.Any(), gomock.Any()).Return(&ethpb.Validators{}, nil)
prysmBeaconClient.EXPECT().GetValidatorCount(
gomock.Any(),
"head",
[]validatorType.ValidatorStatus{validatorType.Active},
).Return([]iface.ValidatorCount{}, nil)
resp := generateMockStatusResponse([][]byte{kp.pub[:]})
resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE
clientStream.EXPECT().Recv().Return(resp, nil)
@ -82,11 +91,13 @@ func TestWaitForActivation_ReceiveErrorFromStream_AttemptsReconnection(t *testin
defer ctrl.Finish()
validatorClient := validatormock.NewMockValidatorClient(ctrl)
beaconClient := validatormock.NewMockBeaconChainClient(ctrl)
prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
kp := randKeypair(t)
v := validator{
validatorClient: validatorClient,
keyManager: newMockKeymanager(t, kp),
beaconClient: beaconClient,
prysmBeaconClient: prysmBeaconClient,
}
clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
validatorClient.EXPECT().WaitForActivation(
@ -95,7 +106,11 @@ func TestWaitForActivation_ReceiveErrorFromStream_AttemptsReconnection(t *testin
PublicKeys: [][]byte{kp.pub[:]},
},
).Return(clientStream, nil)
beaconClient.EXPECT().ListValidators(gomock.Any(), gomock.Any()).Return(&ethpb.Validators{}, nil)
prysmBeaconClient.EXPECT().GetValidatorCount(
gomock.Any(),
"head",
[]validatorType.ValidatorStatus{validatorType.Active},
).Return([]iface.ValidatorCount{}, nil)
// A stream fails the first time, but succeeds the second time.
resp := generateMockStatusResponse([][]byte{kp.pub[:]})
resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE
@ -112,12 +127,14 @@ func TestWaitActivation_LogsActivationEpochOK(t *testing.T) {
defer ctrl.Finish()
validatorClient := validatormock.NewMockValidatorClient(ctrl)
beaconClient := validatormock.NewMockBeaconChainClient(ctrl)
prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
kp := randKeypair(t)
v := validator{
validatorClient: validatorClient,
keyManager: newMockKeymanager(t, kp),
genesisTime: 1,
beaconClient: beaconClient,
prysmBeaconClient: prysmBeaconClient,
}
resp := generateMockStatusResponse([][]byte{kp.pub[:]})
resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE
@ -128,7 +145,11 @@ func TestWaitActivation_LogsActivationEpochOK(t *testing.T) {
PublicKeys: [][]byte{kp.pub[:]},
},
).Return(clientStream, nil)
beaconClient.EXPECT().ListValidators(gomock.Any(), gomock.Any()).Return(&ethpb.Validators{}, nil)
prysmBeaconClient.EXPECT().GetValidatorCount(
gomock.Any(),
"head",
[]validatorType.ValidatorStatus{validatorType.Active},
).Return([]iface.ValidatorCount{}, nil)
clientStream.EXPECT().Recv().Return(
resp,
nil,
@ -142,11 +163,13 @@ func TestWaitForActivation_Exiting(t *testing.T) {
defer ctrl.Finish()
validatorClient := validatormock.NewMockValidatorClient(ctrl)
beaconClient := validatormock.NewMockBeaconChainClient(ctrl)
prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
kp := randKeypair(t)
v := validator{
validatorClient: validatorClient,
keyManager: newMockKeymanager(t, kp),
beaconClient: beaconClient,
prysmBeaconClient: prysmBeaconClient,
}
resp := generateMockStatusResponse([][]byte{kp.pub[:]})
resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_EXITING
@ -157,7 +180,11 @@ func TestWaitForActivation_Exiting(t *testing.T) {
PublicKeys: [][]byte{kp.pub[:]},
},
).Return(clientStream, nil)
beaconClient.EXPECT().ListValidators(gomock.Any(), gomock.Any()).Return(&ethpb.Validators{}, nil)
prysmBeaconClient.EXPECT().GetValidatorCount(
gomock.Any(),
"head",
[]validatorType.ValidatorStatus{validatorType.Active},
).Return([]iface.ValidatorCount{}, nil)
clientStream.EXPECT().Recv().Return(
resp,
nil,
@ -177,6 +204,7 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) {
defer ctrl.Finish()
validatorClient := validatormock.NewMockValidatorClient(ctrl)
beaconClient := validatormock.NewMockBeaconChainClient(ctrl)
prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
kp := randKeypair(t)
km := newMockKeymanager(t, kp)
@ -186,6 +214,7 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) {
validatorClient: validatorClient,
keyManager: km,
beaconClient: beaconClient,
prysmBeaconClient: prysmBeaconClient,
}
resp := generateMockStatusResponse([][]byte{kp.pub[:]})
resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE
@ -196,7 +225,11 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) {
PublicKeys: [][]byte{kp.pub[:]},
},
).Return(clientStream, nil)
beaconClient.EXPECT().ListValidators(gomock.Any(), gomock.Any()).Return(&ethpb.Validators{}, nil)
prysmBeaconClient.EXPECT().GetValidatorCount(
gomock.Any(),
"head",
[]validatorType.ValidatorStatus{validatorType.Active},
).Return([]iface.ValidatorCount{}, nil)
clientStream.EXPECT().Recv().Return(
resp,
nil)
@ -217,10 +250,12 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) {
km := newMockKeymanager(t, inactive)
validatorClient := validatormock.NewMockValidatorClient(ctrl)
beaconClient := validatormock.NewMockBeaconChainClient(ctrl)
prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
v := validator{
validatorClient: validatorClient,
keyManager: km,
beaconClient: beaconClient,
prysmBeaconClient: prysmBeaconClient,
}
inactiveResp := generateMockStatusResponse([][]byte{inactive.pub[:]})
inactiveResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS
@ -231,7 +266,11 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) {
PublicKeys: [][]byte{inactive.pub[:]},
},
).Return(inactiveClientStream, nil)
beaconClient.EXPECT().ListValidators(gomock.Any(), gomock.Any()).Return(&ethpb.Validators{}, nil).AnyTimes()
prysmBeaconClient.EXPECT().GetValidatorCount(
gomock.Any(),
"head",
[]validatorType.ValidatorStatus{validatorType.Active},
).Return([]iface.ValidatorCount{}, nil).AnyTimes()
inactiveClientStream.EXPECT().Recv().Return(
inactiveResp,
nil,
@ -297,11 +336,13 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) {
require.NoError(t, err)
validatorClient := validatormock.NewMockValidatorClient(ctrl)
beaconClient := validatormock.NewMockBeaconChainClient(ctrl)
prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl)
v := validator{
validatorClient: validatorClient,
keyManager: km,
genesisTime: 1,
beaconClient: beaconClient,
prysmBeaconClient: prysmBeaconClient,
}
inactiveResp := generateMockStatusResponse([][]byte{inactivePubKey[:]})
@ -313,7 +354,11 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) {
PublicKeys: [][]byte{inactivePubKey[:]},
},
).Return(inactiveClientStream, nil)
beaconClient.EXPECT().ListValidators(gomock.Any(), gomock.Any()).Return(&ethpb.Validators{}, nil).AnyTimes()
prysmBeaconClient.EXPECT().GetValidatorCount(
gomock.Any(),
"head",
[]validatorType.ValidatorStatus{validatorType.Active},
).Return([]iface.ValidatorCount{}, nil).AnyTimes()
inactiveClientStream.EXPECT().Recv().Return(
inactiveResp,
nil,