mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 07:58:22 -05:00
410 lines
11 KiB
Go
410 lines
11 KiB
Go
package beacon_api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/OffchainLabs/prysm/v7/api/apiutil"
|
|
"github.com/OffchainLabs/prysm/v7/api/server/structs"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
|
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
|
"github.com/OffchainLabs/prysm/v7/testing/require"
|
|
"github.com/OffchainLabs/prysm/v7/time/slots"
|
|
"github.com/OffchainLabs/prysm/v7/validator/client/beacon-api/mock"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/golang/protobuf/ptypes/empty"
|
|
"github.com/pkg/errors"
|
|
"go.uber.org/mock/gomock"
|
|
)
|
|
|
|
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 := &structs.SyncCommitteeMessage{
|
|
Slot: "42",
|
|
BeaconBlockRoot: beaconBlockRoot,
|
|
ValidatorIndex: "12345",
|
|
Signature: signature,
|
|
}
|
|
|
|
marshalledJsonRegistrations, err := json.Marshal([]*structs.SyncCommitteeMessage{jsonSyncCommitteeMessage})
|
|
require.NoError(t, err)
|
|
|
|
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
|
|
jsonRestHandler.EXPECT().Post(
|
|
gomock.Any(),
|
|
"/eth/v1/beacon/pool/sync_committees",
|
|
nil,
|
|
bytes.NewBuffer(marshalledJsonRegistrations),
|
|
nil,
|
|
).Return(
|
|
nil,
|
|
).Times(1)
|
|
|
|
protoSyncCommitteeMessage := ethpb.SyncCommitteeMessage{
|
|
Slot: primitives.Slot(42),
|
|
BlockRoot: decodedBeaconBlockRoot,
|
|
ValidatorIndex: primitives.ValidatorIndex(12345),
|
|
Signature: decodedSignature,
|
|
}
|
|
|
|
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
|
res, err := validatorClient.SubmitSyncMessage(t.Context(), &protoSyncCommitteeMessage)
|
|
|
|
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().Post(
|
|
gomock.Any(),
|
|
"/eth/v1/beacon/pool/sync_committees",
|
|
nil,
|
|
gomock.Any(),
|
|
nil,
|
|
).Return(
|
|
errors.New("foo error"),
|
|
).Times(1)
|
|
|
|
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
|
_, err := validatorClient.SubmitSyncMessage(t.Context(), ðpb.SyncCommitteeMessage{})
|
|
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 structs.BlockRootResponse
|
|
}{
|
|
{
|
|
name: "valid request",
|
|
expectedResponse: structs.BlockRootResponse{
|
|
Data: &structs.BlockRoot{
|
|
Root: blockRoot,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "internal server error",
|
|
expectedErrorMessage: "internal server error",
|
|
endpointError: errors.New("internal server error"),
|
|
},
|
|
{
|
|
name: "execution optimistic",
|
|
expectedResponse: structs.BlockRootResponse{
|
|
ExecutionOptimistic: true,
|
|
},
|
|
expectedErrorMessage: "the node is currently optimistic and cannot serve validators",
|
|
},
|
|
{
|
|
name: "no data",
|
|
expectedResponse: structs.BlockRootResponse{},
|
|
expectedErrorMessage: "no data returned",
|
|
},
|
|
{
|
|
name: "no root",
|
|
expectedResponse: structs.BlockRootResponse{
|
|
Data: new(structs.BlockRoot),
|
|
},
|
|
expectedErrorMessage: "no root returned",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := t.Context()
|
|
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
|
|
jsonRestHandler.EXPECT().Get(
|
|
gomock.Any(),
|
|
"/eth/v1/beacon/blocks/head/root",
|
|
&structs.BlockRootResponse{},
|
|
).SetArg(
|
|
2,
|
|
test.expectedResponse,
|
|
).Return(
|
|
test.endpointError,
|
|
).Times(1)
|
|
|
|
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
|
actualResponse, err := validatorClient.syncMessageBlockRoot(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 := ðpb.SyncCommitteeContributionRequest{
|
|
Slot: primitives.Slot(1),
|
|
PublicKey: nil,
|
|
SubnetId: 1,
|
|
}
|
|
|
|
contributionJson := &structs.SyncCommitteeContribution{
|
|
Slot: "1",
|
|
BeaconBlockRoot: blockRoot,
|
|
SubcommitteeIndex: "1",
|
|
AggregationBits: "0x01",
|
|
Signature: "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
contribution structs.ProduceSyncCommitteeContributionResponse
|
|
endpointErr error
|
|
expectedErrMsg string
|
|
}{
|
|
{
|
|
name: "valid request",
|
|
contribution: structs.ProduceSyncCommitteeContributionResponse{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 := t.Context()
|
|
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
|
|
jsonRestHandler.EXPECT().Get(
|
|
gomock.Any(),
|
|
"/eth/v1/beacon/blocks/head/root",
|
|
&structs.BlockRootResponse{},
|
|
).SetArg(
|
|
2,
|
|
structs.BlockRootResponse{
|
|
Data: &structs.BlockRoot{
|
|
Root: blockRoot,
|
|
},
|
|
},
|
|
).Return(
|
|
nil,
|
|
).Times(1)
|
|
|
|
jsonRestHandler.EXPECT().Get(
|
|
gomock.Any(),
|
|
fmt.Sprintf("/eth/v1/validator/sync_committee_contribution?beacon_block_root=%s&slot=%d&subcommittee_index=%d",
|
|
blockRoot, uint64(request.Slot), request.SubnetId),
|
|
&structs.ProduceSyncCommitteeContributionResponse{},
|
|
).SetArg(
|
|
2,
|
|
test.contribution,
|
|
).Return(
|
|
test.endpointErr,
|
|
).Times(1)
|
|
|
|
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
|
actualResponse, err := validatorClient.syncCommitteeContribution(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 := ðpb.SyncSubcommitteeIndexResponse{
|
|
Indices: []primitives.CommitteeIndex{123, 456},
|
|
}
|
|
|
|
syncDuties := []*structs.SyncCommitteeDuty{
|
|
{
|
|
Pubkey: hexutil.Encode([]byte{1}),
|
|
ValidatorIndex: validatorIndex,
|
|
ValidatorSyncCommitteeIndices: []string{
|
|
"123",
|
|
"456",
|
|
},
|
|
},
|
|
}
|
|
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
tests := []struct {
|
|
name string
|
|
duties []*structs.SyncCommitteeDuty
|
|
validatorsErr error
|
|
dutiesErr error
|
|
expectedErrorMsg string
|
|
}{
|
|
{
|
|
name: "success",
|
|
duties: syncDuties,
|
|
},
|
|
{
|
|
name: "no sync duties",
|
|
duties: []*structs.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 := t.Context()
|
|
valsReq := &structs.GetValidatorsRequest{
|
|
Ids: []string{pubkeyStr},
|
|
Statuses: []string{},
|
|
}
|
|
valsReqBytes, err := json.Marshal(valsReq)
|
|
require.NoError(t, err)
|
|
jsonRestHandler := mock.NewMockJsonRestHandler(ctrl)
|
|
jsonRestHandler.EXPECT().Post(
|
|
gomock.Any(),
|
|
validatorsEndpoint,
|
|
nil,
|
|
bytes.NewBuffer(valsReqBytes),
|
|
&structs.GetValidatorsResponse{},
|
|
).SetArg(
|
|
4,
|
|
structs.GetValidatorsResponse{
|
|
Data: []*structs.ValidatorContainer{
|
|
{
|
|
Index: validatorIndex,
|
|
Status: "active_ongoing",
|
|
Validator: &structs.Validator{
|
|
Pubkey: stringPubKey,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
).Return(
|
|
test.validatorsErr,
|
|
).Times(1)
|
|
|
|
if test.validatorsErr != nil {
|
|
// Then try the GET call which will also return error.
|
|
queryParams := url.Values{}
|
|
for _, id := range valsReq.Ids {
|
|
queryParams.Add("id", id)
|
|
}
|
|
for _, st := range valsReq.Statuses {
|
|
queryParams.Add("status", st)
|
|
}
|
|
|
|
query := apiutil.BuildURL("/eth/v1/beacon/states/head/validators", queryParams)
|
|
|
|
jsonRestHandler.EXPECT().Get(
|
|
gomock.Any(),
|
|
query,
|
|
&structs.GetValidatorsResponse{},
|
|
).Return(
|
|
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().Post(
|
|
gomock.Any(),
|
|
fmt.Sprintf("%s/%d", syncDutiesEndpoint, slots.ToEpoch(slot)),
|
|
nil,
|
|
bytes.NewBuffer(validatorIndicesBytes),
|
|
&structs.GetSyncCommitteeDutiesResponse{},
|
|
).SetArg(
|
|
4,
|
|
structs.GetSyncCommitteeDutiesResponse{
|
|
Data: test.duties,
|
|
},
|
|
).Return(
|
|
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.syncSubcommitteeIndex(ctx, ðpb.SyncSubcommitteeIndexRequest{
|
|
PublicKey: pubkey,
|
|
Slot: slot,
|
|
})
|
|
if test.expectedErrorMsg == "" {
|
|
require.NoError(t, err)
|
|
assert.DeepEqual(t, expectedResponse, actualResponse)
|
|
} else {
|
|
require.ErrorContains(t, test.expectedErrorMsg, err)
|
|
}
|
|
})
|
|
}
|
|
}
|