package beacon_api import ( "context" "errors" "fmt" "testing" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/mock/gomock" rpcmiddleware "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware" "github.com/prysmaticlabs/prysm/v4/config/params" "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v4/testing/assert" "github.com/prysmaticlabs/prysm/v4/testing/require" "github.com/prysmaticlabs/prysm/v4/validator/client/beacon-api/mock" ) 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() ctx := context.Background() stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl) stateValidatorsProvider.EXPECT().GetStateValidators( ctx, []string{stringValidatorPubKey}, nil, nil, ).Return( &rpcmiddleware.StateValidatorsResponseJson{ Data: []*rpcmiddleware.ValidatorContainerJson{ { Index: "35000", Status: "active_ongoing", Validator: &rpcmiddleware.ValidatorJson{ PublicKey: stringValidatorPubKey, ActivationEpoch: "56", }, }, }, }, nil, ).Times(1) validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider} actualValidatorStatusResponse, err := validatorClient.ValidatorStatus( ctx, ðpb.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() ctx := context.Background() stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl) stateValidatorsProvider.EXPECT().GetStateValidators( ctx, gomock.Any(), nil, nil, ).Return( &rpcmiddleware.StateValidatorsResponseJson{}, errors.New("a specific error"), ).Times(1) validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider} _, err := validatorClient.ValidatorStatus( ctx, ðpb.ValidatorStatusRequest{ PublicKey: []byte{}, }, ) require.ErrorContains(t, "failed to get validator status response", err) } func TestMultipleValidatorStatus_Nominal(t *testing.T) { stringValidatorsPubKey := []string{ "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // existing "0x8000a6c975761b488bdb0dfba4ed37c0d97d6e6b968562ef5c84aa9a5dfb92d8e309195004e97709077723739bf04463", // existing } ctx := context.Background() 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() stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl) stateValidatorsProvider.EXPECT().GetStateValidators( ctx, stringValidatorsPubKey, nil, nil, ).Return( &rpcmiddleware.StateValidatorsResponseJson{ Data: []*rpcmiddleware.ValidatorContainerJson{ { Index: "11111", Status: "active_ongoing", Validator: &rpcmiddleware.ValidatorJson{ PublicKey: "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", ActivationEpoch: "12", }, }, { Index: "22222", Status: "active_ongoing", Validator: &rpcmiddleware.ValidatorJson{ PublicKey: "0x8000a6c975761b488bdb0dfba4ed37c0d97d6e6b968562ef5c84aa9a5dfb92d8e309195004e97709077723739bf04463", ActivationEpoch: "34", }, }, }, }, nil, ).Times(1) validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider} expectedValidatorStatusResponse := ethpb.MultipleValidatorStatusResponse{ PublicKeys: validatorsPubKey, Indices: []primitives.ValidatorIndex{ 11111, 22222, }, Statuses: []*ethpb.ValidatorStatusResponse{ { Status: ethpb.ValidatorStatus_ACTIVE, ActivationEpoch: 12, }, { Status: ethpb.ValidatorStatus_ACTIVE, ActivationEpoch: 34, }, }, } actualValidatorStatusResponse, err := validatorClient.MultipleValidatorStatus( ctx, ðpb.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() ctx := context.Background() stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl) stateValidatorsProvider.EXPECT().GetStateValidators( ctx, gomock.Any(), nil, nil, ).Return( &rpcmiddleware.StateValidatorsResponseJson{}, errors.New("a specific error"), ).Times(1) validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider} _, err := validatorClient.MultipleValidatorStatus( ctx, ðpb.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() ctx := context.Background() 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( ctx, stringValidatorsPubKey, validatorsIndex, nil, ).Return( &rpcmiddleware.StateValidatorsResponseJson{ Data: []*rpcmiddleware.ValidatorContainerJson{ { 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) stateValidatorsProvider.EXPECT().GetStateValidators( ctx, 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", }, }, }, }, 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 := []primitives.ValidatorIndex{ 11111, 22222, 33333, 40000, 50000, primitives.ValidatorIndex(^uint64(0)), primitives.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(ctx, 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) ctx := context.Background() stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl) stateValidatorsProvider.EXPECT().GetStateValidators( ctx, []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( ctx, nil, nil, []string{"active"}, ).Return( &rpcmiddleware.StateValidatorsResponseJson{ Data: []*rpcmiddleware.ValidatorContainerJson{}, }, nil, ).Times(1) wantedValidatorsPubKey := [][]byte{validatorPubKey} wantedValidatorsIndex := []primitives.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(ctx, 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() ctx := context.Background() stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl) for _, aa := range testCase.inputGetStateValidatorsInterfaces { stateValidatorsProvider.EXPECT().GetStateValidators( ctx, aa.inputStringPubKeys, aa.inputIndexes, aa.inputStatuses, ).Return( aa.outputStateValidatorsResponseJson, aa.outputErr, ).Times(1) } validatorClient := beaconApiValidatorClient{stateValidatorsProvider: stateValidatorsProvider} _, _, _, err := validatorClient.getValidatorsStatusResponse( ctx, testCase.inputPubKeys, testCase.inputIndexes, ) assert.ErrorContains(t, testCase.outputErrMessage, err) }, ) } }