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 ðpb.ValidatorActivationRequest{ 51 PublicKeys: [][]byte{pubKey[:]}, 52 }, 53 ).Return(clientStream, nil) 54 clientStream.EXPECT().Recv().Return( 55 ðpb.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 ðpb.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 ðpb.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 ðpb.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 ðpb.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 ðpb.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 ðpb.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 ðpb.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 ðpb.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 ðpb.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 ðpb.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 ðpb.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 ðpb.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 }