diff --git a/CHANGELOG.md b/CHANGELOG.md index c467c7d8fb..42fad7ce6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Electra: build blocks with blobs. - E2E: fixed gas limit at genesis - Light client support: use LightClientHeader instead of BeaconBlockHeader. +- validator registration log changed to debug, and the frequency of validator registration calls are reduced - Core: Fix process effective balance update to safe copy validator for Electra. - `== nil` checks before calling `IsNil()` on interfaces to prevent panics. diff --git a/api/client/builder/client.go b/api/client/builder/client.go index a6554c6eb9..6e2f52eeaa 100644 --- a/api/client/builder/client.go +++ b/api/client/builder/client.go @@ -278,7 +278,11 @@ func (c *Client) RegisterValidator(ctx context.Context, svr []*ethpb.SignedValid } _, err = c.do(ctx, http.MethodPost, postRegisterValidatorPath, bytes.NewBuffer(body)) - return err + if err != nil { + return err + } + log.WithField("num_registrations", len(svr)).Info("successfully registered validator(s) on builder") + return nil } var errResponseVersionMismatch = errors.New("builder API response uses a different version than requested in " + api.VersionHeader + " header") diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go index 15111c24cd..660e26dd9a 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go @@ -429,7 +429,7 @@ func (vs *Server) PrepareBeaconProposer( if len(validatorIndices) != 0 { log.WithFields(logrus.Fields{ "validatorCount": len(validatorIndices), - }).Info("Updated fee recipient addresses for validator indices") + }).Debug("Updated fee recipient addresses for validator indices") } return &emptypb.Empty{}, nil } diff --git a/validator/accounts/testing/mock.go b/validator/accounts/testing/mock.go index e98a0592a1..a95d5b77e6 100644 --- a/validator/accounts/testing/mock.go +++ b/validator/accounts/testing/mock.go @@ -204,7 +204,7 @@ func (*Validator) HasProposerSettings() bool { } // PushProposerSettings for mocking -func (_ *Validator) PushProposerSettings(_ context.Context, _ keymanager.IKeymanager, _ primitives.Slot) error { +func (_ *Validator) PushProposerSettings(_ context.Context, _ keymanager.IKeymanager, _ primitives.Slot, _ bool) error { panic("implement me") } @@ -214,7 +214,7 @@ func (_ *Validator) SetPubKeyToValidatorIndexMap(_ context.Context, _ keymanager } // SignValidatorRegistrationRequest for mocking -func (_ *Validator) SignValidatorRegistrationRequest(_ context.Context, _ iface2.SigningFunc, _ *ethpb.ValidatorRegistrationV1) (*ethpb.SignedValidatorRegistrationV1, error) { +func (_ *Validator) SignValidatorRegistrationRequest(_ context.Context, _ iface2.SigningFunc, _ *ethpb.ValidatorRegistrationV1) (*ethpb.SignedValidatorRegistrationV1, bool, error) { panic("implement me") } diff --git a/validator/client/BUILD.bazel b/validator/client/BUILD.bazel index 28c9e410cf..5f18801847 100644 --- a/validator/client/BUILD.bazel +++ b/validator/client/BUILD.bazel @@ -175,9 +175,7 @@ go_test( "@com_github_wealdtech_go_eth2_util//:go_default_library", "@in_gopkg_d4l3k_messagediff_v1//:go_default_library", "@io_bazel_rules_go//go/tools/bazel:go_default_library", - "@org_golang_google_grpc//codes:go_default_library", "@org_golang_google_grpc//metadata:go_default_library", - "@org_golang_google_grpc//status:go_default_library", "@org_golang_google_protobuf//types/known/emptypb:go_default_library", "@org_uber_go_mock//gomock:go_default_library", ], diff --git a/validator/client/iface/validator.go b/validator/client/iface/validator.go index 647ef06a2f..354e92a5bf 100644 --- a/validator/client/iface/validator.go +++ b/validator/client/iface/validator.go @@ -57,8 +57,8 @@ type Validator interface { Keymanager() (keymanager.IKeymanager, error) HandleKeyReload(ctx context.Context, currentKeys [][fieldparams.BLSPubkeyLength]byte) (bool, error) CheckDoppelGanger(ctx context.Context) error - PushProposerSettings(ctx context.Context, km keymanager.IKeymanager, slot primitives.Slot) error - SignValidatorRegistrationRequest(ctx context.Context, signer SigningFunc, newValidatorRegistration *ethpb.ValidatorRegistrationV1) (*ethpb.SignedValidatorRegistrationV1, error) + PushProposerSettings(ctx context.Context, km keymanager.IKeymanager, slot primitives.Slot, forceFullPush bool) error + SignValidatorRegistrationRequest(ctx context.Context, signer SigningFunc, newValidatorRegistration *ethpb.ValidatorRegistrationV1) (*ethpb.SignedValidatorRegistrationV1, bool /* isCached */, error) StartEventStream(ctx context.Context, topics []string, eventsChan chan<- *event.Event) EventStreamIsRunning() bool ProcessEvent(event *event.Event) diff --git a/validator/client/key_reload.go b/validator/client/key_reload.go index 99250bb2a3..8f2c3f8cb2 100644 --- a/validator/client/key_reload.go +++ b/validator/client/key_reload.go @@ -7,34 +7,18 @@ import ( fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" validator2 "github.com/prysmaticlabs/prysm/v5/consensus-types/validator" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" - eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/validator/client/iface" ) // HandleKeyReload makes sure the validator keeps operating correctly after a change to the underlying keys. // It is also responsible for logging out information about the new state of keys. -func (v *validator) HandleKeyReload(ctx context.Context, currentKeys [][fieldparams.BLSPubkeyLength]byte) (anyActive bool, err error) { +func (v *validator) HandleKeyReload(ctx context.Context, currentKeys [][fieldparams.BLSPubkeyLength]byte) (bool, error) { ctx, span := trace.StartSpan(ctx, "validator.HandleKeyReload") defer span.End() - statusRequestKeys := make([][]byte, len(currentKeys)) - for i := range currentKeys { - statusRequestKeys[i] = currentKeys[i][:] - } - resp, err := v.validatorClient.MultipleValidatorStatus(ctx, ð.MultipleValidatorStatusRequest{ - PublicKeys: statusRequestKeys, - }) - if err != nil { + if err := v.updateValidatorStatusCache(ctx, currentKeys); err != nil { return false, err } - statuses := make([]*validatorStatus, len(resp.Statuses)) - for i, s := range resp.Statuses { - statuses[i] = &validatorStatus{ - publicKey: resp.PublicKeys[i], - status: s, - index: resp.Indices[i], - } - } // "-1" indicates that validator count endpoint is not supported by the beacon node. var valCount int64 = -1 @@ -47,5 +31,5 @@ func (v *validator) HandleKeyReload(ctx context.Context, currentKeys [][fieldpar valCount = int64(valCounts[0].Count) } - return v.checkAndLogValidatorStatus(statuses, valCount), nil + return v.checkAndLogValidatorStatus(valCount), nil } diff --git a/validator/client/key_reload_test.go b/validator/client/key_reload_test.go index df8e0435e1..41fb4d2466 100644 --- a/validator/client/key_reload_test.go +++ b/validator/client/key_reload_test.go @@ -36,6 +36,7 @@ func TestValidator_HandleKeyReload(t *testing.T) { genesisTime: 1, chainClient: chainClient, prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), } resp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactive.pub[:], active.pub[:]}) @@ -73,6 +74,7 @@ func TestValidator_HandleKeyReload(t *testing.T) { genesisTime: 1, chainClient: chainClient, prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), } resp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{kp.pub[:]}) @@ -103,6 +105,7 @@ func TestValidator_HandleKeyReload(t *testing.T) { validatorClient: client, km: newMockKeymanager(t, kp), genesisTime: 1, + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), } client.EXPECT().MultipleValidatorStatus( diff --git a/validator/client/registration.go b/validator/client/registration.go index fb54d27eb1..63904183a4 100644 --- a/validator/client/registration.go +++ b/validator/client/registration.go @@ -93,24 +93,24 @@ func signValidatorRegistration(ctx context.Context, signer iface.SigningFunc, re } // SignValidatorRegistrationRequest compares and returns either the cached validator registration request or signs a new one. -func (v *validator) SignValidatorRegistrationRequest(ctx context.Context, signer iface.SigningFunc, newValidatorRegistration *ethpb.ValidatorRegistrationV1) (*ethpb.SignedValidatorRegistrationV1, error) { +func (v *validator) SignValidatorRegistrationRequest(ctx context.Context, signer iface.SigningFunc, newValidatorRegistration *ethpb.ValidatorRegistrationV1) (*ethpb.SignedValidatorRegistrationV1, bool /* isCached */, error) { ctx, span := trace.StartSpan(ctx, "validator.SignValidatorRegistrationRequest") defer span.End() signedReg, ok := v.signedValidatorRegistrations[bytesutil.ToBytes48(newValidatorRegistration.Pubkey)] if ok && isValidatorRegistrationSame(signedReg.Message, newValidatorRegistration) { - return signedReg, nil + return signedReg, true, nil } else { sig, err := signValidatorRegistration(ctx, signer, newValidatorRegistration) if err != nil { - return nil, err + return nil, false, err } newRequest := ðpb.SignedValidatorRegistrationV1{ Message: newValidatorRegistration, Signature: sig, } v.signedValidatorRegistrations[bytesutil.ToBytes48(newValidatorRegistration.Pubkey)] = newRequest - return newRequest, nil + return newRequest, false, nil } } diff --git a/validator/client/registration_test.go b/validator/client/registration_test.go index f8c77b2249..bd954317d2 100644 --- a/validator/client/registration_test.go +++ b/validator/client/registration_test.go @@ -10,7 +10,6 @@ import ( "github.com/pkg/errors" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" - "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/testing/require" @@ -172,7 +171,7 @@ func TestValidator_SignValidatorRegistrationRequest(t *testing.T) { }, validatorSetter: func(t *testing.T) *validator { v := validator{ - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1), useWeb: false, genesisTime: 0, @@ -200,7 +199,7 @@ func TestValidator_SignValidatorRegistrationRequest(t *testing.T) { }, validatorSetter: func(t *testing.T) *validator { v := validator{ - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1), useWeb: false, genesisTime: 0, @@ -228,7 +227,7 @@ func TestValidator_SignValidatorRegistrationRequest(t *testing.T) { }, validatorSetter: func(t *testing.T) *validator { v := validator{ - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1), useWeb: false, genesisTime: 0, @@ -256,7 +255,7 @@ func TestValidator_SignValidatorRegistrationRequest(t *testing.T) { }, validatorSetter: func(t *testing.T) *validator { v := validator{ - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1), useWeb: false, genesisTime: 0, @@ -272,7 +271,7 @@ func TestValidator_SignValidatorRegistrationRequest(t *testing.T) { startingReq, ok := v.signedValidatorRegistrations[bytesutil.ToBytes48(tt.arg.Pubkey)] - got, err := v.SignValidatorRegistrationRequest(ctx, m.signfunc, tt.arg) + got, _, err := v.SignValidatorRegistrationRequest(ctx, m.signfunc, tt.arg) require.NoError(t, err) if tt.isCached { require.DeepEqual(t, got, v.signedValidatorRegistrations[bytesutil.ToBytes48(tt.arg.Pubkey)]) diff --git a/validator/client/runner.go b/validator/client/runner.go index 1f42363116..7839c795e2 100644 --- a/validator/client/runner.go +++ b/validator/client/runner.go @@ -62,7 +62,7 @@ func run(ctx context.Context, v iface.Validator) { log.Warn("Validator client started without proposer settings such as fee recipient" + " and will continue to use settings provided in the beacon node.") } - if err := v.PushProposerSettings(ctx, km, headSlot); err != nil { + if err := v.PushProposerSettings(ctx, km, headSlot, true); err != nil { log.WithError(err).Fatal("Failed to update proposer settings") } for { @@ -97,7 +97,7 @@ func run(ctx context.Context, v iface.Validator) { // call push proposer settings often to account for the following edge cases: // proposer is activated at the start of epoch and tries to propose immediately // account has changed in the middle of an epoch - if err := v.PushProposerSettings(ctx, km, slot); err != nil { + if err := v.PushProposerSettings(ctx, km, slot, false); err != nil { log.WithError(err).Warn("Failed to update proposer settings") } @@ -316,7 +316,7 @@ func runHealthCheckRoutine(ctx context.Context, v iface.Validator, eventsChan ch log.WithError(err).Error("Could not get canonical head slot") return } - if err := v.PushProposerSettings(ctx, km, slot); err != nil { + if err := v.PushProposerSettings(ctx, km, slot, true); err != nil { log.WithError(err).Warn("Failed to update proposer settings") } } diff --git a/validator/client/service.go b/validator/client/service.go index 31c039cf52..0997ea4b98 100644 --- a/validator/client/service.go +++ b/validator/client/service.go @@ -184,7 +184,7 @@ func (v *ValidatorService) Start() { startBalances: make(map[[fieldparams.BLSPubkeyLength]byte]uint64), prevEpochBalances: make(map[[fieldparams.BLSPubkeyLength]byte]uint64), blacklistedPubkeys: slashablePublicKeys, - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), wallet: v.wallet, walletInitializedChan: make(chan *wallet.Wallet, 1), walletInitializedFeed: v.walletInitializedFeed, diff --git a/validator/client/testutil/mock_validator.go b/validator/client/testutil/mock_validator.go index 0e462c1c62..e6a48d5b91 100644 --- a/validator/client/testutil/mock_validator.go +++ b/validator/client/testutil/mock_validator.go @@ -254,7 +254,7 @@ func (*FakeValidator) HasProposerSettings() bool { } // PushProposerSettings for mocking -func (fv *FakeValidator) PushProposerSettings(ctx context.Context, _ keymanager.IKeymanager, _ primitives.Slot) error { +func (fv *FakeValidator) PushProposerSettings(ctx context.Context, _ keymanager.IKeymanager, _ primitives.Slot, _ bool) error { time.Sleep(fv.ProposerSettingWait) if errors.Is(ctx.Err(), context.DeadlineExceeded) { log.Error("deadline exceeded") @@ -276,8 +276,8 @@ func (*FakeValidator) SetPubKeyToValidatorIndexMap(_ context.Context, _ keymanag } // SignValidatorRegistrationRequest for mocking -func (*FakeValidator) SignValidatorRegistrationRequest(_ context.Context, _ iface.SigningFunc, _ *ethpb.ValidatorRegistrationV1) (*ethpb.SignedValidatorRegistrationV1, error) { - return nil, nil +func (*FakeValidator) SignValidatorRegistrationRequest(_ context.Context, _ iface.SigningFunc, _ *ethpb.ValidatorRegistrationV1) (*ethpb.SignedValidatorRegistrationV1, bool, error) { + return nil, false, nil } // ProposerSettings for mocking diff --git a/validator/client/validator.go b/validator/client/validator.go index 5b9f99ec27..d6eca9ae6f 100644 --- a/validator/client/validator.go +++ b/validator/client/validator.go @@ -40,7 +40,6 @@ import ( "github.com/prysmaticlabs/prysm/v5/time/slots" accountsiface "github.com/prysmaticlabs/prysm/v5/validator/accounts/iface" "github.com/prysmaticlabs/prysm/v5/validator/accounts/wallet" - beaconapi "github.com/prysmaticlabs/prysm/v5/validator/client/beacon-api" "github.com/prysmaticlabs/prysm/v5/validator/client/iface" "github.com/prysmaticlabs/prysm/v5/validator/db" dbCommon "github.com/prysmaticlabs/prysm/v5/validator/db/common" @@ -49,9 +48,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/validator/keymanager/local" remoteweb3signer "github.com/prysmaticlabs/prysm/v5/validator/keymanager/remote-web3signer" "github.com/sirupsen/logrus" - "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/emptypb" ) @@ -77,7 +74,7 @@ type validator struct { startBalances map[[fieldparams.BLSPubkeyLength]byte]uint64 prevEpochBalances map[[fieldparams.BLSPubkeyLength]byte]uint64 blacklistedPubkeys map[[fieldparams.BLSPubkeyLength]byte]bool - pubkeyToValidatorIndex map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex + pubkeyToStatus map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus wallet *wallet.Wallet walletInitializedChan chan *wallet.Wallet walletInitializedFeed *event.Feed @@ -352,10 +349,10 @@ func (v *validator) WaitForSync(ctx context.Context) error { } } -func (v *validator) checkAndLogValidatorStatus(statuses []*validatorStatus, activeValCount int64) bool { +func (v *validator) checkAndLogValidatorStatus(activeValCount int64) bool { nonexistentIndex := primitives.ValidatorIndex(^uint64(0)) var validatorActivated bool - for _, s := range statuses { + for _, s := range v.pubkeyToStatus { fields := logrus.Fields{ "pubkey": fmt.Sprintf("%#x", bytesutil.Trunc(s.publicKey)), "status": s.status.Status.String(), @@ -1102,7 +1099,7 @@ func (v *validator) SetProposerSettings(ctx context.Context, settings *proposer. } // PushProposerSettings calls the prepareBeaconProposer RPC to set the fee recipient and also the register validator API if using a custom builder. -func (v *validator) PushProposerSettings(ctx context.Context, km keymanager.IKeymanager, slot primitives.Slot) error { +func (v *validator) PushProposerSettings(ctx context.Context, km keymanager.IKeymanager, slot primitives.Slot, forceFullPush bool) error { ctx, span := trace.StartSpan(ctx, "validator.PushProposerSettings") defer span.End() @@ -1143,7 +1140,7 @@ func (v *validator) PushProposerSettings(ctx context.Context, km keymanager.IKey }); err != nil { return err } - signedRegReqs := v.buildSignedRegReqs(ctx, filteredKeys, km.Sign) + signedRegReqs := v.buildSignedRegReqs(ctx, filteredKeys, km.Sign, slot, forceFullPush) if len(signedRegReqs) > 0 { go func() { if err := SubmitValidatorRegistrations(ctx, v.validatorClient, signedRegReqs, v.validatorsRegBatchSize); err != nil { @@ -1212,44 +1209,31 @@ func (v *validator) ChangeHost() { func (v *validator) filterAndCacheActiveKeys(ctx context.Context, pubkeys [][fieldparams.BLSPubkeyLength]byte, slot primitives.Slot) ([][fieldparams.BLSPubkeyLength]byte, error) { ctx, span := trace.StartSpan(ctx, "validator.filterAndCacheActiveKeys") defer span.End() - + isEpochStart := slots.IsEpochStart(slot) filteredKeys := make([][fieldparams.BLSPubkeyLength]byte, 0) - statusRequestKeys := make([][]byte, 0) - for _, k := range pubkeys { - _, ok := v.pubkeyToValidatorIndex[k] - // Get validator index from RPC server if not found. - if !ok { - i, ok, err := v.validatorIndex(ctx, k) - if err != nil { - return nil, err - } - if !ok { // Nothing we can do if RPC server doesn't have validator index. - continue - } - v.pubkeyToValidatorIndex[k] = i + if len(pubkeys) == 0 { + return filteredKeys, nil + } + var err error + // repopulate the statuses if epoch start or if a new key is added missing the cache + if isEpochStart || len(v.pubkeyToStatus) != len(pubkeys) /* cache not populated or updated correctly */ { + if err = v.updateValidatorStatusCache(ctx, pubkeys); err != nil { + return nil, errors.Wrap(err, "failed to update validator status cache") } - copiedk := k - statusRequestKeys = append(statusRequestKeys, copiedk[:]) } - resp, err := v.validatorClient.MultipleValidatorStatus(ctx, ðpb.MultipleValidatorStatusRequest{ - PublicKeys: statusRequestKeys, - }) - if err != nil { - return nil, err - } - for i, s := range resp.Statuses { + for k, s := range v.pubkeyToStatus { currEpoch := primitives.Epoch(slot / params.BeaconConfig().SlotsPerEpoch) - currActivating := s.Status == ethpb.ValidatorStatus_PENDING && currEpoch >= s.ActivationEpoch + currActivating := s.status.Status == ethpb.ValidatorStatus_PENDING && currEpoch >= s.status.ActivationEpoch - active := s.Status == ethpb.ValidatorStatus_ACTIVE - exiting := s.Status == ethpb.ValidatorStatus_EXITING + active := s.status.Status == ethpb.ValidatorStatus_ACTIVE + exiting := s.status.Status == ethpb.ValidatorStatus_EXITING if currActivating || active || exiting { - filteredKeys = append(filteredKeys, bytesutil.ToBytes48(resp.PublicKeys[i])) + filteredKeys = append(filteredKeys, k) } else { log.WithFields(logrus.Fields{ - "pubkey": hexutil.Encode(resp.PublicKeys[i]), - "status": s.Status.String(), + "pubkey": hexutil.Encode(s.publicKey), + "status": s.status.Status.String(), }).Debugf("Skipping non-active status key.") } } @@ -1257,11 +1241,47 @@ func (v *validator) filterAndCacheActiveKeys(ctx context.Context, pubkeys [][fie return filteredKeys, nil } +// updateValidatorStatusCache updates the validator statuses cache, a map of keys currently used by the validator client +func (v *validator) updateValidatorStatusCache(ctx context.Context, pubkeys [][fieldparams.BLSPubkeyLength]byte) error { + statusRequestKeys := make([][]byte, 0) + for _, k := range pubkeys { + statusRequestKeys = append(statusRequestKeys, k[:]) + } + resp, err := v.validatorClient.MultipleValidatorStatus(ctx, ðpb.MultipleValidatorStatusRequest{ + PublicKeys: statusRequestKeys, + }) + if err != nil { + return err + } + if resp == nil { + return errors.New("response is nil") + } + if len(resp.Statuses) != len(resp.PublicKeys) { + return fmt.Errorf("expected %d pubkeys in status, received %d", len(resp.Statuses), len(resp.PublicKeys)) + } + if len(resp.Statuses) != len(resp.Indices) { + return fmt.Errorf("expected %d indices in status, received %d", len(resp.Statuses), len(resp.Indices)) + } + for i, s := range resp.Statuses { + v.pubkeyToStatus[bytesutil.ToBytes48(resp.PublicKeys[i])] = &validatorStatus{ + publicKey: resp.PublicKeys[i], + status: s, + index: resp.Indices[i], + } + } + return nil +} + func (v *validator) buildPrepProposerReqs(activePubkeys [][fieldparams.BLSPubkeyLength]byte) ([]*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer, error) { var prepareProposerReqs []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer for _, k := range activePubkeys { + s, ok := v.pubkeyToStatus[k] + if !ok { + continue + } + // Default case: Define fee recipient to burn address - var feeRecipient common.Address + feeRecipient := common.HexToAddress(params.BeaconConfig().EthBurnAddressHex) // If fee recipient is defined in default configuration, use it if v.ProposerSettings() != nil && v.ProposerSettings().DefaultConfig != nil && v.ProposerSettings().DefaultConfig.FeeRecipientConfig != nil { @@ -1277,13 +1297,8 @@ func (v *validator) buildPrepProposerReqs(activePubkeys [][fieldparams.BLSPubkey } } - validatorIndex, ok := v.pubkeyToValidatorIndex[k] - if !ok { - continue - } - prepareProposerReqs = append(prepareProposerReqs, ðpb.PrepareBeaconProposerRequest_FeeRecipientContainer{ - ValidatorIndex: validatorIndex, + ValidatorIndex: s.index, FeeRecipient: feeRecipient[:], }) } @@ -1294,19 +1309,27 @@ func (v *validator) buildSignedRegReqs( ctx context.Context, activePubkeys [][fieldparams.BLSPubkeyLength]byte, signer iface.SigningFunc, + slot primitives.Slot, + forceFullPush bool, ) []*ethpb.SignedValidatorRegistrationV1 { ctx, span := trace.StartSpan(ctx, "validator.buildSignedRegReqs") defer span.End() - var signedValRegRegs []*ethpb.SignedValidatorRegistrationV1 + var signedValRegRequests []*ethpb.SignedValidatorRegistrationV1 if v.ProposerSettings() == nil { - return signedValRegRegs + return signedValRegRequests } // if the timestamp is pre-genesis, don't create registrations if v.genesisTime > uint64(time.Now().UTC().Unix()) { - return signedValRegRegs + return signedValRegRequests } for i, k := range activePubkeys { + // map is populated before this function in buildPrepProposerReq + _, ok := v.pubkeyToStatus[k] + if !ok { + continue + } + feeRecipient := common.HexToAddress(params.BeaconConfig().EthBurnAddressHex) gasLimit := params.BeaconConfig().DefaultBuilderGasLimit enabled := false @@ -1346,12 +1369,6 @@ func (v *validator) buildSignedRegReqs( continue } - // map is populated before this function in buildPrepProposerReq - _, ok := v.pubkeyToValidatorIndex[k] - if !ok { - continue - } - req := ðpb.ValidatorRegistrationV1{ FeeRecipient: feeRecipient[:], GasLimit: gasLimit, @@ -1359,7 +1376,7 @@ func (v *validator) buildSignedRegReqs( Pubkey: activePubkeys[i][:], } - signedReq, err := v.SignValidatorRegistrationRequest(ctx, signer, req) + signedRequest, isCached, err := v.SignValidatorRegistrationRequest(ctx, signer, req) if err != nil { log.WithFields(logrus.Fields{ "pubkey": fmt.Sprintf("%#x", req.Pubkey), @@ -1368,38 +1385,20 @@ func (v *validator) buildSignedRegReqs( continue } - signedValRegRegs = append(signedValRegRegs, signedReq) - if hexutil.Encode(feeRecipient.Bytes()) == params.BeaconConfig().EthBurnAddressHex { log.WithFields(logrus.Fields{ "pubkey": fmt.Sprintf("%#x", req.Pubkey), "feeRecipient": feeRecipient, }).Warn("Fee recipient is burn address") } - } - return signedValRegRegs -} -func (v *validator) validatorIndex(ctx context.Context, pubkey [fieldparams.BLSPubkeyLength]byte) (primitives.ValidatorIndex, bool, error) { - ctx, span := trace.StartSpan(ctx, "validator.validatorIndex") - defer span.End() - - resp, err := v.validatorClient.ValidatorIndex(ctx, ðpb.ValidatorIndexRequest{PublicKey: pubkey[:]}) - switch { - case status.Code(err) == codes.NotFound: - log.Debugf("Could not find validator index for public key %#x. "+ - "Perhaps the validator is not yet active.", pubkey) - return 0, false, nil - case err != nil: - notFoundErr := &beaconapi.IndexNotFoundError{} - if errors.As(err, ¬FoundErr) { - log.Debugf("Could not find validator index for public key %#x. "+ - "Perhaps the validator is not yet active.", pubkey) - return 0, false, nil + if slots.IsEpochStart(slot) || forceFullPush || !isCached { + // if epoch start (or forced to) send all validator registrations + // otherwise if slot is not epoch start then only send new non cached values + signedValRegRequests = append(signedValRegRequests, signedRequest) } - return 0, false, err } - return resp.Index, true, nil + return signedValRegRequests } func (v *validator) aggregatedSelectionProofs(ctx context.Context, duties *ethpb.DutiesResponse) error { diff --git a/validator/client/validator_test.go b/validator/client/validator_test.go index 2d9eca2ff3..679a2add84 100644 --- a/validator/client/validator_test.go +++ b/validator/client/validator_test.go @@ -9,6 +9,7 @@ import ( "math" "os" "path/filepath" + "sort" "strings" "sync" "testing" @@ -45,8 +46,6 @@ import ( logTest "github.com/sirupsen/logrus/hooks/test" "github.com/urfave/cli/v2" "go.uber.org/mock/gomock" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" ) @@ -59,6 +58,8 @@ var _ iface.Validator = (*validator)(nil) const cancelledCtx = "context has been canceled" +var unknownIndex = primitives.ValidatorIndex(^uint64(0)) + func genMockKeymanager(t *testing.T, numKeys int) *mockKeymanager { pairs := make([]keypair, numKeys) for i := 0; i < numKeys; i++ { @@ -354,6 +355,7 @@ func TestWaitMultipleActivation_LogsActivationEpochOK(t *testing.T) { km: newMockKeymanager(t, kp), chainClient: chainClient, prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } resp := generateMockStatusResponse([][]byte{kp.pub[:]}) @@ -937,9 +939,10 @@ func TestCheckAndLogValidatorStatus_OK(t *testing.T) { }, }, }, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } - - active := v.checkAndLogValidatorStatus([]*validatorStatus{test.status}, 100) + v.pubkeyToStatus[bytesutil.ToBytes48(test.status.publicKey)] = test.status + active := v.checkAndLogValidatorStatus(100) require.Equal(t, test.active, active) if test.log != "" { require.LogsContain(t, hook, test.log) @@ -1489,7 +1492,7 @@ func TestValidator_PushSettings(t *testing.T) { validatorClient: client, nodeClient: nodeClient, db: db, - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1), useWeb: false, interopKeysConfig: &local.InteropKeymanagerConfig{ @@ -1504,14 +1507,23 @@ func TestValidator_PushSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) - v.pubkeyToValidatorIndex[keys[1]] = primitives.ValidatorIndex(2) + v.pubkeyToStatus[keys[0]] = &validatorStatus{ + publicKey: keys[0][:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: primitives.ValidatorIndex(1), + } + v.pubkeyToStatus[keys[1]] = &validatorStatus{ + publicKey: keys[1][:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: primitives.ValidatorIndex(2), + } client.EXPECT().MultipleValidatorStatus( gomock.Any(), gomock.Any()).Return( ðpb.MultipleValidatorStatusResponse{ Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}}, PublicKeys: [][]byte{keys[0][:], keys[1][:]}, + Indices: []primitives.ValidatorIndex{1, 2}, }, nil) client.EXPECT().PrepareBeaconProposer(gomock.Any(), ðpb.PrepareBeaconProposerRequest{ Recipients: []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer{ @@ -1571,7 +1583,7 @@ func TestValidator_PushSettings(t *testing.T) { validatorClient: client, nodeClient: nodeClient, db: db, - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1), useWeb: false, interopKeysConfig: &local.InteropKeymanagerConfig{ @@ -1586,14 +1598,23 @@ func TestValidator_PushSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) - v.pubkeyToValidatorIndex[keys[1]] = primitives.ValidatorIndex(2) + v.pubkeyToStatus[keys[0]] = &validatorStatus{ + publicKey: keys[0][:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: primitives.ValidatorIndex(1), + } + v.pubkeyToStatus[keys[1]] = &validatorStatus{ + publicKey: keys[1][:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: primitives.ValidatorIndex(2), + } client.EXPECT().MultipleValidatorStatus( gomock.Any(), gomock.Any()).Return( ðpb.MultipleValidatorStatusResponse{ Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}}, PublicKeys: [][]byte{keys[0][:], keys[1][:]}, + Indices: []primitives.ValidatorIndex{1, 2}, }, nil) client.EXPECT().PrepareBeaconProposer(gomock.Any(), ðpb.PrepareBeaconProposerRequest{ Recipients: []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer{ @@ -1644,12 +1665,11 @@ func TestValidator_PushSettings(t *testing.T) { { name: " Happy Path default doesn't send any validator registrations", validatorSetter: func(t *testing.T) *validator { - v := validator{ validatorClient: client, nodeClient: nodeClient, db: db, - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1), useWeb: false, interopKeysConfig: &local.InteropKeymanagerConfig{ @@ -1664,14 +1684,23 @@ func TestValidator_PushSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) - v.pubkeyToValidatorIndex[keys[1]] = primitives.ValidatorIndex(2) + v.pubkeyToStatus[keys[0]] = &validatorStatus{ + publicKey: keys[0][:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: primitives.ValidatorIndex(1), + } + v.pubkeyToStatus[keys[1]] = &validatorStatus{ + publicKey: keys[1][:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: primitives.ValidatorIndex(2), + } client.EXPECT().MultipleValidatorStatus( gomock.Any(), gomock.Any()).Return( ðpb.MultipleValidatorStatusResponse{ Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}}, PublicKeys: [][]byte{keys[0][:], keys[1][:]}, + Indices: []primitives.ValidatorIndex{1, 2}, }, nil) client.EXPECT().PrepareBeaconProposer(gomock.Any(), ðpb.PrepareBeaconProposerRequest{ Recipients: []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer{ @@ -1710,7 +1739,7 @@ func TestValidator_PushSettings(t *testing.T) { validatorClient: client, nodeClient: nodeClient, db: db, - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1), useWeb: false, interopKeysConfig: &local.InteropKeymanagerConfig{ @@ -1740,13 +1769,18 @@ func TestValidator_PushSettings(t *testing.T) { }, }) require.NoError(t, err) - v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) + v.pubkeyToStatus[keys[0]] = &validatorStatus{ + publicKey: keys[0][:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: primitives.ValidatorIndex(1), + } client.EXPECT().MultipleValidatorStatus( gomock.Any(), gomock.Any()).Return( ðpb.MultipleValidatorStatusResponse{ Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}}, PublicKeys: [][]byte{keys[0][:]}, + Indices: []primitives.ValidatorIndex{1}, }, nil) client.EXPECT().SubmitValidatorRegistrations( @@ -1778,7 +1812,7 @@ func TestValidator_PushSettings(t *testing.T) { validatorClient: client, nodeClient: nodeClient, db: db, - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1), useWeb: false, interopKeysConfig: &local.InteropKeymanagerConfig{ @@ -1805,13 +1839,18 @@ func TestValidator_PushSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) + v.pubkeyToStatus[keys[0]] = &validatorStatus{ + publicKey: keys[0][:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: primitives.ValidatorIndex(1), + } client.EXPECT().MultipleValidatorStatus( gomock.Any(), gomock.Any()).Return( ðpb.MultipleValidatorStatusResponse{ Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}}, PublicKeys: [][]byte{keys[0][:]}, + Indices: []primitives.ValidatorIndex{1}, }, nil) client.EXPECT().SubmitValidatorRegistrations( gomock.Any(), @@ -1842,7 +1881,7 @@ func TestValidator_PushSettings(t *testing.T) { validatorClient: client, nodeClient: nodeClient, db: db, - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1), useWeb: false, interopKeysConfig: &local.InteropKeymanagerConfig{ @@ -1857,13 +1896,18 @@ func TestValidator_PushSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) + v.pubkeyToStatus[keys[0]] = &validatorStatus{ + publicKey: keys[0][:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: primitives.ValidatorIndex(1), + } client.EXPECT().MultipleValidatorStatus( gomock.Any(), gomock.Any()).Return( ðpb.MultipleValidatorStatusResponse{ Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}}, PublicKeys: [][]byte{keys[0][:]}, + Indices: []primitives.ValidatorIndex{1}, }, nil) client.EXPECT().PrepareBeaconProposer(gomock.Any(), ðpb.PrepareBeaconProposerRequest{ Recipients: []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer{ @@ -1894,7 +1938,7 @@ func TestValidator_PushSettings(t *testing.T) { v := validator{ validatorClient: client, db: db, - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1), useWeb: false, interopKeysConfig: &local.InteropKeymanagerConfig{ @@ -1909,15 +1953,19 @@ func TestValidator_PushSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - client.EXPECT().ValidatorIndex( - gomock.Any(), // ctx - ðpb.ValidatorIndexRequest{PublicKey: keys[0][:]}, - ).Return(nil, errors.New("could not find validator index for public key")) config[keys[0]] = &proposer.Option{ FeeRecipientConfig: &proposer.FeeRecipientConfig{ FeeRecipient: common.HexToAddress("0x046Fb65722E7b2455043BFEBf6177F1D2e9738D9"), }, } + client.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any()).Return( + ðpb.MultipleValidatorStatusResponse{ + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_UNKNOWN_STATUS}}, + PublicKeys: [][]byte{keys[0][:]}, + Indices: []primitives.ValidatorIndex{unknownIndex}, + }, nil) err = v.SetProposerSettings(context.Background(), &proposer.Settings{ ProposeConfig: config, DefaultConfig: &proposer.Option{ @@ -1937,7 +1985,7 @@ func TestValidator_PushSettings(t *testing.T) { validatorClient: client, nodeClient: nodeClient, db: db, - pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus), signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1), useWeb: false, interopKeysConfig: &local.InteropKeymanagerConfig{ @@ -1952,13 +2000,18 @@ func TestValidator_PushSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) + v.pubkeyToStatus[keys[0]] = &validatorStatus{ + publicKey: keys[0][:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: primitives.ValidatorIndex(1), + } client.EXPECT().MultipleValidatorStatus( gomock.Any(), gomock.Any()).Return( ðpb.MultipleValidatorStatusResponse{ Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}}, PublicKeys: [][]byte{keys[0][:]}, + Indices: []primitives.ValidatorIndex{1}, }, nil) config[keys[0]] = &proposer.Option{ @@ -2009,7 +2062,7 @@ func TestValidator_PushSettings(t *testing.T) { if tt.feeRecipientMap != nil { feeRecipients, err := v.buildPrepProposerReqs(pubkeys) require.NoError(t, err) - signedRegisterValidatorRequests := v.buildSignedRegReqs(ctx, pubkeys, km.Sign) + signedRegisterValidatorRequests := v.buildSignedRegReqs(ctx, pubkeys, km.Sign, 0, false) for _, recipient := range feeRecipients { require.Equal(t, strings.ToLower(tt.feeRecipientMap[recipient.ValidatorIndex]), strings.ToLower(hexutil.Encode(recipient.FeeRecipient))) } @@ -2027,7 +2080,7 @@ func TestValidator_PushSettings(t *testing.T) { require.Equal(t, len(tt.mockExpectedRequests), len(signedRegisterValidatorRequests)) require.Equal(t, len(signedRegisterValidatorRequests), len(v.signedValidatorRegistrations)) } - if err := v.PushProposerSettings(ctx, km, 0); tt.err != "" { + if err := v.PushProposerSettings(ctx, km, 0, false); tt.err != "" { assert.ErrorContains(t, tt.err, err) } if len(tt.logMessages) > 0 { @@ -2091,28 +2144,14 @@ func TestValidator_buildPrepProposerReqs_WithoutDefaultConfig(t *testing.T) { ctx := context.Background() client := validatormock.NewMockValidatorClient(ctrl) - client.EXPECT().ValidatorIndex( - gomock.Any(), - ðpb.ValidatorIndexRequest{ - PublicKey: pubkey2[:], - }, - ).Return(ðpb.ValidatorIndexResponse{ - Index: 2, - }, nil) - - client.EXPECT().ValidatorIndex( - gomock.Any(), - ðpb.ValidatorIndexRequest{ - PublicKey: pubkey3[:], - }, - ).Return(nil, status.Error(codes.NotFound, "NOT_FOUND")) client.EXPECT().MultipleValidatorStatus( gomock.Any(), gomock.Any()).Return( ðpb.MultipleValidatorStatusResponse{ - Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}}, - PublicKeys: [][]byte{pubkey1[:], pubkey2[:], pubkey4[:]}, + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_UNKNOWN_STATUS}, {Status: ethpb.ValidatorStatus_ACTIVE}}, + PublicKeys: [][]byte{pubkey1[:], pubkey2[:], pubkey3[:], pubkey4[:]}, + Indices: []primitives.ValidatorIndex{1, 2, unknownIndex, 4}, }, nil) v := validator{ validatorClient: client, @@ -2141,9 +2180,17 @@ func TestValidator_buildPrepProposerReqs_WithoutDefaultConfig(t *testing.T) { }, }, }, - pubkeyToValidatorIndex: map[[48]byte]primitives.ValidatorIndex{ - pubkey1: 1, - pubkey4: 4, + pubkeyToStatus: map[[48]byte]*validatorStatus{ + pubkey1: { + publicKey: pubkey1[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 1, + }, + pubkey4: { + publicKey: pubkey4[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 4, + }, }, } @@ -2167,18 +2214,121 @@ func TestValidator_buildPrepProposerReqs_WithoutDefaultConfig(t *testing.T) { require.NoError(t, err) actual, err := v.buildPrepProposerReqs(filteredKeys) require.NoError(t, err) + sort.Slice(actual, func(i, j int) bool { + return actual[i].ValidatorIndex < actual[j].ValidatorIndex + }) assert.DeepEqual(t, expected, actual) } +func TestValidator_filterAndCacheActiveKeys(t *testing.T) { + // Public keys + pubkey1 := pubkeyFromString(t, "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111") + pubkey2 := pubkeyFromString(t, "0x222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222") + pubkey3 := pubkeyFromString(t, "0x333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333") + pubkey4 := pubkeyFromString(t, "0x444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444") + + t.Run("refetch all keys at start of epoch", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := context.Background() + client := validatormock.NewMockValidatorClient(ctrl) + + client.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any()).Return( + ðpb.MultipleValidatorStatusResponse{ + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_UNKNOWN_STATUS}, {Status: ethpb.ValidatorStatus_ACTIVE}}, + PublicKeys: [][]byte{pubkey1[:], pubkey2[:], pubkey3[:], pubkey4[:]}, + Indices: []primitives.ValidatorIndex{1, 2, unknownIndex, 4}, + }, nil) + v := validator{ + validatorClient: client, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), + } + keys, err := v.filterAndCacheActiveKeys(ctx, [][48]byte{pubkey1, pubkey2, pubkey3, pubkey4}, 0) + require.NoError(t, err) + // one key is unknown status + require.Equal(t, 3, len(keys)) + }) + t.Run("refetch all keys at start of epoch, even with cache", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := context.Background() + client := validatormock.NewMockValidatorClient(ctrl) + + client.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any()).Return( + ðpb.MultipleValidatorStatusResponse{ + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_UNKNOWN_STATUS}, {Status: ethpb.ValidatorStatus_ACTIVE}}, + PublicKeys: [][]byte{pubkey1[:], pubkey2[:], pubkey3[:], pubkey4[:]}, + Indices: []primitives.ValidatorIndex{1, 2, unknownIndex, 4}, + }, nil) + v := validator{ + validatorClient: client, + pubkeyToStatus: map[[48]byte]*validatorStatus{ + pubkey1: { + publicKey: pubkey1[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 1, + }, + pubkey2: { + publicKey: pubkey2[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 2, + }, + pubkey3: { + publicKey: pubkey3[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, // gets overridden + index: 3, + }, + pubkey4: { + publicKey: pubkey4[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 4, + }, + }, + } + keys, err := v.filterAndCacheActiveKeys(ctx, [][48]byte{pubkey1, pubkey2, pubkey3, pubkey4}, 0) + require.NoError(t, err) + // one key is unknown status + require.Equal(t, 3, len(keys)) + }) + t.Run("cache used mid epoch, no new keys added", func(t *testing.T) { + ctx := context.Background() + v := validator{ + pubkeyToStatus: map[[48]byte]*validatorStatus{ + pubkey1: { + publicKey: pubkey1[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 1, + }, + pubkey4: { + publicKey: pubkey4[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 4, + }, + }, + } + keys, err := v.filterAndCacheActiveKeys(ctx, [][48]byte{pubkey1, pubkey4}, 5) + require.NoError(t, err) + // one key is unknown status + require.Equal(t, 2, len(keys)) + }) + +} + func TestValidator_buildPrepProposerReqs_WithDefaultConfig(t *testing.T) { - // pubkey1 => feeRecipient1 - Status: active (already in `v.validatorIndex`) - // pubkey2 => feeRecipient2 - Status: active (NOT in `v.validatorIndex`, index found by beacon node) - // pubkey3 => feeRecipient3 - Status: active (NOT in `v.validatorIndex`, index NOT found by beacon node) - // pubkey4 => Nothing - Status: active (already in `v.validatorIndex`) - // pubkey5 => Nothing - Status: unknown (already in `v.validatorIndex`) - // pubkey6 => Nothing - Status: pending (already in `v.validatorIndex`) - ActivationEpoch: 35 (current slot: 641 - current epoch: 20) - // pubkey7 => Nothing - Status: pending (already in `v.validatorIndex`) - ActivationEpoch: 20 (current slot: 641 - current epoch: 20) - // pubkey8 => feeRecipient8 - Status: exiting (already in `v.validatorIndex`) + // pubkey1 => feeRecipient1 - Status: active + // pubkey2 => feeRecipient2 - Status: active + // pubkey3 => feeRecipient3 - Status: unknown + // pubkey4 => Nothing - Status: active + // pubkey5 => Nothing - Status: exited + // pubkey6 => Nothing - Status: pending - ActivationEpoch: 35 (current slot: 641 - current epoch: 20) + // pubkey7 => Nothing - Status: pending - ActivationEpoch: 20 (current slot: 641 - current epoch: 20) + // pubkey8 => feeRecipient8 - Status: exiting // Public keys pubkey1 := pubkeyFromString(t, "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111") @@ -2201,9 +2351,9 @@ func TestValidator_buildPrepProposerReqs_WithDefaultConfig(t *testing.T) { pubkeyToStatus := map[[fieldparams.BLSPubkeyLength]byte]ethpb.ValidatorStatus{ pubkey1: ethpb.ValidatorStatus_ACTIVE, pubkey2: ethpb.ValidatorStatus_ACTIVE, - pubkey3: ethpb.ValidatorStatus_ACTIVE, + pubkey3: ethpb.ValidatorStatus_UNKNOWN_STATUS, pubkey4: ethpb.ValidatorStatus_ACTIVE, - pubkey5: ethpb.ValidatorStatus_UNKNOWN_STATUS, + pubkey5: ethpb.ValidatorStatus_EXITED, pubkey6: ethpb.ValidatorStatus_PENDING, pubkey7: ethpb.ValidatorStatus_PENDING, pubkey8: ethpb.ValidatorStatus_EXITING, @@ -2220,28 +2370,23 @@ func TestValidator_buildPrepProposerReqs_WithDefaultConfig(t *testing.T) { pubkey8: 0, } + pubkeyToIndex := map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex{ + pubkey1: 1, + pubkey2: 2, + pubkey3: unknownIndex, + pubkey4: 4, + pubkey5: 5, + pubkey6: 6, + pubkey7: 7, + pubkey8: 8, + } + ctrl := gomock.NewController(t) defer ctrl.Finish() ctx := context.Background() client := validatormock.NewMockValidatorClient(ctrl) - client.EXPECT().ValidatorIndex( - gomock.Any(), - ðpb.ValidatorIndexRequest{ - PublicKey: pubkey2[:], - }, - ).Return(ðpb.ValidatorIndexResponse{ - Index: 2, - }, nil) - - client.EXPECT().ValidatorIndex( - gomock.Any(), - ðpb.ValidatorIndexRequest{ - PublicKey: pubkey3[:], - }, - ).Return(nil, status.Error(codes.NotFound, "NOT_FOUND")) - client.EXPECT().MultipleValidatorStatus( gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, val *ethpb.MultipleValidatorStatusRequest) (*ethpb.MultipleValidatorStatusResponse, error) { @@ -2253,6 +2398,8 @@ func TestValidator_buildPrepProposerReqs_WithDefaultConfig(t *testing.T) { Status: pubkeyToStatus[bytesutil.ToBytes48(k)], ActivationEpoch: pubkeyToActivationEpoch[bytesutil.ToBytes48(k)], }) + index := pubkeyToIndex[bytesutil.ToBytes48(k)] + resp.Indices = append(resp.Indices, index) } return resp, nil }) @@ -2288,13 +2435,47 @@ func TestValidator_buildPrepProposerReqs_WithDefaultConfig(t *testing.T) { }, }, }, - pubkeyToValidatorIndex: map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex{ - pubkey1: 1, - pubkey4: 4, - pubkey5: 5, - pubkey6: 6, - pubkey7: 7, - pubkey8: 8, + pubkeyToStatus: map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus{ + pubkey1: { + publicKey: pubkey1[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 1, + }, + pubkey2: { + publicKey: pubkey2[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 2, + }, + pubkey3: { + publicKey: pubkey3[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_UNKNOWN_STATUS}, + index: unknownIndex, + }, + pubkey4: { + publicKey: pubkey4[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 4, + }, + pubkey5: { + publicKey: pubkey5[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 5, + }, + pubkey6: { + publicKey: pubkey6[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 6, + }, + pubkey7: { + publicKey: pubkey7[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 7, + }, + pubkey8: { + publicKey: pubkey8[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 8, + }, }, } @@ -2331,10 +2512,13 @@ func TestValidator_buildPrepProposerReqs_WithDefaultConfig(t *testing.T) { FeeRecipient: feeRecipient8[:], }, } - filteredKeys, err := v.filterAndCacheActiveKeys(ctx, pubkeys, 641) + filteredKeys, err := v.filterAndCacheActiveKeys(ctx, pubkeys, 640) require.NoError(t, err) actual, err := v.buildPrepProposerReqs(filteredKeys) require.NoError(t, err) + sort.Slice(actual, func(i, j int) bool { + return actual[i].ValidatorIndex < actual[j].ValidatorIndex + }) assert.DeepEqual(t, expected, actual) } @@ -2404,7 +2588,7 @@ func TestValidator_buildSignedRegReqs_DefaultConfigDisabled(t *testing.T) { }, }, }, - pubkeyToValidatorIndex: make(map[[48]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } pubkeys := [][fieldparams.BLSPubkeyLength]byte{pubkey1, pubkey2, pubkey3} @@ -2412,26 +2596,41 @@ func TestValidator_buildSignedRegReqs_DefaultConfigDisabled(t *testing.T) { var signer = func(_ context.Context, _ *validatorpb.SignRequest) (bls.Signature, error) { return signature, nil } - v.pubkeyToValidatorIndex[pubkey1] = primitives.ValidatorIndex(1) - v.pubkeyToValidatorIndex[pubkey2] = primitives.ValidatorIndex(2) - v.pubkeyToValidatorIndex[pubkey3] = primitives.ValidatorIndex(3) - actual := v.buildSignedRegReqs(ctx, pubkeys, signer) + v.pubkeyToStatus[pubkey1] = &validatorStatus{ + publicKey: pubkey1[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 1, + } + v.pubkeyToStatus[pubkey2] = &validatorStatus{ + publicKey: pubkey2[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 2, + } + v.pubkeyToStatus[pubkey3] = &validatorStatus{ + publicKey: pubkey3[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 3, + } + actual := v.buildSignedRegReqs(ctx, pubkeys, signer, 0, false) assert.Equal(t, 1, len(actual)) assert.DeepEqual(t, feeRecipient1[:], actual[0].Message.FeeRecipient) assert.Equal(t, uint64(1111), actual[0].Message.GasLimit) assert.DeepEqual(t, pubkey1[:], actual[0].Message.Pubkey) + } func TestValidator_buildSignedRegReqs_DefaultConfigEnabled(t *testing.T) { // pubkey1 => feeRecipient1, builder enabled // pubkey2 => feeRecipient2, builder disabled // pubkey3 => Nothing, builder enabled + // pubkey4 => added after builder requests built once, used in mid epoch test // Public keys pubkey1 := pubkeyFromString(t, "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111") pubkey2 := pubkeyFromString(t, "0x222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222") pubkey3 := pubkeyFromString(t, "0x333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333") + pubkey4 := pubkeyFromString(t, "0x444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444") // Fee recipients feeRecipient1 := feeRecipientFromString(t, "0x0000000000000000000000000000000000000000") @@ -2446,8 +2645,7 @@ func TestValidator_buildSignedRegReqs_DefaultConfigEnabled(t *testing.T) { client := validatormock.NewMockValidatorClient(ctrl) signature := blsmock.NewMockSignature(ctrl) - signature.EXPECT().Marshal().Return([]byte{}).Times(2) - + signature.EXPECT().Marshal().Return([]byte{}).AnyTimes() v := validator{ signedValidatorRegistrations: map[[48]byte]*ethpb.SignedValidatorRegistrationV1{}, validatorClient: client, @@ -2489,7 +2687,7 @@ func TestValidator_buildSignedRegReqs_DefaultConfigEnabled(t *testing.T) { }, }, }, - pubkeyToValidatorIndex: make(map[[48]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } pubkeys := [][fieldparams.BLSPubkeyLength]byte{pubkey1, pubkey2, pubkey3} @@ -2497,10 +2695,22 @@ func TestValidator_buildSignedRegReqs_DefaultConfigEnabled(t *testing.T) { var signer = func(_ context.Context, _ *validatorpb.SignRequest) (bls.Signature, error) { return signature, nil } - v.pubkeyToValidatorIndex[pubkey1] = primitives.ValidatorIndex(1) - v.pubkeyToValidatorIndex[pubkey2] = primitives.ValidatorIndex(2) - v.pubkeyToValidatorIndex[pubkey3] = primitives.ValidatorIndex(3) - actual := v.buildSignedRegReqs(ctx, pubkeys, signer) + v.pubkeyToStatus[pubkey1] = &validatorStatus{ + publicKey: pubkey1[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 1, + } + v.pubkeyToStatus[pubkey2] = &validatorStatus{ + publicKey: pubkey2[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 2, + } + v.pubkeyToStatus[pubkey3] = &validatorStatus{ + publicKey: pubkey3[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 3, + } + actual := v.buildSignedRegReqs(ctx, pubkeys, signer, 0, false) assert.Equal(t, 2, len(actual)) @@ -2511,6 +2721,26 @@ func TestValidator_buildSignedRegReqs_DefaultConfigEnabled(t *testing.T) { assert.DeepEqual(t, defaultFeeRecipient[:], actual[1].Message.FeeRecipient) assert.Equal(t, uint64(9999), actual[1].Message.GasLimit) assert.DeepEqual(t, pubkey3[:], actual[1].Message.Pubkey) + + t.Run("mid epoch only pushes newly added key", func(t *testing.T) { + v.pubkeyToStatus[pubkey4] = &validatorStatus{ + publicKey: pubkey4[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 4, + } + pubkeys = append(pubkeys, pubkey4) + actual = v.buildSignedRegReqs(ctx, pubkeys, signer, 5, false) + assert.Equal(t, 1, len(actual)) + + assert.DeepEqual(t, defaultFeeRecipient[:], actual[0].Message.FeeRecipient) + assert.Equal(t, uint64(9999), actual[0].Message.GasLimit) + assert.DeepEqual(t, pubkey4[:], actual[0].Message.Pubkey) + }) + + t.Run("force push all keys mid epoch", func(t *testing.T) { + actual = v.buildSignedRegReqs(ctx, pubkeys, signer, 5, true) + assert.Equal(t, 3, len(actual)) + }) } func TestValidator_buildSignedRegReqs_SignerOnError(t *testing.T) { @@ -2548,7 +2778,7 @@ func TestValidator_buildSignedRegReqs_SignerOnError(t *testing.T) { return nil, errors.New("custom error") } - actual := v.buildSignedRegReqs(ctx, pubkeys, signer) + actual := v.buildSignedRegReqs(ctx, pubkeys, signer, 0, false) assert.Equal(t, 0, len(actual)) } @@ -2595,7 +2825,7 @@ func TestValidator_buildSignedRegReqs_TimestampBeforeGenesis(t *testing.T) { }, }, }, - pubkeyToValidatorIndex: make(map[[48]byte]primitives.ValidatorIndex), + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } pubkeys := [][fieldparams.BLSPubkeyLength]byte{pubkey1} @@ -2603,8 +2833,12 @@ func TestValidator_buildSignedRegReqs_TimestampBeforeGenesis(t *testing.T) { var signer = func(_ context.Context, _ *validatorpb.SignRequest) (bls.Signature, error) { return signature, nil } - v.pubkeyToValidatorIndex[pubkey1] = primitives.ValidatorIndex(1) - actual := v.buildSignedRegReqs(ctx, pubkeys, signer) + v.pubkeyToStatus[pubkey1] = &validatorStatus{ + publicKey: pubkey1[:], + status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE}, + index: 1, + } + actual := v.buildSignedRegReqs(ctx, pubkeys, signer, 0, false) assert.Equal(t, 0, len(actual)) } diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index 28b3e048ac..3051410272 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -109,9 +109,8 @@ func (v *validator) internalWaitForActivation(ctx context.Context, accountsChang return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) } - statuses := make([]*validatorStatus, len(res.Statuses)) - for i, s := range res.Statuses { - statuses[i] = &validatorStatus{ + for _, s := range res.Statuses { + v.pubkeyToStatus[bytesutil.ToBytes48(s.PublicKey)] = &validatorStatus{ publicKey: s.PublicKey, status: s.Status, index: s.Index, @@ -129,7 +128,7 @@ func (v *validator) internalWaitForActivation(ctx context.Context, accountsChang valCount = int64(valCounts[0].Count) } - someAreActive = v.checkAndLogValidatorStatus(statuses, valCount) + someAreActive = v.checkAndLogValidatorStatus(valCount) } } diff --git a/validator/client/wait_for_activation_test.go b/validator/client/wait_for_activation_test.go index e3e5f33921..9e38b5db14 100644 --- a/validator/client/wait_for_activation_test.go +++ b/validator/client/wait_for_activation_test.go @@ -37,6 +37,7 @@ func TestWaitActivation_ContextCanceled(t *testing.T) { validatorClient: validatorClient, km: newMockKeymanager(t, kp), chainClient: chainClient, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) ctx, cancel := context.WithCancel(context.Background()) @@ -65,6 +66,7 @@ func TestWaitActivation_StreamSetupFails_AttemptsToReconnect(t *testing.T) { km: newMockKeymanager(t, kp), chainClient: chainClient, prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) validatorClient.EXPECT().WaitForActivation( @@ -96,6 +98,7 @@ func TestWaitForActivation_ReceiveErrorFromStream_AttemptsReconnection(t *testin km: newMockKeymanager(t, kp), chainClient: chainClient, prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) validatorClient.EXPECT().WaitForActivation( @@ -133,6 +136,7 @@ func TestWaitActivation_LogsActivationEpochOK(t *testing.T) { genesisTime: 1, chainClient: chainClient, prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } resp := generateMockStatusResponse([][]byte{kp.pub[:]}) resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE @@ -168,6 +172,7 @@ func TestWaitForActivation_Exiting(t *testing.T) { km: newMockKeymanager(t, kp), chainClient: chainClient, prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } resp := generateMockStatusResponse([][]byte{kp.pub[:]}) resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_EXITING @@ -211,6 +216,7 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) { km: km, chainClient: chainClient, prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } resp := generateMockStatusResponse([][]byte{kp.pub[:]}) resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE @@ -264,6 +270,7 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) { km: km, chainClient: chainClient, prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } inactiveResp := generateMockStatusResponse([][]byte{inactive.pub[:]}) inactiveResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS @@ -355,6 +362,7 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) { genesisTime: 1, chainClient: chainClient, prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } inactiveResp := generateMockStatusResponse([][]byte{inactivePubKey[:]}) @@ -423,6 +431,7 @@ func TestWaitActivation_NotAllValidatorsActivatedOK(t *testing.T) { km: newMockKeymanager(t, kp), chainClient: chainClient, prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), } resp := generateMockStatusResponse([][]byte{kp.pub[:]}) resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE