From 790a09f9b1d3431c8142ee9c2bf23a1bb2317503 Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:04:54 -0600 Subject: [PATCH] Improve wait for activation (#13448) * removing timeout on wait for activation, instead switched to an event driven approach * fixing unit tests * linting * simplifying return * adding sleep for the remaining slot to avoid cpu spikes * removing ifstatement on log * removing ifstatement on log * improving switch statement * removing the loop entirely * fixing unit test * fixing manu's reported issue with deletion of json file * missed change around writefile at path * gofmt * fixing deepsource issue with reading file * trying to clean file to avoid deepsource issue * still getting error trying a different approach * fixing stream loop * fixing unit test * Update validator/keymanager/local/keymanager.go Co-authored-by: Manu NALEPA * fixing linting --------- Co-authored-by: Manu NALEPA --- validator/accounts/iface/wallet.go | 2 +- validator/accounts/testing/mock.go | 6 +- validator/accounts/wallet/wallet.go | 11 +-- validator/accounts/wallet_create.go | 3 +- validator/client/key_reload.go | 7 +- validator/client/validator.go | 19 ++--- validator/client/validator_test.go | 37 --------- validator/client/wait_for_activation.go | 72 ++++++----------- validator/client/wait_for_activation_test.go | 84 ++++++++++++++++---- validator/keymanager/local/keymanager.go | 28 +++++-- validator/keymanager/local/refresh.go | 52 +++++++----- 11 files changed, 161 insertions(+), 160 deletions(-) diff --git a/validator/accounts/iface/wallet.go b/validator/accounts/iface/wallet.go index 9eb8299fa5b6..b5afbc90aa25 100644 --- a/validator/accounts/iface/wallet.go +++ b/validator/accounts/iface/wallet.go @@ -23,7 +23,7 @@ type Wallet interface { // Read methods for important wallet and accounts-related files. ReadFileAtPath(ctx context.Context, filePath string, fileName string) ([]byte, error) // Write methods to persist important wallet and accounts-related files to disk. - WriteFileAtPath(ctx context.Context, pathName string, fileName string, data []byte) error + WriteFileAtPath(ctx context.Context, pathName string, fileName string, data []byte) (bool, error) // Method for initializing a new keymanager. InitializeKeymanager(ctx context.Context, cfg InitKeymanagerConfig) (keymanager.IKeymanager, error) } diff --git a/validator/accounts/testing/mock.go b/validator/accounts/testing/mock.go index cb903c65ff46..f43ed5130e89 100644 --- a/validator/accounts/testing/mock.go +++ b/validator/accounts/testing/mock.go @@ -55,19 +55,19 @@ func (w *Wallet) Password() string { } // WriteFileAtPath -- -func (w *Wallet) WriteFileAtPath(_ context.Context, pathName, fileName string, data []byte) error { +func (w *Wallet) WriteFileAtPath(_ context.Context, pathName, fileName string, data []byte) (bool, error) { w.lock.Lock() defer w.lock.Unlock() if w.HasWriteFileError { // reset the flag to not contaminate other tests w.HasWriteFileError = false - return errors.New("could not write keystore file for accounts") + return false, errors.New("could not write keystore file for accounts") } if w.Files[pathName] == nil { w.Files[pathName] = make(map[string][]byte) } w.Files[pathName][fileName] = data - return nil + return true, nil } // ReadFileAtPath -- diff --git a/validator/accounts/wallet/wallet.go b/validator/accounts/wallet/wallet.go index acbc07a1e998..fc97d577f29d 100644 --- a/validator/accounts/wallet/wallet.go +++ b/validator/accounts/wallet/wallet.go @@ -366,26 +366,27 @@ func (w *Wallet) InitializeKeymanager(ctx context.Context, cfg iface.InitKeymana } // WriteFileAtPath within the wallet directory given the desired path, filename, and raw data. -func (w *Wallet) WriteFileAtPath(_ context.Context, filePath, fileName string, data []byte) error { +func (w *Wallet) WriteFileAtPath(_ context.Context, filePath, fileName string, data []byte) (bool /* exited previously */, error) { accountPath := filepath.Join(w.accountsPath, filePath) hasDir, err := file.HasDir(accountPath) if err != nil { - return err + return false, err } if !hasDir { if err := file.MkdirAll(accountPath); err != nil { - return errors.Wrapf(err, "could not create path: %s", accountPath) + return false, errors.Wrapf(err, "could not create path: %s", accountPath) } } fullPath := filepath.Join(accountPath, fileName) + existedPreviously := file.Exists(fullPath) if err := file.WriteFile(fullPath, data); err != nil { - return errors.Wrapf(err, "could not write %s", filePath) + return false, errors.Wrapf(err, "could not write %s", filePath) } log.WithFields(logrus.Fields{ "path": fullPath, "fileName": fileName, }).Debug("Wrote new file at path") - return nil + return existedPreviously, nil } // ReadFileAtPath within the wallet directory given the desired path and filename. diff --git a/validator/accounts/wallet_create.go b/validator/accounts/wallet_create.go index b1b081bf4f2b..1e2852863228 100644 --- a/validator/accounts/wallet_create.go +++ b/validator/accounts/wallet_create.go @@ -32,7 +32,8 @@ func (acm *CLIManager) WalletCreate(ctx context.Context) (*wallet.Wallet, error) if err != nil { return nil, err } - if err = w.WriteFileAtPath(ctx, local.AccountsPath, local.AccountsKeystoreFileName, encodedAccounts); err != nil { + _, err = w.WriteFileAtPath(ctx, local.AccountsPath, local.AccountsKeystoreFileName, encodedAccounts) + if err != nil { return nil, err } log.WithField("--wallet-dir", acm.walletDir).Info( diff --git a/validator/client/key_reload.go b/validator/client/key_reload.go index cbfc67a74eed..bb8add0fde3f 100644 --- a/validator/client/key_reload.go +++ b/validator/client/key_reload.go @@ -48,10 +48,5 @@ func (v *validator) HandleKeyReload(ctx context.Context, currentKeys [][fieldpar valCount = int64(valCounts[0].Count) } - anyActive = v.checkAndLogValidatorStatus(statuses, valCount) - if anyActive { - logActiveValidatorStatus(statuses) - } - - return anyActive, nil + return v.checkAndLogValidatorStatus(statuses, valCount), nil } diff --git a/validator/client/validator.go b/validator/client/validator.go index d263ed3b23a4..bcf28905a12a 100644 --- a/validator/client/validator.go +++ b/validator/client/validator.go @@ -54,14 +54,13 @@ import ( // keyFetchPeriod is the frequency that we try to refetch validating keys // in case no keys were fetched previously. var ( - keyRefetchPeriod = 30 * time.Second ErrBuilderValidatorRegistration = errors.New("Builder API validator registration unsuccessful") ErrValidatorsAllExited = errors.New("All validators are exited, no more work to perform...") ) var ( msgCouldNotFetchKeys = "could not fetch validating keys" - msgNoKeysFetched = "No validating keys fetched. Trying again" + msgNoKeysFetched = "No validating keys fetched. Waiting for keys..." ) type validator struct { @@ -403,6 +402,10 @@ func (v *validator) checkAndLogValidatorStatus(statuses []*validatorStatus, acti } case ethpb.ValidatorStatus_ACTIVE, ethpb.ValidatorStatus_EXITING: validatorActivated = true + log.WithFields(logrus.Fields{ + "publicKey": fmt.Sprintf("%#x", bytesutil.Trunc(status.publicKey)), + "index": status.index, + }).Info("Validator activated") case ethpb.ValidatorStatus_EXITED: log.Info("Validator exited") case ethpb.ValidatorStatus_INVALID: @@ -416,18 +419,6 @@ func (v *validator) checkAndLogValidatorStatus(statuses []*validatorStatus, acti return validatorActivated } -func logActiveValidatorStatus(statuses []*validatorStatus) { - for _, s := range statuses { - if s.status.Status != ethpb.ValidatorStatus_ACTIVE { - continue - } - log.WithFields(logrus.Fields{ - "publicKey": fmt.Sprintf("%#x", bytesutil.Trunc(s.publicKey)), - "index": s.index, - }).Info("Validator activated") - } -} - // CanonicalHeadSlot returns the slot of canonical block currently found in the // beacon chain via RPC. func (v *validator) CanonicalHeadSlot(ctx context.Context) (primitives.Slot, error) { diff --git a/validator/client/validator_test.go b/validator/client/validator_test.go index 8ea977848006..55aea89d7c3c 100644 --- a/validator/client/validator_test.go +++ b/validator/client/validator_test.go @@ -388,43 +388,6 @@ func TestWaitMultipleActivation_LogsActivationEpochOK(t *testing.T) { require.LogsContain(t, hook, "Validator activated") } -func TestWaitActivation_NotAllValidatorsActivatedOK(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - validatorClient := validatormock.NewMockValidatorClient(ctrl) - beaconClient := validatormock.NewMockBeaconChainClient(ctrl) - prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl) - - kp := randKeypair(t) - v := validator{ - validatorClient: validatorClient, - keyManager: newMockKeymanager(t, kp), - beaconClient: beaconClient, - prysmBeaconClient: prysmBeaconClient, - } - resp := generateMockStatusResponse([][]byte{kp.pub[:]}) - resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE - clientStream := mock2.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - validatorClient.EXPECT().WaitForActivation( - gomock.Any(), - gomock.Any(), - ).Return(clientStream, nil) - prysmBeaconClient.EXPECT().GetValidatorCount( - gomock.Any(), - "head", - []validatorType.Status{validatorType.Active}, - ).Return([]iface.ValidatorCount{}, nil).Times(2) - clientStream.EXPECT().Recv().Return( - ðpb.ValidatorActivationResponse{}, - nil, - ) - clientStream.EXPECT().Recv().Return( - resp, - nil, - ) - assert.NoError(t, v.WaitForActivation(context.Background(), nil), "Could not wait for activation") -} - func TestWaitSync_ContextCanceled(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index c2c3d10fab34..61bbd6810c1b 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -5,17 +5,14 @@ import ( "io" "time" - validator2 "github.com/prysmaticlabs/prysm/v4/consensus-types/validator" - "github.com/prysmaticlabs/prysm/v4/validator/client/iface" - "github.com/pkg/errors" fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" - "github.com/prysmaticlabs/prysm/v4/config/params" + validator2 "github.com/prysmaticlabs/prysm/v4/consensus-types/validator" "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v4/math" "github.com/prysmaticlabs/prysm/v4/monitoring/tracing" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" - "github.com/prysmaticlabs/prysm/v4/time/slots" + "github.com/prysmaticlabs/prysm/v4/validator/client/iface" "go.opencensus.io/trace" ) @@ -33,18 +30,18 @@ func (v *validator) WaitForActivation(ctx context.Context, accountsChangedChan c if err != nil { return err } + // subscribe to the channel if it's the first time sub := km.SubscribeAccountChanges(accountsChangedChan) defer func() { sub.Unsubscribe() close(accountsChangedChan) }() } - return v.internalWaitForActivation(ctx, accountsChangedChan) } // internalWaitForActivation performs the following: -// 1) While the key manager is empty, poll the key manager until some validator keys exist. +// 1) While the key manager is empty, subscribe to keymanager changes until some validator keys exist. // 2) Open a server side stream for activation events against the given keys. // 3) In another go routine, the key manager is monitored for updates and emits an update event on // the accountsChangedChan. When an event signal is received, restart the internalWaitForActivation routine. @@ -53,39 +50,26 @@ func (v *validator) WaitForActivation(ctx context.Context, accountsChangedChan c func (v *validator) internalWaitForActivation(ctx context.Context, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { ctx, span := trace.StartSpan(ctx, "validator.WaitForActivation") defer span.End() - validatingKeys, err := v.keyManager.FetchValidatingPublicKeys(ctx) if err != nil { - return errors.Wrap(err, "could not fetch validating keys") + return errors.Wrap(err, msgCouldNotFetchKeys) } + // if there are no validating keys, wait for some if len(validatingKeys) == 0 { log.Warn(msgNoKeysFetched) - - ticker := time.NewTicker(keyRefetchPeriod) - defer ticker.Stop() - for { - select { - case <-ticker.C: - validatingKeys, err = v.keyManager.FetchValidatingPublicKeys(ctx) - if err != nil { - return errors.Wrap(err, msgCouldNotFetchKeys) - } - if len(validatingKeys) == 0 { - log.Warn(msgNoKeysFetched) - continue - } - case <-ctx.Done(): - log.Debug("Context closed, exiting fetching validating keys") - return ctx.Err() - } - break + select { + case <-ctx.Done(): + log.Debug("Context closed, exiting fetching validating keys") + return ctx.Err() + case <-accountsChangedChan: + // if the accounts changed try it again + return v.internalWaitForActivation(ctx, accountsChangedChan) } } - req := ðpb.ValidatorActivationRequest{ + stream, err := v.validatorClient.WaitForActivation(ctx, ðpb.ValidatorActivationRequest{ PublicKeys: bytesutil.FromBytes48Array(validatingKeys), - } - stream, err := v.validatorClient.WaitForActivation(ctx, req) + }) if err != nil { tracing.AnnotateError(span, err) attempts := streamAttempts(ctx) @@ -96,22 +80,17 @@ func (v *validator) internalWaitForActivation(ctx context.Context, accountsChang return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) } - if err = v.handleAccountsChanged(ctx, accountsChangedChan, &stream, span); err != nil { - return err - } - - v.ticker = slots.NewSlotTicker(time.Unix(int64(v.genesisTime), 0), params.BeaconConfig().SecondsPerSlot) - return nil -} - -func (v *validator) handleAccountsChanged(ctx context.Context, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte, stream *ethpb.BeaconNodeValidator_WaitForActivationClient, span *trace.Span) error { - for { + someAreActive := false + for !someAreActive { select { + case <-ctx.Done(): + log.Debug("Context closed, exiting fetching validating keys") + return ctx.Err() case <-accountsChangedChan: // Accounts (keys) changed, restart the process. return v.internalWaitForActivation(ctx, accountsChangedChan) default: - res, err := (*stream).Recv() + res, err := (stream).Recv() // retrieve from stream one loop at a time // If the stream is closed, we stop the loop. if errors.Is(err, io.EOF) { break @@ -150,15 +129,10 @@ func (v *validator) handleAccountsChanged(ctx context.Context, accountsChangedCh valCount = int64(valCounts[0].Count) } - valActivated := v.checkAndLogValidatorStatus(statuses, valCount) - if valActivated { - logActiveValidatorStatus(statuses) - } else { - continue - } + someAreActive = v.checkAndLogValidatorStatus(statuses, valCount) } - break } + return nil } diff --git a/validator/client/wait_for_activation_test.go b/validator/client/wait_for_activation_test.go index e28aa2451481..25c8ca39096f 100644 --- a/validator/client/wait_for_activation_test.go +++ b/validator/client/wait_for_activation_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/prysmaticlabs/prysm/v4/config/params" validatorType "github.com/prysmaticlabs/prysm/v4/consensus-types/validator" "github.com/prysmaticlabs/prysm/v4/validator/client/iface" @@ -39,7 +40,7 @@ func TestWaitActivation_ContextCanceled(t *testing.T) { beaconClient: beaconClient, } clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - + ctx, cancel := context.WithCancel(context.Background()) validatorClient.EXPECT().WaitForActivation( gomock.Any(), ðpb.ValidatorActivationRequest{ @@ -49,9 +50,7 @@ func TestWaitActivation_ContextCanceled(t *testing.T) { clientStream.EXPECT().Recv().Return( ðpb.ValidatorActivationResponse{}, nil, - ) - ctx, cancel := context.WithCancel(context.Background()) - cancel() + ).Do(func() { cancel() }) assert.ErrorContains(t, cancelledCtx, v.WaitForActivation(ctx, nil)) } @@ -193,12 +192,11 @@ func TestWaitForActivation_Exiting(t *testing.T) { } func TestWaitForActivation_RefetchKeys(t *testing.T) { - originalPeriod := keyRefetchPeriod - defer func() { - keyRefetchPeriod = originalPeriod - }() - keyRefetchPeriod = 1 * time.Second - + params.SetupTestConfigCleanup(t) + cfg := params.MainnetConfig().Copy() + cfg.ConfigName = "test" + cfg.SecondsPerSlot = 1 + params.OverrideBeaconConfig(cfg) hook := logTest.NewGlobal() ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -207,8 +205,7 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) { prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl) kp := randKeypair(t) - km := newMockKeymanager(t, kp) - km.fetchNoKeys = true + km := newMockKeymanager(t) v := validator{ validatorClient: validatorClient, @@ -233,7 +230,19 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) { clientStream.EXPECT().Recv().Return( resp, nil) - assert.NoError(t, v.internalWaitForActivation(context.Background(), make(chan [][fieldparams.BLSPubkeyLength]byte)), "Could not wait for activation") + accountChan := make(chan [][fieldparams.BLSPubkeyLength]byte) + sub := km.SubscribeAccountChanges(accountChan) + defer func() { + sub.Unsubscribe() + close(accountChan) + }() + // update the accounts after a delay + go func() { + time.Sleep(2 * time.Second) + require.NoError(t, km.add(kp)) + km.SimulateAccountChanges([][48]byte{kp.pub}) + }() + assert.NoError(t, v.internalWaitForActivation(context.Background(), accountChan), "Could not wait for activation") assert.LogsContain(t, hook, msgNoKeysFetched) assert.LogsContain(t, hook, "Validator activated") } @@ -265,7 +274,11 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) { ðpb.ValidatorActivationRequest{ PublicKeys: [][]byte{inactive.pub[:]}, }, - ).Return(inactiveClientStream, nil) + ).DoAndReturn(func(ctx context.Context, in *ethpb.ValidatorActivationRequest) (*mock.MockBeaconNodeValidator_WaitForActivationClient, error) { + //delay a bit so that other key can be added + time.Sleep(time.Second * 2) + return inactiveClientStream, nil + }) prysmBeaconClient.EXPECT().GetValidatorCount( gomock.Any(), "head", @@ -353,7 +366,11 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) { ðpb.ValidatorActivationRequest{ PublicKeys: [][]byte{inactivePubKey[:]}, }, - ).Return(inactiveClientStream, nil) + ).DoAndReturn(func(ctx context.Context, in *ethpb.ValidatorActivationRequest) (*mock.MockBeaconNodeValidator_WaitForActivationClient, error) { + //delay a bit so that other key can be added + time.Sleep(time.Second * 2) + return inactiveClientStream, nil + }) prysmBeaconClient.EXPECT().GetValidatorCount( gomock.Any(), "head", @@ -393,3 +410,40 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) { assert.LogsContain(t, hook, "Validator activated") }) } + +func TestWaitActivation_NotAllValidatorsActivatedOK(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + validatorClient := validatormock.NewMockValidatorClient(ctrl) + beaconClient := validatormock.NewMockBeaconChainClient(ctrl) + prysmBeaconClient := validatormock.NewMockPrysmBeaconChainClient(ctrl) + + kp := randKeypair(t) + v := validator{ + validatorClient: validatorClient, + keyManager: newMockKeymanager(t, kp), + beaconClient: beaconClient, + prysmBeaconClient: prysmBeaconClient, + } + resp := generateMockStatusResponse([][]byte{kp.pub[:]}) + resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE + clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) + validatorClient.EXPECT().WaitForActivation( + gomock.Any(), + gomock.Any(), + ).Return(clientStream, nil) + prysmBeaconClient.EXPECT().GetValidatorCount( + gomock.Any(), + "head", + []validatorType.Status{validatorType.Active}, + ).Return([]iface.ValidatorCount{}, nil).Times(2) + clientStream.EXPECT().Recv().Return( + ðpb.ValidatorActivationResponse{}, + nil, + ) + clientStream.EXPECT().Recv().Return( + resp, + nil, + ) + assert.NoError(t, v.WaitForActivation(context.Background(), nil), "Could not wait for activation") +} diff --git a/validator/keymanager/local/keymanager.go b/validator/keymanager/local/keymanager.go index 3b4a2c933f98..4cae2ed8f299 100644 --- a/validator/keymanager/local/keymanager.go +++ b/validator/keymanager/local/keymanager.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "path/filepath" "strings" "sync" @@ -282,18 +283,29 @@ func (km *Keymanager) SaveStoreAndReInitialize(ctx context.Context, store *accou if err != nil { return err } - if err := km.wallet.WriteFileAtPath(ctx, AccountsPath, AccountsKeystoreFileName, encodedAccounts); err != nil { + + existedPreviously, err := km.wallet.WriteFileAtPath(ctx, AccountsPath, AccountsKeystoreFileName, encodedAccounts) + if err != nil { return err } - // Reinitialize account store and cache - // This will update the in-memory information instead of reading from the file itself for safety concerns - km.accountsStore = store - err = km.initializeKeysCachesFromKeystore() - if err != nil { - return errors.Wrap(err, "failed to initialize keys caches") + if existedPreviously { + // Reinitialize account store and cache + // This will update the in-memory information instead of reading from the file itself for safety concerns + km.accountsStore = store + err = km.initializeKeysCachesFromKeystore() + if err != nil { + return errors.Wrap(err, "failed to initialize keys caches") + } + + return nil } - return err + + // manually reload the account from the keystore the first time + km.reloadAccountsFromKeystoreFile(filepath.Join(km.wallet.AccountsDir(), AccountsPath, AccountsKeystoreFileName)) + // listen to account changes of the new file + go km.listenForAccountChanges(ctx) + return nil } // CreateAccountsKeystoreRepresentation is a pure function that takes an accountStore and wallet password and returns the encrypted formatted json version for local writing. diff --git a/validator/keymanager/local/refresh.go b/validator/keymanager/local/refresh.go index 472d42831dd8..eeaa9e766e4a 100644 --- a/validator/keymanager/local/refresh.go +++ b/validator/keymanager/local/refresh.go @@ -26,6 +26,7 @@ func (km *Keymanager) listenForAccountChanges(ctx context.Context) { debounceFileChangesInterval := features.Get().KeystoreImportDebounceInterval accountsFilePath := filepath.Join(km.wallet.AccountsDir(), AccountsPath, AccountsKeystoreFileName) if !file.Exists(accountsFilePath) { + log.Warnf("Starting without accounts located in wallet at %s", accountsFilePath) return } watcher, err := fsnotify.NewWatcher() @@ -56,27 +57,7 @@ func (km *Keymanager) listenForAccountChanges(ctx context.Context) { log.Errorf("Type %T is not a valid file system event", event) return } - fileBytes, err := os.ReadFile(ev.Name) - if err != nil { - log.WithError(err).Errorf("Could not read file at path: %s", ev.Name) - return - } - if fileBytes == nil { - log.WithError(err).Errorf("Loaded in an empty file: %s", ev.Name) - return - } - accountsKeystore := &AccountsKeystoreRepresentation{} - if err := json.Unmarshal(fileBytes, accountsKeystore); err != nil { - log.WithError( - err, - ).Errorf("Could not read valid, EIP-2335 keystore json file at path: %s", ev.Name) - return - } - if err := km.reloadAccountsFromKeystore(accountsKeystore); err != nil { - log.WithError( - err, - ).Error("Could not replace the accounts store from keystore file") - } + km.reloadAccountsFromKeystoreFile(ev.Name) }) for { select { @@ -92,6 +73,34 @@ func (km *Keymanager) listenForAccountChanges(ctx context.Context) { } } +func (km *Keymanager) reloadAccountsFromKeystoreFile(accountsFilePath string) { + if km.wallet == nil { + log.Error("Could not reload accounts because wallet was undefined") + return + } + fileBytes, err := os.ReadFile(filepath.Clean(accountsFilePath)) + if err != nil { + log.WithError(err).Errorf("Could not read file at path: %s", accountsFilePath) + return + } + if fileBytes == nil { + log.WithError(err).Errorf("Loaded in an empty file: %s", accountsFilePath) + return + } + accountsKeystore := &AccountsKeystoreRepresentation{} + if err := json.Unmarshal(fileBytes, accountsKeystore); err != nil { + log.WithError( + err, + ).Errorf("Could not read valid, EIP-2335 keystore json file at path: %s", accountsFilePath) + return + } + if err := km.reloadAccountsFromKeystore(accountsKeystore); err != nil { + log.WithError( + err, + ).Error("Could not replace the accounts store from keystore file") + } +} + // Replaces the accounts store struct in the local keymanager with // the contents of a keystore file by decrypting it with the accounts password. func (km *Keymanager) reloadAccountsFromKeystore(keystore *AccountsKeystoreRepresentation) error { @@ -107,6 +116,7 @@ func (km *Keymanager) reloadAccountsFromKeystore(keystore *AccountsKeystoreRepre if len(newAccountsStore.PublicKeys) != len(newAccountsStore.PrivateKeys) { return errors.New("number of public and private keys in keystore do not match") } + pubKeys := make([][fieldparams.BLSPubkeyLength]byte, len(newAccountsStore.PublicKeys)) for i := 0; i < len(newAccountsStore.PrivateKeys); i++ { privKey, err := bls.SecretKeyFromBytes(newAccountsStore.PrivateKeys[i])