github.com/prysmaticlabs/prysm@v1.4.4/validator/client/validator_test.go (about) 1 package client 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/golang/mock/gomock" 13 types "github.com/prysmaticlabs/eth2-types" 14 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 15 validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2" 16 "github.com/prysmaticlabs/prysm/shared/bls" 17 "github.com/prysmaticlabs/prysm/shared/bytesutil" 18 "github.com/prysmaticlabs/prysm/shared/event" 19 "github.com/prysmaticlabs/prysm/shared/featureconfig" 20 "github.com/prysmaticlabs/prysm/shared/mock" 21 "github.com/prysmaticlabs/prysm/shared/params" 22 "github.com/prysmaticlabs/prysm/shared/testutil" 23 "github.com/prysmaticlabs/prysm/shared/testutil/assert" 24 "github.com/prysmaticlabs/prysm/shared/testutil/require" 25 "github.com/prysmaticlabs/prysm/validator/client/iface" 26 dbTest "github.com/prysmaticlabs/prysm/validator/db/testing" 27 "github.com/sirupsen/logrus" 28 logTest "github.com/sirupsen/logrus/hooks/test" 29 grpc "google.golang.org/grpc" 30 "google.golang.org/protobuf/types/known/emptypb" 31 ) 32 33 func init() { 34 logrus.SetLevel(logrus.DebugLevel) 35 logrus.SetOutput(ioutil.Discard) 36 } 37 38 var _ iface.Validator = (*validator)(nil) 39 40 const cancelledCtx = "context has been canceled" 41 42 func genMockKeymanager(numKeys int) *mockKeymanager { 43 km := make(map[[48]byte]bls.SecretKey, numKeys) 44 for i := 0; i < numKeys; i++ { 45 k, err := bls.RandKey() 46 if err != nil { 47 panic(err) 48 } 49 km[bytesutil.ToBytes48(k.PublicKey().Marshal())] = k 50 } 51 52 return &mockKeymanager{keysMap: km} 53 } 54 55 type mockKeymanager struct { 56 lock sync.RWMutex 57 keysMap map[[48]byte]bls.SecretKey 58 fetchNoKeys bool 59 accountsChangedFeed *event.Feed 60 } 61 62 func (m *mockKeymanager) FetchValidatingPublicKeys(ctx context.Context) ([][48]byte, error) { 63 m.lock.RLock() 64 defer m.lock.RUnlock() 65 keys := make([][48]byte, 0) 66 if m.fetchNoKeys { 67 m.fetchNoKeys = false 68 return keys, nil 69 } 70 for pubKey := range m.keysMap { 71 keys = append(keys, pubKey) 72 } 73 return keys, nil 74 } 75 76 func (m *mockKeymanager) Sign(ctx context.Context, req *validatorpb.SignRequest) (bls.Signature, error) { 77 pubKey := [48]byte{} 78 copy(pubKey[:], req.PublicKey) 79 privKey, ok := m.keysMap[pubKey] 80 if !ok { 81 return nil, errors.New("not found") 82 } 83 sig := privKey.Sign(req.SigningRoot) 84 return sig, nil 85 } 86 87 func (m *mockKeymanager) SubscribeAccountChanges(pubKeysChan chan [][48]byte) event.Subscription { 88 if m.accountsChangedFeed == nil { 89 m.accountsChangedFeed = &event.Feed{} 90 } 91 return m.accountsChangedFeed.Subscribe(pubKeysChan) 92 } 93 94 func (m *mockKeymanager) SimulateAccountChanges(newKeys [][48]byte) { 95 m.accountsChangedFeed.Send(newKeys) 96 } 97 98 func generateMockStatusResponse(pubkeys [][]byte) *ethpb.ValidatorActivationResponse { 99 multipleStatus := make([]*ethpb.ValidatorActivationResponse_Status, len(pubkeys)) 100 for i, key := range pubkeys { 101 multipleStatus[i] = ðpb.ValidatorActivationResponse_Status{ 102 PublicKey: key, 103 Status: ðpb.ValidatorStatusResponse{ 104 Status: ethpb.ValidatorStatus_UNKNOWN_STATUS, 105 }, 106 } 107 } 108 return ðpb.ValidatorActivationResponse{Statuses: multipleStatus} 109 } 110 111 func TestWaitForChainStart_SetsGenesisInfo(t *testing.T) { 112 ctrl := gomock.NewController(t) 113 defer ctrl.Finish() 114 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 115 116 db := dbTest.SetupDB(t, [][48]byte{}) 117 v := validator{ 118 validatorClient: client, 119 db: db, 120 } 121 122 // Make sure its clean at the start. 123 savedGenValRoot, err := db.GenesisValidatorsRoot(context.Background()) 124 require.NoError(t, err) 125 assert.DeepEqual(t, []byte(nil), savedGenValRoot, "Unexpected saved genesis validator root") 126 127 genesis := uint64(time.Unix(1, 0).Unix()) 128 genesisValidatorsRoot := bytesutil.ToBytes32([]byte("validators")) 129 clientStream := mock.NewMockBeaconNodeValidator_WaitForChainStartClient(ctrl) 130 client.EXPECT().WaitForChainStart( 131 gomock.Any(), 132 &emptypb.Empty{}, 133 ).Return(clientStream, nil) 134 clientStream.EXPECT().Recv().Return( 135 ðpb.ChainStartResponse{ 136 Started: true, 137 GenesisTime: genesis, 138 GenesisValidatorsRoot: genesisValidatorsRoot[:], 139 }, 140 nil, 141 ) 142 require.NoError(t, v.WaitForChainStart(context.Background())) 143 savedGenValRoot, err = db.GenesisValidatorsRoot(context.Background()) 144 require.NoError(t, err) 145 146 assert.DeepEqual(t, genesisValidatorsRoot[:], savedGenValRoot, "Unexpected saved genesis validator root") 147 assert.Equal(t, genesis, v.genesisTime, "Unexpected chain start time") 148 assert.NotNil(t, v.ticker, "Expected ticker to be set, received nil") 149 150 // Make sure theres no errors running if its the same data. 151 client.EXPECT().WaitForChainStart( 152 gomock.Any(), 153 &emptypb.Empty{}, 154 ).Return(clientStream, nil) 155 clientStream.EXPECT().Recv().Return( 156 ðpb.ChainStartResponse{ 157 Started: true, 158 GenesisTime: genesis, 159 GenesisValidatorsRoot: genesisValidatorsRoot[:], 160 }, 161 nil, 162 ) 163 require.NoError(t, v.WaitForChainStart(context.Background())) 164 } 165 166 func TestWaitForChainStart_SetsGenesisInfo_IncorrectSecondTry(t *testing.T) { 167 ctrl := gomock.NewController(t) 168 defer ctrl.Finish() 169 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 170 171 db := dbTest.SetupDB(t, [][48]byte{}) 172 v := validator{ 173 validatorClient: client, 174 db: db, 175 } 176 genesis := uint64(time.Unix(1, 0).Unix()) 177 genesisValidatorsRoot := bytesutil.ToBytes32([]byte("validators")) 178 clientStream := mock.NewMockBeaconNodeValidator_WaitForChainStartClient(ctrl) 179 client.EXPECT().WaitForChainStart( 180 gomock.Any(), 181 &emptypb.Empty{}, 182 ).Return(clientStream, nil) 183 clientStream.EXPECT().Recv().Return( 184 ðpb.ChainStartResponse{ 185 Started: true, 186 GenesisTime: genesis, 187 GenesisValidatorsRoot: genesisValidatorsRoot[:], 188 }, 189 nil, 190 ) 191 require.NoError(t, v.WaitForChainStart(context.Background())) 192 savedGenValRoot, err := db.GenesisValidatorsRoot(context.Background()) 193 require.NoError(t, err) 194 195 assert.DeepEqual(t, genesisValidatorsRoot[:], savedGenValRoot, "Unexpected saved genesis validator root") 196 assert.Equal(t, genesis, v.genesisTime, "Unexpected chain start time") 197 assert.NotNil(t, v.ticker, "Expected ticker to be set, received nil") 198 199 genesisValidatorsRoot = bytesutil.ToBytes32([]byte("badvalidators")) 200 201 // Make sure theres no errors running if its the same data. 202 client.EXPECT().WaitForChainStart( 203 gomock.Any(), 204 &emptypb.Empty{}, 205 ).Return(clientStream, nil) 206 clientStream.EXPECT().Recv().Return( 207 ðpb.ChainStartResponse{ 208 Started: true, 209 GenesisTime: genesis, 210 GenesisValidatorsRoot: genesisValidatorsRoot[:], 211 }, 212 nil, 213 ) 214 err = v.WaitForChainStart(context.Background()) 215 require.ErrorContains(t, "does not match root saved", err) 216 } 217 218 func TestWaitForChainStart_ContextCanceled(t *testing.T) { 219 ctrl := gomock.NewController(t) 220 defer ctrl.Finish() 221 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 222 223 v := validator{ 224 //keyManager: testKeyManager, 225 validatorClient: client, 226 } 227 genesis := uint64(time.Unix(0, 0).Unix()) 228 genesisValidatorsRoot := bytesutil.PadTo([]byte("validators"), 32) 229 clientStream := mock.NewMockBeaconNodeValidator_WaitForChainStartClient(ctrl) 230 client.EXPECT().WaitForChainStart( 231 gomock.Any(), 232 &emptypb.Empty{}, 233 ).Return(clientStream, nil) 234 clientStream.EXPECT().Recv().Return( 235 ðpb.ChainStartResponse{ 236 Started: true, 237 GenesisTime: genesis, 238 GenesisValidatorsRoot: genesisValidatorsRoot, 239 }, 240 nil, 241 ) 242 ctx, cancel := context.WithCancel(context.Background()) 243 cancel() 244 assert.ErrorContains(t, cancelledCtx, v.WaitForChainStart(ctx)) 245 } 246 247 func TestWaitForChainStart_StreamSetupFails(t *testing.T) { 248 ctrl := gomock.NewController(t) 249 defer ctrl.Finish() 250 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 251 252 privKey, err := bls.RandKey() 253 require.NoError(t, err) 254 pubKey := [48]byte{} 255 copy(pubKey[:], privKey.PublicKey().Marshal()) 256 km := &mockKeymanager{ 257 keysMap: make(map[[48]byte]bls.SecretKey), 258 } 259 v := validator{ 260 validatorClient: client, 261 keyManager: km, 262 } 263 clientStream := mock.NewMockBeaconNodeValidator_WaitForChainStartClient(ctrl) 264 client.EXPECT().WaitForChainStart( 265 gomock.Any(), 266 &emptypb.Empty{}, 267 ).Return(clientStream, errors.New("failed stream")) 268 err = v.WaitForChainStart(context.Background()) 269 want := "could not setup beacon chain ChainStart streaming client" 270 assert.ErrorContains(t, want, err) 271 } 272 273 func TestWaitForChainStart_ReceiveErrorFromStream(t *testing.T) { 274 ctrl := gomock.NewController(t) 275 defer ctrl.Finish() 276 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 277 278 v := validator{ 279 validatorClient: client, 280 } 281 clientStream := mock.NewMockBeaconNodeValidator_WaitForChainStartClient(ctrl) 282 client.EXPECT().WaitForChainStart( 283 gomock.Any(), 284 &emptypb.Empty{}, 285 ).Return(clientStream, nil) 286 clientStream.EXPECT().Recv().Return( 287 nil, 288 errors.New("fails"), 289 ) 290 err := v.WaitForChainStart(context.Background()) 291 want := "could not receive ChainStart from stream" 292 assert.ErrorContains(t, want, err) 293 } 294 295 func TestCanonicalHeadSlot_FailedRPC(t *testing.T) { 296 ctrl := gomock.NewController(t) 297 defer ctrl.Finish() 298 client := mock.NewMockBeaconChainClient(ctrl) 299 v := validator{ 300 beaconClient: client, 301 genesisTime: 1, 302 } 303 client.EXPECT().GetChainHead( 304 gomock.Any(), 305 gomock.Any(), 306 ).Return(nil, errors.New("failed")) 307 _, err := v.CanonicalHeadSlot(context.Background()) 308 assert.ErrorContains(t, "failed", err) 309 } 310 311 func TestCanonicalHeadSlot_OK(t *testing.T) { 312 ctrl := gomock.NewController(t) 313 defer ctrl.Finish() 314 client := mock.NewMockBeaconChainClient(ctrl) 315 v := validator{ 316 beaconClient: client, 317 } 318 client.EXPECT().GetChainHead( 319 gomock.Any(), 320 gomock.Any(), 321 ).Return(ðpb.ChainHead{HeadSlot: 0}, nil) 322 headSlot, err := v.CanonicalHeadSlot(context.Background()) 323 require.NoError(t, err) 324 assert.Equal(t, types.Slot(0), headSlot, "Mismatch slots") 325 } 326 327 func TestWaitMultipleActivation_LogsActivationEpochOK(t *testing.T) { 328 hook := logTest.NewGlobal() 329 ctrl := gomock.NewController(t) 330 defer ctrl.Finish() 331 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 332 privKey, err := bls.RandKey() 333 require.NoError(t, err) 334 pubKey := [48]byte{} 335 copy(pubKey[:], privKey.PublicKey().Marshal()) 336 km := &mockKeymanager{ 337 keysMap: map[[48]byte]bls.SecretKey{ 338 pubKey: privKey, 339 }, 340 } 341 v := validator{ 342 validatorClient: client, 343 keyManager: km, 344 genesisTime: 1, 345 } 346 347 resp := generateMockStatusResponse([][]byte{pubKey[:]}) 348 resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE 349 clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) 350 client.EXPECT().WaitForActivation( 351 gomock.Any(), 352 ðpb.ValidatorActivationRequest{ 353 PublicKeys: [][]byte{pubKey[:]}, 354 }, 355 ).Return(clientStream, nil) 356 clientStream.EXPECT().Recv().Return( 357 resp, 358 nil, 359 ) 360 require.NoError(t, v.WaitForActivation(context.Background(), nil), "Could not wait for activation") 361 require.LogsContain(t, hook, "Validator activated") 362 } 363 364 func TestWaitActivation_NotAllValidatorsActivatedOK(t *testing.T) { 365 ctrl := gomock.NewController(t) 366 defer ctrl.Finish() 367 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 368 369 privKey, err := bls.RandKey() 370 require.NoError(t, err) 371 pubKey := [48]byte{} 372 copy(pubKey[:], privKey.PublicKey().Marshal()) 373 km := &mockKeymanager{ 374 keysMap: map[[48]byte]bls.SecretKey{ 375 pubKey: privKey, 376 }, 377 } 378 v := validator{ 379 validatorClient: client, 380 keyManager: km, 381 genesisTime: 1, 382 } 383 resp := generateMockStatusResponse([][]byte{pubKey[:]}) 384 resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE 385 clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) 386 client.EXPECT().WaitForActivation( 387 gomock.Any(), 388 gomock.Any(), 389 ).Return(clientStream, nil) 390 clientStream.EXPECT().Recv().Return( 391 ðpb.ValidatorActivationResponse{}, 392 nil, 393 ) 394 clientStream.EXPECT().Recv().Return( 395 resp, 396 nil, 397 ) 398 assert.NoError(t, v.WaitForActivation(context.Background(), nil), "Could not wait for activation") 399 } 400 401 func TestWaitSync_ContextCanceled(t *testing.T) { 402 ctrl := gomock.NewController(t) 403 defer ctrl.Finish() 404 n := mock.NewMockNodeClient(ctrl) 405 406 v := validator{ 407 node: n, 408 } 409 410 ctx, cancel := context.WithCancel(context.Background()) 411 cancel() 412 413 n.EXPECT().GetSyncStatus( 414 gomock.Any(), 415 gomock.Any(), 416 ).Return(ðpb.SyncStatus{Syncing: true}, nil) 417 418 assert.ErrorContains(t, cancelledCtx, v.WaitForSync(ctx)) 419 } 420 421 func TestWaitSync_NotSyncing(t *testing.T) { 422 ctrl := gomock.NewController(t) 423 defer ctrl.Finish() 424 n := mock.NewMockNodeClient(ctrl) 425 426 v := validator{ 427 node: n, 428 } 429 430 n.EXPECT().GetSyncStatus( 431 gomock.Any(), 432 gomock.Any(), 433 ).Return(ðpb.SyncStatus{Syncing: false}, nil) 434 435 require.NoError(t, v.WaitForSync(context.Background())) 436 } 437 438 func TestWaitSync_Syncing(t *testing.T) { 439 ctrl := gomock.NewController(t) 440 defer ctrl.Finish() 441 n := mock.NewMockNodeClient(ctrl) 442 443 v := validator{ 444 node: n, 445 } 446 447 n.EXPECT().GetSyncStatus( 448 gomock.Any(), 449 gomock.Any(), 450 ).Return(ðpb.SyncStatus{Syncing: true}, nil) 451 452 n.EXPECT().GetSyncStatus( 453 gomock.Any(), 454 gomock.Any(), 455 ).Return(ðpb.SyncStatus{Syncing: false}, nil) 456 457 require.NoError(t, v.WaitForSync(context.Background())) 458 } 459 460 func TestUpdateDuties_DoesNothingWhenNotEpochStart_AlreadyExistingAssignments(t *testing.T) { 461 ctrl := gomock.NewController(t) 462 defer ctrl.Finish() 463 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 464 465 slot := types.Slot(1) 466 v := validator{ 467 validatorClient: client, 468 duties: ðpb.DutiesResponse{ 469 Duties: []*ethpb.DutiesResponse_Duty{ 470 { 471 Committee: []types.ValidatorIndex{}, 472 AttesterSlot: 10, 473 CommitteeIndex: 20, 474 }, 475 }, 476 }, 477 } 478 client.EXPECT().GetDuties( 479 gomock.Any(), 480 gomock.Any(), 481 ).Times(0) 482 483 assert.NoError(t, v.UpdateDuties(context.Background(), slot), "Could not update assignments") 484 } 485 486 func TestUpdateDuties_ReturnsError(t *testing.T) { 487 ctrl := gomock.NewController(t) 488 defer ctrl.Finish() 489 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 490 491 privKey, err := bls.RandKey() 492 require.NoError(t, err) 493 pubKey := [48]byte{} 494 copy(pubKey[:], privKey.PublicKey().Marshal()) 495 km := &mockKeymanager{ 496 keysMap: map[[48]byte]bls.SecretKey{ 497 pubKey: privKey, 498 }, 499 } 500 v := validator{ 501 validatorClient: client, 502 keyManager: km, 503 duties: ðpb.DutiesResponse{ 504 Duties: []*ethpb.DutiesResponse_Duty{ 505 { 506 CommitteeIndex: 1, 507 }, 508 }, 509 }, 510 } 511 512 expected := errors.New("bad") 513 514 client.EXPECT().GetDuties( 515 gomock.Any(), 516 gomock.Any(), 517 ).Return(nil, expected) 518 519 assert.ErrorContains(t, expected.Error(), v.UpdateDuties(context.Background(), params.BeaconConfig().SlotsPerEpoch)) 520 assert.Equal(t, (*ethpb.DutiesResponse)(nil), v.duties, "Assignments should have been cleared on failure") 521 } 522 523 func TestUpdateDuties_OK(t *testing.T) { 524 ctrl := gomock.NewController(t) 525 defer ctrl.Finish() 526 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 527 528 slot := params.BeaconConfig().SlotsPerEpoch 529 privKey, err := bls.RandKey() 530 require.NoError(t, err) 531 pubKey := [48]byte{} 532 copy(pubKey[:], privKey.PublicKey().Marshal()) 533 km := &mockKeymanager{ 534 keysMap: map[[48]byte]bls.SecretKey{ 535 pubKey: privKey, 536 }, 537 } 538 resp := ðpb.DutiesResponse{ 539 Duties: []*ethpb.DutiesResponse_Duty{ 540 { 541 AttesterSlot: params.BeaconConfig().SlotsPerEpoch, 542 ValidatorIndex: 200, 543 CommitteeIndex: 100, 544 Committee: []types.ValidatorIndex{0, 1, 2, 3}, 545 PublicKey: []byte("testPubKey_1"), 546 ProposerSlots: []types.Slot{params.BeaconConfig().SlotsPerEpoch + 1}, 547 }, 548 }, 549 } 550 v := validator{ 551 keyManager: km, 552 validatorClient: client, 553 } 554 client.EXPECT().GetDuties( 555 gomock.Any(), 556 gomock.Any(), 557 ).Return(resp, nil) 558 559 var wg sync.WaitGroup 560 wg.Add(1) 561 562 client.EXPECT().SubscribeCommitteeSubnets( 563 gomock.Any(), 564 gomock.Any(), 565 ).DoAndReturn(func(_ context.Context, _ *ethpb.CommitteeSubnetsSubscribeRequest, arg2 ...grpc.CallOption) (*emptypb.Empty, error) { 566 wg.Done() 567 return nil, nil 568 }) 569 570 require.NoError(t, v.UpdateDuties(context.Background(), slot), "Could not update assignments") 571 572 testutil.WaitTimeout(&wg, 3*time.Second) 573 574 assert.Equal(t, params.BeaconConfig().SlotsPerEpoch+1, v.duties.Duties[0].ProposerSlots[0], "Unexpected validator assignments") 575 assert.Equal(t, params.BeaconConfig().SlotsPerEpoch, v.duties.Duties[0].AttesterSlot, "Unexpected validator assignments") 576 assert.Equal(t, resp.Duties[0].CommitteeIndex, v.duties.Duties[0].CommitteeIndex, "Unexpected validator assignments") 577 assert.Equal(t, resp.Duties[0].ValidatorIndex, v.duties.Duties[0].ValidatorIndex, "Unexpected validator assignments") 578 } 579 580 func TestUpdateDuties_OK_FilterBlacklistedPublicKeys(t *testing.T) { 581 hook := logTest.NewGlobal() 582 ctrl := gomock.NewController(t) 583 defer ctrl.Finish() 584 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 585 slot := params.BeaconConfig().SlotsPerEpoch 586 587 numValidators := 10 588 keysMap := make(map[[48]byte]bls.SecretKey) 589 blacklistedPublicKeys := make(map[[48]byte]bool) 590 for i := 0; i < numValidators; i++ { 591 priv, err := bls.RandKey() 592 require.NoError(t, err) 593 pubKey := [48]byte{} 594 copy(pubKey[:], priv.PublicKey().Marshal()) 595 keysMap[pubKey] = priv 596 blacklistedPublicKeys[pubKey] = true 597 } 598 599 km := &mockKeymanager{ 600 keysMap: keysMap, 601 } 602 resp := ðpb.DutiesResponse{ 603 Duties: []*ethpb.DutiesResponse_Duty{}, 604 } 605 v := validator{ 606 keyManager: km, 607 validatorClient: client, 608 eipImportBlacklistedPublicKeys: blacklistedPublicKeys, 609 } 610 client.EXPECT().GetDuties( 611 gomock.Any(), 612 gomock.Any(), 613 ).Return(resp, nil) 614 615 var wg sync.WaitGroup 616 wg.Add(1) 617 client.EXPECT().SubscribeCommitteeSubnets( 618 gomock.Any(), 619 gomock.Any(), 620 ).DoAndReturn(func(_ context.Context, _ *ethpb.CommitteeSubnetsSubscribeRequest, arg2 ...grpc.CallOption) (*emptypb.Empty, error) { 621 wg.Done() 622 return nil, nil 623 }) 624 625 require.NoError(t, v.UpdateDuties(context.Background(), slot), "Could not update assignments") 626 627 testutil.WaitTimeout(&wg, 3*time.Second) 628 629 for range blacklistedPublicKeys { 630 assert.LogsContain(t, hook, "Not including slashable public key") 631 } 632 } 633 634 func TestRolesAt_OK(t *testing.T) { 635 v, m, validatorKey, finish := setup(t) 636 defer finish() 637 638 v.duties = ðpb.DutiesResponse{ 639 Duties: []*ethpb.DutiesResponse_Duty{ 640 { 641 CommitteeIndex: 1, 642 AttesterSlot: 1, 643 PublicKey: validatorKey.PublicKey().Marshal(), 644 }, 645 }, 646 } 647 648 m.validatorClient.EXPECT().DomainData( 649 gomock.Any(), // ctx 650 gomock.Any(), // epoch 651 ).Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/) 652 653 roleMap, err := v.RolesAt(context.Background(), 1) 654 require.NoError(t, err) 655 656 assert.Equal(t, iface.RoleAttester, roleMap[bytesutil.ToBytes48(validatorKey.PublicKey().Marshal())][0]) 657 } 658 659 func TestRolesAt_DoesNotAssignProposer_Slot0(t *testing.T) { 660 v, m, validatorKey, finish := setup(t) 661 defer finish() 662 663 v.duties = ðpb.DutiesResponse{ 664 Duties: []*ethpb.DutiesResponse_Duty{ 665 { 666 CommitteeIndex: 1, 667 AttesterSlot: 0, 668 ProposerSlots: []types.Slot{0}, 669 PublicKey: validatorKey.PublicKey().Marshal(), 670 }, 671 }, 672 } 673 674 m.validatorClient.EXPECT().DomainData( 675 gomock.Any(), // ctx 676 gomock.Any(), // epoch 677 ).Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/) 678 679 roleMap, err := v.RolesAt(context.Background(), 0) 680 require.NoError(t, err) 681 682 assert.Equal(t, iface.RoleAttester, roleMap[bytesutil.ToBytes48(validatorKey.PublicKey().Marshal())][0]) 683 } 684 685 func TestCheckAndLogValidatorStatus_OK(t *testing.T) { 686 nonexistentIndex := types.ValidatorIndex(^uint64(0)) 687 type statusTest struct { 688 name string 689 status *validatorStatus 690 log string 691 active bool 692 } 693 pubKeys := [][]byte{bytesutil.Uint64ToBytesLittleEndian(0)} 694 tests := []statusTest{ 695 { 696 name: "UNKNOWN_STATUS, no deposit found yet", 697 status: &validatorStatus{ 698 publicKey: pubKeys[0], 699 index: nonexistentIndex, 700 status: ðpb.ValidatorStatusResponse{ 701 Status: ethpb.ValidatorStatus_UNKNOWN_STATUS, 702 }, 703 }, 704 log: "Waiting for deposit to be observed by beacon node", 705 active: false, 706 }, 707 { 708 name: "DEPOSITED into state", 709 status: &validatorStatus{ 710 publicKey: pubKeys[0], 711 index: 30, 712 status: ðpb.ValidatorStatusResponse{ 713 Status: ethpb.ValidatorStatus_DEPOSITED, 714 PositionInActivationQueue: 30, 715 }, 716 }, 717 log: "Deposit processed, entering activation queue after finalization\" index=30 positionInActivationQueue=30", 718 active: false, 719 }, 720 { 721 name: "PENDING", 722 status: &validatorStatus{ 723 publicKey: pubKeys[0], 724 index: 50, 725 status: ðpb.ValidatorStatusResponse{ 726 Status: ethpb.ValidatorStatus_PENDING, 727 ActivationEpoch: params.BeaconConfig().FarFutureEpoch, 728 PositionInActivationQueue: 6, 729 }, 730 }, 731 log: "Waiting to be assigned activation epoch\" index=50 positionInActivationQueue=6", 732 active: false, 733 }, 734 { 735 name: "PENDING", 736 status: &validatorStatus{ 737 publicKey: pubKeys[0], 738 index: 89, 739 status: ðpb.ValidatorStatusResponse{ 740 Status: ethpb.ValidatorStatus_PENDING, 741 ActivationEpoch: 60, 742 PositionInActivationQueue: 5, 743 }, 744 }, 745 log: "Waiting for activation\" activationEpoch=60 index=89", 746 active: false, 747 }, 748 { 749 name: "ACTIVE", 750 status: &validatorStatus{ 751 publicKey: pubKeys[0], 752 index: 89, 753 status: ðpb.ValidatorStatusResponse{ 754 Status: ethpb.ValidatorStatus_ACTIVE, 755 }, 756 }, 757 active: true, 758 }, 759 { 760 name: "EXITING", 761 status: &validatorStatus{ 762 publicKey: pubKeys[0], 763 index: 89, 764 status: ðpb.ValidatorStatusResponse{ 765 Status: ethpb.ValidatorStatus_EXITING, 766 }, 767 }, 768 active: true, 769 }, 770 { 771 name: "EXITED", 772 status: &validatorStatus{ 773 publicKey: pubKeys[0], 774 status: ðpb.ValidatorStatusResponse{ 775 Status: ethpb.ValidatorStatus_EXITED, 776 }, 777 }, 778 log: "Validator exited", 779 active: false, 780 }, 781 } 782 for _, test := range tests { 783 t.Run(test.name, func(t *testing.T) { 784 hook := logTest.NewGlobal() 785 ctrl := gomock.NewController(t) 786 defer ctrl.Finish() 787 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 788 v := validator{ 789 validatorClient: client, 790 duties: ðpb.DutiesResponse{ 791 Duties: []*ethpb.DutiesResponse_Duty{ 792 { 793 CommitteeIndex: 1, 794 }, 795 }, 796 }, 797 } 798 799 active := v.checkAndLogValidatorStatus([]*validatorStatus{test.status}) 800 require.Equal(t, test.active, active) 801 if test.log != "" { 802 require.LogsContain(t, hook, test.log) 803 } 804 }) 805 } 806 } 807 808 func TestAllValidatorsAreExited_AllExited(t *testing.T) { 809 ctrl := gomock.NewController(t) 810 defer ctrl.Finish() 811 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 812 813 statuses := []*ethpb.ValidatorStatusResponse{ 814 {Status: ethpb.ValidatorStatus_EXITED}, 815 {Status: ethpb.ValidatorStatus_EXITED}, 816 } 817 818 client.EXPECT().MultipleValidatorStatus( 819 gomock.Any(), // ctx 820 gomock.Any(), // request 821 ).Return(ðpb.MultipleValidatorStatusResponse{Statuses: statuses}, nil /*err*/) 822 823 v := validator{keyManager: genMockKeymanager(2), validatorClient: client} 824 exited, err := v.AllValidatorsAreExited(context.Background()) 825 require.NoError(t, err) 826 assert.Equal(t, true, exited) 827 } 828 829 func TestAllValidatorsAreExited_NotAllExited(t *testing.T) { 830 ctrl := gomock.NewController(t) 831 defer ctrl.Finish() 832 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 833 834 statuses := []*ethpb.ValidatorStatusResponse{ 835 {Status: ethpb.ValidatorStatus_ACTIVE}, 836 {Status: ethpb.ValidatorStatus_EXITED}, 837 } 838 839 client.EXPECT().MultipleValidatorStatus( 840 gomock.Any(), // ctx 841 gomock.Any(), // request 842 ).Return(ðpb.MultipleValidatorStatusResponse{Statuses: statuses}, nil /*err*/) 843 844 v := validator{keyManager: genMockKeymanager(2), validatorClient: client} 845 exited, err := v.AllValidatorsAreExited(context.Background()) 846 require.NoError(t, err) 847 assert.Equal(t, false, exited) 848 } 849 850 func TestAllValidatorsAreExited_PartialResult(t *testing.T) { 851 ctrl := gomock.NewController(t) 852 defer ctrl.Finish() 853 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 854 855 statuses := []*ethpb.ValidatorStatusResponse{ 856 {Status: ethpb.ValidatorStatus_EXITED}, 857 } 858 859 client.EXPECT().MultipleValidatorStatus( 860 gomock.Any(), // ctx 861 gomock.Any(), // request 862 ).Return(ðpb.MultipleValidatorStatusResponse{Statuses: statuses}, nil /*err*/) 863 864 v := validator{keyManager: genMockKeymanager(2), validatorClient: client} 865 exited, err := v.AllValidatorsAreExited(context.Background()) 866 require.ErrorContains(t, "number of status responses did not match number of requested keys", err) 867 assert.Equal(t, false, exited) 868 } 869 870 func TestAllValidatorsAreExited_NoKeys(t *testing.T) { 871 ctrl := gomock.NewController(t) 872 defer ctrl.Finish() 873 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 874 v := validator{keyManager: genMockKeymanager(0), validatorClient: client} 875 exited, err := v.AllValidatorsAreExited(context.Background()) 876 require.NoError(t, err) 877 assert.Equal(t, false, exited) 878 } 879 880 // TestAllValidatorsAreExited_CorrectRequest is a regression test that checks if the request contains the correct keys 881 func TestAllValidatorsAreExited_CorrectRequest(t *testing.T) { 882 ctrl := gomock.NewController(t) 883 defer ctrl.Finish() 884 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 885 886 // Create two different public keys 887 pubKey0 := [48]byte{1, 2, 3, 4} 888 pubKey1 := [48]byte{6, 7, 8, 9} 889 // This is the request expected from AllValidatorsAreExited() 890 request := ðpb.MultipleValidatorStatusRequest{ 891 PublicKeys: [][]byte{ 892 pubKey0[:], 893 pubKey1[:], 894 }, 895 } 896 statuses := []*ethpb.ValidatorStatusResponse{ 897 {Status: ethpb.ValidatorStatus_ACTIVE}, 898 {Status: ethpb.ValidatorStatus_EXITED}, 899 } 900 901 client.EXPECT().MultipleValidatorStatus( 902 gomock.Any(), // ctx 903 request, // request 904 ).Return(ðpb.MultipleValidatorStatusResponse{Statuses: statuses}, nil /*err*/) 905 906 keysMap := make(map[[48]byte]bls.SecretKey) 907 // secretKey below is just filler and is used multiple times 908 secretKeyBytes := [32]byte{1} 909 secretKey, err := bls.SecretKeyFromBytes(secretKeyBytes[:]) 910 require.NoError(t, err) 911 keysMap[pubKey0] = secretKey 912 keysMap[pubKey1] = secretKey 913 914 // If AllValidatorsAreExited does not create the expected request, this test will fail 915 v := validator{keyManager: &mockKeymanager{keysMap: keysMap}, validatorClient: client} 916 exited, err := v.AllValidatorsAreExited(context.Background()) 917 require.NoError(t, err) 918 assert.Equal(t, false, exited) 919 } 920 921 func TestService_ReceiveBlocks_NilBlock(t *testing.T) { 922 ctrl := gomock.NewController(t) 923 defer ctrl.Finish() 924 client := mock.NewMockBeaconChainClient(ctrl) 925 926 v := validator{ 927 beaconClient: client, 928 blockFeed: new(event.Feed), 929 } 930 stream := mock.NewMockBeaconChain_StreamBlocksClient(ctrl) 931 ctx, cancel := context.WithCancel(context.Background()) 932 client.EXPECT().StreamBlocks( 933 gomock.Any(), 934 ðpb.StreamBlocksRequest{VerifiedOnly: true}, 935 ).Return(stream, nil) 936 stream.EXPECT().Context().Return(ctx).AnyTimes() 937 stream.EXPECT().Recv().Return( 938 ðpb.SignedBeaconBlock{}, 939 nil, 940 ).Do(func() { 941 cancel() 942 }) 943 connectionErrorChannel := make(chan error) 944 v.ReceiveBlocks(ctx, connectionErrorChannel) 945 require.Equal(t, types.Slot(0), v.highestValidSlot) 946 } 947 948 func TestService_ReceiveBlocks_SetHighest(t *testing.T) { 949 ctrl := gomock.NewController(t) 950 defer ctrl.Finish() 951 client := mock.NewMockBeaconChainClient(ctrl) 952 953 v := validator{ 954 beaconClient: client, 955 blockFeed: new(event.Feed), 956 } 957 stream := mock.NewMockBeaconChain_StreamBlocksClient(ctrl) 958 ctx, cancel := context.WithCancel(context.Background()) 959 client.EXPECT().StreamBlocks( 960 gomock.Any(), 961 ðpb.StreamBlocksRequest{VerifiedOnly: true}, 962 ).Return(stream, nil) 963 stream.EXPECT().Context().Return(ctx).AnyTimes() 964 slot := types.Slot(100) 965 stream.EXPECT().Recv().Return( 966 ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{Slot: slot}}, 967 nil, 968 ).Do(func() { 969 cancel() 970 }) 971 connectionErrorChannel := make(chan error) 972 v.ReceiveBlocks(ctx, connectionErrorChannel) 973 require.Equal(t, slot, v.highestValidSlot) 974 } 975 976 type doppelGangerRequestMatcher struct { 977 req *ethpb.DoppelGangerRequest 978 } 979 980 var _ gomock.Matcher = (*doppelGangerRequestMatcher)(nil) 981 982 func (m *doppelGangerRequestMatcher) Matches(x interface{}) bool { 983 r, ok := x.(*ethpb.DoppelGangerRequest) 984 if !ok { 985 panic("Invalid match type") 986 } 987 return gomock.InAnyOrder(m.req.ValidatorRequests).Matches(r.ValidatorRequests) 988 } 989 990 func (m *doppelGangerRequestMatcher) String() string { 991 return fmt.Sprintf("%#v", m.req.ValidatorRequests) 992 } 993 994 func TestValidator_CheckDoppelGanger(t *testing.T) { 995 ctrl := gomock.NewController(t) 996 defer ctrl.Finish() 997 flgs := featureconfig.Get() 998 flgs.EnableDoppelGanger = true 999 reset := featureconfig.InitWithReset(flgs) 1000 defer reset() 1001 tests := []struct { 1002 name string 1003 validatorSetter func(t *testing.T) *validator 1004 err string 1005 }{ 1006 { 1007 name: "no doppelganger", 1008 validatorSetter: func(t *testing.T) *validator { 1009 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 1010 km := genMockKeymanager(10) 1011 keys, err := km.FetchValidatingPublicKeys(context.Background()) 1012 assert.NoError(t, err) 1013 db := dbTest.SetupDB(t, keys) 1014 req := ðpb.DoppelGangerRequest{ValidatorRequests: []*ethpb.DoppelGangerRequest_ValidatorRequest{}} 1015 resp := ðpb.DoppelGangerRequest{ValidatorRequests: []*ethpb.DoppelGangerRequest_ValidatorRequest{}} 1016 for _, k := range keys { 1017 pkey := k 1018 att := createAttestation(10, 12) 1019 rt, err := att.Data.HashTreeRoot() 1020 assert.NoError(t, err) 1021 assert.NoError(t, db.SaveAttestationForPubKey(context.Background(), pkey, rt, att)) 1022 resp.ValidatorRequests = append(resp.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{PublicKey: pkey[:], Epoch: att.Data.Target.Epoch, SignedRoot: rt[:]}) 1023 req.ValidatorRequests = append(req.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{PublicKey: pkey[:], Epoch: att.Data.Target.Epoch, SignedRoot: rt[:]}) 1024 } 1025 v := &validator{ 1026 validatorClient: client, 1027 keyManager: km, 1028 db: db, 1029 } 1030 client.EXPECT().CheckDoppelGanger( 1031 gomock.Any(), // ctx 1032 &doppelGangerRequestMatcher{req}, // request 1033 ).Return(nil, nil /*err*/) 1034 1035 return v 1036 }, 1037 }, 1038 { 1039 name: "multiple doppelganger exists", 1040 validatorSetter: func(t *testing.T) *validator { 1041 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 1042 km := genMockKeymanager(10) 1043 keys, err := km.FetchValidatingPublicKeys(context.Background()) 1044 assert.NoError(t, err) 1045 db := dbTest.SetupDB(t, keys) 1046 req := ðpb.DoppelGangerRequest{ValidatorRequests: []*ethpb.DoppelGangerRequest_ValidatorRequest{}} 1047 resp := ðpb.DoppelGangerResponse{Responses: []*ethpb.DoppelGangerResponse_ValidatorResponse{}} 1048 for i, k := range keys { 1049 pkey := k 1050 att := createAttestation(10, 12) 1051 rt, err := att.Data.HashTreeRoot() 1052 assert.NoError(t, err) 1053 assert.NoError(t, db.SaveAttestationForPubKey(context.Background(), pkey, rt, att)) 1054 if i%3 == 0 { 1055 resp.Responses = append(resp.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{PublicKey: pkey[:], DuplicateExists: true}) 1056 } 1057 req.ValidatorRequests = append(req.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{PublicKey: pkey[:], Epoch: att.Data.Target.Epoch, SignedRoot: rt[:]}) 1058 } 1059 v := &validator{ 1060 validatorClient: client, 1061 keyManager: km, 1062 db: db, 1063 } 1064 client.EXPECT().CheckDoppelGanger( 1065 gomock.Any(), // ctx 1066 &doppelGangerRequestMatcher{req}, // request 1067 ).Return(resp, nil /*err*/) 1068 return v 1069 }, 1070 err: "Duplicate instances exists in the network for validator keys", 1071 }, 1072 { 1073 name: "single doppelganger exists", 1074 validatorSetter: func(t *testing.T) *validator { 1075 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 1076 km := genMockKeymanager(10) 1077 keys, err := km.FetchValidatingPublicKeys(context.Background()) 1078 assert.NoError(t, err) 1079 db := dbTest.SetupDB(t, keys) 1080 req := ðpb.DoppelGangerRequest{ValidatorRequests: []*ethpb.DoppelGangerRequest_ValidatorRequest{}} 1081 resp := ðpb.DoppelGangerResponse{Responses: []*ethpb.DoppelGangerResponse_ValidatorResponse{}} 1082 for i, k := range keys { 1083 pkey := k 1084 att := createAttestation(10, 12) 1085 rt, err := att.Data.HashTreeRoot() 1086 assert.NoError(t, err) 1087 assert.NoError(t, db.SaveAttestationForPubKey(context.Background(), pkey, rt, att)) 1088 if i%9 == 0 { 1089 resp.Responses = append(resp.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{PublicKey: pkey[:], DuplicateExists: true}) 1090 } 1091 req.ValidatorRequests = append(req.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{PublicKey: pkey[:], Epoch: att.Data.Target.Epoch, SignedRoot: rt[:]}) 1092 } 1093 v := &validator{ 1094 validatorClient: client, 1095 keyManager: km, 1096 db: db, 1097 } 1098 client.EXPECT().CheckDoppelGanger( 1099 gomock.Any(), // ctx 1100 &doppelGangerRequestMatcher{req}, // request 1101 ).Return(resp, nil /*err*/) 1102 return v 1103 }, 1104 err: "Duplicate instances exists in the network for validator keys", 1105 }, 1106 { 1107 name: "multiple attestations saved", 1108 validatorSetter: func(t *testing.T) *validator { 1109 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 1110 km := genMockKeymanager(10) 1111 keys, err := km.FetchValidatingPublicKeys(context.Background()) 1112 assert.NoError(t, err) 1113 db := dbTest.SetupDB(t, keys) 1114 req := ðpb.DoppelGangerRequest{ValidatorRequests: []*ethpb.DoppelGangerRequest_ValidatorRequest{}} 1115 resp := ðpb.DoppelGangerResponse{Responses: []*ethpb.DoppelGangerResponse_ValidatorResponse{}} 1116 attLimit := 5 1117 for i, k := range keys { 1118 pkey := k 1119 for j := 0; j < attLimit; j++ { 1120 att := createAttestation(10+types.Epoch(j), 12+types.Epoch(j)) 1121 rt, err := att.Data.HashTreeRoot() 1122 assert.NoError(t, err) 1123 assert.NoError(t, db.SaveAttestationForPubKey(context.Background(), pkey, rt, att)) 1124 if j == attLimit-1 { 1125 req.ValidatorRequests = append(req.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{PublicKey: pkey[:], Epoch: att.Data.Target.Epoch, SignedRoot: rt[:]}) 1126 } 1127 } 1128 if i%3 == 0 { 1129 resp.Responses = append(resp.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{PublicKey: pkey[:], DuplicateExists: true}) 1130 } 1131 } 1132 v := &validator{ 1133 validatorClient: client, 1134 keyManager: km, 1135 db: db, 1136 } 1137 client.EXPECT().CheckDoppelGanger( 1138 gomock.Any(), // ctx 1139 &doppelGangerRequestMatcher{req}, // request 1140 ).Return(resp, nil /*err*/) 1141 return v 1142 }, 1143 err: "Duplicate instances exists in the network for validator keys", 1144 }, 1145 { 1146 name: "no history exists", 1147 validatorSetter: func(t *testing.T) *validator { 1148 client := mock.NewMockBeaconNodeValidatorClient(ctrl) 1149 // Use only 1 key for deterministic order. 1150 km := genMockKeymanager(1) 1151 keys, err := km.FetchValidatingPublicKeys(context.Background()) 1152 assert.NoError(t, err) 1153 db := dbTest.SetupDB(t, keys) 1154 resp := ðpb.DoppelGangerResponse{Responses: []*ethpb.DoppelGangerResponse_ValidatorResponse{}} 1155 req := ðpb.DoppelGangerRequest{ValidatorRequests: []*ethpb.DoppelGangerRequest_ValidatorRequest{}} 1156 for _, k := range keys { 1157 resp.Responses = append(resp.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{PublicKey: k[:], DuplicateExists: false}) 1158 req.ValidatorRequests = append(req.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{PublicKey: k[:], SignedRoot: make([]byte, 32), Epoch: 0}) 1159 } 1160 v := &validator{ 1161 validatorClient: client, 1162 keyManager: km, 1163 db: db, 1164 } 1165 client.EXPECT().CheckDoppelGanger( 1166 gomock.Any(), // ctx 1167 req, // request 1168 ).Return(resp, nil /*err*/) 1169 return v 1170 }, 1171 err: "", 1172 }, 1173 } 1174 for _, tt := range tests { 1175 t.Run(tt.name, func(t *testing.T) { 1176 v := tt.validatorSetter(t) 1177 if err := v.CheckDoppelGanger(context.Background()); tt.err != "" { 1178 assert.ErrorContains(t, tt.err, err) 1179 } 1180 }) 1181 } 1182 } 1183 1184 func TestValidatorAttestationsAreOrdered(t *testing.T) { 1185 km := genMockKeymanager(10) 1186 keys, err := km.FetchValidatingPublicKeys(context.Background()) 1187 assert.NoError(t, err) 1188 db := dbTest.SetupDB(t, keys) 1189 1190 k := keys[0] 1191 att := createAttestation(10, 14) 1192 rt, err := att.Data.HashTreeRoot() 1193 assert.NoError(t, err) 1194 assert.NoError(t, db.SaveAttestationForPubKey(context.Background(), k, rt, att)) 1195 1196 att = createAttestation(6, 8) 1197 rt, err = att.Data.HashTreeRoot() 1198 assert.NoError(t, err) 1199 assert.NoError(t, db.SaveAttestationForPubKey(context.Background(), k, rt, att)) 1200 1201 att = createAttestation(10, 12) 1202 rt, err = att.Data.HashTreeRoot() 1203 assert.NoError(t, err) 1204 assert.NoError(t, db.SaveAttestationForPubKey(context.Background(), k, rt, att)) 1205 1206 att = createAttestation(2, 3) 1207 rt, err = att.Data.HashTreeRoot() 1208 assert.NoError(t, err) 1209 assert.NoError(t, db.SaveAttestationForPubKey(context.Background(), k, rt, att)) 1210 1211 histories, err := db.AttestationHistoryForPubKey(context.Background(), k) 1212 assert.NoError(t, err) 1213 r := retrieveLatestRecord(histories) 1214 assert.Equal(t, r.Target, types.Epoch(14)) 1215 } 1216 1217 func createAttestation(source, target types.Epoch) *ethpb.IndexedAttestation { 1218 return ðpb.IndexedAttestation{ 1219 Data: ðpb.AttestationData{ 1220 Source: ðpb.Checkpoint{ 1221 Epoch: source, 1222 Root: make([]byte, 32), 1223 }, 1224 Target: ðpb.Checkpoint{ 1225 Epoch: target, 1226 Root: make([]byte, 32), 1227 }, 1228 BeaconBlockRoot: make([]byte, 32), 1229 }, 1230 Signature: make([]byte, 96), 1231 } 1232 }