github.com/prysmaticlabs/prysm@v1.4.4/validator/client/wait_for_activation_test.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/golang/mock/gomock"
    10  	"github.com/pkg/errors"
    11  	types "github.com/prysmaticlabs/eth2-types"
    12  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    13  	"github.com/prysmaticlabs/prysm/shared/bls"
    14  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    15  	"github.com/prysmaticlabs/prysm/shared/mock"
    16  	slotutilmock "github.com/prysmaticlabs/prysm/shared/slotutil/testing"
    17  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    18  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    19  	walletMock "github.com/prysmaticlabs/prysm/validator/accounts/testing"
    20  	"github.com/prysmaticlabs/prysm/validator/client/testutil"
    21  	"github.com/prysmaticlabs/prysm/validator/keymanager/derived"
    22  	"github.com/prysmaticlabs/prysm/validator/keymanager/remote"
    23  	constant "github.com/prysmaticlabs/prysm/validator/testing"
    24  	logTest "github.com/sirupsen/logrus/hooks/test"
    25  	"github.com/tyler-smith/go-bip39"
    26  	util "github.com/wealdtech/go-eth2-util"
    27  )
    28  
    29  func TestWaitActivation_ContextCanceled(t *testing.T) {
    30  	ctrl := gomock.NewController(t)
    31  	defer ctrl.Finish()
    32  	client := mock.NewMockBeaconNodeValidatorClient(ctrl)
    33  	privKey, err := bls.RandKey()
    34  	require.NoError(t, err)
    35  	pubKey := [48]byte{}
    36  	copy(pubKey[:], privKey.PublicKey().Marshal())
    37  	km := &mockKeymanager{
    38  		keysMap: map[[48]byte]bls.SecretKey{
    39  			pubKey: privKey,
    40  		},
    41  	}
    42  	v := validator{
    43  		validatorClient: client,
    44  		keyManager:      km,
    45  	}
    46  	clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
    47  
    48  	client.EXPECT().WaitForActivation(
    49  		gomock.Any(),
    50  		&ethpb.ValidatorActivationRequest{
    51  			PublicKeys: [][]byte{pubKey[:]},
    52  		},
    53  	).Return(clientStream, nil)
    54  	clientStream.EXPECT().Recv().Return(
    55  		&ethpb.ValidatorActivationResponse{},
    56  		nil,
    57  	)
    58  	ctx, cancel := context.WithCancel(context.Background())
    59  	cancel()
    60  	assert.ErrorContains(t, cancelledCtx, v.WaitForActivation(ctx, nil))
    61  }
    62  
    63  func TestWaitActivation_StreamSetupFails_AttemptsToReconnect(t *testing.T) {
    64  	ctrl := gomock.NewController(t)
    65  	defer ctrl.Finish()
    66  	client := mock.NewMockBeaconNodeValidatorClient(ctrl)
    67  	privKey, err := bls.RandKey()
    68  	require.NoError(t, err)
    69  	pubKey := [48]byte{}
    70  	copy(pubKey[:], privKey.PublicKey().Marshal())
    71  	km := &mockKeymanager{
    72  		keysMap: map[[48]byte]bls.SecretKey{
    73  			pubKey: privKey,
    74  		},
    75  	}
    76  	v := validator{
    77  		validatorClient: client,
    78  		keyManager:      km,
    79  	}
    80  	clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
    81  	client.EXPECT().WaitForActivation(
    82  		gomock.Any(),
    83  		&ethpb.ValidatorActivationRequest{
    84  			PublicKeys: [][]byte{pubKey[:]},
    85  		},
    86  	).Return(clientStream, errors.New("failed stream")).Return(clientStream, nil)
    87  
    88  	resp := generateMockStatusResponse([][]byte{pubKey[:]})
    89  	resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE
    90  	clientStream.EXPECT().Recv().Return(resp, nil)
    91  	assert.NoError(t, v.WaitForActivation(context.Background(), nil))
    92  }
    93  
    94  func TestWaitForActivation_ReceiveErrorFromStream_AttemptsReconnection(t *testing.T) {
    95  	ctrl := gomock.NewController(t)
    96  	defer ctrl.Finish()
    97  	client := mock.NewMockBeaconNodeValidatorClient(ctrl)
    98  
    99  	privKey, err := bls.RandKey()
   100  	require.NoError(t, err)
   101  	pubKey := [48]byte{}
   102  	copy(pubKey[:], privKey.PublicKey().Marshal())
   103  	km := &mockKeymanager{
   104  		keysMap: map[[48]byte]bls.SecretKey{
   105  			pubKey: privKey,
   106  		},
   107  	}
   108  	v := validator{
   109  		validatorClient: client,
   110  		keyManager:      km,
   111  	}
   112  	clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
   113  	client.EXPECT().WaitForActivation(
   114  		gomock.Any(),
   115  		&ethpb.ValidatorActivationRequest{
   116  			PublicKeys: [][]byte{pubKey[:]},
   117  		},
   118  	).Return(clientStream, nil)
   119  	// A stream fails the first time, but succeeds the second time.
   120  	resp := generateMockStatusResponse([][]byte{pubKey[:]})
   121  	resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE
   122  	clientStream.EXPECT().Recv().Return(
   123  		nil,
   124  		errors.New("fails"),
   125  	).Return(resp, nil)
   126  	assert.NoError(t, v.WaitForActivation(context.Background(), nil))
   127  }
   128  
   129  func TestWaitActivation_LogsActivationEpochOK(t *testing.T) {
   130  	hook := logTest.NewGlobal()
   131  	ctrl := gomock.NewController(t)
   132  	defer ctrl.Finish()
   133  	client := mock.NewMockBeaconNodeValidatorClient(ctrl)
   134  	privKey, err := bls.RandKey()
   135  	require.NoError(t, err)
   136  	pubKey := [48]byte{}
   137  	copy(pubKey[:], privKey.PublicKey().Marshal())
   138  	km := &mockKeymanager{
   139  		keysMap: map[[48]byte]bls.SecretKey{
   140  			pubKey: privKey,
   141  		},
   142  	}
   143  	v := validator{
   144  		validatorClient: client,
   145  		keyManager:      km,
   146  		genesisTime:     1,
   147  	}
   148  	resp := generateMockStatusResponse([][]byte{pubKey[:]})
   149  	resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE
   150  	clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
   151  	client.EXPECT().WaitForActivation(
   152  		gomock.Any(),
   153  		&ethpb.ValidatorActivationRequest{
   154  			PublicKeys: [][]byte{pubKey[:]},
   155  		},
   156  	).Return(clientStream, nil)
   157  	clientStream.EXPECT().Recv().Return(
   158  		resp,
   159  		nil,
   160  	)
   161  	assert.NoError(t, v.WaitForActivation(context.Background(), nil), "Could not wait for activation")
   162  	assert.LogsContain(t, hook, "Validator activated")
   163  }
   164  
   165  func TestWaitForActivation_Exiting(t *testing.T) {
   166  	ctrl := gomock.NewController(t)
   167  	defer ctrl.Finish()
   168  	client := mock.NewMockBeaconNodeValidatorClient(ctrl)
   169  	privKey, err := bls.RandKey()
   170  	require.NoError(t, err)
   171  	pubKey := [48]byte{}
   172  	copy(pubKey[:], privKey.PublicKey().Marshal())
   173  	km := &mockKeymanager{
   174  		keysMap: map[[48]byte]bls.SecretKey{
   175  			pubKey: privKey,
   176  		},
   177  	}
   178  	v := validator{
   179  		validatorClient: client,
   180  		keyManager:      km,
   181  		genesisTime:     1,
   182  	}
   183  	resp := generateMockStatusResponse([][]byte{pubKey[:]})
   184  	resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_EXITING
   185  	clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
   186  	client.EXPECT().WaitForActivation(
   187  		gomock.Any(),
   188  		&ethpb.ValidatorActivationRequest{
   189  			PublicKeys: [][]byte{pubKey[:]},
   190  		},
   191  	).Return(clientStream, nil)
   192  	clientStream.EXPECT().Recv().Return(
   193  		resp,
   194  		nil,
   195  	)
   196  	assert.NoError(t, v.WaitForActivation(context.Background(), nil))
   197  }
   198  
   199  func TestWaitForActivation_RefetchKeys(t *testing.T) {
   200  	originalPeriod := keyRefetchPeriod
   201  	defer func() {
   202  		keyRefetchPeriod = originalPeriod
   203  	}()
   204  	keyRefetchPeriod = 1 * time.Second
   205  
   206  	hook := logTest.NewGlobal()
   207  	ctrl := gomock.NewController(t)
   208  	defer ctrl.Finish()
   209  	client := mock.NewMockBeaconNodeValidatorClient(ctrl)
   210  	privKey, err := bls.RandKey()
   211  	require.NoError(t, err)
   212  	pubKey := [48]byte{}
   213  	copy(pubKey[:], privKey.PublicKey().Marshal())
   214  	km := &mockKeymanager{
   215  		keysMap: map[[48]byte]bls.SecretKey{
   216  			pubKey: privKey,
   217  		},
   218  		fetchNoKeys: true,
   219  	}
   220  	v := validator{
   221  		validatorClient: client,
   222  		keyManager:      km,
   223  		genesisTime:     1,
   224  	}
   225  	resp := generateMockStatusResponse([][]byte{pubKey[:]})
   226  	resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE
   227  	clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
   228  	client.EXPECT().WaitForActivation(
   229  		gomock.Any(),
   230  		&ethpb.ValidatorActivationRequest{
   231  			PublicKeys: [][]byte{pubKey[:]},
   232  		},
   233  	).Return(clientStream, nil)
   234  	clientStream.EXPECT().Recv().Return(
   235  		resp,
   236  		nil,
   237  	)
   238  	assert.NoError(t, v.waitForActivation(context.Background(), make(chan [][48]byte)), "Could not wait for activation")
   239  	assert.LogsContain(t, hook, msgNoKeysFetched)
   240  	assert.LogsContain(t, hook, "Validator activated")
   241  }
   242  
   243  // Regression test for a scenario where you start with an inactive key and then import an active key.
   244  func TestWaitForActivation_AccountsChanged(t *testing.T) {
   245  	hook := logTest.NewGlobal()
   246  	ctrl := gomock.NewController(t)
   247  	defer ctrl.Finish()
   248  
   249  	t.Run("Imported keymanager", func(t *testing.T) {
   250  		inactivePrivKey, err := bls.RandKey()
   251  		require.NoError(t, err)
   252  		inactivePubKey := [48]byte{}
   253  		copy(inactivePubKey[:], inactivePrivKey.PublicKey().Marshal())
   254  		activePrivKey, err := bls.RandKey()
   255  		require.NoError(t, err)
   256  		activePubKey := [48]byte{}
   257  		copy(activePubKey[:], activePrivKey.PublicKey().Marshal())
   258  		km := &mockKeymanager{
   259  			keysMap: map[[48]byte]bls.SecretKey{
   260  				inactivePubKey: inactivePrivKey,
   261  			},
   262  		}
   263  		client := mock.NewMockBeaconNodeValidatorClient(ctrl)
   264  		v := validator{
   265  			validatorClient: client,
   266  			keyManager:      km,
   267  			genesisTime:     1,
   268  		}
   269  
   270  		inactiveResp := generateMockStatusResponse([][]byte{inactivePubKey[:]})
   271  		inactiveResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS
   272  		inactiveClientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
   273  		client.EXPECT().WaitForActivation(
   274  			gomock.Any(),
   275  			&ethpb.ValidatorActivationRequest{
   276  				PublicKeys: [][]byte{inactivePubKey[:]},
   277  			},
   278  		).Return(inactiveClientStream, nil)
   279  		inactiveClientStream.EXPECT().Recv().Return(
   280  			inactiveResp,
   281  			nil,
   282  		).AnyTimes()
   283  
   284  		activeResp := generateMockStatusResponse([][]byte{inactivePubKey[:], activePubKey[:]})
   285  		activeResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS
   286  		activeResp.Statuses[1].Status.Status = ethpb.ValidatorStatus_ACTIVE
   287  		activeClientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
   288  		client.EXPECT().WaitForActivation(
   289  			gomock.Any(),
   290  			&ethpb.ValidatorActivationRequest{
   291  				PublicKeys: [][]byte{inactivePubKey[:], activePubKey[:]},
   292  			},
   293  		).Return(activeClientStream, nil)
   294  		activeClientStream.EXPECT().Recv().Return(
   295  			activeResp,
   296  			nil,
   297  		)
   298  
   299  		go func() {
   300  			// We add the active key into the keymanager and simulate a key refresh.
   301  			time.Sleep(time.Second * 1)
   302  			km.keysMap[activePubKey] = activePrivKey
   303  			km.SimulateAccountChanges(make([][48]byte, 0))
   304  		}()
   305  
   306  		assert.NoError(t, v.WaitForActivation(context.Background(), nil))
   307  		assert.LogsContain(t, hook, "Waiting for deposit to be observed by beacon node")
   308  		assert.LogsContain(t, hook, "Validator activated")
   309  	})
   310  
   311  	t.Run("Derived keymanager", func(t *testing.T) {
   312  		seed := bip39.NewSeed(constant.TestMnemonic, "")
   313  		inactivePrivKey, err :=
   314  			util.PrivateKeyFromSeedAndPath(seed, fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, 0))
   315  		require.NoError(t, err)
   316  		inactivePubKey := [48]byte{}
   317  		copy(inactivePubKey[:], inactivePrivKey.PublicKey().Marshal())
   318  		activePrivKey, err :=
   319  			util.PrivateKeyFromSeedAndPath(seed, fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, 1))
   320  		require.NoError(t, err)
   321  		activePubKey := [48]byte{}
   322  		copy(activePubKey[:], activePrivKey.PublicKey().Marshal())
   323  		wallet := &walletMock.Wallet{
   324  			Files:            make(map[string]map[string][]byte),
   325  			AccountPasswords: make(map[string]string),
   326  			WalletPassword:   "secretPassw0rd$1999",
   327  		}
   328  		ctx := context.Background()
   329  		km, err := derived.NewKeymanager(ctx, &derived.SetupConfig{
   330  			Wallet:           wallet,
   331  			ListenForChanges: true,
   332  		})
   333  		require.NoError(t, err)
   334  		err = km.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, "", 1)
   335  		require.NoError(t, err)
   336  		client := mock.NewMockBeaconNodeValidatorClient(ctrl)
   337  		v := validator{
   338  			validatorClient: client,
   339  			keyManager:      km,
   340  			genesisTime:     1,
   341  		}
   342  
   343  		inactiveResp := generateMockStatusResponse([][]byte{inactivePubKey[:]})
   344  		inactiveResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS
   345  		inactiveClientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
   346  		client.EXPECT().WaitForActivation(
   347  			gomock.Any(),
   348  			&ethpb.ValidatorActivationRequest{
   349  				PublicKeys: [][]byte{inactivePubKey[:]},
   350  			},
   351  		).Return(inactiveClientStream, nil)
   352  		inactiveClientStream.EXPECT().Recv().Return(
   353  			inactiveResp,
   354  			nil,
   355  		).AnyTimes()
   356  
   357  		activeResp := generateMockStatusResponse([][]byte{inactivePubKey[:], activePubKey[:]})
   358  		activeResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS
   359  		activeResp.Statuses[1].Status.Status = ethpb.ValidatorStatus_ACTIVE
   360  		activeClientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
   361  		client.EXPECT().WaitForActivation(
   362  			gomock.Any(),
   363  			&ethpb.ValidatorActivationRequest{
   364  				PublicKeys: [][]byte{inactivePubKey[:], activePubKey[:]},
   365  			},
   366  		).Return(activeClientStream, nil)
   367  		activeClientStream.EXPECT().Recv().Return(
   368  			activeResp,
   369  			nil,
   370  		)
   371  
   372  		channel := make(chan [][48]byte)
   373  		go func() {
   374  			// We add the active key into the keymanager and simulate a key refresh.
   375  			time.Sleep(time.Second * 1)
   376  			err = km.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, "", 2)
   377  			require.NoError(t, err)
   378  			channel <- [][48]byte{}
   379  		}()
   380  
   381  		assert.NoError(t, v.waitForActivation(context.Background(), channel))
   382  		assert.LogsContain(t, hook, "Waiting for deposit to be observed by beacon node")
   383  		assert.LogsContain(t, hook, "Validator activated")
   384  	})
   385  }
   386  
   387  func TestWaitForActivation_RemoteKeymanager(t *testing.T) {
   388  	ctrl := gomock.NewController(t)
   389  	defer ctrl.Finish()
   390  
   391  	client := mock.NewMockBeaconNodeValidatorClient(ctrl)
   392  	stream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
   393  	client.EXPECT().WaitForActivation(
   394  		gomock.Any(),
   395  		gomock.Any(),
   396  	).Return(stream, nil /* err */).AnyTimes()
   397  
   398  	inactiveKey := bytesutil.ToBytes48([]byte("inactive"))
   399  	activeKey := bytesutil.ToBytes48([]byte("active"))
   400  	km := remote.NewMock()
   401  	km.PublicKeys = [][48]byte{inactiveKey, activeKey}
   402  	slot := types.Slot(0)
   403  
   404  	t.Run("activated", func(t *testing.T) {
   405  		ctx, cancel := context.WithCancel(context.Background())
   406  		hook := logTest.NewGlobal()
   407  
   408  		tickerChan := make(chan types.Slot)
   409  		ticker := &slotutilmock.MockTicker{
   410  			Channel: tickerChan,
   411  		}
   412  		v := validator{
   413  			validatorClient: client,
   414  			keyManager:      &km,
   415  			ticker:          ticker,
   416  		}
   417  		go func() {
   418  			tickerChan <- slot
   419  			// Cancel after timeout to avoid waiting on channel forever in case test goes wrong.
   420  			time.Sleep(time.Second)
   421  			cancel()
   422  		}()
   423  
   424  		resp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactiveKey[:], activeKey[:]})
   425  		resp.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS
   426  		resp.Statuses[1].Status = ethpb.ValidatorStatus_ACTIVE
   427  		client.EXPECT().MultipleValidatorStatus(
   428  			gomock.Any(),
   429  			&ethpb.MultipleValidatorStatusRequest{
   430  				PublicKeys: [][]byte{inactiveKey[:], activeKey[:]},
   431  			},
   432  		).Return(resp, nil /* err */)
   433  
   434  		err := v.waitForActivation(ctx, nil /* accountsChangedChan */)
   435  		require.NoError(t, err)
   436  		assert.LogsContain(t, hook, "Waiting for deposit to be observed by beacon node")
   437  		assert.LogsContain(t, hook, "Validator activated")
   438  	})
   439  
   440  	t.Run("cancelled", func(t *testing.T) {
   441  		ctx, cancel := context.WithCancel(context.Background())
   442  
   443  		tickerChan := make(chan types.Slot)
   444  		ticker := &slotutilmock.MockTicker{
   445  			Channel: tickerChan,
   446  		}
   447  		v := validator{
   448  			validatorClient: client,
   449  			keyManager:      &km,
   450  			ticker:          ticker,
   451  		}
   452  		go func() {
   453  			cancel()
   454  			tickerChan <- slot
   455  		}()
   456  
   457  		err := v.waitForActivation(ctx, nil /* accountsChangedChan */)
   458  		assert.ErrorContains(t, "context canceled, not waiting for activation anymore", err)
   459  	})
   460  	t.Run("reloaded", func(t *testing.T) {
   461  		ctx, cancel := context.WithCancel(context.Background())
   462  		hook := logTest.NewGlobal()
   463  		remoteKm := remote.NewMock()
   464  		remoteKm.PublicKeys = [][48]byte{inactiveKey}
   465  
   466  		tickerChan := make(chan types.Slot)
   467  		ticker := &slotutilmock.MockTicker{
   468  			Channel: tickerChan,
   469  		}
   470  		v := validator{
   471  			validatorClient: client,
   472  			keyManager:      &remoteKm,
   473  			ticker:          ticker,
   474  		}
   475  		go func() {
   476  			tickerChan <- slot
   477  			time.Sleep(time.Second)
   478  			remoteKm.PublicKeys = [][48]byte{inactiveKey, activeKey}
   479  			tickerChan <- slot
   480  			// Cancel after timeout to avoid waiting on channel forever in case test goes wrong.
   481  			time.Sleep(time.Second)
   482  			cancel()
   483  		}()
   484  
   485  		resp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactiveKey[:]})
   486  		resp.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS
   487  		client.EXPECT().MultipleValidatorStatus(
   488  			gomock.Any(),
   489  			&ethpb.MultipleValidatorStatusRequest{
   490  				PublicKeys: [][]byte{inactiveKey[:]},
   491  			},
   492  		).Return(resp, nil /* err */)
   493  		resp2 := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactiveKey[:], activeKey[:]})
   494  		resp2.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS
   495  		resp2.Statuses[1].Status = ethpb.ValidatorStatus_ACTIVE
   496  		client.EXPECT().MultipleValidatorStatus(
   497  			gomock.Any(),
   498  			&ethpb.MultipleValidatorStatusRequest{
   499  				PublicKeys: [][]byte{inactiveKey[:], activeKey[:]},
   500  			},
   501  		).Return(resp2, nil /* err */)
   502  
   503  		err := v.waitForActivation(ctx, remoteKm.ReloadPublicKeysChan /* accountsChangedChan */)
   504  		require.NoError(t, err)
   505  		assert.LogsContain(t, hook, "Waiting for deposit to be observed by beacon node")
   506  		assert.LogsContain(t, hook, "Validator activated")
   507  	})
   508  }