From e100fb0c08921eeb484b1d89ac7bd208f38b4008 Mon Sep 17 00:00:00 2001 From: Dhruv Bodani Date: Fri, 23 Feb 2024 19:23:42 +0530 Subject: [PATCH] Add support for sync committee selections (#13633) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add support for sync committee selections * go mod tidy * remove unused fields * fix build * fix build --------- Co-authored-by: RadosÅ‚aw Kapka --- beacon-chain/rpc/eth/validator/handlers.go | 6 + beacon-chain/rpc/service.go | 1 + beacon-chain/rpc/service_test.go | 10 +- .../validator-mock/validator_client_mock.go | 15 ++ validator/client/beacon-api/BUILD.bazel | 2 + .../beacon-api/beacon_api_validator_client.go | 4 + .../beacon-api/sync_committee_selections.go | 35 +++++ .../sync_committee_selections_test.go | 130 ++++++++++++++++++ .../client/grpc-api/grpc_validator_client.go | 4 + validator/client/iface/validator_client.go | 59 ++++++++ validator/client/sync_committee.go | 28 +++- validator/client/validator.go | 25 +++- validator/client/validator_test.go | 61 +++++++- 13 files changed, 368 insertions(+), 12 deletions(-) create mode 100644 validator/client/beacon-api/sync_committee_selections.go create mode 100644 validator/client/beacon-api/sync_committee_selections_test.go diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index ddc9e464ca20..53cff078d2b5 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -1092,6 +1092,12 @@ func (s *Server) BeaconCommitteeSelections(w http.ResponseWriter, _ *http.Reques httputil.HandleError(w, "Endpoint not implemented", 501) } +// SyncCommitteeSelections responds with appropriate message and status code according the spec: +// https://ethereum.github.io/beacon-APIs/#/Validator/submitSyncCommitteeSelections. +func (s *Server) SyncCommitteeSelections(w http.ResponseWriter, _ *http.Request) { + httputil.HandleError(w, "Endpoint not implemented", 501) +} + // attestationDependentRoot is get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch - 1) - 1) // or the genesis block root in the case of underflow. func attestationDependentRoot(s state.BeaconState, epoch primitives.Epoch) ([]byte, error) { diff --git a/beacon-chain/rpc/service.go b/beacon-chain/rpc/service.go index abfc45194bed..b888b0dea45b 100644 --- a/beacon-chain/rpc/service.go +++ b/beacon-chain/rpc/service.go @@ -226,6 +226,7 @@ func (s *Service) initializeValidatorServerRoutes(validatorServer *validator.Ser s.cfg.Router.HandleFunc("/eth/v1/validator/blinded_blocks/{slot}", validatorServer.ProduceBlindedBlock).Methods(http.MethodGet) s.cfg.Router.HandleFunc("/eth/v3/validator/blocks/{slot}", validatorServer.ProduceBlockV3).Methods(http.MethodGet) s.cfg.Router.HandleFunc("/eth/v1/validator/beacon_committee_selections", validatorServer.BeaconCommitteeSelections).Methods(http.MethodPost) + s.cfg.Router.HandleFunc("/eth/v1/validator/sync_committee_selections", validatorServer.SyncCommitteeSelections).Methods(http.MethodPost) } func (s *Service) initializeNodeServerRoutes(nodeServer *node.Server) { diff --git a/beacon-chain/rpc/service_test.go b/beacon-chain/rpc/service_test.go index b72cc89aab9b..f001e1b37a15 100644 --- a/beacon-chain/rpc/service_test.go +++ b/beacon-chain/rpc/service_test.go @@ -152,11 +152,11 @@ func TestServer_InitializeRoutes(t *testing.T) { "/eth/v1/validator/sync_committee_subscriptions": {http.MethodPost}, "/eth/v1/validator/beacon_committee_selections": {http.MethodPost}, "/eth/v1/validator/sync_committee_contribution": {http.MethodGet}, - //"/eth/v1/validator/sync_committee_selections": {http.MethodPost}, // not implemented - "/eth/v1/validator/contribution_and_proofs": {http.MethodPost}, - "/eth/v1/validator/prepare_beacon_proposer": {http.MethodPost}, - "/eth/v1/validator/register_validator": {http.MethodPost}, - "/eth/v1/validator/liveness/{epoch}": {http.MethodPost}, + "/eth/v1/validator/sync_committee_selections": {http.MethodPost}, + "/eth/v1/validator/contribution_and_proofs": {http.MethodPost}, + "/eth/v1/validator/prepare_beacon_proposer": {http.MethodPost}, + "/eth/v1/validator/register_validator": {http.MethodPost}, + "/eth/v1/validator/liveness/{epoch}": {http.MethodPost}, } prysmCustomRoutes := map[string][]string{ diff --git a/testing/validator-mock/validator_client_mock.go b/testing/validator-mock/validator_client_mock.go index 582fa2ede641..3f75228f5b9b 100644 --- a/testing/validator-mock/validator_client_mock.go +++ b/testing/validator-mock/validator_client_mock.go @@ -102,6 +102,21 @@ func (mr *MockValidatorClientMockRecorder) GetAggregatedSelections(arg0, arg1 an return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAggregatedSelections", reflect.TypeOf((*MockValidatorClient)(nil).GetAggregatedSelections), arg0, arg1) } +// GetAggregatedSyncSelections mocks base method. +func (m *MockValidatorClient) GetAggregatedSyncSelections(arg0 context.Context, arg1 []iface.SyncCommitteeSelection) ([]iface.SyncCommitteeSelection, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAggregatedSyncSelections", arg0, arg1) + ret0, _ := ret[0].([]iface.SyncCommitteeSelection) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAggregatedSyncSelections indicates an expected call of GetAggregatedSyncSelections. +func (mr *MockValidatorClientMockRecorder) GetAggregatedSyncSelections(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAggregatedSyncSelections", reflect.TypeOf((*MockValidatorClient)(nil).GetAggregatedSyncSelections), arg0, arg1) +} + // GetAttestationData mocks base method. func (m *MockValidatorClient) GetAttestationData(arg0 context.Context, arg1 *eth.AttestationDataRequest) (*eth.AttestationData, error) { m.ctrl.T.Helper() diff --git a/validator/client/beacon-api/BUILD.bazel b/validator/client/beacon-api/BUILD.bazel index f7d1b6a57ebf..994936bd105b 100644 --- a/validator/client/beacon-api/BUILD.bazel +++ b/validator/client/beacon-api/BUILD.bazel @@ -36,6 +36,7 @@ go_library( "submit_signed_contribution_and_proof.go", "subscribe_committee_subnets.go", "sync_committee.go", + "sync_committee_selections.go", ], importpath = "github.com/prysmaticlabs/prysm/v5/validator/client/beacon-api", visibility = ["//validator:__subpackages__"], @@ -107,6 +108,7 @@ go_test( "submit_signed_aggregate_proof_test.go", "submit_signed_contribution_and_proof_test.go", "subscribe_committee_subnets_test.go", + "sync_committee_selections_test.go", "sync_committee_test.go", "validator_count_test.go", "wait_for_chain_start_test.go", diff --git a/validator/client/beacon-api/beacon_api_validator_client.go b/validator/client/beacon-api/beacon_api_validator_client.go index f1a46438dd99..614559f0f1c7 100644 --- a/validator/client/beacon-api/beacon_api_validator_client.go +++ b/validator/client/beacon-api/beacon_api_validator_client.go @@ -179,3 +179,7 @@ func (c *beaconApiValidatorClient) EventStreamIsRunning() bool { func (c *beaconApiValidatorClient) GetAggregatedSelections(ctx context.Context, selections []iface.BeaconCommitteeSelection) ([]iface.BeaconCommitteeSelection, error) { return c.getAggregatedSelection(ctx, selections) } + +func (c *beaconApiValidatorClient) GetAggregatedSyncSelections(ctx context.Context, selections []iface.SyncCommitteeSelection) ([]iface.SyncCommitteeSelection, error) { + return c.getAggregatedSyncSelections(ctx, selections) +} diff --git a/validator/client/beacon-api/sync_committee_selections.go b/validator/client/beacon-api/sync_committee_selections.go new file mode 100644 index 000000000000..97163308e950 --- /dev/null +++ b/validator/client/beacon-api/sync_committee_selections.go @@ -0,0 +1,35 @@ +package beacon_api + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/validator/client/iface" +) + +type aggregatedSyncSelectionResponse struct { + Data []iface.SyncCommitteeSelection `json:"data"` +} + +func (c *beaconApiValidatorClient) getAggregatedSyncSelections(ctx context.Context, selections []iface.SyncCommitteeSelection) ([]iface.SyncCommitteeSelection, error) { + body, err := json.Marshal(selections) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal selections") + } + + var resp aggregatedSyncSelectionResponse + err = c.jsonRestHandler.Post(ctx, "/eth/v1/validator/sync_committee_selections", nil, bytes.NewBuffer(body), &resp) + if err != nil { + return nil, errors.Wrap(err, "error calling post endpoint") + } + if len(resp.Data) == 0 { + return nil, errors.New("no aggregated sync selections returned") + } + if len(selections) != len(resp.Data) { + return nil, errors.New("mismatching number of sync selections") + } + + return resp.Data, nil +} diff --git a/validator/client/beacon-api/sync_committee_selections_test.go b/validator/client/beacon-api/sync_committee_selections_test.go new file mode 100644 index 000000000000..6afc78e5a14c --- /dev/null +++ b/validator/client/beacon-api/sync_committee_selections_test.go @@ -0,0 +1,130 @@ +package beacon_api + +import ( + "bytes" + "context" + "encoding/json" + "testing" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/validator/client/beacon-api/mock" + test_helpers "github.com/prysmaticlabs/prysm/v5/validator/client/beacon-api/test-helpers" + "github.com/prysmaticlabs/prysm/v5/validator/client/iface" + "go.uber.org/mock/gomock" +) + +func TestGetAggregatedSyncSelections(t *testing.T) { + testcases := []struct { + name string + req []iface.SyncCommitteeSelection + res []iface.SyncCommitteeSelection + endpointError error + expectedErrorMessage string + }{ + { + name: "valid", + req: []iface.SyncCommitteeSelection{ + { + SelectionProof: test_helpers.FillByteSlice(96, 82), + Slot: 75, + ValidatorIndex: 76, + SubcommitteeIndex: 77, + }, + }, + res: []iface.SyncCommitteeSelection{ + { + SelectionProof: test_helpers.FillByteSlice(96, 100), + Slot: 75, + ValidatorIndex: 76, + SubcommitteeIndex: 77, + }, + }, + }, + { + name: "endpoint error", + req: []iface.SyncCommitteeSelection{ + { + SelectionProof: test_helpers.FillByteSlice(96, 82), + Slot: 75, + ValidatorIndex: 76, + SubcommitteeIndex: 77, + }, + }, + endpointError: errors.New("bad request"), + expectedErrorMessage: "bad request", + }, + { + name: "no response error", + req: []iface.SyncCommitteeSelection{ + { + SelectionProof: test_helpers.FillByteSlice(96, 82), + Slot: 75, + ValidatorIndex: 76, + SubcommitteeIndex: 77, + }, + }, + expectedErrorMessage: "no aggregated sync selections returned", + }, + { + name: "mismatch response", + req: []iface.SyncCommitteeSelection{ + { + SelectionProof: test_helpers.FillByteSlice(96, 82), + Slot: 75, + ValidatorIndex: 76, + SubcommitteeIndex: 77, + }, + { + SelectionProof: test_helpers.FillByteSlice(96, 100), + Slot: 75, + ValidatorIndex: 76, + SubcommitteeIndex: 78, + }, + }, + res: []iface.SyncCommitteeSelection{ + { + SelectionProof: test_helpers.FillByteSlice(96, 100), + Slot: 75, + ValidatorIndex: 76, + SubcommitteeIndex: 77, + }, + }, + expectedErrorMessage: "mismatching number of sync selections", + }, + } + + for _, test := range testcases { + t.Run(test.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + reqBody, err := json.Marshal(test.req) + require.NoError(t, err) + + ctx := context.Background() + jsonRestHandler.EXPECT().Post( + ctx, + "/eth/v1/validator/sync_committee_selections", + nil, + bytes.NewBuffer(reqBody), + &aggregatedSyncSelectionResponse{}, + ).SetArg( + 4, + aggregatedSyncSelectionResponse{Data: test.res}, + ).Return( + test.endpointError, + ).Times(1) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + res, err := validatorClient.GetAggregatedSyncSelections(ctx, test.req) + if test.expectedErrorMessage != "" { + require.ErrorContains(t, test.expectedErrorMessage, err) + return + } + + require.NoError(t, err) + require.DeepEqual(t, test.res, res) + }) + } +} diff --git a/validator/client/grpc-api/grpc_validator_client.go b/validator/client/grpc-api/grpc_validator_client.go index 53acda13f836..e0fee5dfd60a 100644 --- a/validator/client/grpc-api/grpc_validator_client.go +++ b/validator/client/grpc-api/grpc_validator_client.go @@ -142,6 +142,10 @@ func (grpcValidatorClient) GetAggregatedSelections(context.Context, []iface.Beac return nil, iface.ErrNotSupported } +func (grpcValidatorClient) GetAggregatedSyncSelections(context.Context, []iface.SyncCommitteeSelection) ([]iface.SyncCommitteeSelection, error) { + return nil, iface.ErrNotSupported +} + func NewGrpcValidatorClient(cc grpc.ClientConnInterface) iface.ValidatorClient { return &grpcValidatorClient{ethpb.NewBeaconNodeValidatorClient(cc)} } diff --git a/validator/client/iface/validator_client.go b/validator/client/iface/validator_client.go index 24956e881a29..cebdda041761 100644 --- a/validator/client/iface/validator_client.go +++ b/validator/client/iface/validator_client.go @@ -62,6 +62,64 @@ func (b *BeaconCommitteeSelection) UnmarshalJSON(input []byte) error { return nil } +type SyncCommitteeSelection struct { + SelectionProof []byte + Slot primitives.Slot + SubcommitteeIndex primitives.CommitteeIndex + ValidatorIndex primitives.ValidatorIndex +} + +type syncCommitteeSelectionJson struct { + SelectionProof string `json:"selection_proof"` + Slot string `json:"slot"` + SubcommitteeIndex string `json:"subcommittee_index"` + ValidatorIndex string `json:"validator_index"` +} + +func (s SyncCommitteeSelection) MarshalJSON() ([]byte, error) { + return json.Marshal(syncCommitteeSelectionJson{ + SelectionProof: hexutil.Encode(s.SelectionProof), + Slot: strconv.FormatUint(uint64(s.Slot), 10), + SubcommitteeIndex: strconv.FormatUint(uint64(s.SubcommitteeIndex), 10), + ValidatorIndex: strconv.FormatUint(uint64(s.ValidatorIndex), 10), + }) +} + +func (s *SyncCommitteeSelection) UnmarshalJSON(input []byte) error { + var resJson syncCommitteeSelectionJson + err := json.Unmarshal(input, &resJson) + if err != nil { + return errors.Wrap(err, "failed to unmarshal sync committee selection") + } + + slot, err := strconv.ParseUint(resJson.Slot, 10, 64) + if err != nil { + return errors.Wrap(err, "failed to parse slot") + } + + vIdx, err := strconv.ParseUint(resJson.ValidatorIndex, 10, 64) + if err != nil { + return errors.Wrap(err, "failed to parse validator index") + } + + subcommIdx, err := strconv.ParseUint(resJson.SubcommitteeIndex, 10, 64) + if err != nil { + return errors.Wrap(err, "failed to parse subcommittee index") + } + + selectionProof, err := hexutil.Decode(resJson.SelectionProof) + if err != nil { + return errors.Wrap(err, "failed to parse selection proof") + } + + s.Slot = primitives.Slot(slot) + s.SelectionProof = selectionProof + s.ValidatorIndex = primitives.ValidatorIndex(vIdx) + s.SubcommitteeIndex = primitives.CommitteeIndex(subcommIdx) + + return nil +} + type ValidatorClient interface { GetDuties(ctx context.Context, in *ethpb.DutiesRequest) (*ethpb.DutiesResponse, error) DomainData(ctx context.Context, in *ethpb.DomainRequest) (*ethpb.DomainResponse, error) @@ -91,4 +149,5 @@ type ValidatorClient interface { StartEventStream(ctx context.Context) error EventStreamIsRunning() bool GetAggregatedSelections(ctx context.Context, selections []BeaconCommitteeSelection) ([]BeaconCommitteeSelection, error) + GetAggregatedSyncSelections(ctx context.Context, selections []SyncCommitteeSelection) ([]SyncCommitteeSelection, error) } diff --git a/validator/client/sync_committee.go b/validator/client/sync_committee.go index 626e9f5e4342..7658e49ad4a2 100644 --- a/validator/client/sync_committee.go +++ b/validator/client/sync_committee.go @@ -6,6 +6,9 @@ import ( "sync/atomic" "time" + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/validator/client/iface" + "github.com/ethereum/go-ethereum/common/hexutil" emptypb "github.com/golang/protobuf/ptypes/empty" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair" @@ -117,7 +120,7 @@ func (v *validator) SubmitSignedContributionAndProof(ctx context.Context, slot p return } - selectionProofs, err := v.selectionProofs(ctx, slot, pubKey, indexRes) + selectionProofs, err := v.selectionProofs(ctx, slot, pubKey, indexRes, duty.ValidatorIndex) if err != nil { log.WithError(err).Error("Could not get selection proofs") return @@ -188,11 +191,12 @@ func (v *validator) SubmitSignedContributionAndProof(ctx context.Context, slot p } // Signs and returns selection proofs per validator for slot and pub key. -func (v *validator) selectionProofs(ctx context.Context, slot primitives.Slot, pubKey [fieldparams.BLSPubkeyLength]byte, indexRes *ethpb.SyncSubcommitteeIndexResponse) ([][]byte, error) { +func (v *validator) selectionProofs(ctx context.Context, slot primitives.Slot, pubKey [fieldparams.BLSPubkeyLength]byte, indexRes *ethpb.SyncSubcommitteeIndexResponse, validatorIndex primitives.ValidatorIndex) ([][]byte, error) { selectionProofs := make([][]byte, len(indexRes.Indices)) cfg := params.BeaconConfig() size := cfg.SyncCommitteeSize subCount := cfg.SyncCommitteeSubnetCount + selections := make([]iface.SyncCommitteeSelection, len(indexRes.Indices)) for i, index := range indexRes.Indices { subSize := size / subCount subnet := uint64(index) / subSize @@ -201,7 +205,27 @@ func (v *validator) selectionProofs(ctx context.Context, slot primitives.Slot, p return nil, err } selectionProofs[i] = selectionProof + selections[i] = iface.SyncCommitteeSelection{ + SelectionProof: selectionProof, + Slot: slot, + SubcommitteeIndex: primitives.CommitteeIndex(subnet), + ValidatorIndex: validatorIndex, + } } + + // Override selection proofs with aggregated ones if the node is part of a Distributed Validator. + if v.distributed && len(selections) > 0 { + var err error + selections, err := v.validatorClient.GetAggregatedSyncSelections(ctx, selections) + if err != nil { + return nil, errors.Wrap(err, "failed to get aggregated sync selections") + } + + for i, s := range selections { + selectionProofs[i] = s.SelectionProof + } + } + return selectionProofs, nil } diff --git a/validator/client/validator.go b/validator/client/validator.go index 93b3ef6ba4f3..d298eaf5d6d5 100644 --- a/validator/client/validator.go +++ b/validator/client/validator.go @@ -753,7 +753,7 @@ func (v *validator) RolesAt(ctx context.Context, slot primitives.Slot) (map[[fie } } if inSyncCommittee { - aggregator, err := v.isSyncCommitteeAggregator(ctx, slot, bytesutil.ToBytes48(duty.PublicKey)) + aggregator, err := v.isSyncCommitteeAggregator(ctx, slot, bytesutil.ToBytes48(duty.PublicKey), duty.ValidatorIndex) if err != nil { return nil, errors.Wrap(err, "could not check if a validator is a sync committee aggregator") } @@ -818,7 +818,7 @@ func (v *validator) isAggregator(ctx context.Context, committee []primitives.Val // // modulo = max(1, SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT // TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE) // return bytes_to_uint64(hash(signature)[0:8]) % modulo == 0 -func (v *validator) isSyncCommitteeAggregator(ctx context.Context, slot primitives.Slot, pubKey [fieldparams.BLSPubkeyLength]byte) (bool, error) { +func (v *validator) isSyncCommitteeAggregator(ctx context.Context, slot primitives.Slot, pubKey [fieldparams.BLSPubkeyLength]byte, validatorIndex primitives.ValidatorIndex) (bool, error) { res, err := v.validatorClient.GetSyncSubcommitteeIndex(ctx, ðpb.SyncSubcommitteeIndexRequest{ PublicKey: pubKey[:], Slot: slot, @@ -827,6 +827,7 @@ func (v *validator) isSyncCommitteeAggregator(ctx context.Context, slot primitiv return false, err } + var selections []iface.SyncCommitteeSelection for _, index := range res.Indices { subCommitteeSize := params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount subnet := uint64(index) / subCommitteeSize @@ -834,7 +835,25 @@ func (v *validator) isSyncCommitteeAggregator(ctx context.Context, slot primitiv if err != nil { return false, err } - isAggregator, err := altair.IsSyncCommitteeAggregator(sig) + + selections = append(selections, iface.SyncCommitteeSelection{ + SelectionProof: sig, + Slot: slot, + SubcommitteeIndex: primitives.CommitteeIndex(subnet), + ValidatorIndex: validatorIndex, + }) + } + + // Override selections with aggregated ones if the node is part of a Distributed Validator. + if v.distributed && len(selections) > 0 { + selections, err = v.validatorClient.GetAggregatedSyncSelections(ctx, selections) + if err != nil { + return false, errors.Wrap(err, "failed to get aggregated sync selections") + } + } + + for _, s := range selections { + isAggregator, err := altair.IsSyncCommitteeAggregator(s.SelectionProof) if err != nil { return false, err } diff --git a/validator/client/validator_test.go b/validator/client/validator_test.go index adcca25b9a18..e2692d8a848a 100644 --- a/validator/client/validator_test.go +++ b/validator/client/validator_test.go @@ -1251,7 +1251,7 @@ func TestIsSyncCommitteeAggregator_OK(t *testing.T) { }, ).Return(ðpb.SyncSubcommitteeIndexResponse{}, nil /*err*/) - aggregator, err := v.isSyncCommitteeAggregator(context.Background(), slot, bytesutil.ToBytes48(pubKey)) + aggregator, err := v.isSyncCommitteeAggregator(context.Background(), slot, bytesutil.ToBytes48(pubKey), 0) require.NoError(t, err) require.Equal(t, false, aggregator) @@ -1272,7 +1272,64 @@ func TestIsSyncCommitteeAggregator_OK(t *testing.T) { }, ).Return(ðpb.SyncSubcommitteeIndexResponse{Indices: []primitives.CommitteeIndex{0}}, nil /*err*/) - aggregator, err = v.isSyncCommitteeAggregator(context.Background(), slot, bytesutil.ToBytes48(pubKey)) + aggregator, err = v.isSyncCommitteeAggregator(context.Background(), slot, bytesutil.ToBytes48(pubKey), 0) + require.NoError(t, err) + require.Equal(t, true, aggregator) +} + +func TestIsSyncCommitteeAggregator_Distributed_OK(t *testing.T) { + params.SetupTestConfigCleanup(t) + v, m, validatorKey, finish := setup(t) + defer finish() + + v.distributed = true + slot := primitives.Slot(1) + pubKey := validatorKey.PublicKey().Marshal() + + m.validatorClient.EXPECT().GetSyncSubcommitteeIndex( + gomock.Any(), // ctx + ðpb.SyncSubcommitteeIndexRequest{ + PublicKey: validatorKey.PublicKey().Marshal(), + Slot: 1, + }, + ).Return(ðpb.SyncSubcommitteeIndexResponse{}, nil /*err*/) + + aggregator, err := v.isSyncCommitteeAggregator(context.Background(), slot, bytesutil.ToBytes48(pubKey), 0) + require.NoError(t, err) + require.Equal(t, false, aggregator) + + c := params.BeaconConfig().Copy() + c.TargetAggregatorsPerSyncSubcommittee = math.MaxUint64 + params.OverrideBeaconConfig(c) + + m.validatorClient.EXPECT().DomainData( + gomock.Any(), // ctx + gomock.Any(), // epoch + ).Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/).Times(2) + + m.validatorClient.EXPECT().GetSyncSubcommitteeIndex( + gomock.Any(), // ctx + ðpb.SyncSubcommitteeIndexRequest{ + PublicKey: validatorKey.PublicKey().Marshal(), + Slot: 1, + }, + ).Return(ðpb.SyncSubcommitteeIndexResponse{Indices: []primitives.CommitteeIndex{0}}, nil /*err*/) + + sig, err := v.signSyncSelectionData(context.Background(), bytesutil.ToBytes48(pubKey), 0, slot) + require.NoError(t, err) + + selection := iface.SyncCommitteeSelection{ + SelectionProof: sig, + Slot: 1, + ValidatorIndex: 123, + SubcommitteeIndex: 0, + } + m.validatorClient.EXPECT().GetAggregatedSyncSelections( + gomock.Any(), // ctx + []iface.SyncCommitteeSelection{selection}, + ).Return([]iface.SyncCommitteeSelection{selection}, nil) + + aggregator, err = v.isSyncCommitteeAggregator(context.Background(), slot, bytesutil.ToBytes48(pubKey), 123) require.NoError(t, err) require.Equal(t, true, aggregator) }