diff --git a/beacon-chain/rpc/eth/shared/structs.go b/beacon-chain/rpc/eth/shared/structs.go index 93fb926ecf5a..d61e433f50fa 100644 --- a/beacon-chain/rpc/eth/shared/structs.go +++ b/beacon-chain/rpc/eth/shared/structs.go @@ -1,5 +1,14 @@ package shared +import ( + "encoding/json" + "strconv" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" +) + type Validator struct { PublicKey string `json:"pubkey"` WithdrawalCredentials string `json:"withdrawal_credentials"` @@ -207,3 +216,52 @@ type Withdrawal struct { ExecutionAddress string `json:"address"` Amount string `json:"amount"` } + +type BeaconCommitteeSelection struct { + SelectionProof []byte + Slot primitives.Slot + ValidatorIndex primitives.ValidatorIndex +} + +type beaconCommitteeSelectionJson struct { + SelectionProof string `json:"selection_proof"` + Slot string `json:"slot"` + ValidatorIndex string `json:"validator_index"` +} + +func (b BeaconCommitteeSelection) MarshalJSON() ([]byte, error) { + return json.Marshal(beaconCommitteeSelectionJson{ + SelectionProof: hexutil.Encode(b.SelectionProof), + Slot: strconv.FormatUint(uint64(b.Slot), 10), + ValidatorIndex: strconv.FormatUint(uint64(b.ValidatorIndex), 10), + }) +} + +func (b *BeaconCommitteeSelection) UnmarshalJSON(input []byte) error { + var bjson beaconCommitteeSelectionJson + err := json.Unmarshal(input, &bjson) + if err != nil { + return errors.Wrap(err, "unmarshal beacon committee selection") + } + + slot, err := strconv.ParseUint(bjson.Slot, 10, 64) + if err != nil { + return errors.Wrap(err, "failed to parse slot") + } + + vIdx, err := strconv.ParseUint(bjson.ValidatorIndex, 10, 64) + if err != nil { + return errors.Wrap(err, "failed to parse validator index") + } + + selectionProof, err := hexutil.Decode(bjson.SelectionProof) + if err != nil { + return errors.Wrap(err, "failed to parse selection proof") + } + + b.Slot = primitives.Slot(slot) + b.SelectionProof = selectionProof + b.ValidatorIndex = primitives.ValidatorIndex(vIdx) + + return nil +} diff --git a/cmd/validator/flags/flags.go b/cmd/validator/flags/flags.go index d9eb8d66cf1e..e24c06490383 100644 --- a/cmd/validator/flags/flags.go +++ b/cmd/validator/flags/flags.go @@ -377,6 +377,13 @@ var ( Usage: "Sets the maximum size for one batch of validator registrations. Use a non-positive value to disable batching.", Value: 0, } + + // EnableDistributed enables the usage of prysm validator client in a Distributed Validator Cluster. + EnableDistributed = &cli.BoolFlag{ + Name: "distributed", + Usage: "To enable the use of prysm validator client in Distributed Validator Cluster", + Value: false, + } ) // DefaultValidatorDir returns OS-specific default validator directory. diff --git a/cmd/validator/main.go b/cmd/validator/main.go index 150425dd117d..099ced70644a 100644 --- a/cmd/validator/main.go +++ b/cmd/validator/main.go @@ -74,6 +74,7 @@ var appFlags = []cli.Flag{ flags.WalletDirFlag, flags.EnableWebFlag, flags.GraffitiFileFlag, + flags.EnableDistributed, // Consensys' Web3Signer flags flags.Web3SignerURLFlag, flags.Web3SignerPublicValidatorKeysFlag, diff --git a/cmd/validator/usage.go b/cmd/validator/usage.go index 8369551336f4..0746f4b7d7d8 100644 --- a/cmd/validator/usage.go +++ b/cmd/validator/usage.go @@ -122,6 +122,7 @@ var appHelpFlagGroups = []flagGroup{ flags.EnableBuilderFlag, flags.BuilderGasLimitFlag, flags.ValidatorsRegistrationBatchSizeFlag, + flags.EnableDistributed, }, }, { diff --git a/crypto/bls/common/mock/interface_mock.go b/crypto/bls/common/mock/interface_mock.go index 3400c9d80dee..939ccf9b3784 100644 --- a/crypto/bls/common/mock/interface_mock.go +++ b/crypto/bls/common/mock/interface_mock.go @@ -12,30 +12,30 @@ import ( ) // MockSecretKey is a mock of SecretKey interface. -type SecretKey struct { +type MockSecretKey struct { ctrl *gomock.Controller - recorder *SecretKeyMockRecorder + recorder *MockSecretKeyMockRecorder } // MockSecretKeyMockRecorder is the mock recorder for MockSecretKey. -type SecretKeyMockRecorder struct { - mock *SecretKey +type MockSecretKeyMockRecorder struct { + mock *MockSecretKey } -// NewSecretKey creates a new mock instance. -func NewSecretKey(ctrl *gomock.Controller) *SecretKey { - mock := &SecretKey{ctrl: ctrl} - mock.recorder = &SecretKeyMockRecorder{mock} +// NewMockSecretKey creates a new mock instance. +func NewMockSecretKey(ctrl *gomock.Controller) *MockSecretKey { + mock := &MockSecretKey{ctrl: ctrl} + mock.recorder = &MockSecretKeyMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *SecretKey) EXPECT() *SecretKeyMockRecorder { +func (m *MockSecretKey) EXPECT() *MockSecretKeyMockRecorder { return m.recorder } // Marshal mocks base method. -func (m *SecretKey) Marshal() []byte { +func (m *MockSecretKey) Marshal() []byte { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Marshal") ret0, _ := ret[0].([]byte) @@ -43,13 +43,13 @@ func (m *SecretKey) Marshal() []byte { } // Marshal indicates an expected call of Marshal. -func (mr *SecretKeyMockRecorder) Marshal() *gomock.Call { +func (mr *MockSecretKeyMockRecorder) Marshal() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Marshal", reflect.TypeOf((*SecretKey)(nil).Marshal)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Marshal", reflect.TypeOf((*MockSecretKey)(nil).Marshal)) } // PublicKey mocks base method. -func (m *SecretKey) PublicKey() common.PublicKey { +func (m *MockSecretKey) PublicKey() common.PublicKey { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PublicKey") ret0, _ := ret[0].(common.PublicKey) @@ -57,13 +57,13 @@ func (m *SecretKey) PublicKey() common.PublicKey { } // PublicKey indicates an expected call of PublicKey. -func (mr *SecretKeyMockRecorder) PublicKey() *gomock.Call { +func (mr *MockSecretKeyMockRecorder) PublicKey() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublicKey", reflect.TypeOf((*SecretKey)(nil).PublicKey)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublicKey", reflect.TypeOf((*MockSecretKey)(nil).PublicKey)) } // Sign mocks base method. -func (m *SecretKey) Sign(msg []byte) common.Signature { +func (m *MockSecretKey) Sign(msg []byte) common.Signature { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Sign", msg) ret0, _ := ret[0].(common.Signature) @@ -71,36 +71,36 @@ func (m *SecretKey) Sign(msg []byte) common.Signature { } // Sign indicates an expected call of Sign. -func (mr *SecretKeyMockRecorder) Sign(msg interface{}) *gomock.Call { +func (mr *MockSecretKeyMockRecorder) Sign(msg interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sign", reflect.TypeOf((*SecretKey)(nil).Sign), msg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sign", reflect.TypeOf((*MockSecretKey)(nil).Sign), msg) } // MockPublicKey is a mock of PublicKey interface. -type PublicKey struct { +type MockPublicKey struct { ctrl *gomock.Controller - recorder *PublicKeyMockRecorder + recorder *MockPublicKeyMockRecorder } // MockPublicKeyMockRecorder is the mock recorder for MockPublicKey. -type PublicKeyMockRecorder struct { - mock *PublicKey +type MockPublicKeyMockRecorder struct { + mock *MockPublicKey } -// NewPublicKey creates a new mock instance. -func NewPublicKey(ctrl *gomock.Controller) *PublicKey { - mock := &PublicKey{ctrl: ctrl} - mock.recorder = &PublicKeyMockRecorder{mock} +// NewMockPublicKey creates a new mock instance. +func NewMockPublicKey(ctrl *gomock.Controller) *MockPublicKey { + mock := &MockPublicKey{ctrl: ctrl} + mock.recorder = &MockPublicKeyMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *PublicKey) EXPECT() *PublicKeyMockRecorder { +func (m *MockPublicKey) EXPECT() *MockPublicKeyMockRecorder { return m.recorder } // Aggregate mocks base method. -func (m *PublicKey) Aggregate(p2 common.PublicKey) common.PublicKey { +func (m *MockPublicKey) Aggregate(p2 common.PublicKey) common.PublicKey { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Aggregate", p2) ret0, _ := ret[0].(common.PublicKey) @@ -108,13 +108,13 @@ func (m *PublicKey) Aggregate(p2 common.PublicKey) common.PublicKey { } // Aggregate indicates an expected call of Aggregate. -func (mr *PublicKeyMockRecorder) Aggregate(p2 interface{}) *gomock.Call { +func (mr *MockPublicKeyMockRecorder) Aggregate(p2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Aggregate", reflect.TypeOf((*PublicKey)(nil).Aggregate), p2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Aggregate", reflect.TypeOf((*MockPublicKey)(nil).Aggregate), p2) } // Copy mocks base method. -func (m *PublicKey) Copy() common.PublicKey { +func (m *MockPublicKey) Copy() common.PublicKey { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Copy") ret0, _ := ret[0].(common.PublicKey) @@ -122,13 +122,13 @@ func (m *PublicKey) Copy() common.PublicKey { } // Copy indicates an expected call of Copy. -func (mr *PublicKeyMockRecorder) Copy() *gomock.Call { +func (mr *MockPublicKeyMockRecorder) Copy() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Copy", reflect.TypeOf((*PublicKey)(nil).Copy)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Copy", reflect.TypeOf((*MockPublicKey)(nil).Copy)) } // Equals mocks base method. -func (m *PublicKey) Equals(p2 common.PublicKey) bool { +func (m *MockPublicKey) Equals(p2 common.PublicKey) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Equals", p2) ret0, _ := ret[0].(bool) @@ -136,13 +136,13 @@ func (m *PublicKey) Equals(p2 common.PublicKey) bool { } // Equals indicates an expected call of Equals. -func (mr *PublicKeyMockRecorder) Equals(p2 interface{}) *gomock.Call { +func (mr *MockPublicKeyMockRecorder) Equals(p2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Equals", reflect.TypeOf((*PublicKey)(nil).Equals), p2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Equals", reflect.TypeOf((*MockPublicKey)(nil).Equals), p2) } // IsInfinite mocks base method. -func (m *PublicKey) IsInfinite() bool { +func (m *MockPublicKey) IsInfinite() bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IsInfinite") ret0, _ := ret[0].(bool) @@ -150,13 +150,13 @@ func (m *PublicKey) IsInfinite() bool { } // IsInfinite indicates an expected call of IsInfinite. -func (mr *PublicKeyMockRecorder) IsInfinite() *gomock.Call { +func (mr *MockPublicKeyMockRecorder) IsInfinite() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsInfinite", reflect.TypeOf((*PublicKey)(nil).IsInfinite)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsInfinite", reflect.TypeOf((*MockPublicKey)(nil).IsInfinite)) } // Marshal mocks base method. -func (m *PublicKey) Marshal() []byte { +func (m *MockPublicKey) Marshal() []byte { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Marshal") ret0, _ := ret[0].([]byte) @@ -164,36 +164,36 @@ func (m *PublicKey) Marshal() []byte { } // Marshal indicates an expected call of Marshal. -func (mr *PublicKeyMockRecorder) Marshal() *gomock.Call { +func (mr *MockPublicKeyMockRecorder) Marshal() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Marshal", reflect.TypeOf((*PublicKey)(nil).Marshal)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Marshal", reflect.TypeOf((*MockPublicKey)(nil).Marshal)) } // MockSignature is a mock of Signature interface. -type Signature struct { +type MockSignature struct { ctrl *gomock.Controller - recorder *SignatureMockRecorder + recorder *MockSignatureMockRecorder } // MockSignatureMockRecorder is the mock recorder for MockSignature. -type SignatureMockRecorder struct { - mock *Signature +type MockSignatureMockRecorder struct { + mock *MockSignature } -// NewSignature creates a new mock instance. -func NewSignature(ctrl *gomock.Controller) *Signature { - mock := &Signature{ctrl: ctrl} - mock.recorder = &SignatureMockRecorder{mock} +// NewMockSignature creates a new mock instance. +func NewMockSignature(ctrl *gomock.Controller) *MockSignature { + mock := &MockSignature{ctrl: ctrl} + mock.recorder = &MockSignatureMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *Signature) EXPECT() *SignatureMockRecorder { +func (m *MockSignature) EXPECT() *MockSignatureMockRecorder { return m.recorder } // AggregateVerify mocks base method. -func (m *Signature) AggregateVerify(pubKeys []common.PublicKey, msgs [][32]byte) bool { +func (m *MockSignature) AggregateVerify(pubKeys []common.PublicKey, msgs [][32]byte) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AggregateVerify", pubKeys, msgs) ret0, _ := ret[0].(bool) @@ -201,13 +201,13 @@ func (m *Signature) AggregateVerify(pubKeys []common.PublicKey, msgs [][32]byte) } // AggregateVerify indicates an expected call of AggregateVerify. -func (mr *SignatureMockRecorder) AggregateVerify(pubKeys, msgs interface{}) *gomock.Call { +func (mr *MockSignatureMockRecorder) AggregateVerify(pubKeys, msgs interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AggregateVerify", reflect.TypeOf((*Signature)(nil).AggregateVerify), pubKeys, msgs) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AggregateVerify", reflect.TypeOf((*MockSignature)(nil).AggregateVerify), pubKeys, msgs) } // Copy mocks base method. -func (m *Signature) Copy() common.Signature { +func (m *MockSignature) Copy() common.Signature { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Copy") ret0, _ := ret[0].(common.Signature) @@ -215,13 +215,13 @@ func (m *Signature) Copy() common.Signature { } // Copy indicates an expected call of Copy. -func (mr *SignatureMockRecorder) Copy() *gomock.Call { +func (mr *MockSignatureMockRecorder) Copy() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Copy", reflect.TypeOf((*Signature)(nil).Copy)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Copy", reflect.TypeOf((*MockSignature)(nil).Copy)) } // Eth2FastAggregateVerify mocks base method. -func (m *Signature) Eth2FastAggregateVerify(pubKeys []common.PublicKey, msg [32]byte) bool { +func (m *MockSignature) Eth2FastAggregateVerify(pubKeys []common.PublicKey, msg [32]byte) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Eth2FastAggregateVerify", pubKeys, msg) ret0, _ := ret[0].(bool) @@ -229,13 +229,13 @@ func (m *Signature) Eth2FastAggregateVerify(pubKeys []common.PublicKey, msg [32] } // Eth2FastAggregateVerify indicates an expected call of Eth2FastAggregateVerify. -func (mr *SignatureMockRecorder) Eth2FastAggregateVerify(pubKeys, msg interface{}) *gomock.Call { +func (mr *MockSignatureMockRecorder) Eth2FastAggregateVerify(pubKeys, msg interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Eth2FastAggregateVerify", reflect.TypeOf((*Signature)(nil).Eth2FastAggregateVerify), pubKeys, msg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Eth2FastAggregateVerify", reflect.TypeOf((*MockSignature)(nil).Eth2FastAggregateVerify), pubKeys, msg) } // FastAggregateVerify mocks base method. -func (m *Signature) FastAggregateVerify(pubKeys []common.PublicKey, msg [32]byte) bool { +func (m *MockSignature) FastAggregateVerify(pubKeys []common.PublicKey, msg [32]byte) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "FastAggregateVerify", pubKeys, msg) ret0, _ := ret[0].(bool) @@ -243,13 +243,13 @@ func (m *Signature) FastAggregateVerify(pubKeys []common.PublicKey, msg [32]byte } // FastAggregateVerify indicates an expected call of FastAggregateVerify. -func (mr *SignatureMockRecorder) FastAggregateVerify(pubKeys, msg interface{}) *gomock.Call { +func (mr *MockSignatureMockRecorder) FastAggregateVerify(pubKeys, msg interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FastAggregateVerify", reflect.TypeOf((*Signature)(nil).FastAggregateVerify), pubKeys, msg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FastAggregateVerify", reflect.TypeOf((*MockSignature)(nil).FastAggregateVerify), pubKeys, msg) } // Marshal mocks base method. -func (m *Signature) Marshal() []byte { +func (m *MockSignature) Marshal() []byte { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Marshal") ret0, _ := ret[0].([]byte) @@ -257,13 +257,13 @@ func (m *Signature) Marshal() []byte { } // Marshal indicates an expected call of Marshal. -func (mr *SignatureMockRecorder) Marshal() *gomock.Call { +func (mr *MockSignatureMockRecorder) Marshal() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Marshal", reflect.TypeOf((*Signature)(nil).Marshal)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Marshal", reflect.TypeOf((*MockSignature)(nil).Marshal)) } // Verify mocks base method. -func (m *Signature) Verify(pubKey common.PublicKey, msg []byte) bool { +func (m *MockSignature) Verify(pubKey common.PublicKey, msg []byte) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Verify", pubKey, msg) ret0, _ := ret[0].(bool) @@ -271,7 +271,7 @@ func (m *Signature) Verify(pubKey common.PublicKey, msg []byte) bool { } // Verify indicates an expected call of Verify. -func (mr *SignatureMockRecorder) Verify(pubKey, msg interface{}) *gomock.Call { +func (mr *MockSignatureMockRecorder) Verify(pubKey, msg interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*Signature)(nil).Verify), pubKey, msg) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*MockSignature)(nil).Verify), pubKey, msg) } diff --git a/testing/validator-mock/validator_client_mock.go b/testing/validator-mock/validator_client_mock.go index d71baea2b21b..5cee2646ed9a 100644 --- a/testing/validator-mock/validator_client_mock.go +++ b/testing/validator-mock/validator_client_mock.go @@ -9,6 +9,7 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" + shared "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" primitives "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" emptypb "google.golang.org/protobuf/types/known/emptypb" @@ -81,6 +82,21 @@ func (mr *MockValidatorClientMockRecorder) EventStreamIsRunning() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EventStreamIsRunning", reflect.TypeOf((*MockValidatorClient)(nil).EventStreamIsRunning)) } +// GetAggregatedSelections mocks base method. +func (m *MockValidatorClient) GetAggregatedSelections(arg0 context.Context, arg1 []shared.BeaconCommitteeSelection) ([]shared.BeaconCommitteeSelection, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAggregatedSelections", arg0, arg1) + ret0, _ := ret[0].([]shared.BeaconCommitteeSelection) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAggregatedSelections indicates an expected call of GetAggregatedSelections. +func (mr *MockValidatorClientMockRecorder) GetAggregatedSelections(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAggregatedSelections", reflect.TypeOf((*MockValidatorClient)(nil).GetAggregatedSelections), 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/BUILD.bazel b/validator/client/BUILD.bazel index 268d0afd603c..799c89e79469 100644 --- a/validator/client/BUILD.bazel +++ b/validator/client/BUILD.bazel @@ -31,6 +31,7 @@ go_library( "//beacon-chain/builder:go_default_library", "//beacon-chain/core/altair:go_default_library", "//beacon-chain/core/signing:go_default_library", + "//beacon-chain/rpc/eth/shared:go_default_library", "//cache/lru:go_default_library", "//cmd:go_default_library", "//config/features:go_default_library", diff --git a/validator/client/aggregate.go b/validator/client/aggregate.go index 37cc1d2aeb5d..ef112ee0341d 100644 --- a/validator/client/aggregate.go +++ b/validator/client/aggregate.go @@ -53,13 +53,25 @@ func (v *validator) SubmitAggregateAndProof(ctx context.Context, slot primitives v.aggregatedSlotCommitteeIDCache.Add(k, true) v.aggregatedSlotCommitteeIDCacheLock.Unlock() - slotSig, err := v.signSlotWithSelectionProof(ctx, pubKey, slot) - if err != nil { - log.WithError(err).Error("Could not sign slot") - if v.emitAccountMetrics { - ValidatorAggFailVec.WithLabelValues(fmtKey).Inc() + var slotSig []byte + if v.distributed { + slotSig, err = v.getAttSelection(attSelectionKey{slot: slot, index: duty.ValidatorIndex}) + if err != nil { + log.WithError(err).Error("Could not find aggregated selection proof") + if v.emitAccountMetrics { + ValidatorAggFailVec.WithLabelValues(fmtKey).Inc() + } + return + } + } else { + slotSig, err = v.signSlotWithSelectionProof(ctx, pubKey, slot) + if err != nil { + log.WithError(err).Error("Could not sign slot") + if v.emitAccountMetrics { + ValidatorAggFailVec.WithLabelValues(fmtKey).Inc() + } + return } - return } // As specified in spec, an aggregator should wait until two thirds of the way through slot diff --git a/validator/client/aggregate_test.go b/validator/client/aggregate_test.go index 93f0032f10c0..75970ef6f2b6 100644 --- a/validator/client/aggregate_test.go +++ b/validator/client/aggregate_test.go @@ -5,6 +5,8 @@ import ( "errors" "testing" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" + "github.com/golang/mock/gomock" "github.com/prysmaticlabs/go-bitfield" fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" @@ -116,6 +118,63 @@ func TestSubmitAggregateAndProof_Ok(t *testing.T) { validator.SubmitAggregateAndProof(context.Background(), 0, pubKey) } +func TestSubmitAggregateAndProof_Distributed(t *testing.T) { + validatorIdx := primitives.ValidatorIndex(123) + slot := primitives.Slot(456) + ctx := context.Background() + + validator, m, validatorKey, finish := setup(t) + defer finish() + + var pubKey [fieldparams.BLSPubkeyLength]byte + copy(pubKey[:], validatorKey.PublicKey().Marshal()) + validator.duties = ðpb.DutiesResponse{ + Duties: []*ethpb.DutiesResponse_Duty{ + { + PublicKey: validatorKey.PublicKey().Marshal(), + ValidatorIndex: validatorIdx, + AttesterSlot: slot, + }, + }, + } + + validator.distributed = true + validator.attSelections = make(map[attSelectionKey]shared.BeaconCommitteeSelection) + validator.attSelections[attSelectionKey{ + slot: slot, + index: 123, + }] = shared.BeaconCommitteeSelection{ + SelectionProof: make([]byte, 96), + Slot: slot, + ValidatorIndex: validatorIdx, + } + + m.validatorClient.EXPECT().SubmitAggregateSelectionProof( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(ðpb.AggregateSelectionRequest{}), + ).Return(ðpb.AggregateSelectionResponse{ + AggregateAndProof: ðpb.AggregateAttestationAndProof{ + AggregatorIndex: 0, + Aggregate: util.HydrateAttestation(ðpb.Attestation{ + AggregationBits: make([]byte, 1), + }), + SelectionProof: make([]byte, 96), + }, + }, nil) + + m.validatorClient.EXPECT().DomainData( + gomock.Any(), // ctx + gomock.Any(), // epoch + ).Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/) + + m.validatorClient.EXPECT().SubmitSignedAggregateSelectionProof( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(ðpb.SignedAggregateSubmitRequest{}), + ).Return(ðpb.SignedAggregateSubmitResponse{AttestationDataRoot: make([]byte, 32)}, nil) + + validator.SubmitAggregateAndProof(ctx, slot, pubKey) +} + func TestWaitForSlotTwoThird_WaitCorrectly(t *testing.T) { validator, _, _, finish := setup(t) defer finish() diff --git a/validator/client/beacon-api/BUILD.bazel b/validator/client/beacon-api/BUILD.bazel index fad6d0ea3bb0..b98a8cb90dab 100644 --- a/validator/client/beacon-api/BUILD.bazel +++ b/validator/client/beacon-api/BUILD.bazel @@ -12,6 +12,7 @@ go_library( "beacon_block_converter.go", "beacon_block_json_helpers.go", "beacon_block_proto_helpers.go", + "beacon_committee_selections.go", "domain_data.go", "doppelganger.go", "duties.go", diff --git a/validator/client/beacon-api/activation_test.go b/validator/client/beacon-api/activation_test.go index dd1b8fb637bd..7b3ffd868412 100644 --- a/validator/client/beacon-api/activation_test.go +++ b/validator/client/beacon-api/activation_test.go @@ -1,9 +1,8 @@ package beacon_api import ( - "bytes" "context" - "encoding/json" + "net/url" "testing" "time" @@ -111,20 +110,25 @@ func TestActivation_Nominal(t *testing.T) { Ids: stringPubKeys, Statuses: []string{}, } - reqBytes, err := json.Marshal(req) - require.NoError(t, err) + queryParams := url.Values{} + for _, id := range req.Ids { + queryParams.Add("id", id) + } + for _, st := range req.Statuses { + queryParams.Add("status", st) + } + + query := buildURL("/eth/v1/beacon/states/head/validators", queryParams) // Get does not return any result for non existing key - jsonRestHandler.EXPECT().Post( + jsonRestHandler.EXPECT().Get( ctx, - "/eth/v1/beacon/states/head/validators", - nil, - bytes.NewBuffer(reqBytes), + query, &stateValidatorsResponseJson, ).Return( nil, ).SetArg( - 4, + 2, beacon.GetValidatorsResponse{ Data: []*beacon.ValidatorContainer{ { @@ -239,16 +243,14 @@ func TestActivation_InvalidData(t *testing.T) { ctx := context.Background() jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - jsonRestHandler.EXPECT().Post( + jsonRestHandler.EXPECT().Get( ctx, gomock.Any(), gomock.Any(), - gomock.Any(), - gomock.Any(), ).Return( nil, ).SetArg( - 4, + 2, beacon.GetValidatorsResponse{ Data: testCase.data, }, @@ -280,12 +282,10 @@ func TestActivation_JsonResponseError(t *testing.T) { ctx := context.Background() jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - jsonRestHandler.EXPECT().Post( + jsonRestHandler.EXPECT().Get( ctx, gomock.Any(), gomock.Any(), - gomock.Any(), - gomock.Any(), ).Return( errors.New("some specific json error"), ).Times(1) diff --git a/validator/client/beacon-api/beacon_api_validator_client.go b/validator/client/beacon-api/beacon_api_validator_client.go index df7fc6a06333..db8050e4d789 100644 --- a/validator/client/beacon-api/beacon_api_validator_client.go +++ b/validator/client/beacon-api/beacon_api_validator_client.go @@ -4,6 +4,8 @@ import ( "context" "time" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/protobuf/ptypes/empty" "github.com/pkg/errors" @@ -175,3 +177,7 @@ func (c *beaconApiValidatorClient) StartEventStream(ctx context.Context) error { func (c *beaconApiValidatorClient) EventStreamIsRunning() bool { return c.eventHandler.running } + +func (c *beaconApiValidatorClient) GetAggregatedSelections(ctx context.Context, selections []shared.BeaconCommitteeSelection) ([]shared.BeaconCommitteeSelection, error) { + return c.getAggregatedSelection(ctx, selections) +} diff --git a/validator/client/beacon-api/beacon_committee_selections.go b/validator/client/beacon-api/beacon_committee_selections.go new file mode 100644 index 000000000000..f1424192de14 --- /dev/null +++ b/validator/client/beacon-api/beacon_committee_selections.go @@ -0,0 +1,40 @@ +package beacon_api + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" +) + +type aggregatedSelectionResponse struct { + Data []shared.BeaconCommitteeSelection `json:"data"` +} + +func (c *beaconApiValidatorClient) getAggregatedSelection(ctx context.Context, selections []shared.BeaconCommitteeSelection) ([]shared.BeaconCommitteeSelection, error) { + body, err := json.Marshal(selections) + if err != nil { + return nil, errors.Wrap(err, "marshal request body selections") + } + + var resp aggregatedSelectionResponse + errJson, err := c.jsonRestHandler.Post(ctx, "/eth/v1/validator/beacon_committee_selections", nil, bytes.NewBuffer(body), &resp) + if err != nil { + return nil, errors.Wrap(err, "error calling post endpoint") + } + if errJson != nil { + return nil, errJson + } + + if len(resp.Data) == 0 { + return nil, errors.New("no aggregated selection returned") + } + + if len(selections) != len(resp.Data) { + return nil, errors.New("mismatching number of selections") + } + + return resp.Data, nil +} diff --git a/validator/client/beacon-api/beacon_committee_selections_test.go b/validator/client/beacon-api/beacon_committee_selections_test.go new file mode 100644 index 000000000000..97e5f59e3b51 --- /dev/null +++ b/validator/client/beacon-api/beacon_committee_selections_test.go @@ -0,0 +1,124 @@ +package beacon_api + +import ( + "bytes" + "context" + "encoding/json" + "testing" + + "github.com/golang/mock/gomock" + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" + "github.com/prysmaticlabs/prysm/v4/testing/require" + "github.com/prysmaticlabs/prysm/v4/validator/client/beacon-api/mock" + test_helpers "github.com/prysmaticlabs/prysm/v4/validator/client/beacon-api/test-helpers" +) + +func TestGetAggregatedSelections(t *testing.T) { + testcases := []struct { + name string + req []shared.BeaconCommitteeSelection + res []shared.BeaconCommitteeSelection + endpointError error + expectedErrorMessage string + }{ + { + name: "valid", + req: []shared.BeaconCommitteeSelection{ + { + SelectionProof: test_helpers.FillByteSlice(96, 82), + Slot: 75, + ValidatorIndex: 76, + }, + }, + res: []shared.BeaconCommitteeSelection{ + { + SelectionProof: test_helpers.FillByteSlice(96, 100), + Slot: 75, + ValidatorIndex: 76, + }, + }, + }, + { + name: "endpoint error", + req: []shared.BeaconCommitteeSelection{ + { + SelectionProof: test_helpers.FillByteSlice(96, 82), + Slot: 75, + ValidatorIndex: 76, + }, + }, + endpointError: errors.New("bad request"), + expectedErrorMessage: "bad request", + }, + { + name: "no response error", + req: []shared.BeaconCommitteeSelection{ + { + SelectionProof: test_helpers.FillByteSlice(96, 82), + Slot: 75, + ValidatorIndex: 76, + }, + }, + expectedErrorMessage: "no aggregated selection returned", + }, + { + name: "mismatch response", + req: []shared.BeaconCommitteeSelection{ + { + SelectionProof: test_helpers.FillByteSlice(96, 82), + Slot: 75, + ValidatorIndex: 76, + }, + { + SelectionProof: test_helpers.FillByteSlice(96, 102), + Slot: 75, + ValidatorIndex: 79, + }, + }, + res: []shared.BeaconCommitteeSelection{ + { + SelectionProof: test_helpers.FillByteSlice(96, 100), + Slot: 75, + ValidatorIndex: 76, + }, + }, + expectedErrorMessage: "mismatching number of 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/beacon_committee_selections", + nil, + bytes.NewBuffer(reqBody), + &aggregatedSelectionResponse{}, + ).SetArg( + 4, + aggregatedSelectionResponse{Data: test.res}, + ).Return( + nil, + test.endpointError, + ).Times(1) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + res, err := validatorClient.GetAggregatedSelections(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/beacon-api/index_test.go b/validator/client/beacon-api/index_test.go index 8ec1f0923077..f2202bace5c6 100644 --- a/validator/client/beacon-api/index_test.go +++ b/validator/client/beacon-api/index_test.go @@ -1,9 +1,8 @@ package beacon_api import ( - "bytes" "context" - "encoding/json" + "net/url" "testing" "github.com/ethereum/go-ethereum/common/hexutil" @@ -19,38 +18,43 @@ import ( const stringPubKey = "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13" -func getPubKeyAndReqBuffer(t *testing.T) ([]byte, *bytes.Buffer) { +func getPubKeyAndQueryPath(t *testing.T) ([]byte, string) { pubKey, err := hexutil.Decode(stringPubKey) require.NoError(t, err) req := beacon.GetValidatorsRequest{ Ids: []string{stringPubKey}, Statuses: []string{}, } - reqBytes, err := json.Marshal(req) - require.NoError(t, err) - return pubKey, bytes.NewBuffer(reqBytes) + + queryParams := url.Values{} + for _, id := range req.Ids { + queryParams.Add("id", id) + } + for _, st := range req.Statuses { + queryParams.Add("status", st) + } + + return pubKey, buildURL("/eth/v1/beacon/states/head/validators", queryParams) } func TestIndex_Nominal(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - pubKey, reqBuffer := getPubKeyAndReqBuffer(t) + pubKey, query := getPubKeyAndQueryPath(t) ctx := context.Background() stateValidatorsResponseJson := beacon.GetValidatorsResponse{} jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - jsonRestHandler.EXPECT().Post( + jsonRestHandler.EXPECT().Get( ctx, - "/eth/v1/beacon/states/head/validators", - nil, - reqBuffer, + query, &stateValidatorsResponseJson, ).Return( nil, ).SetArg( - 4, + 2, beacon.GetValidatorsResponse{ Data: []*beacon.ValidatorContainer{ { @@ -85,22 +89,20 @@ func TestIndex_UnexistingValidator(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - pubKey, reqBuffer := getPubKeyAndReqBuffer(t) + pubKey, query := getPubKeyAndQueryPath(t) ctx := context.Background() stateValidatorsResponseJson := beacon.GetValidatorsResponse{} jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - jsonRestHandler.EXPECT().Post( + jsonRestHandler.EXPECT().Get( ctx, - "/eth/v1/beacon/states/head/validators", - nil, - reqBuffer, + query, &stateValidatorsResponseJson, ).Return( nil, ).SetArg( - 4, + 2, beacon.GetValidatorsResponse{ Data: []*beacon.ValidatorContainer{}, }, @@ -127,22 +129,20 @@ func TestIndex_BadIndexError(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - pubKey, reqBuffer := getPubKeyAndReqBuffer(t) + pubKey, query := getPubKeyAndQueryPath(t) ctx := context.Background() stateValidatorsResponseJson := beacon.GetValidatorsResponse{} jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - jsonRestHandler.EXPECT().Post( + jsonRestHandler.EXPECT().Get( ctx, - "/eth/v1/beacon/states/head/validators", - nil, - reqBuffer, + query, &stateValidatorsResponseJson, ).Return( nil, ).SetArg( - 4, + 2, beacon.GetValidatorsResponse{ Data: []*beacon.ValidatorContainer{ { @@ -176,17 +176,15 @@ func TestIndex_JsonResponseError(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - pubKey, reqBuffer := getPubKeyAndReqBuffer(t) + pubKey, query := getPubKeyAndQueryPath(t) ctx := context.Background() stateValidatorsResponseJson := beacon.GetValidatorsResponse{} jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - jsonRestHandler.EXPECT().Post( + jsonRestHandler.EXPECT().Get( ctx, - "/eth/v1/beacon/states/head/validators", - nil, - reqBuffer, + query, &stateValidatorsResponseJson, ).Return( errors.New("some specific json error"), diff --git a/validator/client/beacon-api/mock/beacon_block_converter_mock.go b/validator/client/beacon-api/mock/beacon_block_converter_mock.go index 07ee9d9254cd..cc7b1c4eea07 100644 --- a/validator/client/beacon-api/mock/beacon_block_converter_mock.go +++ b/validator/client/beacon-api/mock/beacon_block_converter_mock.go @@ -9,7 +9,7 @@ import ( gomock "github.com/golang/mock/gomock" shared "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" - eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" + v1alpha1 "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" ) // MockBeaconBlockConverter is a mock of BeaconBlockConverter interface. @@ -36,10 +36,10 @@ func (m *MockBeaconBlockConverter) EXPECT() *MockBeaconBlockConverterMockRecorde } // ConvertRESTAltairBlockToProto mocks base method. -func (m *MockBeaconBlockConverter) ConvertRESTAltairBlockToProto(block *shared.BeaconBlockAltair) (*eth.BeaconBlockAltair, error) { +func (m *MockBeaconBlockConverter) ConvertRESTAltairBlockToProto(block *shared.BeaconBlockAltair) (*v1alpha1.BeaconBlockAltair, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ConvertRESTAltairBlockToProto", block) - ret0, _ := ret[0].(*eth.BeaconBlockAltair) + ret0, _ := ret[0].(*v1alpha1.BeaconBlockAltair) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -51,10 +51,10 @@ func (mr *MockBeaconBlockConverterMockRecorder) ConvertRESTAltairBlockToProto(bl } // ConvertRESTBellatrixBlockToProto mocks base method. -func (m *MockBeaconBlockConverter) ConvertRESTBellatrixBlockToProto(block *shared.BeaconBlockBellatrix) (*eth.BeaconBlockBellatrix, error) { +func (m *MockBeaconBlockConverter) ConvertRESTBellatrixBlockToProto(block *shared.BeaconBlockBellatrix) (*v1alpha1.BeaconBlockBellatrix, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ConvertRESTBellatrixBlockToProto", block) - ret0, _ := ret[0].(*eth.BeaconBlockBellatrix) + ret0, _ := ret[0].(*v1alpha1.BeaconBlockBellatrix) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -66,10 +66,10 @@ func (mr *MockBeaconBlockConverterMockRecorder) ConvertRESTBellatrixBlockToProto } // ConvertRESTCapellaBlockToProto mocks base method. -func (m *MockBeaconBlockConverter) ConvertRESTCapellaBlockToProto(block *shared.BeaconBlockCapella) (*eth.BeaconBlockCapella, error) { +func (m *MockBeaconBlockConverter) ConvertRESTCapellaBlockToProto(block *shared.BeaconBlockCapella) (*v1alpha1.BeaconBlockCapella, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ConvertRESTCapellaBlockToProto", block) - ret0, _ := ret[0].(*eth.BeaconBlockCapella) + ret0, _ := ret[0].(*v1alpha1.BeaconBlockCapella) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -81,10 +81,10 @@ func (mr *MockBeaconBlockConverterMockRecorder) ConvertRESTCapellaBlockToProto(b } // ConvertRESTPhase0BlockToProto mocks base method. -func (m *MockBeaconBlockConverter) ConvertRESTPhase0BlockToProto(block *shared.BeaconBlock) (*eth.BeaconBlock, error) { +func (m *MockBeaconBlockConverter) ConvertRESTPhase0BlockToProto(block *shared.BeaconBlock) (*v1alpha1.BeaconBlock, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ConvertRESTPhase0BlockToProto", block) - ret0, _ := ret[0].(*eth.BeaconBlock) + ret0, _ := ret[0].(*v1alpha1.BeaconBlock) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/validator/client/beacon-api/mock/genesis_mock.go b/validator/client/beacon-api/mock/genesis_mock.go index caa7aa74d391..6dfb8a74ed1c 100644 --- a/validator/client/beacon-api/mock/genesis_mock.go +++ b/validator/client/beacon-api/mock/genesis_mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/prysmaticlabs/prysm/v4/validator/client/beacon-api (interfaces: GenesisProvider) +// Source: validator/client/beacon-api/genesis.go // Package mock is a generated GoMock package. package mock @@ -36,16 +36,16 @@ func (m *MockGenesisProvider) EXPECT() *MockGenesisProviderMockRecorder { } // GetGenesis mocks base method. -func (m *MockGenesisProvider) GetGenesis(arg0 context.Context) (*beacon.Genesis, error) { +func (m *MockGenesisProvider) GetGenesis(ctx context.Context) (*beacon.Genesis, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetGenesis", arg0) + ret := m.ctrl.Call(m, "GetGenesis", ctx) ret0, _ := ret[0].(*beacon.Genesis) ret1, _ := ret[1].(error) return ret0, ret1 } // GetGenesis indicates an expected call of GetGenesis. -func (mr *MockGenesisProviderMockRecorder) GetGenesis(arg0 interface{}) *gomock.Call { +func (mr *MockGenesisProviderMockRecorder) GetGenesis(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGenesis", reflect.TypeOf((*MockGenesisProvider)(nil).GetGenesis), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGenesis", reflect.TypeOf((*MockGenesisProvider)(nil).GetGenesis), ctx) } diff --git a/validator/client/beacon-api/mock/json_rest_handler_mock.go b/validator/client/beacon-api/mock/json_rest_handler_mock.go index ae95ecd74ffd..b939c1221b1c 100644 --- a/validator/client/beacon-api/mock/json_rest_handler_mock.go +++ b/validator/client/beacon-api/mock/json_rest_handler_mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/prysmaticlabs/prysm/v4/validator/client/beacon-api (interfaces: JsonRestHandler) +// Source: validator/client/beacon-api/json_rest_handler.go // Package mock is a generated GoMock package. package mock @@ -36,29 +36,29 @@ func (m *MockJsonRestHandler) EXPECT() *MockJsonRestHandlerMockRecorder { } // Get mocks base method. -func (m *MockJsonRestHandler) Get(arg0 context.Context, arg1 string, arg2 interface{}) error { +func (m *MockJsonRestHandler) Get(ctx context.Context, endpoint string, resp interface{}) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "Get", ctx, endpoint, resp) ret0, _ := ret[0].(error) return ret0 } // Get indicates an expected call of Get. -func (mr *MockJsonRestHandlerMockRecorder) Get(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockJsonRestHandlerMockRecorder) Get(ctx, endpoint, resp interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockJsonRestHandler)(nil).Get), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockJsonRestHandler)(nil).Get), ctx, endpoint, resp) } // Post mocks base method. -func (m *MockJsonRestHandler) Post(arg0 context.Context, arg1 string, arg2 map[string]string, arg3 *bytes.Buffer, arg4 interface{}) error { +func (m *MockJsonRestHandler) Post(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer, resp interface{}) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Post", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "Post", ctx, endpoint, headers, data, resp) ret0, _ := ret[0].(error) return ret0 } // Post indicates an expected call of Post. -func (mr *MockJsonRestHandlerMockRecorder) Post(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockJsonRestHandlerMockRecorder) Post(ctx, endpoint, headers, data, resp interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Post", reflect.TypeOf((*MockJsonRestHandler)(nil).Post), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Post", reflect.TypeOf((*MockJsonRestHandler)(nil).Post), ctx, endpoint, headers, data, resp) } diff --git a/validator/client/beacon-api/state_validators.go b/validator/client/beacon-api/state_validators.go index 165ca0e40c01..5f276136a6b7 100644 --- a/validator/client/beacon-api/state_validators.go +++ b/validator/client/beacon-api/state_validators.go @@ -1,10 +1,9 @@ package beacon_api import ( - "bytes" "context" - "encoding/json" "fmt" + "net/url" "strconv" "github.com/pkg/errors" @@ -86,12 +85,19 @@ func (c beaconApiStateValidatorsProvider) getStateValidatorsHelper( } } - reqBytes, err := json.Marshal(req) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal request into JSON") + queryParams := url.Values{} + for _, id := range req.Ids { + queryParams.Add("id", id) + } + for _, st := range req.Statuses { + queryParams.Add("status", st) } + + query := buildURL(endpoint, queryParams) stateValidatorsJson := &beacon.GetValidatorsResponse{} - if err = c.jsonRestHandler.Post(ctx, endpoint, nil, bytes.NewBuffer(reqBytes), stateValidatorsJson); err != nil { + + err := c.jsonRestHandler.Get(ctx, query, stateValidatorsJson) + if err != nil { return nil, err } diff --git a/validator/client/beacon-api/state_validators_test.go b/validator/client/beacon-api/state_validators_test.go index 066ecd03a47a..660cff792d16 100644 --- a/validator/client/beacon-api/state_validators_test.go +++ b/validator/client/beacon-api/state_validators_test.go @@ -1,9 +1,8 @@ package beacon_api import ( - "bytes" "context" - "encoding/json" + "net/url" "testing" "github.com/golang/mock/gomock" @@ -29,8 +28,6 @@ func TestGetStateValidators_Nominal(t *testing.T) { }, Statuses: []string{"active_ongoing", "active_exiting", "exited_slashed", "exited_unslashed"}, } - reqBytes, err := json.Marshal(req) - require.NoError(t, err) stateValidatorsResponseJson := beacon.GetValidatorsResponse{} jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) @@ -68,16 +65,24 @@ func TestGetStateValidators_Nominal(t *testing.T) { ctx := context.Background() - jsonRestHandler.EXPECT().Post( + queryParams := url.Values{} + for _, id := range req.Ids { + queryParams.Add("id", id) + } + for _, st := range req.Statuses { + queryParams.Add("status", st) + } + + query := buildURL("/eth/v1/beacon/states/head/validators", queryParams) + + jsonRestHandler.EXPECT().Get( ctx, - "/eth/v1/beacon/states/head/validators", - nil, - bytes.NewBuffer(reqBytes), + query, &stateValidatorsResponseJson, ).Return( nil, ).SetArg( - 4, + 2, beacon.GetValidatorsResponse{ Data: wanted, }, @@ -109,26 +114,32 @@ func TestGetStateValidators_GetRestJsonResponseOnError(t *testing.T) { Ids: []string{"0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13"}, Statuses: []string{}, } - reqBytes, err := json.Marshal(req) - require.NoError(t, err) stateValidatorsResponseJson := beacon.GetValidatorsResponse{} jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) ctx := context.Background() - jsonRestHandler.EXPECT().Post( + queryParams := url.Values{} + for _, id := range req.Ids { + queryParams.Add("id", id) + } + for _, st := range req.Statuses { + queryParams.Add("status", st) + } + + query := buildURL("/eth/v1/beacon/states/head/validators", queryParams) + + jsonRestHandler.EXPECT().Get( ctx, - "/eth/v1/beacon/states/head/validators", - nil, - bytes.NewBuffer(reqBytes), + query, &stateValidatorsResponseJson, ).Return( errors.New("an error"), ).Times(1) stateValidatorsProvider := beaconApiStateValidatorsProvider{jsonRestHandler: jsonRestHandler} - _, err = stateValidatorsProvider.GetStateValidators(ctx, []string{ + _, err := stateValidatorsProvider.GetStateValidators(ctx, []string{ "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // active_ongoing }, nil, @@ -145,29 +156,36 @@ func TestGetStateValidators_DataIsNil(t *testing.T) { Ids: []string{"0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13"}, Statuses: []string{}, } - reqBytes, err := json.Marshal(req) - require.NoError(t, err) ctx := context.Background() stateValidatorsResponseJson := beacon.GetValidatorsResponse{} jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - jsonRestHandler.EXPECT().Post( + queryParams := url.Values{} + for _, id := range req.Ids { + queryParams.Add("id", id) + } + for _, st := range req.Statuses { + queryParams.Add("status", st) + } + + query := buildURL("/eth/v1/beacon/states/head/validators", queryParams) + + jsonRestHandler.EXPECT().Get( ctx, - "/eth/v1/beacon/states/head/validators", - nil, bytes.NewBuffer(reqBytes), + query, &stateValidatorsResponseJson, ).Return( nil, ).SetArg( - 4, + 2, beacon.GetValidatorsResponse{ Data: nil, }, ).Times(1) stateValidatorsProvider := beaconApiStateValidatorsProvider{jsonRestHandler: jsonRestHandler} - _, err = stateValidatorsProvider.GetStateValidators(ctx, []string{ + _, err := stateValidatorsProvider.GetStateValidators(ctx, []string{ "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // active_ongoing }, nil, diff --git a/validator/client/grpc-api/BUILD.bazel b/validator/client/grpc-api/BUILD.bazel index 8558091f7ab0..c0660d8659b0 100644 --- a/validator/client/grpc-api/BUILD.bazel +++ b/validator/client/grpc-api/BUILD.bazel @@ -12,6 +12,7 @@ go_library( visibility = ["//validator:__subpackages__"], deps = [ "//beacon-chain/rpc/eth/helpers:go_default_library", + "//beacon-chain/rpc/eth/shared:go_default_library", "//beacon-chain/state/state-native:go_default_library", "//consensus-types/primitives:go_default_library", "//consensus-types/validator:go_default_library", diff --git a/validator/client/grpc-api/grpc_validator_client.go b/validator/client/grpc-api/grpc_validator_client.go index c97e1a621709..d67e5975a086 100644 --- a/validator/client/grpc-api/grpc_validator_client.go +++ b/validator/client/grpc-api/grpc_validator_client.go @@ -3,6 +3,8 @@ package grpc_api import ( "context" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" + "github.com/golang/protobuf/ptypes/empty" "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" @@ -138,6 +140,10 @@ func (c *grpcValidatorClient) AggregatedSigAndAggregationBits( return c.beaconNodeValidatorClient.AggregatedSigAndAggregationBits(ctx, in) } +func (grpcValidatorClient) GetAggregatedSelections(context.Context, []shared.BeaconCommitteeSelection) ([]shared.BeaconCommitteeSelection, error) { + return nil, iface.ErrNotSupported +} + func NewGrpcValidatorClient(cc grpc.ClientConnInterface) iface.ValidatorClient { return &grpcValidatorClient{ethpb.NewBeaconNodeValidatorClient(cc)} } diff --git a/validator/client/iface/BUILD.bazel b/validator/client/iface/BUILD.bazel index 4acfedda99f1..4fb9cae140a1 100644 --- a/validator/client/iface/BUILD.bazel +++ b/validator/client/iface/BUILD.bazel @@ -12,6 +12,7 @@ go_library( importpath = "github.com/prysmaticlabs/prysm/v4/validator/client/iface", visibility = ["//visibility:public"], deps = [ + "//beacon-chain/rpc/eth/shared:go_default_library", "//config/fieldparams:go_default_library", "//config/validator/service:go_default_library", "//consensus-types/primitives:go_default_library", diff --git a/validator/client/iface/validator_client.go b/validator/client/iface/validator_client.go index bb04fb3f72bf..255709bf9309 100644 --- a/validator/client/iface/validator_client.go +++ b/validator/client/iface/validator_client.go @@ -3,6 +3,8 @@ package iface import ( "context" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" + "github.com/golang/protobuf/ptypes/empty" "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" @@ -36,4 +38,5 @@ type ValidatorClient interface { SubmitValidatorRegistrations(ctx context.Context, in *ethpb.SignedValidatorRegistrationsV1) (*empty.Empty, error) StartEventStream(ctx context.Context) error EventStreamIsRunning() bool + GetAggregatedSelections(ctx context.Context, selections []shared.BeaconCommitteeSelection) ([]shared.BeaconCommitteeSelection, error) } diff --git a/validator/client/service.go b/validator/client/service.go index d10d3a80c2b1..6b064080e9f3 100644 --- a/validator/client/service.go +++ b/validator/client/service.go @@ -6,6 +6,8 @@ import ( "strings" "time" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" + "github.com/dgraph-io/ristretto" middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpcretry "github.com/grpc-ecosystem/go-grpc-middleware/retry" @@ -76,6 +78,7 @@ type ValidatorService struct { Web3SignerConfig *remoteweb3signer.SetupConfig proposerSettings *validatorserviceconfig.ProposerSettings validatorsRegBatchSize int + distributed bool } // Config for the validator service. @@ -102,6 +105,7 @@ type Config struct { BeaconApiEndpoint string BeaconApiTimeout time.Duration ValidatorsRegBatchSize int + Distributed bool } // NewValidatorService creates a new validator service for the service @@ -131,6 +135,7 @@ func NewValidatorService(ctx context.Context, cfg *Config) (*ValidatorService, e Web3SignerConfig: cfg.Web3SignerConfig, proposerSettings: cfg.ProposerSettings, validatorsRegBatchSize: cfg.ValidatorsRegBatchSize, + distributed: cfg.Distributed, } dialOpts := ConstructDialOptions( @@ -230,6 +235,8 @@ func (v *ValidatorService) Start() { proposerSettings: v.proposerSettings, walletInitializedChannel: make(chan *wallet.Wallet, 1), validatorsRegBatchSize: v.validatorsRegBatchSize, + distributed: v.distributed, + attSelections: make(map[attSelectionKey]shared.BeaconCommitteeSelection), } v.validator = valStruct diff --git a/validator/client/validator.go b/validator/client/validator.go index 629fb9bc056c..44d720e5469f 100644 --- a/validator/client/validator.go +++ b/validator/client/validator.go @@ -15,6 +15,8 @@ import ( "sync" "time" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" + "github.com/dgraph-io/ristretto" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -105,6 +107,9 @@ type validator struct { proposerSettings *validatorserviceconfig.ProposerSettings walletInitializedChannel chan *wallet.Wallet validatorsRegBatchSize int + distributed bool + attSelectionLock sync.Mutex + attSelections map[attSelectionKey]shared.BeaconCommitteeSelection } type validatorStatus struct { @@ -113,6 +118,11 @@ type validatorStatus struct { index primitives.ValidatorIndex } +type attSelectionKey struct { + slot primitives.Slot + index primitives.ValidatorIndex +} + // Done cleans up the validator. func (v *validator) Done() { v.ticker.Done() @@ -629,6 +639,13 @@ func (v *validator) subscribeToSubnets(ctx context.Context, res *ethpb.DutiesRes subscribeValidatorIndices := make([]primitives.ValidatorIndex, 0, len(res.CurrentEpochDuties)+len(res.NextEpochDuties)) alreadySubscribed := make(map[[64]byte]bool) + if v.distributed { + // Get aggregated selection proofs to calculate isAggregator. + if err := v.getAggregatedSelectionProofs(ctx, res); err != nil { + return errors.Wrap(err, "could not get aggregated selection proofs") + } + } + for _, duty := range res.CurrentEpochDuties { pk := bytesutil.ToBytes48(duty.PublicKey) if duty.Status == ethpb.ValidatorStatus_ACTIVE || duty.Status == ethpb.ValidatorStatus_EXITING { @@ -641,7 +658,7 @@ func (v *validator) subscribeToSubnets(ctx context.Context, res *ethpb.DutiesRes continue } - aggregator, err := v.isAggregator(ctx, duty.Committee, attesterSlot, pk) + aggregator, err := v.isAggregator(ctx, duty.Committee, attesterSlot, pk, validatorIndex) if err != nil { return errors.Wrap(err, "could not check if a validator is an aggregator") } @@ -667,7 +684,7 @@ func (v *validator) subscribeToSubnets(ctx context.Context, res *ethpb.DutiesRes continue } - aggregator, err := v.isAggregator(ctx, duty.Committee, attesterSlot, bytesutil.ToBytes48(duty.PublicKey)) + aggregator, err := v.isAggregator(ctx, duty.Committee, attesterSlot, bytesutil.ToBytes48(duty.PublicKey), validatorIndex) if err != nil { return errors.Wrap(err, "could not check if a validator is an aggregator") } @@ -718,7 +735,7 @@ func (v *validator) RolesAt(ctx context.Context, slot primitives.Slot) (map[[fie if duty.AttesterSlot == slot { roles = append(roles, iface.RoleAttester) - aggregator, err := v.isAggregator(ctx, duty.Committee, slot, bytesutil.ToBytes48(duty.PublicKey)) + aggregator, err := v.isAggregator(ctx, duty.Committee, slot, bytesutil.ToBytes48(duty.PublicKey), duty.ValidatorIndex) if err != nil { return nil, errors.Wrap(err, "could not check if a validator is an aggregator") } @@ -773,15 +790,26 @@ func (v *validator) Keymanager() (keymanager.IKeymanager, error) { // isAggregator checks if a validator is an aggregator of a given slot and committee, // it uses a modulo calculated by validator count in committee and samples randomness around it. -func (v *validator) isAggregator(ctx context.Context, committee []primitives.ValidatorIndex, slot primitives.Slot, pubKey [fieldparams.BLSPubkeyLength]byte) (bool, error) { +func (v *validator) isAggregator(ctx context.Context, committee []primitives.ValidatorIndex, slot primitives.Slot, pubKey [fieldparams.BLSPubkeyLength]byte, validatorIndex primitives.ValidatorIndex) (bool, error) { modulo := uint64(1) if len(committee)/int(params.BeaconConfig().TargetAggregatorsPerCommittee) > 1 { modulo = uint64(len(committee)) / params.BeaconConfig().TargetAggregatorsPerCommittee } - slotSig, err := v.signSlotWithSelectionProof(ctx, pubKey, slot) - if err != nil { - return false, err + var ( + slotSig []byte + err error + ) + if v.distributed { + slotSig, err = v.getAttSelection(attSelectionKey{slot: slot, index: validatorIndex}) + if err != nil { + return false, err + } + } else { + slotSig, err = v.signSlotWithSelectionProof(ctx, pubKey, slot) + if err != nil { + return false, err + } } b := hash.Hash(slotSig) @@ -1230,6 +1258,89 @@ func (v *validator) validatorIndex(ctx context.Context, pubkey [fieldparams.BLSP return resp.Index, true, nil } +func (v *validator) getAggregatedSelectionProofs(ctx context.Context, duties *ethpb.DutiesResponse) error { + // Create new instance of attestation selections map. + v.newAttSelections() + + var req []shared.BeaconCommitteeSelection + for _, duty := range duties.CurrentEpochDuties { + if duty.Status != ethpb.ValidatorStatus_ACTIVE && duty.Status != ethpb.ValidatorStatus_EXITING { + continue + } + + pk := bytesutil.ToBytes48(duty.PublicKey) + slotSig, err := v.signSlotWithSelectionProof(ctx, pk, duty.AttesterSlot) + if err != nil { + return err + } + + req = append(req, shared.BeaconCommitteeSelection{ + SelectionProof: slotSig, + Slot: duty.AttesterSlot, + ValidatorIndex: duty.ValidatorIndex, + }) + } + + for _, duty := range duties.NextEpochDuties { + if duty.Status != ethpb.ValidatorStatus_ACTIVE && duty.Status != ethpb.ValidatorStatus_EXITING { + continue + } + + pk := bytesutil.ToBytes48(duty.PublicKey) + slotSig, err := v.signSlotWithSelectionProof(ctx, pk, duty.AttesterSlot) + if err != nil { + return err + } + + req = append(req, shared.BeaconCommitteeSelection{ + SelectionProof: slotSig, + Slot: duty.AttesterSlot, + ValidatorIndex: duty.ValidatorIndex, + }) + } + + resp, err := v.validatorClient.GetAggregatedSelections(ctx, req) + if err != nil { + return err + } + + // Store aggregated selection proofs in state. + v.addAttSelections(resp) + + return nil +} + +func (v *validator) addAttSelections(selections []shared.BeaconCommitteeSelection) { + v.attSelectionLock.Lock() + defer v.attSelectionLock.Unlock() + + for _, s := range selections { + v.attSelections[attSelectionKey{ + slot: s.Slot, + index: s.ValidatorIndex, + }] = s + } +} + +func (v *validator) newAttSelections() { + v.attSelectionLock.Lock() + defer v.attSelectionLock.Unlock() + + v.attSelections = make(map[attSelectionKey]shared.BeaconCommitteeSelection) +} + +func (v *validator) getAttSelection(key attSelectionKey) ([]byte, error) { + v.attSelectionLock.Lock() + defer v.attSelectionLock.Unlock() + + s, ok := v.attSelections[key] + if !ok { + return nil, errors.Errorf("selection proof not found for the given slot=%d and validator_index=%d", key.slot, key.index) + } + + return s.SelectionProof, nil +} + // This constructs a validator subscribed key, it's used to track // which subnet has already been pending requested. func validatorSubscribeKey(slot primitives.Slot, committeeID primitives.CommitteeIndex) [64]byte { diff --git a/validator/client/validator_test.go b/validator/client/validator_test.go index d041a3d87b15..3778bbdb80c5 100644 --- a/validator/client/validator_test.go +++ b/validator/client/validator_test.go @@ -11,6 +11,8 @@ import ( "testing" "time" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/mock/gomock" @@ -639,6 +641,92 @@ func TestUpdateDuties_AllValidatorsExited(t *testing.T) { } +func TestUpdateDuties_Distributed(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := validatormock.NewMockValidatorClient(ctrl) + + // Start of third epoch. + slot := 2 * params.BeaconConfig().SlotsPerEpoch + keys := randKeypair(t) + resp := ðpb.DutiesResponse{ + CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{ + { + AttesterSlot: slot, // First slot in epoch. + ValidatorIndex: 200, + CommitteeIndex: 100, + PublicKey: keys.pub[:], + Status: ethpb.ValidatorStatus_ACTIVE, + }, + }, + NextEpochDuties: []*ethpb.DutiesResponse_Duty{ + { + AttesterSlot: slot + params.BeaconConfig().SlotsPerEpoch, // First slot in next epoch. + ValidatorIndex: 200, + CommitteeIndex: 100, + PublicKey: keys.pub[:], + Status: ethpb.ValidatorStatus_ACTIVE, + }, + }, + } + + v := validator{ + keyManager: newMockKeymanager(t, keys), + validatorClient: client, + distributed: true, + } + + sigDomain := make([]byte, 32) + + client.EXPECT().GetDuties( + gomock.Any(), + gomock.Any(), + ).Return(resp, nil) + + client.EXPECT().DomainData( + gomock.Any(), // ctx + gomock.Any(), // epoch + ).Return( + ðpb.DomainResponse{SignatureDomain: sigDomain}, + nil, /*err*/ + ).Times(2) + + client.EXPECT().GetAggregatedSelections( + gomock.Any(), + gomock.Any(), // fill this properly + ).Return( + []shared.BeaconCommitteeSelection{ + { + SelectionProof: make([]byte, 32), + Slot: slot, + ValidatorIndex: 200, + }, + { + SelectionProof: make([]byte, 32), + Slot: slot + params.BeaconConfig().SlotsPerEpoch, + ValidatorIndex: 200, + }, + }, + nil, + ) + + var wg sync.WaitGroup + wg.Add(1) + + client.EXPECT().SubscribeCommitteeSubnets( + gomock.Any(), + gomock.Any(), + gomock.Any(), + ).DoAndReturn(func(_ context.Context, _ *ethpb.CommitteeSubnetsSubscribeRequest, _ []primitives.ValidatorIndex) (*emptypb.Empty, error) { + wg.Done() + return nil, nil + }) + + require.NoError(t, v.UpdateDuties(context.Background(), slot), "Could not update assignments") + util.WaitTimeout(&wg, 2*time.Second) + require.Equal(t, 2, len(v.attSelections)) +} + func TestRolesAt_OK(t *testing.T) { v, m, validatorKey, finish := setup(t) defer finish() diff --git a/validator/node/node.go b/validator/node/node.go index dc33784abf3f..b343c34189bf 100644 --- a/validator/node/node.go +++ b/validator/node/node.go @@ -497,6 +497,7 @@ func (c *ValidatorClient) registerValidatorService(cliCtx *cli.Context) error { BeaconApiTimeout: time.Second * 30, BeaconApiEndpoint: c.cliCtx.String(flags.BeaconRESTApiProviderFlag.Name), ValidatorsRegBatchSize: c.cliCtx.Int(flags.ValidatorsRegistrationBatchSizeFlag.Name), + Distributed: c.cliCtx.Bool(flags.EnableDistributed.Name), }) if err != nil { return errors.Wrap(err, "could not initialize validator service")