Add REST implementation for MultipleValidatorStatus (#11786)

* Add REST implementation for `MultipleValidatorStatus`

* Fix PR comments

* Address PR comments

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
This commit is contained in:
Manu NALEPA 2023-01-05 00:15:23 +01:00 committed by GitHub
parent 51601716dc
commit e1408deb40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 910 additions and 354 deletions

View File

@ -69,11 +69,12 @@ goimports -w "$mock_path/."
gofmt -s -w "$mock_path/."
# github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api
# --------------------------------------------------------
# -------------------------------------------------------------
beacon_api_mock_path="validator/client/beacon-api/mock"
beacon_api_mocks=(
"$beacon_api_mock_path/genesis_mock.go genesis.go"
"$beacon_api_mock_path/json_rest_handler_mock.go json_rest_handler.go"
"$beacon_api_mock_path/state_validators_mock.go state_validators.go"
)
for ((i = 0; i < ${#beacon_api_mocks[@]}; i++)); do

View File

@ -69,7 +69,7 @@ func (c *waitForActivationClient) Recv() (*ethpb.ValidatorActivationResponse, er
stringTargetPubKeys[index] = stringPubKey
}
stateValidators, err := c.beaconApiValidatorClient.getStateValidators(stringTargetPubKeys, nil)
stateValidators, err := c.stateValidatorsProvider.GetStateValidators(stringTargetPubKeys, nil, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to get state validators")
}

View File

@ -147,7 +147,11 @@ func TestActivation_Nominal(t *testing.T) {
},
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
validatorClient := beaconApiValidatorClient{
stateValidatorsProvider: beaconApiStateValidatorsProvider{
jsonRestHandler: jsonRestHandler,
},
}
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
@ -245,7 +249,11 @@ func TestActivation_InvalidData(t *testing.T) {
},
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
validatorClient := beaconApiValidatorClient{
stateValidatorsProvider: beaconApiStateValidatorsProvider{
jsonRestHandler: jsonRestHandler,
},
}
waitForActivation, err := validatorClient.WaitForActivation(
context.Background(),
@ -274,7 +282,11 @@ func TestActivation_JsonResponseError(t *testing.T) {
errors.New("some specific json error"),
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
validatorClient := beaconApiValidatorClient{
stateValidatorsProvider: beaconApiStateValidatorsProvider{
jsonRestHandler: jsonRestHandler,
},
}
waitForActivation, err := validatorClient.WaitForActivation(
context.Background(),

View File

@ -14,9 +14,10 @@ import (
)
type beaconApiValidatorClient struct {
genesisProvider genesisProvider
jsonRestHandler jsonRestHandler
fallbackClient iface.ValidatorClient
genesisProvider genesisProvider
stateValidatorsProvider stateValidatorsProvider
jsonRestHandler jsonRestHandler
fallbackClient iface.ValidatorClient
}
func NewBeaconApiValidatorClient(host string, timeout time.Duration) iface.ValidatorClient {
@ -30,9 +31,10 @@ func NewBeaconApiValidatorClientWithFallback(host string, timeout time.Duration,
}
return &beaconApiValidatorClient{
genesisProvider: beaconApiGenesisProvider{jsonRestHandler: jsonRestHandler},
jsonRestHandler: jsonRestHandler,
fallbackClient: fallbackClient,
genesisProvider: beaconApiGenesisProvider{jsonRestHandler: jsonRestHandler},
stateValidatorsProvider: beaconApiStateValidatorsProvider{jsonRestHandler: jsonRestHandler},
jsonRestHandler: jsonRestHandler,
fallbackClient: fallbackClient,
}
}
@ -111,13 +113,8 @@ func (c *beaconApiValidatorClient) GetSyncSubcommitteeIndex(ctx context.Context,
panic("beaconApiValidatorClient.GetSyncSubcommitteeIndex is not implemented. To use a fallback client, create this validator with NewBeaconApiValidatorClientWithFallback instead.")
}
func (c *beaconApiValidatorClient) MultipleValidatorStatus(ctx context.Context, in *ethpb.MultipleValidatorStatusRequest) (*ethpb.MultipleValidatorStatusResponse, error) {
if c.fallbackClient != nil {
return c.fallbackClient.MultipleValidatorStatus(ctx, in)
}
// TODO: Implement me
panic("beaconApiValidatorClient.MultipleValidatorStatus is not implemented. To use a fallback client, create this validator with NewBeaconApiValidatorClientWithFallback instead.")
func (c *beaconApiValidatorClient) MultipleValidatorStatus(_ context.Context, in *ethpb.MultipleValidatorStatusRequest) (*ethpb.MultipleValidatorStatusResponse, error) {
return c.multipleValidatorStatus(in)
}
func (c *beaconApiValidatorClient) PrepareBeaconProposer(_ context.Context, in *ethpb.PrepareBeaconProposerRequest) (*empty.Empty, error) {

View File

@ -12,7 +12,7 @@ import (
func (c beaconApiValidatorClient) validatorIndex(in *ethpb.ValidatorIndexRequest) (*ethpb.ValidatorIndexResponse, error) {
stringPubKey := hexutil.Encode(in.PublicKey)
stateValidator, err := c.getStateValidators([]string{stringPubKey}, nil)
stateValidator, err := c.stateValidatorsProvider.GetStateValidators([]string{stringPubKey}, nil, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to get state validator")
}

View File

@ -58,7 +58,11 @@ func TestIndex_Nominal(t *testing.T) {
},
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
validatorClient := beaconApiValidatorClient{
stateValidatorsProvider: beaconApiStateValidatorsProvider{
jsonRestHandler: jsonRestHandler,
},
}
validatorIndex, err := validatorClient.ValidatorIndex(
context.Background(),
@ -93,7 +97,11 @@ func TestIndex_UnexistingValidator(t *testing.T) {
},
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
validatorClient := beaconApiValidatorClient{
stateValidatorsProvider: beaconApiStateValidatorsProvider{
jsonRestHandler: jsonRestHandler,
},
}
_, err := validatorClient.ValidatorIndex(
context.Background(),
@ -136,7 +144,11 @@ func TestIndex_BadIndexError(t *testing.T) {
},
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
validatorClient := beaconApiValidatorClient{
stateValidatorsProvider: beaconApiStateValidatorsProvider{
jsonRestHandler: jsonRestHandler,
},
}
_, err := validatorClient.ValidatorIndex(
context.Background(),
@ -165,7 +177,11 @@ func TestIndex_JsonResponseError(t *testing.T) {
errors.New("some specific json error"),
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
validatorClient := beaconApiValidatorClient{
stateValidatorsProvider: beaconApiStateValidatorsProvider{
jsonRestHandler: jsonRestHandler,
},
}
_, err := validatorClient.ValidatorIndex(
context.Background(),

View File

@ -5,6 +5,7 @@ go_library(
srcs = [
"genesis_mock.go",
"json_rest_handler_mock.go",
"state_validators_mock.go",
],
importpath = "github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock",
visibility = ["//visibility:public"],

View File

@ -0,0 +1,50 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: validator/client/beacon-api/state_validators.go
// Package mock is a generated GoMock package.
package mock
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
apimiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
)
// MockstateValidatorsProvider is a mock of stateValidatorsProvider interface.
type MockstateValidatorsProvider struct {
ctrl *gomock.Controller
recorder *MockstateValidatorsProviderMockRecorder
}
// MockstateValidatorsProviderMockRecorder is the mock recorder for MockstateValidatorsProvider.
type MockstateValidatorsProviderMockRecorder struct {
mock *MockstateValidatorsProvider
}
// NewMockstateValidatorsProvider creates a new mock instance.
func NewMockstateValidatorsProvider(ctrl *gomock.Controller) *MockstateValidatorsProvider {
mock := &MockstateValidatorsProvider{ctrl: ctrl}
mock.recorder = &MockstateValidatorsProviderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockstateValidatorsProvider) EXPECT() *MockstateValidatorsProviderMockRecorder {
return m.recorder
}
// GetStateValidators mocks base method.
func (m *MockstateValidatorsProvider) GetStateValidators(arg0 []string, arg1 []int64, arg2 []string) (*apimiddleware.StateValidatorsResponseJson, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetStateValidators", arg0, arg1, arg2)
ret0, _ := ret[0].(*apimiddleware.StateValidatorsResponseJson)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetStateValidators indicates an expected call of GetStateValidators.
func (mr *MockstateValidatorsProviderMockRecorder) GetStateValidators(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStateValidators", reflect.TypeOf((*MockstateValidatorsProvider)(nil).GetStateValidators), arg0, arg1, arg2)
}

View File

@ -2,19 +2,42 @@ package beacon_api
import (
neturl "net/url"
"strconv"
"github.com/pkg/errors"
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
)
func (c *beaconApiValidatorClient) getStateValidators(
type stateValidatorsProvider interface {
GetStateValidators([]string, []int64, []string) (*rpcmiddleware.StateValidatorsResponseJson, error)
}
type beaconApiStateValidatorsProvider struct {
jsonRestHandler jsonRestHandler
}
func (c beaconApiStateValidatorsProvider) GetStateValidators(
stringPubkeys []string,
indexes []int64,
statuses []string,
) (*rpcmiddleware.StateValidatorsResponseJson, error) {
params := neturl.Values{}
stringPubKeysSet := make(map[string]struct{}, len(stringPubkeys))
indexesSet := make(map[int64]struct{}, len(indexes))
for _, stringPubkey := range stringPubkeys {
params.Add("id", stringPubkey)
if _, ok := stringPubKeysSet[stringPubkey]; !ok {
stringPubKeysSet[stringPubkey] = struct{}{}
params.Add("id", stringPubkey)
}
}
for _, index := range indexes {
if _, ok := indexesSet[index]; !ok {
indexesSet[index] = struct{}{}
params.Add("id", strconv.FormatInt(index, 10))
}
}
for _, status := range statuses {
@ -30,11 +53,11 @@ func (c *beaconApiValidatorClient) getStateValidators(
_, err := c.jsonRestHandler.GetRestJsonResponse(url, stateValidatorsJson)
if err != nil {
return nil, errors.Wrap(err, "failed to get json response")
return &rpcmiddleware.StateValidatorsResponseJson{}, errors.Wrap(err, "failed to get json response")
}
if stateValidatorsJson.Data == nil {
return nil, errors.New("stateValidatorsJson.Data is nil")
return &rpcmiddleware.StateValidatorsResponseJson{}, errors.New("stateValidatorsJson.Data is nil")
}
return stateValidatorsJson, nil

View File

@ -22,6 +22,7 @@ func TestGetStateValidators_Nominal(t *testing.T) {
"id=0x80000e851c0f53c3246ff726d7ff7766661ca5e12a07c45c114d208d54f0f8233d4380b2e9aff759d69795d1df905526&", // active_exiting
"id=0x424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242&", // does not exist
"id=0x800015473bdc3a7f45ef8eb8abc598bc20021e55ad6e6ad1d745aaef9730dd2c28ec08bf42df18451de94dd4a6d24ec5&", // exited_slashed
"id=12345&", // active_ongoing
"status=active_ongoing&status=active_exiting&status=exited_slashed&status=exited_unslashed",
}, "")
@ -29,6 +30,13 @@ func TestGetStateValidators_Nominal(t *testing.T) {
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
wanted := []*rpcmiddleware.ValidatorContainerJson{
{
Index: "12345",
Status: "active_ongoing",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be19",
},
},
{
Index: "55293",
Status: "active_ongoing",
@ -65,13 +73,18 @@ func TestGetStateValidators_Nominal(t *testing.T) {
},
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
actual, err := validatorClient.getStateValidators([]string{
stateValidatorsProvider := beaconApiStateValidatorsProvider{jsonRestHandler: jsonRestHandler}
actual, err := stateValidatorsProvider.GetStateValidators([]string{
"0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // active_ongoing
"0x80000e851c0f53c3246ff726d7ff7766661ca5e12a07c45c114d208d54f0f8233d4380b2e9aff759d69795d1df905526", // active_exiting
"0x424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242", // does not exist
"0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // active_ongoing - duplicate
"0x800015473bdc3a7f45ef8eb8abc598bc20021e55ad6e6ad1d745aaef9730dd2c28ec08bf42df18451de94dd4a6d24ec5", // exited_slashed
},
[]int64{
12345, // active_ongoing
12345, // active_ongoing - duplicate
},
[]string{"active_ongoing", "active_exiting", "exited_slashed", "exited_unslashed"},
)
require.NoError(t, err)
@ -95,11 +108,12 @@ func TestGetStateValidators_GetRestJsonResponseOnError(t *testing.T) {
errors.New("an error"),
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
_, err := validatorClient.getStateValidators([]string{
stateValidatorsProvider := beaconApiStateValidatorsProvider{jsonRestHandler: jsonRestHandler}
_, err := stateValidatorsProvider.GetStateValidators([]string{
"0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // active_ongoing
},
nil,
nil,
)
assert.ErrorContains(t, "an error", err)
assert.ErrorContains(t, "failed to get json response", err)
@ -127,11 +141,12 @@ func TestGetStateValidators_DataIsNil(t *testing.T) {
},
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
_, err := validatorClient.getStateValidators([]string{
stateValidatorsProvider := beaconApiStateValidatorsProvider{jsonRestHandler: jsonRestHandler}
_, err := stateValidatorsProvider.GetStateValidators([]string{
"0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // active_ongoing
},
nil,
nil,
)
assert.ErrorContains(t, "stateValidatorsJson.Data is nil", err)
}

View File

@ -11,69 +11,158 @@ import (
)
func (c *beaconApiValidatorClient) validatorStatus(in *ethpb.ValidatorStatusRequest) (*ethpb.ValidatorStatusResponse, error) {
stringPubKey := hexutil.Encode(in.PublicKey)
resp := &ethpb.ValidatorStatusResponse{
Status: ethpb.ValidatorStatus_UNKNOWN_STATUS,
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
}
stateValidator, err := c.getStateValidators([]string{stringPubKey}, nil)
_, _, validatorsStatusResponse, err := c.getValidatorsStatusResponse([][]byte{in.PublicKey}, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to get state validator")
return nil, errors.Wrap(err, "failed to get validator status response")
}
// If no data, the validator is in unknown status
if len(stateValidator.Data) == 0 {
return resp, nil
if len(validatorsStatusResponse) != 1 {
return nil, errors.New("number of validator status response not expected")
}
validatorContainer := stateValidator.Data[0]
validatorStatusResponse := validatorsStatusResponse[0]
// Set Status
status, ok := beaconAPITogRPCValidatorStatus[validatorContainer.Status]
if !ok {
return nil, errors.New("invalid validator status: " + validatorContainer.Status)
}
return validatorStatusResponse, nil
}
resp.Status = status
// Set activation epoch
activationEpoch, err := strconv.ParseInt(validatorContainer.Validator.ActivationEpoch, 10, 64)
func (c *beaconApiValidatorClient) multipleValidatorStatus(in *ethpb.MultipleValidatorStatusRequest) (*ethpb.MultipleValidatorStatusResponse, error) {
publicKeys, indices, statuses, err := c.getValidatorsStatusResponse(in.PublicKeys, in.Indices)
if err != nil {
return nil, errors.Wrap(err, "failed to parse activation epoch")
return nil, errors.Wrap(err, "failed to get validators status response")
}
resp.ActivationEpoch = types.Epoch(activationEpoch)
return &ethpb.MultipleValidatorStatusResponse{
PublicKeys: publicKeys,
Indices: indices,
Statuses: statuses,
}, nil
}
// Set PositionInActivationQueue
switch status {
case ethpb.ValidatorStatus_DEPOSITED, ethpb.ValidatorStatus_PENDING, ethpb.ValidatorStatus_PARTIALLY_DEPOSITED:
validatorIndex, err := strconv.ParseUint(validatorContainer.Index, 10, 64)
if err != nil {
return nil, errors.Wrap(err, "failed to parse validator index")
}
func (c *beaconApiValidatorClient) getValidatorsStatusResponse(inPubKeys [][]byte, inIndexes []int64) (
[][]byte,
[]types.ValidatorIndex,
[]*ethpb.ValidatorStatusResponse,
error,
) {
// Represents the target set of keys
stringTargetPubKeysToPubKeys := make(map[string][]byte, len(inPubKeys))
stringTargetPubKeys := make([]string, len(inPubKeys))
activeStateValidators, err := c.getStateValidators(nil, []string{"active"})
if err != nil {
return nil, errors.Wrap(err, "failed to get state validators")
}
// Represents the set of keys actually returned by the beacon node
stringRetrievedPubKeys := make(map[string]struct{})
data := activeStateValidators.Data
// Contains all keys in targetPubKeys but not in retrievedPubKeys
missingPubKeys := [][]byte{}
var lastActivatedValidatorIndex uint64 = 0
totalLen := len(inPubKeys) + len(inIndexes)
if nbActiveValidators := len(data); nbActiveValidators != 0 {
lastValidator := data[nbActiveValidators-1]
outPubKeys := make([][]byte, totalLen)
outIndexes := make([]types.ValidatorIndex, totalLen)
outValidatorsStatuses := make([]*ethpb.ValidatorStatusResponse, totalLen)
lastActivatedValidatorIndex, err = strconv.ParseUint(lastValidator.Index, 10, 64)
for index, publicKey := range inPubKeys {
stringPubKey := hexutil.Encode(publicKey)
stringTargetPubKeysToPubKeys[stringPubKey] = publicKey
stringTargetPubKeys[index] = stringPubKey
}
// Get state for the current validator
stateValidatorsResponse, err := c.stateValidatorsProvider.GetStateValidators(stringTargetPubKeys, inIndexes, nil)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to get state validators")
}
isLastActivatedValidatorIndexRetrieved := false
var lastActivatedValidatorIndex uint64 = 0
for i, validatorContainer := range stateValidatorsResponse.Data {
stringPubKey := validatorContainer.Validator.PublicKey
stringRetrievedPubKeys[stringPubKey] = struct{}{}
pubKey, ok := stringTargetPubKeysToPubKeys[stringPubKey]
if !ok {
// string pub key is not already known because the index was used for this validator
pubKey, err = hexutil.Decode(stringPubKey)
if err != nil {
return nil, errors.Wrap(err, "failed to parse last validator index")
return nil, nil, nil, errors.Wrapf(err, "failed to parse validator public key %s", stringPubKey)
}
}
resp.PositionInActivationQueue = validatorIndex - lastActivatedValidatorIndex
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] = types.ValidatorIndex(validatorIndex)
validatorStatus := &ethpb.ValidatorStatusResponse{}
// Set Status
status, ok := beaconAPITogRPCValidatorStatus[validatorContainer.Status]
if !ok {
return nil, nil, nil, errors.New("invalid validator status " + validatorContainer.Status)
}
validatorStatus.Status = status
// Set activation epoch
activationEpoch, err := strconv.ParseUint(validatorContainer.Validator.ActivationEpoch, 10, 64)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "failed to parse activation epoch %s", validatorContainer.Validator.ActivationEpoch)
}
validatorStatus.ActivationEpoch = types.Epoch(activationEpoch)
// Set PositionInActivationQueue
switch status {
case ethpb.ValidatorStatus_PENDING, ethpb.ValidatorStatus_PARTIALLY_DEPOSITED, ethpb.ValidatorStatus_DEPOSITED:
if !isLastActivatedValidatorIndexRetrieved {
isLastActivatedValidatorIndexRetrieved = true
activeStateValidators, err := c.stateValidatorsProvider.GetStateValidators(nil, nil, []string{"active"})
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to get state validators")
}
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
}
return resp, nil
for _, stringTargetPubKey := range stringTargetPubKeys {
if _, ok := stringRetrievedPubKeys[stringTargetPubKey]; !ok {
targetPubKey := stringTargetPubKeysToPubKeys[stringTargetPubKey]
missingPubKeys = append(missingPubKeys, targetPubKey)
}
}
nbStringRetrievedPubKeys := len(stringRetrievedPubKeys)
for i, missingPubKey := range missingPubKeys {
outPubKeys[nbStringRetrievedPubKeys+i] = missingPubKey
outIndexes[nbStringRetrievedPubKeys+i] = types.ValidatorIndex(^uint64(0))
outValidatorsStatuses[nbStringRetrievedPubKeys+i] = &ethpb.ValidatorStatusResponse{
Status: ethpb.ValidatorStatus_UNKNOWN_STATUS,
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
}
}
outLen := len(stateValidatorsResponse.Data) + len(missingPubKeys)
return outPubKeys[:outLen], outIndexes[:outLen], outValidatorsStatuses[:outLen], nil
}

View File

@ -2,346 +2,698 @@ package beacon_api
import (
"context"
"errors"
"fmt"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/mock/gomock"
"github.com/pkg/errors"
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
"github.com/prysmaticlabs/prysm/v3/config/params"
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/require"
"github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock"
)
const defaultStringPubKey = "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13"
type urlAndData struct {
Url string
Data []*rpcmiddleware.ValidatorContainerJson
}
func TestValidatorsStatus_Nominal(t *testing.T) {
testCases := []struct {
name string
urlsAndData []urlAndData
wanted *ethpb.ValidatorStatusResponse
}{
{
name: "Some active validators",
urlsAndData: []urlAndData{
{
Url: fmt.Sprintf("/eth/v1/beacon/states/head/validators?id=%s", stringPubKey),
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "55300",
Status: "pending_queued",
Validator: &rpcmiddleware.ValidatorJson{
ActivationEpoch: "100",
},
},
},
},
{
Url: "/eth/v1/beacon/states/head/validators?status=active",
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "55293",
Status: "active_ongoing",
Validator: &rpcmiddleware.ValidatorJson{},
},
{
Index: "55294",
Status: "active_ongoing",
Validator: &rpcmiddleware.ValidatorJson{},
},
},
},
},
wanted: &ethpb.ValidatorStatusResponse{
Status: ethpb.ValidatorStatus_PENDING,
ActivationEpoch: 100,
PositionInActivationQueue: 6,
},
},
{
name: "No active validators",
urlsAndData: []urlAndData{
{
Url: fmt.Sprintf("/eth/v1/beacon/states/head/validators?id=%s", stringPubKey),
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "55300",
Status: "pending_queued",
Validator: &rpcmiddleware.ValidatorJson{
ActivationEpoch: "100",
},
},
},
},
{
Url: "/eth/v1/beacon/states/head/validators?status=active",
Data: []*rpcmiddleware.ValidatorContainerJson{},
},
},
wanted: &ethpb.ValidatorStatusResponse{
Status: ethpb.ValidatorStatus_PENDING,
ActivationEpoch: 100,
PositionInActivationQueue: 55300,
},
},
{
name: "Unknown status",
urlsAndData: []urlAndData{
{
Url: fmt.Sprintf("/eth/v1/beacon/states/head/validators?id=%s", stringPubKey),
Data: []*rpcmiddleware.ValidatorContainerJson{},
},
},
wanted: &ethpb.ValidatorStatusResponse{
Status: ethpb.ValidatorStatus_UNKNOWN_STATUS,
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
},
},
}
pubKey, err := hexutil.Decode(defaultStringPubKey)
require.NoError(t, err)
for _, testCase := range testCases {
t.Run(testCase.name,
func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
for _, urlAndData := range testCase.urlsAndData {
jsonRestHandler.EXPECT().GetRestJsonResponse(
urlAndData.Url,
gomock.Any(),
).Return(
nil,
nil,
).SetArg(
1,
rpcmiddleware.StateValidatorsResponseJson{
Data: urlAndData.Data,
},
).Times(1)
}
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
actual, err := validatorClient.validatorStatus(
&ethpb.ValidatorStatusRequest{
PublicKey: pubKey,
},
)
require.NoError(t, err)
assert.DeepEqual(t, testCase.wanted, actual)
},
)
}
}
func TestValidatorStatus_InvalidData(t *testing.T) {
testCases := []struct {
name string
data []*rpcmiddleware.ValidatorContainerJson
expectedErrorMessage string
err error
}{
{
name: "bad validator status",
data: []*rpcmiddleware.ValidatorContainerJson{},
expectedErrorMessage: "failed to get state validator",
err: errors.New("some specific json error"),
},
{
name: "bad validator status",
data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "12345",
Status: "NotAStatus",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: stringPubKey,
},
},
},
expectedErrorMessage: "invalid validator status: NotAStatus",
err: nil,
},
{
name: "bad activation epoch",
data: []*rpcmiddleware.ValidatorContainerJson{
{
Status: "pending_queued",
Validator: &rpcmiddleware.ValidatorJson{
ActivationEpoch: "NotAnEpoch",
},
},
},
expectedErrorMessage: "failed to parse activation epoch",
err: nil,
},
{
name: "bad validator index",
data: []*rpcmiddleware.ValidatorContainerJson{
{
Status: "pending_queued",
Validator: &rpcmiddleware.ValidatorJson{
ActivationEpoch: "12345",
},
Index: "NotAnIndex",
},
},
expectedErrorMessage: "failed to parse validator index",
err: nil,
},
}
for _, testCase := range testCases {
t.Run(testCase.name,
func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
gomock.Any(),
gomock.Any(),
).Return(
nil,
testCase.err,
).SetArg(
1,
rpcmiddleware.StateValidatorsResponseJson{
Data: testCase.data,
},
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
_, err := validatorClient.ValidatorStatus(
context.Background(),
&ethpb.ValidatorStatusRequest{},
)
assert.ErrorContains(t, testCase.expectedErrorMessage, err)
},
)
}
}
func TestValidatorStatus_InvalidValidatorsState(t *testing.T) {
pubKey, err := hexutil.Decode(defaultStringPubKey)
func TestValidatorStatus_Nominal(t *testing.T) {
const stringValidatorPubKey = "0x8000a6c975761b488bdb0dfba4ed37c0d97d6e6b968562ef5c84aa9a5dfb92d8e309195004e97709077723739bf04463"
validatorPubKey, err := hexutil.Decode(stringValidatorPubKey)
require.NoError(t, err)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
stateValidatorsResponseJson := rpcmiddleware.StateValidatorsResponseJson{}
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
gomock.Any(),
&stateValidatorsResponseJson,
stateValidatorsProvider.EXPECT().GetStateValidators(
[]string{stringValidatorPubKey},
nil,
nil,
).Return(
nil,
nil,
).SetArg(
1,
rpcmiddleware.StateValidatorsResponseJson{
&rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "55300",
Status: "pending_queued",
Index: "35000",
Status: "active_ongoing",
Validator: &rpcmiddleware.ValidatorJson{
ActivationEpoch: "100",
PublicKey: stringValidatorPubKey,
ActivationEpoch: "56",
},
},
},
},
nil,
).Times(1)
jsonRestHandler.EXPECT().GetRestJsonResponse(
"/eth/v1/beacon/states/head/validators?status=active",
&stateValidatorsResponseJson,
).Return(
validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider}
actualValidatorStatusResponse, err := validatorClient.ValidatorStatus(
context.Background(),
&ethpb.ValidatorStatusRequest{
PublicKey: validatorPubKey,
},
)
expectedValidatorStatusResponse := ethpb.ValidatorStatusResponse{
Status: ethpb.ValidatorStatus_ACTIVE,
ActivationEpoch: 56,
}
require.NoError(t, err)
assert.DeepEqual(t, &expectedValidatorStatusResponse, actualValidatorStatusResponse)
}
func TestValidatorStatus_Error(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidators(
gomock.Any(),
nil,
nil,
).Return(
&rpcmiddleware.StateValidatorsResponseJson{},
errors.New("a specific error"),
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider}
_, err = validatorClient.validatorStatus(
_, err := validatorClient.ValidatorStatus(
context.Background(),
&ethpb.ValidatorStatusRequest{
PublicKey: pubKey,
PublicKey: []byte{},
},
)
require.ErrorContains(t, "failed to get state validators", err)
require.ErrorContains(t, "failed to get validator status response", err)
}
func TestValidatorStatus_InvalidLastValidatorIndex(t *testing.T) {
pubKey, err := hexutil.Decode(defaultStringPubKey)
require.NoError(t, err)
func TestMultipleValidatorStatus_Nominal(t *testing.T) {
stringValidatorsPubKey := []string{
"0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // existing
"0x8000a6c975761b488bdb0dfba4ed37c0d97d6e6b968562ef5c84aa9a5dfb92d8e309195004e97709077723739bf04463", // existing
}
validatorsPubKey := make([][]byte, len(stringValidatorsPubKey))
for i, stringValidatorPubKey := range stringValidatorsPubKey {
validatorPubKey, err := hexutil.Decode(stringValidatorPubKey)
require.NoError(t, err)
validatorsPubKey[i] = validatorPubKey
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
stateValidatorsResponseJson := rpcmiddleware.StateValidatorsResponseJson{}
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
gomock.Any(),
&stateValidatorsResponseJson,
stateValidatorsProvider.EXPECT().GetStateValidators(
stringValidatorsPubKey,
nil,
nil,
).Return(
nil,
nil,
).SetArg(
1,
rpcmiddleware.StateValidatorsResponseJson{
&rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "55300",
Status: "pending_queued",
Index: "11111",
Status: "active_ongoing",
Validator: &rpcmiddleware.ValidatorJson{
ActivationEpoch: "100",
PublicKey: "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13",
ActivationEpoch: "12",
},
},
{
Index: "22222",
Status: "active_ongoing",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: "0x8000a6c975761b488bdb0dfba4ed37c0d97d6e6b968562ef5c84aa9a5dfb92d8e309195004e97709077723739bf04463",
ActivationEpoch: "34",
},
},
},
},
nil,
).Times(1)
jsonRestHandler.EXPECT().GetRestJsonResponse(
"/eth/v1/beacon/states/head/validators?status=active",
&stateValidatorsResponseJson,
validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider}
expectedValidatorStatusResponse := ethpb.MultipleValidatorStatusResponse{
PublicKeys: validatorsPubKey,
Indices: []types.ValidatorIndex{
11111,
22222,
},
Statuses: []*ethpb.ValidatorStatusResponse{
{
Status: ethpb.ValidatorStatus_ACTIVE,
ActivationEpoch: 12,
},
{
Status: ethpb.ValidatorStatus_ACTIVE,
ActivationEpoch: 34,
},
},
}
actualValidatorStatusResponse, err := validatorClient.MultipleValidatorStatus(
context.Background(),
&ethpb.MultipleValidatorStatusRequest{
PublicKeys: validatorsPubKey,
},
)
require.NoError(t, err)
assert.DeepEqual(t, &expectedValidatorStatusResponse, actualValidatorStatusResponse)
}
func TestMultipleValidatorStatus_Error(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidators(
gomock.Any(),
nil,
nil,
).Return(
&rpcmiddleware.StateValidatorsResponseJson{},
errors.New("a specific error"),
).Times(1)
validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider}
_, err := validatorClient.MultipleValidatorStatus(
context.Background(),
&ethpb.MultipleValidatorStatusRequest{
PublicKeys: [][]byte{},
},
)
require.ErrorContains(t, "failed to get validators status response", err)
}
func TestGetValidatorsStatusResponse_Nominal_SomeActiveValidators(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
stringValidatorsPubKey := []string{
"0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // existing
"0x8000a6c975761b488bdb0dfba4ed37c0d97d6e6b968562ef5c84aa9a5dfb92d8e309195004e97709077723739bf04463", // existing
"0x80000e851c0f53c3246ff726d7ff7766661ca5e12a07c45c114d208d54f0f8233d4380b2e9aff759d69795d1df905526", // NOT existing
"0x8000ab56b051f9d8f31c687528c6e91c9b98e4c3a241e752f9ccfbea7c5a7fbbd272bdf2c0a7e52ce7e0b57693df364c", // existing
"0x8000b3e51de7e2e319b23a42d468dc8e63cd61daa5c4609cf2d800026d92706d1240414e155057bdc35e0574bba3ad80", // NOT existing
"0x800010c20716ef4264a6d93b3873a008ece58fb9312ac2cc3b0ccc40aedb050f2038281e6a92242a35476af9903c7919", // existing
}
validatorsPubKey := make([][]byte, len(stringValidatorsPubKey))
for i, stringValidatorPubKey := range stringValidatorsPubKey {
validatorPubKey, err := hexutil.Decode(stringValidatorPubKey)
require.NoError(t, err)
validatorsPubKey[i] = validatorPubKey
}
validatorsIndex := []int64{
12345, // NOT existing
33333, // existing
}
extraStringValidatorKey := "0x80003eb1e78ffdea6c878026b7074f84aaa16536c8e1960a652e817c848e7ccb051087f837b7d2bb6773cd9705601ede"
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidators(
stringValidatorsPubKey,
validatorsIndex,
nil,
nil,
).SetArg(
1,
rpcmiddleware.StateValidatorsResponseJson{
).Return(
&rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "NotAnIndex",
Status: "active_ongoing",
Validator: &rpcmiddleware.ValidatorJson{},
Index: "11111",
Status: "active_ongoing",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13",
ActivationEpoch: "12",
},
},
{
Index: "22222",
Status: "active_exiting",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: "0x800010c20716ef4264a6d93b3873a008ece58fb9312ac2cc3b0ccc40aedb050f2038281e6a92242a35476af9903c7919",
ActivationEpoch: "34",
},
},
{
Index: "33333",
Status: "active_ongoing",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: extraStringValidatorKey,
ActivationEpoch: "56",
},
},
{
Index: "40000",
Status: "pending_queued",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: "0x8000a6c975761b488bdb0dfba4ed37c0d97d6e6b968562ef5c84aa9a5dfb92d8e309195004e97709077723739bf04463",
ActivationEpoch: fmt.Sprintf("%d", params.BeaconConfig().FarFutureEpoch),
},
},
{
Index: "50000",
Status: "pending_queued",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: "0x8000ab56b051f9d8f31c687528c6e91c9b98e4c3a241e752f9ccfbea7c5a7fbbd272bdf2c0a7e52ce7e0b57693df364c",
ActivationEpoch: fmt.Sprintf("%d", params.BeaconConfig().FarFutureEpoch),
},
},
},
},
nil,
).Times(1)
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
_, err = validatorClient.validatorStatus(
&ethpb.ValidatorStatusRequest{
PublicKey: pubKey,
stateValidatorsProvider.EXPECT().GetStateValidators(
nil,
nil,
[]string{"active"},
).Return(
&rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "35000",
Status: "active_ongoing",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: "0x8000ab56b051f9d8f31c687528c6e91c9b98e4c3a241e752f9ccfbea7c5a7fbbd272bdf2c0a7e52ce7e0b57693df364d",
ActivationEpoch: "56",
},
},
{
Index: "39000",
Status: "active_ongoing",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: "0x8000ab56b051f9d8f31c687528c6e91c9b98e4c3a241e752f9ccfbea7c5a7fbbd272bdf2c0a7e52ce7e0b57693df364e",
ActivationEpoch: "56",
},
},
},
},
)
require.ErrorContains(t, "failed to parse last validator index", err)
nil,
).Times(1)
wantedStringValidatorsPubkey := []string{
"0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // existing
"0x800010c20716ef4264a6d93b3873a008ece58fb9312ac2cc3b0ccc40aedb050f2038281e6a92242a35476af9903c7919", // existing,
extraStringValidatorKey, // existing,
"0x8000a6c975761b488bdb0dfba4ed37c0d97d6e6b968562ef5c84aa9a5dfb92d8e309195004e97709077723739bf04463", // existing,
"0x8000ab56b051f9d8f31c687528c6e91c9b98e4c3a241e752f9ccfbea7c5a7fbbd272bdf2c0a7e52ce7e0b57693df364c", // existing
"0x80000e851c0f53c3246ff726d7ff7766661ca5e12a07c45c114d208d54f0f8233d4380b2e9aff759d69795d1df905526", // NOT existing
"0x8000b3e51de7e2e319b23a42d468dc8e63cd61daa5c4609cf2d800026d92706d1240414e155057bdc35e0574bba3ad80", // NOT existing
}
wantedValidatorsPubKey := make([][]byte, len(wantedStringValidatorsPubkey))
for i, stringValidatorPubKey := range wantedStringValidatorsPubkey {
validatorPubKey, err := hexutil.Decode(stringValidatorPubKey)
require.NoError(t, err)
wantedValidatorsPubKey[i] = validatorPubKey
}
wantedValidatorsIndex := []types.ValidatorIndex{
11111,
22222,
33333,
40000,
50000,
types.ValidatorIndex(^uint64(0)),
types.ValidatorIndex(^uint64(0)),
}
wantedValidatorsStatusResponse := []*ethpb.ValidatorStatusResponse{
{
Status: ethpb.ValidatorStatus_ACTIVE,
ActivationEpoch: 12,
},
{
Status: ethpb.ValidatorStatus_EXITING,
ActivationEpoch: 34,
},
{
Status: ethpb.ValidatorStatus_ACTIVE,
ActivationEpoch: 56,
},
{
Status: ethpb.ValidatorStatus_PENDING,
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
PositionInActivationQueue: 1000,
},
{
Status: ethpb.ValidatorStatus_PENDING,
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
PositionInActivationQueue: 11000,
},
{
Status: ethpb.ValidatorStatus_UNKNOWN_STATUS,
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
},
{
Status: ethpb.ValidatorStatus_UNKNOWN_STATUS,
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
},
}
validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider}
actualValidatorsPubKey, actualValidatorsIndex, actualValidatorsStatusResponse, err := validatorClient.getValidatorsStatusResponse(validatorsPubKey, validatorsIndex)
require.NoError(t, err)
assert.DeepEqual(t, wantedValidatorsPubKey, actualValidatorsPubKey)
assert.DeepEqual(t, wantedValidatorsIndex, actualValidatorsIndex)
assert.DeepEqual(t, wantedValidatorsStatusResponse, actualValidatorsStatusResponse)
}
func TestGetValidatorsStatusResponse_Nominal_NoActiveValidators(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
const stringValidatorPubKey = "0x8000a6c975761b488bdb0dfba4ed37c0d97d6e6b968562ef5c84aa9a5dfb92d8e309195004e97709077723739bf04463"
validatorPubKey, err := hexutil.Decode(stringValidatorPubKey)
require.NoError(t, err)
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidators(
[]string{stringValidatorPubKey},
nil,
nil,
).Return(
&rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "40000",
Status: "pending_queued",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: "0x8000a6c975761b488bdb0dfba4ed37c0d97d6e6b968562ef5c84aa9a5dfb92d8e309195004e97709077723739bf04463",
ActivationEpoch: fmt.Sprintf("%d", params.BeaconConfig().FarFutureEpoch),
},
},
},
},
nil,
).Times(1)
stateValidatorsProvider.EXPECT().GetStateValidators(
nil,
nil,
[]string{"active"},
).Return(
&rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{},
},
nil,
).Times(1)
wantedValidatorsPubKey := [][]byte{validatorPubKey}
wantedValidatorsIndex := []types.ValidatorIndex{40000}
wantedValidatorsStatusResponse := []*ethpb.ValidatorStatusResponse{
{
Status: ethpb.ValidatorStatus_PENDING,
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
PositionInActivationQueue: 40000,
},
}
validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider}
actualValidatorsPubKey, actualValidatorsIndex, actualValidatorsStatusResponse, err := validatorClient.getValidatorsStatusResponse(wantedValidatorsPubKey, nil)
require.NoError(t, err)
require.NoError(t, err)
assert.DeepEqual(t, wantedValidatorsPubKey, actualValidatorsPubKey)
assert.DeepEqual(t, wantedValidatorsIndex, actualValidatorsIndex)
assert.DeepEqual(t, wantedValidatorsStatusResponse, actualValidatorsStatusResponse)
}
type getStateValidatorsInterface struct {
// Inputs
inputStringPubKeys []string
inputIndexes []int64
inputStatuses []string
// Outputs
outputStateValidatorsResponseJson *rpcmiddleware.StateValidatorsResponseJson
outputErr error
}
func TestValidatorStatusResponse_InvalidData(t *testing.T) {
stringPubKey := "0x8000a6c975761b488bdb0dfba4ed37c0d97d6e6b968562ef5c84aa9a5dfb92d8e309195004e97709077723739bf04463"
pubKey, err := hexutil.Decode(stringPubKey)
require.NoError(t, err)
testCases := []struct {
name string
// Inputs
inputPubKeys [][]byte
inputIndexes []int64
inputGetStateValidatorsInterfaces []getStateValidatorsInterface
// Outputs
outputErrMessage string
}{
{
name: "failed getStateValidators",
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
outputStateValidatorsResponseJson: &rpcmiddleware.StateValidatorsResponseJson{},
outputErr: errors.New("a specific error"),
},
},
outputErrMessage: "failed to get state validators",
},
{
name: "failed to parse validator public key NotAPublicKey",
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
outputStateValidatorsResponseJson: &rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: "NotAPublicKey",
},
},
},
},
outputErr: nil,
},
},
outputErrMessage: "failed to parse validator public key",
},
{
name: "failed to parse validator index NotAnIndex",
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
outputStateValidatorsResponseJson: &rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "NotAnIndex",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: stringPubKey,
},
},
},
},
outputErr: nil,
},
},
outputErrMessage: "failed to parse validator index",
},
{
name: "invalid validator status",
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
outputStateValidatorsResponseJson: &rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "12345",
Status: "NotAStatus",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: stringPubKey,
},
},
},
},
outputErr: nil,
},
},
outputErrMessage: "invalid validator status NotAStatus",
},
{
name: "failed to parse activation epoch",
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
outputStateValidatorsResponseJson: &rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "12345",
Status: "active_ongoing",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: stringPubKey,
ActivationEpoch: "NotAnEpoch",
},
},
},
},
outputErr: nil,
},
},
outputErrMessage: "failed to parse activation epoch NotAnEpoch",
},
{
name: "failed to get state validators",
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
outputStateValidatorsResponseJson: &rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "12345",
Status: "pending_queued",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: stringPubKey,
ActivationEpoch: "10",
},
},
},
},
outputErr: nil,
},
{
inputStringPubKeys: nil,
inputIndexes: nil,
inputStatuses: []string{"active"},
outputStateValidatorsResponseJson: &rpcmiddleware.StateValidatorsResponseJson{},
outputErr: errors.New("a specific error"),
},
},
outputErrMessage: "failed to get state validators",
},
{
name: "failed to parse last validator index",
inputPubKeys: [][]byte{pubKey},
inputIndexes: nil,
inputGetStateValidatorsInterfaces: []getStateValidatorsInterface{
{
inputStringPubKeys: []string{stringPubKey},
inputIndexes: nil,
inputStatuses: nil,
outputStateValidatorsResponseJson: &rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "12345",
Status: "pending_queued",
Validator: &rpcmiddleware.ValidatorJson{
PublicKey: stringPubKey,
ActivationEpoch: "10",
},
},
},
},
outputErr: nil,
},
{
inputStringPubKeys: nil,
inputIndexes: nil,
inputStatuses: []string{"active"},
outputStateValidatorsResponseJson: &rpcmiddleware.StateValidatorsResponseJson{
Data: []*rpcmiddleware.ValidatorContainerJson{
{
Index: "NotAnIndex",
},
},
},
outputErr: nil,
},
},
outputErrMessage: "failed to parse last validator index NotAnIndex",
},
}
for _, testCase := range testCases {
t.Run(testCase.name,
func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
for _, aa := range testCase.inputGetStateValidatorsInterfaces {
stateValidatorsProvider.EXPECT().GetStateValidators(
aa.inputStringPubKeys,
aa.inputIndexes,
aa.inputStatuses,
).Return(
aa.outputStateValidatorsResponseJson,
aa.outputErr,
).Times(1)
}
validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider}
_, _, _, err := validatorClient.getValidatorsStatusResponse(
testCase.inputPubKeys,
testCase.inputIndexes,
)
assert.ErrorContains(t, testCase.outputErrMessage, err)
},
)
}
}