Files
prysm/validator/client/beacon-api/sync_committee_test.go
Bastin 92bd211e4d upgrade v6 to v7 (#15989)
* upgrade v6 to v7

* changelog

* update-go-ssz
2025-11-06 16:16:23 +00:00

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(), &ethpb.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 := &ethpb.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 := &ethpb.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, &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)
}
})
}
}