package validator import ( "context" "fmt" "strings" "testing" "time" ptypes "github.com/gogo/protobuf/types" "github.com/golang/mock/gomock" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" mockChain "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing" "github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache" blk "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks" "github.com/prysmaticlabs/prysm/beacon-chain/core/feed" statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" dbutil "github.com/prysmaticlabs/prysm/beacon-chain/db/testing" mockPOW "github.com/prysmaticlabs/prysm/beacon-chain/powchain/testing" internal "github.com/prysmaticlabs/prysm/beacon-chain/rpc/testing" mockRPC "github.com/prysmaticlabs/prysm/beacon-chain/rpc/testing" stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state" "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" mockSync "github.com/prysmaticlabs/prysm/beacon-chain/sync/initial-sync/testing" pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/event" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/testutil" "github.com/prysmaticlabs/prysm/shared/trieutil" logTest "github.com/sirupsen/logrus/hooks/test" ) func TestValidatorIndex_OK(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() st := testutil.NewBeaconState() if err := db.SaveState(ctx, st.Copy(), [32]byte{}); err != nil { t.Fatal(err) } pubKey := pubKey(1) err := st.SetValidators([]*ethpb.Validator{{PublicKey: pubKey}}) if err != nil { t.Fatal(err) } Server := &Server{ BeaconDB: db, HeadFetcher: &mockChain.ChainService{State: st}, } req := ðpb.ValidatorIndexRequest{ PublicKey: pubKey, } if _, err := Server.ValidatorIndex(context.Background(), req); err != nil { t.Errorf("Could not get validator index: %v", err) } } func TestWaitForActivation_ContextClosed(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() beaconState, err := stateTrie.InitializeFromProto(&pbp2p.BeaconState{ Slot: 0, Validators: []*ethpb.Validator{}, }) if err != nil { t.Fatal(err) } block := blk.NewGenesisBlock([]byte{}) if err := db.SaveBlock(ctx, block); err != nil { t.Fatalf("Could not save genesis block: %v", err) } genesisRoot, err := stateutil.BlockRoot(block.Block) if err != nil { t.Fatalf("Could not get signing root %v", err) } ctx, cancel := context.WithCancel(context.Background()) vs := &Server{ BeaconDB: db, Ctx: ctx, ChainStartFetcher: &mockPOW.POWChain{}, BlockFetcher: &mockPOW.POWChain{}, Eth1InfoFetcher: &mockPOW.POWChain{}, CanonicalStateChan: make(chan *pbp2p.BeaconState, 1), DepositFetcher: depositcache.NewDepositCache(), HeadFetcher: &mockChain.ChainService{State: beaconState, Root: genesisRoot[:]}, } req := ðpb.ValidatorActivationRequest{ PublicKeys: [][]byte{pubKey(1)}, } ctrl := gomock.NewController(t) defer ctrl.Finish() mockChainStream := mockRPC.NewMockBeaconNodeValidator_WaitForActivationServer(ctrl) mockChainStream.EXPECT().Context().Return(context.Background()) mockChainStream.EXPECT().Send(gomock.Any()).Return(nil) mockChainStream.EXPECT().Context().Return(context.Background()) exitRoutine := make(chan bool) go func(tt *testing.T) { want := "context canceled" if err := vs.WaitForActivation(req, mockChainStream); err == nil || !strings.Contains(err.Error(), want) { tt.Errorf("Could not call RPC method: %v", err) } <-exitRoutine }(t) cancel() exitRoutine <- true } func TestWaitForActivation_ValidatorOriginallyExists(t *testing.T) { db := dbutil.SetupDB(t) // This test breaks if it doesnt use mainnet config params.SetupTestConfigCleanup(t) params.OverrideBeaconConfig(params.MainnetConfig()) ctx := context.Background() priv1 := bls.RandKey() priv2 := bls.RandKey() pubKey1 := priv1.PublicKey().Marshal()[:] pubKey2 := priv2.PublicKey().Marshal()[:] beaconState := &pbp2p.BeaconState{ Slot: 4000, Validators: []*ethpb.Validator{ { ActivationEpoch: 0, ExitEpoch: params.BeaconConfig().FarFutureEpoch, PublicKey: pubKey1, }, }, } block := blk.NewGenesisBlock([]byte{}) genesisRoot, err := stateutil.BlockRoot(block.Block) if err != nil { t.Fatalf("Could not get signing root %v", err) } depData := ðpb.Deposit_Data{ PublicKey: pubKey1, WithdrawalCredentials: []byte("hey"), } domain, err := helpers.ComputeDomain(params.BeaconConfig().DomainDeposit, nil, nil) if err != nil { t.Fatal(err) } signingRoot, err := helpers.ComputeSigningRoot(depData, domain) if err != nil { t.Error(err) } depData.Signature = priv1.Sign(signingRoot[:]).Marshal()[:] deposit := ðpb.Deposit{ Data: depData, } depositTrie, err := trieutil.NewTrie(int(params.BeaconConfig().DepositContractTreeDepth)) if err != nil { t.Fatal(fmt.Errorf("could not setup deposit trie: %v", err)) } depositCache := depositcache.NewDepositCache() depositCache.InsertDeposit(ctx, deposit, 10 /*blockNum*/, 0, depositTrie.Root()) trie, err := stateTrie.InitializeFromProtoUnsafe(beaconState) if err != nil { t.Fatal(err) } vs := &Server{ BeaconDB: db, Ctx: context.Background(), CanonicalStateChan: make(chan *pbp2p.BeaconState, 1), ChainStartFetcher: &mockPOW.POWChain{}, BlockFetcher: &mockPOW.POWChain{}, Eth1InfoFetcher: &mockPOW.POWChain{}, DepositFetcher: depositCache, HeadFetcher: &mockChain.ChainService{State: trie, Root: genesisRoot[:]}, } req := ðpb.ValidatorActivationRequest{ PublicKeys: [][]byte{pubKey1, pubKey2}, } ctrl := gomock.NewController(t) defer ctrl.Finish() mockChainStream := internal.NewMockBeaconNodeValidator_WaitForActivationServer(ctrl) mockChainStream.EXPECT().Context().Return(context.Background()) mockChainStream.EXPECT().Send( ðpb.ValidatorActivationResponse{ Statuses: []*ethpb.ValidatorActivationResponse_Status{ {PublicKey: pubKey1, Status: ðpb.ValidatorStatusResponse{ Status: ethpb.ValidatorStatus_ACTIVE, }, }, {PublicKey: pubKey2, Status: ðpb.ValidatorStatusResponse{ ActivationEpoch: params.BeaconConfig().FarFutureEpoch, }, }, }, }, ).Return(nil) if err := vs.WaitForActivation(req, mockChainStream); err != nil { t.Fatalf("Could not setup wait for activation stream: %v", err) } } func TestWaitForActivation_MultipleStatuses(t *testing.T) { db := dbutil.SetupDB(t) priv1 := bls.RandKey() priv2 := bls.RandKey() priv3 := bls.RandKey() pubKey1 := priv1.PublicKey().Marshal()[:] pubKey2 := priv2.PublicKey().Marshal()[:] pubKey3 := priv3.PublicKey().Marshal()[:] beaconState := &pbp2p.BeaconState{ Slot: 4000, Validators: []*ethpb.Validator{ { PublicKey: pubKey1, ActivationEpoch: 1, ExitEpoch: params.BeaconConfig().FarFutureEpoch, }, { PublicKey: pubKey2, ActivationEpoch: params.BeaconConfig().FarFutureEpoch, ActivationEligibilityEpoch: 6, ExitEpoch: params.BeaconConfig().FarFutureEpoch, }, { PublicKey: pubKey3, ActivationEpoch: 0, ActivationEligibilityEpoch: 0, ExitEpoch: 0, }, }, } block := blk.NewGenesisBlock([]byte{}) genesisRoot, err := stateutil.BlockRoot(block.Block) if err != nil { t.Fatalf("Could not get signing root %v", err) } trie, err := stateTrie.InitializeFromProtoUnsafe(beaconState) if err != nil { t.Fatal(err) } vs := &Server{ BeaconDB: db, Ctx: context.Background(), CanonicalStateChan: make(chan *pbp2p.BeaconState, 1), ChainStartFetcher: &mockPOW.POWChain{}, HeadFetcher: &mockChain.ChainService{State: trie, Root: genesisRoot[:]}, } req := ðpb.ValidatorActivationRequest{ PublicKeys: [][]byte{pubKey1, pubKey2, pubKey3}, } ctrl := gomock.NewController(t) defer ctrl.Finish() mockChainStream := internal.NewMockBeaconNodeValidator_WaitForActivationServer(ctrl) mockChainStream.EXPECT().Context().Return(context.Background()) mockChainStream.EXPECT().Send( ðpb.ValidatorActivationResponse{ Statuses: []*ethpb.ValidatorActivationResponse_Status{ { PublicKey: pubKey1, Status: ðpb.ValidatorStatusResponse{ Status: ethpb.ValidatorStatus_ACTIVE, ActivationEpoch: 1, }, }, { PublicKey: pubKey2, Status: ðpb.ValidatorStatusResponse{ Status: ethpb.ValidatorStatus_PENDING, ActivationEpoch: params.BeaconConfig().FarFutureEpoch, PositionInActivationQueue: 1, }, }, { PublicKey: pubKey3, Status: ðpb.ValidatorStatusResponse{ Status: ethpb.ValidatorStatus_EXITED, }, }, }, }, ).Return(nil) if err := vs.WaitForActivation(req, mockChainStream); err != nil { t.Fatalf("Could not setup wait for activation stream: %v", err) } } func TestWaitForChainStart_ContextClosed(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() ctx, cancel := context.WithCancel(context.Background()) chainService := &mockChain.ChainService{} Server := &Server{ Ctx: ctx, ChainStartFetcher: &mockPOW.FaultyMockPOWChain{ ChainFeed: new(event.Feed), }, StateNotifier: chainService.StateNotifier(), BeaconDB: db, HeadFetcher: chainService, } exitRoutine := make(chan bool) ctrl := gomock.NewController(t) defer ctrl.Finish() mockStream := mockRPC.NewMockBeaconNodeValidator_WaitForChainStartServer(ctrl) go func(tt *testing.T) { if err := Server.WaitForChainStart(&ptypes.Empty{}, mockStream); err == nil || !strings.Contains(err.Error(), "Context canceled") { tt.Errorf("Could not call RPC method: %v", err) } <-exitRoutine }(t) cancel() exitRoutine <- true } func TestWaitForChainStart_AlreadyStarted(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() headBlockRoot := [32]byte{0x01, 0x02} trie := testutil.NewBeaconState() if err := trie.SetSlot(3); err != nil { t.Fatal(err) } if err := db.SaveState(ctx, trie, headBlockRoot); err != nil { t.Fatal(err) } if err := db.SaveHeadBlockRoot(ctx, headBlockRoot); err != nil { t.Fatal(err) } chainService := &mockChain.ChainService{State: trie} Server := &Server{ Ctx: context.Background(), ChainStartFetcher: &mockPOW.POWChain{ ChainFeed: new(event.Feed), }, BeaconDB: db, StateNotifier: chainService.StateNotifier(), HeadFetcher: chainService, } ctrl := gomock.NewController(t) defer ctrl.Finish() mockStream := mockRPC.NewMockBeaconNodeValidator_WaitForChainStartServer(ctrl) mockStream.EXPECT().Send( ðpb.ChainStartResponse{ Started: true, GenesisTime: uint64(time.Unix(0, 0).Unix()), }, ).Return(nil) if err := Server.WaitForChainStart(&ptypes.Empty{}, mockStream); err != nil { t.Errorf("Could not call RPC method: %v", err) } } func TestWaitForChainStart_NotStartedThenLogFired(t *testing.T) { db := dbutil.SetupDB(t) hook := logTest.NewGlobal() chainService := &mockChain.ChainService{} Server := &Server{ Ctx: context.Background(), ChainStartFetcher: &mockPOW.FaultyMockPOWChain{ ChainFeed: new(event.Feed), }, BeaconDB: db, StateNotifier: chainService.StateNotifier(), HeadFetcher: chainService, } exitRoutine := make(chan bool) ctrl := gomock.NewController(t) defer ctrl.Finish() mockStream := mockRPC.NewMockBeaconNodeValidator_WaitForChainStartServer(ctrl) mockStream.EXPECT().Send( ðpb.ChainStartResponse{ Started: true, GenesisTime: uint64(time.Unix(0, 0).Unix()), }, ).Return(nil) go func(tt *testing.T) { if err := Server.WaitForChainStart(&ptypes.Empty{}, mockStream); err != nil { tt.Errorf("Could not call RPC method: %v", err) } <-exitRoutine }(t) // Send in a loop to ensure it is delivered (busy wait for the service to subscribe to the state feed). for sent := 0; sent == 0; { sent = Server.StateNotifier.StateFeed().Send(&feed.Event{ Type: statefeed.ChainStarted, Data: &statefeed.ChainStartedData{ StartTime: time.Unix(0, 0), }, }) } exitRoutine <- true testutil.AssertLogsContain(t, hook, "Sending genesis time") } func TestWaitForSynced_ContextClosed(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() ctx, cancel := context.WithCancel(context.Background()) chainService := &mockChain.ChainService{} Server := &Server{ Ctx: ctx, ChainStartFetcher: &mockPOW.FaultyMockPOWChain{ ChainFeed: new(event.Feed), }, StateNotifier: chainService.StateNotifier(), BeaconDB: db, HeadFetcher: chainService, } exitRoutine := make(chan bool) ctrl := gomock.NewController(t) defer ctrl.Finish() mockStream := mockRPC.NewMockBeaconNodeValidator_WaitForSyncedServer(ctrl) go func(tt *testing.T) { if err := Server.WaitForSynced(&ptypes.Empty{}, mockStream); err == nil || !strings.Contains(err.Error(), "Context canceled") { tt.Errorf("Could not call RPC method: %v", err) } <-exitRoutine }(t) cancel() exitRoutine <- true } func TestWaitForSynced_AlreadySynced(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() headBlockRoot := [32]byte{0x01, 0x02} trie := testutil.NewBeaconState() if err := trie.SetSlot(3); err != nil { t.Fatal(err) } if err := db.SaveState(ctx, trie, headBlockRoot); err != nil { t.Fatal(err) } if err := db.SaveHeadBlockRoot(ctx, headBlockRoot); err != nil { t.Fatal(err) } chainService := &mockChain.ChainService{State: trie} Server := &Server{ Ctx: context.Background(), ChainStartFetcher: &mockPOW.POWChain{ ChainFeed: new(event.Feed), }, BeaconDB: db, StateNotifier: chainService.StateNotifier(), HeadFetcher: chainService, SyncChecker: &mockSync.Sync{IsSyncing: false}, } ctrl := gomock.NewController(t) defer ctrl.Finish() mockStream := mockRPC.NewMockBeaconNodeValidator_WaitForSyncedServer(ctrl) mockStream.EXPECT().Send( ðpb.SyncedResponse{ Synced: true, GenesisTime: uint64(time.Unix(0, 0).Unix()), }, ).Return(nil) if err := Server.WaitForSynced(&ptypes.Empty{}, mockStream); err != nil { t.Errorf("Could not call RPC method: %v", err) } } func TestWaitForSynced_NotStartedThenLogFired(t *testing.T) { db := dbutil.SetupDB(t) hook := logTest.NewGlobal() chainService := &mockChain.ChainService{} Server := &Server{ Ctx: context.Background(), ChainStartFetcher: &mockPOW.FaultyMockPOWChain{ ChainFeed: new(event.Feed), }, BeaconDB: db, StateNotifier: chainService.StateNotifier(), HeadFetcher: chainService, } exitRoutine := make(chan bool) ctrl := gomock.NewController(t) defer ctrl.Finish() mockStream := mockRPC.NewMockBeaconNodeValidator_WaitForSyncedServer(ctrl) mockStream.EXPECT().Send( ðpb.SyncedResponse{ Synced: true, GenesisTime: uint64(time.Unix(0, 0).Unix()), }, ).Return(nil) go func(tt *testing.T) { if err := Server.WaitForSynced(&ptypes.Empty{}, mockStream); err != nil { tt.Errorf("Could not call RPC method: %v", err) } <-exitRoutine }(t) // Send in a loop to ensure it is delivered (busy wait for the service to subscribe to the state feed). for sent := 0; sent == 0; { sent = Server.StateNotifier.StateFeed().Send(&feed.Event{ Type: statefeed.Synced, Data: &statefeed.SyncedData{ StartTime: time.Unix(0, 0), }, }) } exitRoutine <- true testutil.AssertLogsContain(t, hook, "Sending genesis time") }