package beacon import ( "context" "fmt" "reflect" "strconv" "strings" "testing" "github.com/gogo/protobuf/proto" ptypes "github.com/gogo/protobuf/types" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" "github.com/prysmaticlabs/go-bitfield" "github.com/prysmaticlabs/go-ssz" mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/db" dbTest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing" pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" "github.com/prysmaticlabs/prysm/shared/params" ) func init() { // Use minimal config to reduce test setup time. params.OverrideBeaconConfig(params.MinimalSpecConfig()) } func TestServer_ListValidatorBalances_CannotRequestFutureEpoch(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{Slot: 0}, }, } wanted := "Cannot retrieve information about an epoch in the future" if _, err := bs.ListValidatorBalances( ctx, ðpb.ListValidatorBalancesRequest{ QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{ Epoch: 1, }, }, ); err != nil && !strings.Contains(err.Error(), wanted) { t.Errorf("Expected error %v, received %v", wanted, err) } } func TestServer_ListValidatorBalances_NoResults(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{Slot: 0}, }, } wanted := ðpb.ValidatorBalances{ Balances: make([]*ethpb.ValidatorBalances_Balance, 0), TotalSize: int32(0), NextPageToken: strconv.Itoa(0), } res, err := bs.ListValidatorBalances( ctx, ðpb.ListValidatorBalancesRequest{ QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{ Epoch: 0, }, }, ) if err != nil { t.Fatal(err) } if !proto.Equal(wanted, res) { t.Errorf("Wanted %v, received %v", wanted, res) } } func TestServer_ListValidatorBalances_DefaultResponse_NoArchive(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() numItems := 100 validators := make([]*ethpb.Validator, numItems) balances := make([]uint64, numItems) balancesResponse := make([]*ethpb.ValidatorBalances_Balance, numItems) for i := 0; i < numItems; i++ { validators[i] = ðpb.Validator{ PublicKey: []byte(strconv.Itoa(i)), } balances[i] = params.BeaconConfig().MaxEffectiveBalance balancesResponse[i] = ðpb.ValidatorBalances_Balance{ PublicKey: []byte(strconv.Itoa(i)), Index: uint64(i), Balance: params.BeaconConfig().MaxEffectiveBalance, } } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{ Slot: 0, Validators: validators, Balances: balances, }, }, } res, err := bs.ListValidatorBalances( ctx, ðpb.ListValidatorBalancesRequest{ QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{ Epoch: 0, }, }, ) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(balancesResponse, res.Balances) { t.Errorf("Wanted %v, received %v", balancesResponse, res.Balances) } } func TestServer_ListValidatorBalances_DefaultResponse_FromArchive(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() currentNumValidators := 100 numOldBalances := 50 validators := make([]*ethpb.Validator, currentNumValidators) balances := make([]uint64, currentNumValidators) oldBalances := make([]uint64, numOldBalances) balancesResponse := make([]*ethpb.ValidatorBalances_Balance, numOldBalances) for i := 0; i < currentNumValidators; i++ { validators[i] = ðpb.Validator{ PublicKey: []byte(strconv.Itoa(i)), } balances[i] = params.BeaconConfig().MaxEffectiveBalance } for i := 0; i < numOldBalances; i++ { oldBalances[i] = params.BeaconConfig().MaxEffectiveBalance balancesResponse[i] = ðpb.ValidatorBalances_Balance{ PublicKey: []byte(strconv.Itoa(i)), Index: uint64(i), Balance: params.BeaconConfig().MaxEffectiveBalance, } } // We archive old balances for epoch 50. if err := db.SaveArchivedBalances(ctx, 50, oldBalances); err != nil { t.Fatal(err) } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{ Slot: helpers.StartSlot(100 /* epoch 100 */), Validators: validators, Balances: balances, }, }, } res, err := bs.ListValidatorBalances( ctx, ðpb.ListValidatorBalancesRequest{ QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{ Epoch: 50, }, }, ) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(balancesResponse, res.Balances) { t.Errorf("Wanted %v, received %v", balancesResponse, res.Balances) } } func TestServer_ListValidatorBalances_PaginationOutOfRange(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) setupValidators(t, db, 3) headState, err := db.HeadState(context.Background()) if err != nil { t.Fatal(err) } bs := &Server{ HeadFetcher: &mock.ChainService{ State: headState, }, } req := ðpb.ListValidatorBalancesRequest{PageToken: strconv.Itoa(1), PageSize: 100} wanted := fmt.Sprintf("page start %d >= list %d", req.PageSize, len(headState.Balances)) if _, err := bs.ListValidatorBalances(context.Background(), req); err != nil && !strings.Contains(err.Error(), wanted) { t.Errorf("Expected error %v, received %v", wanted, err) } } func TestServer_ListValidatorBalances_ExceedsMaxPageSize(t *testing.T) { bs := &Server{} exceedsMax := int32(params.BeaconConfig().MaxPageSize + 1) wanted := fmt.Sprintf( "Requested page size %d can not be greater than max size %d", exceedsMax, params.BeaconConfig().MaxPageSize, ) req := ðpb.ListValidatorBalancesRequest{PageToken: strconv.Itoa(0), PageSize: exceedsMax} if _, err := bs.ListValidatorBalances(context.Background(), req); err != nil && !strings.Contains(err.Error(), wanted) { t.Errorf("Expected error %v, received %v", wanted, err) } } func TestServer_ListValidatorBalances_Pagination_Default(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) setupValidators(t, db, 100) headState, err := db.HeadState(context.Background()) if err != nil { t.Fatal(err) } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{State: headState}, } tests := []struct { req *ethpb.ListValidatorBalancesRequest res *ethpb.ValidatorBalances }{ {req: ðpb.ListValidatorBalancesRequest{PublicKeys: [][]byte{{99}}}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {Index: 99, PublicKey: []byte{99}, Balance: 99}, }, NextPageToken: "", TotalSize: 1, }, }, {req: ðpb.ListValidatorBalancesRequest{Indices: []uint64{1, 2, 3}}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {Index: 1, PublicKey: []byte{1}, Balance: 1}, {Index: 2, PublicKey: []byte{2}, Balance: 2}, {Index: 3, PublicKey: []byte{3}, Balance: 3}, }, NextPageToken: "", TotalSize: 3, }, }, {req: ðpb.ListValidatorBalancesRequest{PublicKeys: [][]byte{{10}, {11}, {12}}}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {Index: 10, PublicKey: []byte{10}, Balance: 10}, {Index: 11, PublicKey: []byte{11}, Balance: 11}, {Index: 12, PublicKey: []byte{12}, Balance: 12}, }, NextPageToken: "", TotalSize: 3, }}, {req: ðpb.ListValidatorBalancesRequest{PublicKeys: [][]byte{{2}, {3}}, Indices: []uint64{3, 4}}, // Duplication res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {Index: 2, PublicKey: []byte{2}, Balance: 2}, {Index: 3, PublicKey: []byte{3}, Balance: 3}, {Index: 4, PublicKey: []byte{4}, Balance: 4}, }, NextPageToken: "", TotalSize: 3, }}, {req: ðpb.ListValidatorBalancesRequest{PublicKeys: [][]byte{{}}, Indices: []uint64{3, 4}}, // Public key has a blank value res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {Index: 3, PublicKey: []byte{3}, Balance: 3}, {Index: 4, PublicKey: []byte{4}, Balance: 4}, }, NextPageToken: "", TotalSize: 2, }}, } for _, test := range tests { res, err := bs.ListValidatorBalances(context.Background(), test.req) if err != nil { t.Fatal(err) } if !proto.Equal(res, test.res) { t.Errorf("Expected %v, received %v", test.res, res) } } } func TestServer_ListValidatorBalances_Pagination_CustomPageSizes(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) count := 1000 setupValidators(t, db, count) headState, err := db.HeadState(context.Background()) if err != nil { t.Fatal(err) } bs := &Server{ HeadFetcher: &mock.ChainService{ State: headState, }, } tests := []struct { req *ethpb.ListValidatorBalancesRequest res *ethpb.ValidatorBalances }{ {req: ðpb.ListValidatorBalancesRequest{PageToken: strconv.Itoa(1), PageSize: 3}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {PublicKey: []byte{3}, Index: 3, Balance: uint64(3)}, {PublicKey: []byte{4}, Index: 4, Balance: uint64(4)}, {PublicKey: []byte{5}, Index: 5, Balance: uint64(5)}}, NextPageToken: strconv.Itoa(2), TotalSize: int32(count)}}, {req: ðpb.ListValidatorBalancesRequest{PageToken: strconv.Itoa(10), PageSize: 5}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {PublicKey: []byte{50}, Index: 50, Balance: uint64(50)}, {PublicKey: []byte{51}, Index: 51, Balance: uint64(51)}, {PublicKey: []byte{52}, Index: 52, Balance: uint64(52)}, {PublicKey: []byte{53}, Index: 53, Balance: uint64(53)}, {PublicKey: []byte{54}, Index: 54, Balance: uint64(54)}}, NextPageToken: strconv.Itoa(11), TotalSize: int32(count)}}, {req: ðpb.ListValidatorBalancesRequest{PageToken: strconv.Itoa(33), PageSize: 3}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {PublicKey: []byte{99}, Index: 99, Balance: uint64(99)}, {PublicKey: []byte{100}, Index: 100, Balance: uint64(100)}, {PublicKey: []byte{101}, Index: 101, Balance: uint64(101)}, }, NextPageToken: "34", TotalSize: int32(count)}}, {req: ðpb.ListValidatorBalancesRequest{PageSize: 2}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {PublicKey: []byte{0}, Index: 0, Balance: uint64(0)}, {PublicKey: []byte{1}, Index: 1, Balance: uint64(1)}}, NextPageToken: strconv.Itoa(1), TotalSize: int32(count)}}, } for _, test := range tests { res, err := bs.ListValidatorBalances(context.Background(), test.req) if err != nil { t.Fatal(err) } if !proto.Equal(res, test.res) { t.Errorf("Expected %v, received %v", test.res, res) } } } func TestServer_ListValidatorBalances_OutOfRange(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) setupValidators(t, db, 1) headState, err := db.HeadState(context.Background()) if err != nil { t.Fatal(err) } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{State: headState}, } req := ðpb.ListValidatorBalancesRequest{Indices: []uint64{uint64(1)}} wanted := "does not exist" if _, err := bs.ListValidatorBalances(context.Background(), req); !strings.Contains(err.Error(), wanted) { t.Errorf("Expected error %v, received %v", wanted, err) } } func TestServer_ListValidatorBalances_FromArchive(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() epoch := uint64(0) validators, balances := setupValidators(t, db, 100) if err := db.SaveArchivedBalances(ctx, epoch, balances); err != nil { t.Fatal(err) } newerBalances := make([]uint64, len(balances)) for i := 0; i < len(newerBalances); i++ { newerBalances[i] = balances[i] * 2 } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{ Slot: params.BeaconConfig().SlotsPerEpoch * 3, Validators: validators, Balances: newerBalances, }, }, } req := ðpb.ListValidatorBalancesRequest{ QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}, Indices: []uint64{uint64(1)}, } res, err := bs.ListValidatorBalances(context.Background(), req) if err != nil { t.Fatal(err) } // We should expect a response containing the old balance from epoch 0, // not the new balance from the current state. want := []*ethpb.ValidatorBalances_Balance{ { PublicKey: validators[1].PublicKey, Index: 1, Balance: balances[1], }, } if !reflect.DeepEqual(want, res.Balances) { t.Errorf("Wanted %v, received %v", want, res.Balances) } } func TestServer_ListValidatorBalances_FromArchive_NewValidatorNotFound(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() epoch := uint64(0) _, balances := setupValidators(t, db, 100) if err := db.SaveArchivedBalances(ctx, epoch, balances); err != nil { t.Fatal(err) } newValidators, newBalances := setupValidators(t, db, 200) bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{ Slot: params.BeaconConfig().SlotsPerEpoch * 3, Validators: newValidators, Balances: newBalances, }, }, } req := ðpb.ListValidatorBalancesRequest{ QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}, Indices: []uint64{1, 150, 161}, } if _, err := bs.ListValidatorBalances(context.Background(), req); !strings.Contains(err.Error(), "does not exist") { t.Errorf("Wanted out of range error for including newer validators in the arguments, received %v", err) } } func TestServer_ListValidators_CannotRequestFutureEpoch(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{Slot: 0}, }, } wanted := "Cannot retrieve information about an epoch in the future" if _, err := bs.ListValidators( ctx, ðpb.ListValidatorsRequest{ QueryFilter: ðpb.ListValidatorsRequest_Epoch{ Epoch: 1, }, }, ); err != nil && !strings.Contains(err.Error(), wanted) { t.Errorf("Expected error %v, received %v", wanted, err) } } func TestServer_ListValidators_NoResults(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{Slot: 0}, }, } wanted := ðpb.Validators{ Validators: make([]*ethpb.Validator, 0), TotalSize: int32(0), NextPageToken: strconv.Itoa(0), } res, err := bs.ListValidators( ctx, ðpb.ListValidatorsRequest{ QueryFilter: ðpb.ListValidatorsRequest_Epoch{ Epoch: 0, }, }, ) if err != nil { t.Fatal(err) } if !proto.Equal(wanted, res) { t.Errorf("Wanted %v, received %v", wanted, res) } } func TestServer_ListValidators_OnlyActiveValidators(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() count := 100 balances := make([]uint64, count) validators := make([]*ethpb.Validator, count) activeValidators := make([]*ethpb.Validator, 0) for i := 0; i < count; i++ { if err := db.SaveValidatorIndex(ctx, [48]byte{byte(i)}, uint64(i)); err != nil { t.Fatal(err) } balances[i] = params.BeaconConfig().MaxEffectiveBalance // We mark even validators as active, and odd validators as inactive. if i%2 == 0 { val := ðpb.Validator{ PublicKey: []byte{byte(i)}, ActivationEpoch: 0, ExitEpoch: params.BeaconConfig().FarFutureEpoch, } validators[i] = val activeValidators = append(activeValidators, val) } else { validators[i] = ðpb.Validator{ PublicKey: []byte{byte(i)}, ActivationEpoch: 0, ExitEpoch: 0, } } } headState := &pbp2p.BeaconState{Validators: validators, Balances: balances} bs := &Server{ HeadFetcher: &mock.ChainService{ State: headState, }, } received, err := bs.ListValidators(context.Background(), ðpb.ListValidatorsRequest{ Active: true, }) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(activeValidators, received.Validators) { t.Errorf("Wanted %v, received %v", activeValidators, received.Validators) } } func TestServer_ListValidators_NoPagination(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) validators, _ := setupValidators(t, db, 100) headState, err := db.HeadState(context.Background()) if err != nil { t.Fatal(err) } bs := &Server{ HeadFetcher: &mock.ChainService{ State: headState, }, FinalizationFetcher: &mock.ChainService{ FinalizedCheckPoint: ðpb.Checkpoint{ Epoch: 0, }, }, } received, err := bs.ListValidators(context.Background(), ðpb.ListValidatorsRequest{}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(validators, received.Validators) { t.Fatal("Incorrect respond of validators") } } func TestServer_ListValidators_Pagination(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) count := 100 setupValidators(t, db, count) headState, err := db.HeadState(context.Background()) if err != nil { t.Fatal(err) } bs := &Server{ HeadFetcher: &mock.ChainService{ State: headState, }, FinalizationFetcher: &mock.ChainService{ FinalizedCheckPoint: ðpb.Checkpoint{ Epoch: 0, }, }, } tests := []struct { req *ethpb.ListValidatorsRequest res *ethpb.Validators }{ {req: ðpb.ListValidatorsRequest{PageToken: strconv.Itoa(1), PageSize: 3}, res: ðpb.Validators{ Validators: []*ethpb.Validator{ {PublicKey: []byte{3}}, {PublicKey: []byte{4}}, {PublicKey: []byte{5}}}, NextPageToken: strconv.Itoa(2), TotalSize: int32(count)}}, {req: ðpb.ListValidatorsRequest{PageToken: strconv.Itoa(10), PageSize: 5}, res: ðpb.Validators{ Validators: []*ethpb.Validator{ {PublicKey: []byte{50}}, {PublicKey: []byte{51}}, {PublicKey: []byte{52}}, {PublicKey: []byte{53}}, {PublicKey: []byte{54}}}, NextPageToken: strconv.Itoa(11), TotalSize: int32(count)}}, {req: ðpb.ListValidatorsRequest{PageToken: strconv.Itoa(33), PageSize: 3}, res: ðpb.Validators{ Validators: []*ethpb.Validator{ {PublicKey: []byte{99}}}, NextPageToken: "", TotalSize: int32(count)}}, {req: ðpb.ListValidatorsRequest{PageSize: 2}, res: ðpb.Validators{ Validators: []*ethpb.Validator{ {PublicKey: []byte{0}}, {PublicKey: []byte{1}}}, NextPageToken: strconv.Itoa(1), TotalSize: int32(count)}}, } for _, test := range tests { res, err := bs.ListValidators(context.Background(), test.req) if err != nil { t.Fatal(err) } if !proto.Equal(res, test.res) { t.Errorf("Incorrect validator response, wanted %v, received %v", test.res, res) } } } func TestServer_ListValidators_PaginationOutOfRange(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) count := 1 validators, _ := setupValidators(t, db, count) headState, err := db.HeadState(context.Background()) if err != nil { t.Fatal(err) } bs := &Server{ HeadFetcher: &mock.ChainService{ State: headState, }, FinalizationFetcher: &mock.ChainService{ FinalizedCheckPoint: ðpb.Checkpoint{ Epoch: 0, }, }, } req := ðpb.ListValidatorsRequest{PageToken: strconv.Itoa(1), PageSize: 100} wanted := fmt.Sprintf("page start %d >= list %d", req.PageSize, len(validators)) if _, err := bs.ListValidators(context.Background(), req); !strings.Contains(err.Error(), wanted) { t.Errorf("Expected error %v, received %v", wanted, err) } } func TestServer_ListValidators_ExceedsMaxPageSize(t *testing.T) { bs := &Server{} exceedsMax := int32(params.BeaconConfig().MaxPageSize + 1) wanted := fmt.Sprintf("Requested page size %d can not be greater than max size %d", exceedsMax, params.BeaconConfig().MaxPageSize) req := ðpb.ListValidatorsRequest{PageToken: strconv.Itoa(0), PageSize: exceedsMax} if _, err := bs.ListValidators(context.Background(), req); !strings.Contains(err.Error(), wanted) { t.Errorf("Expected error %v, received %v", wanted, err) } } func TestServer_ListValidators_DefaultPageSize(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) validators, _ := setupValidators(t, db, 1000) headState, err := db.HeadState(context.Background()) if err != nil { t.Fatal(err) } bs := &Server{ HeadFetcher: &mock.ChainService{ State: headState, }, FinalizationFetcher: &mock.ChainService{ FinalizedCheckPoint: ðpb.Checkpoint{ Epoch: 0, }, }, } req := ðpb.ListValidatorsRequest{} res, err := bs.ListValidators(context.Background(), req) if err != nil { t.Fatal(err) } i := 0 j := params.BeaconConfig().DefaultPageSize if !reflect.DeepEqual(res.Validators, validators[i:j]) { t.Error("Incorrect respond of validators") } } func TestServer_ListValidators_FromOldEpoch(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) numEpochs := 30 validators := make([]*ethpb.Validator, numEpochs) for i := 0; i < numEpochs; i++ { validators[i] = ðpb.Validator{ ActivationEpoch: uint64(i), } } bs := &Server{ HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{ Slot: helpers.StartSlot(30), Validators: validators, }, }, } req := ðpb.ListValidatorsRequest{ QueryFilter: ðpb.ListValidatorsRequest_Genesis{ Genesis: true, }, } res, err := bs.ListValidators(context.Background(), req) if err != nil { t.Fatal(err) } if len(res.Validators) != 1 { t.Errorf("Wanted 1 validator at genesis, received %d", len(res.Validators)) } req = ðpb.ListValidatorsRequest{ QueryFilter: ðpb.ListValidatorsRequest_Epoch{ Epoch: 20, }, } res, err = bs.ListValidators(context.Background(), req) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(res.Validators, validators[:21]) { t.Errorf("Incorrect number of validators, wanted %d received %d", len(validators[:21]), len(res.Validators)) } } func TestServer_GetValidatorActiveSetChanges_CannotRequestFutureEpoch(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{Slot: 0}, }, } wanted := "Cannot retrieve information about an epoch in the future" if _, err := bs.GetValidatorActiveSetChanges( ctx, ðpb.GetValidatorActiveSetChangesRequest{ QueryFilter: ðpb.GetValidatorActiveSetChangesRequest_Epoch{ Epoch: 1, }, }, ); err != nil && !strings.Contains(err.Error(), wanted) { t.Errorf("Expected error %v, received %v", wanted, err) } } func TestServer_GetValidatorActiveSetChanges(t *testing.T) { ctx := context.Background() validators := make([]*ethpb.Validator, 6) headState := &pbp2p.BeaconState{ Slot: 0, Validators: validators, } for i := 0; i < len(validators); i++ { activationEpoch := params.BeaconConfig().FarFutureEpoch withdrawableEpoch := params.BeaconConfig().FarFutureEpoch exitEpoch := params.BeaconConfig().FarFutureEpoch slashed := false // Mark indices divisible by two as activated. if i%2 == 0 { activationEpoch = helpers.DelayedActivationExitEpoch(0) } else if i%3 == 0 { // Mark indices divisible by 3 as slashed. withdrawableEpoch = params.BeaconConfig().EpochsPerSlashingsVector slashed = true } else if i%5 == 0 { // Mark indices divisible by 5 as exited. exitEpoch = 0 withdrawableEpoch = params.BeaconConfig().MinValidatorWithdrawabilityDelay } headState.Validators[i] = ðpb.Validator{ ActivationEpoch: activationEpoch, PublicKey: []byte(strconv.Itoa(i)), WithdrawableEpoch: withdrawableEpoch, Slashed: slashed, ExitEpoch: exitEpoch, } } bs := &Server{ HeadFetcher: &mock.ChainService{ State: headState, }, FinalizationFetcher: &mock.ChainService{ FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 0}, }, } res, err := bs.GetValidatorActiveSetChanges(ctx, ðpb.GetValidatorActiveSetChangesRequest{}) if err != nil { t.Fatal(err) } wantedActive := [][]byte{ []byte("0"), []byte("2"), []byte("4"), } wantedSlashed := [][]byte{ []byte("3"), } wantedExited := [][]byte{ []byte("5"), } wanted := ðpb.ActiveSetChanges{ Epoch: 0, ActivatedPublicKeys: wantedActive, ExitedPublicKeys: wantedExited, SlashedPublicKeys: wantedSlashed, } if !proto.Equal(wanted, res) { t.Errorf("Wanted %v, received %v", wanted, res) } } func TestServer_GetValidatorActiveSetChanges_FromArchive(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() validators := make([]*ethpb.Validator, 6) headState := &pbp2p.BeaconState{ Slot: helpers.StartSlot(100), Validators: validators, } activatedIndices := make([]uint64, 0) slashedIndices := make([]uint64, 0) exitedIndices := make([]uint64, 0) for i := 0; i < len(validators); i++ { // Mark indices divisible by two as activated. if i%2 == 0 { activatedIndices = append(activatedIndices, uint64(i)) } else if i%3 == 0 { // Mark indices divisible by 3 as slashed. slashedIndices = append(slashedIndices, uint64(i)) } else if i%5 == 0 { // Mark indices divisible by 5 as exited. exitedIndices = append(exitedIndices, uint64(i)) } headState.Validators[i] = ðpb.Validator{ PublicKey: []byte(strconv.Itoa(i)), } } archivedChanges := &pbp2p.ArchivedActiveSetChanges{ Activated: activatedIndices, Exited: exitedIndices, Slashed: slashedIndices, } // We store the changes during the genesis epoch. if err := db.SaveArchivedActiveValidatorChanges(ctx, 0, archivedChanges); err != nil { t.Fatal(err) } // We store the same changes during epoch 5 for further testing. if err := db.SaveArchivedActiveValidatorChanges(ctx, 5, archivedChanges); err != nil { t.Fatal(err) } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: headState, }, } res, err := bs.GetValidatorActiveSetChanges(ctx, ðpb.GetValidatorActiveSetChangesRequest{ QueryFilter: ðpb.GetValidatorActiveSetChangesRequest_Genesis{Genesis: true}, }) if err != nil { t.Fatal(err) } wantedActive := [][]byte{ []byte("0"), []byte("2"), []byte("4"), } wantedSlashed := [][]byte{ []byte("3"), } wantedExited := [][]byte{ []byte("5"), } wanted := ðpb.ActiveSetChanges{ Epoch: 0, ActivatedPublicKeys: wantedActive, ExitedPublicKeys: wantedExited, SlashedPublicKeys: wantedSlashed, } if !proto.Equal(wanted, res) { t.Errorf("Wanted %v, received %v", wanted, res) } res, err = bs.GetValidatorActiveSetChanges(ctx, ðpb.GetValidatorActiveSetChangesRequest{ QueryFilter: ðpb.GetValidatorActiveSetChangesRequest_Epoch{Epoch: 5}, }) if err != nil { t.Fatal(err) } wanted.Epoch = 5 if !proto.Equal(wanted, res) { t.Errorf("Wanted %v, received %v", wanted, res) } } func TestServer_GetValidatorQueue_PendingActivation(t *testing.T) { headState := &pbp2p.BeaconState{ Validators: []*ethpb.Validator{ { ActivationEpoch: helpers.DelayedActivationExitEpoch(0), ActivationEligibilityEpoch: 3, PublicKey: []byte("3"), }, { ActivationEpoch: helpers.DelayedActivationExitEpoch(0), ActivationEligibilityEpoch: 2, PublicKey: []byte("2"), }, { ActivationEpoch: helpers.DelayedActivationExitEpoch(0), ActivationEligibilityEpoch: 1, PublicKey: []byte("1"), }, }, FinalizedCheckpoint: ðpb.Checkpoint{ Epoch: 0, }, } bs := &Server{ HeadFetcher: &mock.ChainService{ State: headState, }, } res, err := bs.GetValidatorQueue(context.Background(), &ptypes.Empty{}) if err != nil { t.Fatal(err) } // We verify the keys are properly sorted by the validators' activation eligibility epoch. wanted := [][]byte{ []byte("1"), []byte("2"), []byte("3"), } activeValidatorCount, err := helpers.ActiveValidatorCount(headState, helpers.CurrentEpoch(headState)) if err != nil { t.Fatal(err) } wantChurn, err := helpers.ValidatorChurnLimit(activeValidatorCount) if err != nil { t.Fatal(err) } if res.ChurnLimit != wantChurn { t.Errorf("Wanted churn %d, received %d", wantChurn, res.ChurnLimit) } if !reflect.DeepEqual(res.ActivationPublicKeys, wanted) { t.Errorf("Wanted %v, received %v", wanted, res.ActivationPublicKeys) } } func TestServer_GetValidatorQueue_PendingExit(t *testing.T) { headState := &pbp2p.BeaconState{ Validators: []*ethpb.Validator{ { ActivationEpoch: 0, ExitEpoch: 4, WithdrawableEpoch: 3, PublicKey: []byte("3"), }, { ActivationEpoch: 0, ExitEpoch: 4, WithdrawableEpoch: 2, PublicKey: []byte("2"), }, { ActivationEpoch: 0, ExitEpoch: 4, WithdrawableEpoch: 1, PublicKey: []byte("1"), }, }, FinalizedCheckpoint: ðpb.Checkpoint{ Epoch: 0, }, } bs := &Server{ HeadFetcher: &mock.ChainService{ State: headState, }, } res, err := bs.GetValidatorQueue(context.Background(), &ptypes.Empty{}) if err != nil { t.Fatal(err) } // We verify the keys are properly sorted by the validators' withdrawable epoch. wanted := [][]byte{ []byte("1"), []byte("2"), []byte("3"), } activeValidatorCount, err := helpers.ActiveValidatorCount(headState, helpers.CurrentEpoch(headState)) if err != nil { t.Fatal(err) } wantChurn, err := helpers.ValidatorChurnLimit(activeValidatorCount) if err != nil { t.Fatal(err) } if res.ChurnLimit != wantChurn { t.Errorf("Wanted churn %d, received %d", wantChurn, res.ChurnLimit) } if !reflect.DeepEqual(res.ExitPublicKeys, wanted) { t.Errorf("Wanted %v, received %v", wanted, res.ExitPublicKeys) } } func TestServer_GetValidatorParticipation_CannotRequestCurrentEpoch(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{Slot: helpers.StartSlot(2)}, }, } wanted := "Cannot retrieve information about an epoch currently in progress" if _, err := bs.GetValidatorParticipation( ctx, ðpb.GetValidatorParticipationRequest{ QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{ Epoch: 2, }, }, ); err != nil && !strings.Contains(err.Error(), wanted) { t.Errorf("Expected error %v, received %v", wanted, err) } } func TestServer_GetValidatorParticipation_CannotRequestFutureEpoch(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{Slot: 0}, }, } wanted := "Cannot retrieve information about an epoch in the future" if _, err := bs.GetValidatorParticipation( ctx, ðpb.GetValidatorParticipationRequest{ QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{ Epoch: 1, }, }, ); err != nil && !strings.Contains(err.Error(), wanted) { t.Errorf("Expected error %v, received %v", wanted, err) } } func TestServer_GetValidatorParticipation_FromArchive(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() epoch := uint64(4) part := ðpb.ValidatorParticipation{ GlobalParticipationRate: 1.0, VotedEther: 20, EligibleEther: 20, } if err := db.SaveArchivedValidatorParticipation(ctx, epoch-2, part); err != nil { t.Fatal(err) } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: &pbp2p.BeaconState{ Slot: helpers.StartSlot(epoch + 1), FinalizedCheckpoint: ðpb.Checkpoint{ Epoch: epoch + 1, }, }, }, } if _, err := bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{ QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{ Epoch: epoch + 2, }, }); err == nil { t.Error("Expected error when requesting future epoch, received nil") } // We request data from epoch 0, which we didn't archive, so we should expect an error. if _, err := bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{ QueryFilter: ðpb.GetValidatorParticipationRequest_Genesis{ Genesis: true, }, }); err == nil { t.Error("Expected error when data from archive is not found, received nil") } want := ðpb.ValidatorParticipationResponse{ Epoch: epoch - 2, Finalized: true, Participation: part, } res, err := bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{ QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{ Epoch: epoch - 2, }, }) if err != nil { t.Fatal(err) } if !proto.Equal(want, res) { t.Errorf("Wanted %v, received %v", want, res) } } func TestServer_GetValidatorParticipation_FromArchive_FinalizedEpoch(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() part := ðpb.ValidatorParticipation{ GlobalParticipationRate: 1.0, VotedEther: 20, EligibleEther: 20, } epoch := uint64(1) // We archive data for epoch 1. if err := db.SaveArchivedValidatorParticipation(ctx, epoch, part); err != nil { t.Fatal(err) } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ // 10 epochs into the future. State: &pbp2p.BeaconState{ Slot: helpers.StartSlot(epoch + 10), FinalizedCheckpoint: ðpb.Checkpoint{ // We say there have been 5 epochs since finality. Epoch: epoch + 5, }, }, }, } want := ðpb.ValidatorParticipationResponse{ Epoch: epoch, Finalized: true, Participation: part, } // We request epoch 1. res, err := bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{ QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{ Epoch: epoch, }, }) if err != nil { t.Fatal(err) } if !proto.Equal(want, res) { t.Errorf("Wanted %v, received %v", want, res) } } func TestServer_GetValidatorParticipation_PrevEpoch(t *testing.T) { helpers.ClearAllCaches() db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() epoch := uint64(1) attestedBalance := uint64(1) validatorCount := uint64(100) validators := make([]*ethpb.Validator, validatorCount) balances := make([]uint64, validatorCount) for i := 0; i < len(validators); i++ { validators[i] = ðpb.Validator{ ExitEpoch: params.BeaconConfig().FarFutureEpoch, EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, } balances[i] = params.BeaconConfig().MaxEffectiveBalance } atts := []*pbp2p.PendingAttestation{{Data: ðpb.AttestationData{Target: ðpb.Checkpoint{}}}} s := &pbp2p.BeaconState{ Slot: epoch*params.BeaconConfig().SlotsPerEpoch + 1, Validators: validators, Balances: balances, BlockRoots: make([][]byte, 128), Slashings: []uint64{0, 1e9, 1e9}, RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), CurrentEpochAttestations: atts, FinalizedCheckpoint: ðpb.Checkpoint{}, JustificationBits: bitfield.Bitvector4{0x00}, CurrentJustifiedCheckpoint: ðpb.Checkpoint{}, } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{State: s}, } res, err := bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{}) if err != nil { t.Fatal(err) } wanted := ðpb.ValidatorParticipation{ VotedEther: attestedBalance, EligibleEther: validatorCount * params.BeaconConfig().MaxEffectiveBalance, GlobalParticipationRate: float32(attestedBalance) / float32(validatorCount*params.BeaconConfig().MaxEffectiveBalance), } if !reflect.DeepEqual(res.Participation, wanted) { t.Error("Incorrect validator participation respond") } } func setupValidators(t *testing.T, db db.Database, count int) ([]*ethpb.Validator, []uint64) { ctx := context.Background() balances := make([]uint64, count) validators := make([]*ethpb.Validator, 0, count) for i := 0; i < count; i++ { if err := db.SaveValidatorIndex(ctx, [48]byte{byte(i)}, uint64(i)); err != nil { t.Fatal(err) } balances[i] = uint64(i) validators = append(validators, ðpb.Validator{ PublicKey: []byte{byte(i)}, }) } blk := ðpb.BeaconBlock{ Slot: 0, } blockRoot, err := ssz.SigningRoot(blk) if err != nil { t.Fatal(err) } if err := db.SaveHeadBlockRoot(ctx, blockRoot); err != nil { t.Fatal(err) } if err := db.SaveState( context.Background(), &pbp2p.BeaconState{Validators: validators, Balances: balances}, blockRoot, ); err != nil { t.Fatal(err) } return validators, balances }