prysm-pulse/validator/client/beacon-api/sync_committee_test.go
Dhruv Bodani 5092738be6
add REST implementation for GetSyncSubcommitteeIndex (#11971)
* add implementation for GetSyncSubcommitteeIndex

* fix goimports

* update error parsing for validator sync committee index

Co-authored-by: Radosław Kapka <radek@prysmaticlabs.com>

* fix imports

---------

Co-authored-by: Radosław Kapka <radek@prysmaticlabs.com>
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2023-02-09 10:03:32 +00:00

388 lines
11 KiB
Go

package beacon_api
import (
"bytes"
"context"
"encoding/json"
"fmt"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/mock/gomock"
"github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
"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/time/slots"
"github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock"
)
func TestSubmitSyncMessage_Valid(t *testing.T) {
const beaconBlockRoot = "0x719d4f66a5f25c35d93718821aacb342194391034b11cf0a5822cc249178a274"
const signature = "0xb459ef852bd4e0cb96e6723d67cacc8215406dd9ba663f8874a083167ebf428b28b746431bdbc1820a25289377b2610881e52b3a05c3548c5e99c08c8a36342573be5962d7510c03dcba8ddfb8ae419e59d222ddcf31cc512e704ef2cc3cf8"
decodedBeaconBlockRoot, err := hexutil.Decode(beaconBlockRoot)
require.NoError(t, err)
decodedSignature, err := hexutil.Decode(signature)
require.NoError(t, err)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonSyncCommitteeMessage := &apimiddleware.SyncCommitteeMessageJson{
Slot: "42",
BeaconBlockRoot: beaconBlockRoot,
ValidatorIndex: "12345",
Signature: signature,
}
marshalledJsonRegistrations, err := json.Marshal([]*apimiddleware.SyncCommitteeMessageJson{jsonSyncCommitteeMessage})
require.NoError(t, err)
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().PostRestJson(
context.Background(),
"/eth/v1/beacon/pool/sync_committees",
nil,
bytes.NewBuffer(marshalledJsonRegistrations),
nil,
).Return(
nil,
nil,
).Times(1)
protoSyncCommiteeMessage := ethpb.SyncCommitteeMessage{
Slot: primitives.Slot(42),
BlockRoot: decodedBeaconBlockRoot,
ValidatorIndex: primitives.ValidatorIndex(12345),
Signature: decodedSignature,
}
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
res, err := validatorClient.SubmitSyncMessage(context.Background(), &protoSyncCommiteeMessage)
assert.DeepEqual(t, new(empty.Empty), res)
require.NoError(t, err)
}
func TestSubmitSyncMessage_BadRequest(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().PostRestJson(
context.Background(),
"/eth/v1/beacon/pool/sync_committees",
nil,
gomock.Any(),
nil,
).Return(
nil,
errors.New("foo error"),
).Times(1)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
_, err := validatorClient.SubmitSyncMessage(context.Background(), &ethpb.SyncCommitteeMessage{})
assert.ErrorContains(t, "failed to send POST data to `/eth/v1/beacon/pool/sync_committees` REST endpoint", err)
assert.ErrorContains(t, "foo error", err)
}
func TestGetSyncMessageBlockRoot(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
const blockRoot = "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
tests := []struct {
name string
endpointError error
expectedErrorMessage string
expectedResponse apimiddleware.BlockRootResponseJson
}{
{
name: "valid request",
expectedResponse: apimiddleware.BlockRootResponseJson{
Data: &apimiddleware.BlockRootContainerJson{
Root: blockRoot,
},
},
},
{
name: "internal server error",
expectedErrorMessage: "internal server error",
endpointError: errors.New("internal server error"),
},
{
name: "execution optimistic",
expectedResponse: apimiddleware.BlockRootResponseJson{
ExecutionOptimistic: true,
},
expectedErrorMessage: "the node is currently optimistic and cannot serve validators",
},
{
name: "no data",
expectedResponse: apimiddleware.BlockRootResponseJson{},
expectedErrorMessage: "no data returned",
},
{
name: "no root",
expectedResponse: apimiddleware.BlockRootResponseJson{
Data: new(apimiddleware.BlockRootContainerJson),
},
expectedErrorMessage: "no root returned",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
"/eth/v1/beacon/blocks/head/root",
&apimiddleware.BlockRootResponseJson{},
).SetArg(
2,
test.expectedResponse,
).Return(
nil,
test.endpointError,
).Times(1)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
actualResponse, err := validatorClient.getSyncMessageBlockRoot(ctx)
if test.expectedErrorMessage != "" {
require.ErrorContains(t, test.expectedErrorMessage, err)
return
}
require.NoError(t, err)
expectedRootBytes, err := hexutil.Decode(test.expectedResponse.Data.Root)
require.NoError(t, err)
require.NoError(t, err)
require.DeepEqual(t, expectedRootBytes, actualResponse.Root)
})
}
}
func TestGetSyncCommitteeContribution(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
const blockRoot = "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
request := &ethpb.SyncCommitteeContributionRequest{
Slot: primitives.Slot(1),
PublicKey: nil,
SubnetId: 1,
}
contributionJson := &apimiddleware.SyncCommitteeContributionJson{
Slot: "1",
BeaconBlockRoot: blockRoot,
SubcommitteeIndex: "1",
AggregationBits: "0x01",
Signature: "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
}
tests := []struct {
name string
contribution apimiddleware.ProduceSyncCommitteeContributionResponseJson
endpointErr error
expectedErrMsg string
}{
{
name: "valid request",
contribution: apimiddleware.ProduceSyncCommitteeContributionResponseJson{Data: contributionJson},
},
{
name: "bad request",
endpointErr: errors.New("internal server error"),
expectedErrMsg: "internal server error",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
"/eth/v1/beacon/blocks/head/root",
&apimiddleware.BlockRootResponseJson{},
).SetArg(
2,
apimiddleware.BlockRootResponseJson{
Data: &apimiddleware.BlockRootContainerJson{
Root: blockRoot,
},
},
).Return(
nil,
nil,
).Times(1)
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
fmt.Sprintf("/eth/v1/validator/sync_committee_contribution?beacon_block_root=%s&slot=%d&subcommittee_index=%d",
blockRoot, uint64(request.Slot), request.SubnetId),
&apimiddleware.ProduceSyncCommitteeContributionResponseJson{},
).SetArg(
2,
test.contribution,
).Return(
nil,
test.endpointErr,
).Times(1)
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
actualResponse, err := validatorClient.getSyncCommitteeContribution(ctx, request)
if test.expectedErrMsg != "" {
require.ErrorContains(t, test.expectedErrMsg, err)
return
}
require.NoError(t, err)
expectedResponse, err := convertSyncContributionJsonToProto(test.contribution.Data)
require.NoError(t, err)
assert.DeepEqual(t, expectedResponse, actualResponse)
})
}
}
func TestGetSyncSubCommitteeIndex(t *testing.T) {
const (
pubkeyStr = "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13"
syncDutiesEndpoint = "/eth/v1/validator/duties/sync"
validatorsEndpoint = "/eth/v1/beacon/states/head/validators"
validatorIndex = "55293"
slot = primitives.Slot(123)
)
expectedResponse := &ethpb.SyncSubcommitteeIndexResponse{
Indices: []primitives.CommitteeIndex{123, 456},
}
syncDuties := []*apimiddleware.SyncCommitteeDuty{
{
Pubkey: hexutil.Encode([]byte{1}),
ValidatorIndex: validatorIndex,
ValidatorSyncCommitteeIndices: []string{
"123",
"456",
},
},
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
tests := []struct {
name string
duties []*apimiddleware.SyncCommitteeDuty
validatorsErr error
dutiesErr error
expectedErrorMsg string
}{
{
name: "success",
duties: syncDuties,
},
{
name: "no sync duties",
duties: []*apimiddleware.SyncCommitteeDuty{},
expectedErrorMsg: fmt.Sprintf("no sync committee duty for the given slot %d", slot),
},
{
name: "duties endpoint error",
dutiesErr: errors.New("bad request"),
expectedErrorMsg: "bad request",
},
{
name: "validator index endpoint error",
validatorsErr: errors.New("bad request"),
expectedErrorMsg: "bad request",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := context.Background()
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(
ctx,
fmt.Sprintf("%s?id=%s", validatorsEndpoint, pubkeyStr),
&apimiddleware.StateValidatorsResponseJson{},
).SetArg(
2,
apimiddleware.StateValidatorsResponseJson{
Data: []*apimiddleware.ValidatorContainerJson{
{
Index: validatorIndex,
Status: "active_ongoing",
Validator: &apimiddleware.ValidatorJson{
PublicKey: stringPubKey,
},
},
},
},
).Return(
nil,
test.validatorsErr,
).Times(1)
validatorIndicesBytes, err := json.Marshal([]string{validatorIndex})
require.NoError(t, err)
var syncDutiesCalled int
if test.validatorsErr == nil {
syncDutiesCalled = 1
}
jsonRestHandler.EXPECT().PostRestJson(
ctx,
fmt.Sprintf("%s/%d", syncDutiesEndpoint, slots.ToEpoch(slot)),
nil,
bytes.NewBuffer(validatorIndicesBytes),
&apimiddleware.SyncCommitteeDutiesResponseJson{},
).SetArg(
4,
apimiddleware.SyncCommitteeDutiesResponseJson{
Data: test.duties,
},
).Return(
nil,
test.dutiesErr,
).Times(syncDutiesCalled)
pubkey, err := hexutil.Decode(pubkeyStr)
require.NoError(t, err)
validatorClient := &beaconApiValidatorClient{
jsonRestHandler: jsonRestHandler,
stateValidatorsProvider: beaconApiStateValidatorsProvider{
jsonRestHandler: jsonRestHandler,
},
dutiesProvider: beaconApiDutiesProvider{
jsonRestHandler: jsonRestHandler,
},
}
actualResponse, err := validatorClient.getSyncSubcommitteeIndex(ctx, &ethpb.SyncSubcommitteeIndexRequest{
PublicKey: pubkey,
Slot: slot,
})
if test.expectedErrorMsg == "" {
require.NoError(t, err)
assert.DeepEqual(t, expectedResponse, actualResponse)
} else {
require.ErrorContains(t, test.expectedErrorMsg, err)
}
})
}
}