diff --git a/beacon-chain/rpc/beacon/validators.go b/beacon-chain/rpc/beacon/validators.go index 2ae9ac852a1..1c28f2b933e 100644 --- a/beacon-chain/rpc/beacon/validators.go +++ b/beacon-chain/rpc/beacon/validators.go @@ -30,35 +30,51 @@ func (bs *Server) ListValidatorBalances( req.PageSize, flags.Get().MaxPageSize) } - currentEpoch := helpers.SlotToEpoch(bs.GenesisTimeFetcher.CurrentSlot()) - requestedEpoch := currentEpoch + res := make([]*ethpb.ValidatorBalances_Balance, 0) + filtered := map[uint64]bool{} // Track filtered validators to prevent duplication in the response. + + headState, err := bs.HeadFetcher.HeadState(ctx) + if err != nil { + return nil, status.Error(codes.Internal, "Could not get head state") + } + + var requestingGenesis bool + var epoch uint64 switch q := req.QueryFilter.(type) { case *ethpb.ListValidatorBalancesRequest_Epoch: - requestedEpoch = q.Epoch + epoch = q.Epoch case *ethpb.ListValidatorBalancesRequest_Genesis: - requestedEpoch = 0 + requestingGenesis = q.Genesis default: - requestedEpoch = currentEpoch + epoch = helpers.CurrentEpoch(headState) } - if requestedEpoch > currentEpoch { + var balances []uint64 + validators := headState.Validators() + if requestingGenesis || epoch < helpers.CurrentEpoch(headState) { + balances, err = bs.BeaconDB.ArchivedBalances(ctx, epoch) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not retrieve balances for epoch %d", epoch) + } + if balances == nil { + return nil, status.Errorf( + codes.NotFound, + "Could not retrieve data for epoch %d, perhaps --archive in the running beacon node is disabled", + 0, + ) + } + } else if epoch == helpers.CurrentEpoch(headState) { + balances = headState.Balances() + } else { + // Otherwise, we are requesting data from the future and we return an error. return nil, status.Errorf( codes.InvalidArgument, "Cannot retrieve information about an epoch in the future, current epoch %d, requesting %d", - currentEpoch, - requestedEpoch, + helpers.CurrentEpoch(headState), + epoch, ) } - res := make([]*ethpb.ValidatorBalances_Balance, 0) - filtered := map[uint64]bool{} // Track filtered validators to prevent duplication in the response. - requestedState, err := bs.StateGen.StateBySlot(ctx, helpers.StartSlot(requestedEpoch)) - if err != nil { - return nil, status.Errorf(codes.Internal, "Could not get state") - } - - validators := requestedState.Validators() - balances := requestedState.Balances() balancesCount := len(balances) for _, pubKey := range req.PublicKeys { // Skip empty public key. @@ -66,7 +82,7 @@ func (bs *Server) ListValidatorBalances( continue } pubkeyBytes := bytesutil.ToBytes48(pubKey) - index, ok := requestedState.ValidatorIndexByPubkey(pubkeyBytes) + index, ok := headState.ValidatorIndexByPubkey(pubkeyBytes) if !ok { return nil, status.Errorf(codes.NotFound, "Could not find validator index for public key %#x", pubkeyBytes) } @@ -88,6 +104,10 @@ func (bs *Server) ListValidatorBalances( for _, index := range req.Indices { if int(index) >= len(balances) { + if epoch <= helpers.CurrentEpoch(headState) { + return nil, status.Errorf(codes.OutOfRange, "Validator index %d does not exist in historical balances", + index) + } return nil, status.Errorf(codes.OutOfRange, "Validator index %d >= balance list %d", index, len(balances)) } @@ -110,7 +130,7 @@ func (bs *Server) ListValidatorBalances( // Otherwise, attempting to paginate 0 balances below would result in an error. if balancesCount == 0 { return ðpb.ValidatorBalances{ - Epoch: requestedEpoch, + Epoch: epoch, Balances: make([]*ethpb.ValidatorBalances_Balance, 0), TotalSize: int32(0), NextPageToken: strconv.Itoa(0), @@ -129,7 +149,7 @@ func (bs *Server) ListValidatorBalances( if len(req.Indices) == 0 && len(req.PublicKeys) == 0 { // Return everything. for i := start; i < end; i++ { - pubkey := requestedState.PubkeyAtIndex(uint64(i)) + pubkey := headState.PubkeyAtIndex(uint64(i)) res = append(res, ðpb.ValidatorBalances_Balance{ PublicKey: pubkey[:], Index: uint64(i), @@ -137,7 +157,7 @@ func (bs *Server) ListValidatorBalances( }) } return ðpb.ValidatorBalances{ - Epoch: requestedEpoch, + Epoch: epoch, Balances: res, TotalSize: int32(balancesCount), NextPageToken: nextPageToken, @@ -145,7 +165,7 @@ func (bs *Server) ListValidatorBalances( } return ðpb.ValidatorBalances{ - Epoch: requestedEpoch, + Epoch: epoch, Balances: res[start:end], TotalSize: int32(balancesCount), NextPageToken: nextPageToken, diff --git a/beacon-chain/rpc/beacon/validators_test.go b/beacon-chain/rpc/beacon/validators_test.go index e9697a76c03..0fd3834439b 100644 --- a/beacon-chain/rpc/beacon/validators_test.go +++ b/beacon-chain/rpc/beacon/validators_test.go @@ -49,7 +49,6 @@ func TestServer_ListValidatorBalances_CannotRequestFutureEpoch(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - GenesisTimeFetcher: &mock.ChainService{}, } wanted := "Cannot retrieve information about an epoch in the future" @@ -57,7 +56,7 @@ func TestServer_ListValidatorBalances_CannotRequestFutureEpoch(t *testing.T) { ctx, ðpb.ListValidatorBalancesRequest{ QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{ - Epoch: helpers.SlotToEpoch(bs.GenesisTimeFetcher.CurrentSlot()) + 1, + Epoch: 1, }, }, ); err != nil && !strings.Contains(err.Error(), wanted) { @@ -75,26 +74,11 @@ func TestServer_ListValidatorBalances_NoResults(t *testing.T) { t.Fatal(err) } bs := &Server{ - GenesisTimeFetcher: &mock.ChainService{}, - StateGen: stategen.New(db, cache.NewStateSummaryCache()), - } - - headState := testutil.NewBeaconState() - b := ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{}} - if err := db.SaveBlock(ctx, b); err != nil { - t.Fatal(err) - } - gRoot, err := ssz.HashTreeRoot(b.Block) - if err != nil { - t.Fatal(err) - } - if err := db.SaveGenesisBlockRoot(ctx, gRoot); err != nil { - t.Fatal(err) - } - if err := db.SaveState(ctx, headState, gRoot); err != nil { - t.Fatal(err) + BeaconDB: db, + HeadFetcher: &mock.ChainService{ + State: st, + }, } - wanted := ðpb.ValidatorBalances{ Balances: make([]*ethpb.ValidatorBalances_Balance, 0), TotalSize: int32(0), @@ -147,28 +131,84 @@ func TestServer_ListValidatorBalances_DefaultResponse_NoArchive(t *testing.T) { if err := st.SetBalances(balances); err != nil { t.Fatal(err) } - b := ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{}} - if err := db.SaveBlock(ctx, b); err != nil { - t.Fatal(err) + bs := &Server{ + BeaconDB: db, + HeadFetcher: &mock.ChainService{ + State: st, + }, } - gRoot, err := ssz.HashTreeRoot(b.Block) + res, err := bs.ListValidatorBalances( + ctx, + ðpb.ListValidatorBalancesRequest{ + QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{ + Epoch: 0, + }, + }, + ) if err != nil { t.Fatal(err) } - if err := db.SaveGenesisBlockRoot(ctx, gRoot); err != nil { + 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++ { + key := make([]byte, 48) + copy(key, strconv.Itoa(i)) + validators[i] = ðpb.Validator{ + PublicKey: key, + WithdrawalCredentials: make([]byte, 32), + } + balances[i] = params.BeaconConfig().MaxEffectiveBalance + } + for i := 0; i < numOldBalances; i++ { + oldBalances[i] = params.BeaconConfig().MaxEffectiveBalance + key := make([]byte, 48) + copy(key, strconv.Itoa(i)) + balancesResponse[i] = ðpb.ValidatorBalances_Balance{ + PublicKey: key, + 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) } - if err := db.SaveState(ctx, st, gRoot); err != nil { + st := testutil.NewBeaconState() + if err := st.SetSlot(helpers.StartSlot(100) /* epoch 100 */); err != nil { + t.Fatal(err) + } + if err := st.SetValidators(validators); err != nil { + t.Fatal(err) + } + if err := st.SetBalances(balances); err != nil { t.Fatal(err) } bs := &Server{ - GenesisTimeFetcher: &mock.ChainService{}, - StateGen: stategen.New(db, cache.NewStateSummaryCache()), + BeaconDB: db, + HeadFetcher: &mock.ChainService{ + State: st, + }, } res, err := bs.ListValidatorBalances( ctx, ðpb.ListValidatorBalancesRequest{ - QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}, + QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{ + Epoch: 50, + }, }, ) if err != nil { @@ -182,31 +222,22 @@ func TestServer_ListValidatorBalances_DefaultResponse_NoArchive(t *testing.T) { func TestServer_ListValidatorBalances_PaginationOutOfRange(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) - ctx := context.Background() + setupValidators(t, db, 3) - st := testutil.NewBeaconState() - b := ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{}} - if err := db.SaveBlock(ctx, b); err != nil { - t.Fatal(err) - } - gRoot, err := ssz.HashTreeRoot(b.Block) + + headState, err := db.HeadState(context.Background()) if err != nil { t.Fatal(err) } - if err := db.SaveGenesisBlockRoot(ctx, gRoot); err != nil { - t.Fatal(err) - } - if err := db.SaveState(ctx, st, gRoot); err != nil { - t.Fatal(err) - } bs := &Server{ - GenesisTimeFetcher: &mock.ChainService{}, - StateGen: stategen.New(db, cache.NewStateSummaryCache()), + HeadFetcher: &mock.ChainService{ + State: headState, + }, } - req := ðpb.ListValidatorBalancesRequest{PageToken: strconv.Itoa(1), PageSize: 100, QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}} - wanted := fmt.Sprintf("page start %d >= list %d", req.PageSize, len(st.Balances())) + 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) } @@ -221,7 +252,7 @@ func TestServer_ListValidatorBalances_ExceedsMaxPageSize(t *testing.T) { exceedsMax, flags.Get().MaxPageSize, ) - req := ðpb.ListValidatorBalancesRequest{PageSize: exceedsMax} + 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) } @@ -236,34 +267,24 @@ func pubKey(i uint64) []byte { func TestServer_ListValidatorBalances_Pagination_Default(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) - ctx := context.Background() + setupValidators(t, db, 100) + headState, err := db.HeadState(context.Background()) if err != nil { t.Fatal(err) } - b := ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{}} - gRoot, err := ssz.HashTreeRoot(b.Block) - if err != nil { - t.Fatal(err) - } - if err := db.SaveGenesisBlockRoot(ctx, gRoot); err != nil { - t.Fatal(err) - } - if err := db.SaveState(ctx, headState, gRoot); err != nil { - t.Fatal(err) - } bs := &Server{ - GenesisTimeFetcher: &mock.ChainService{}, - StateGen: stategen.New(db, cache.NewStateSummaryCache()), + BeaconDB: db, + HeadFetcher: &mock.ChainService{State: headState}, } tests := []struct { req *ethpb.ListValidatorBalancesRequest res *ethpb.ValidatorBalances }{ - {req: ðpb.ListValidatorBalancesRequest{PublicKeys: [][]byte{pubKey(99)}, QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}}, + {req: ðpb.ListValidatorBalancesRequest{PublicKeys: [][]byte{pubKey(99)}}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {Index: 99, PublicKey: pubKey(99), Balance: 99}, @@ -272,7 +293,7 @@ func TestServer_ListValidatorBalances_Pagination_Default(t *testing.T) { TotalSize: 1, }, }, - {req: ðpb.ListValidatorBalancesRequest{Indices: []uint64{1, 2, 3}, QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}}, + {req: ðpb.ListValidatorBalancesRequest{Indices: []uint64{1, 2, 3}}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {Index: 1, PublicKey: pubKey(1), Balance: 1}, @@ -283,7 +304,7 @@ func TestServer_ListValidatorBalances_Pagination_Default(t *testing.T) { TotalSize: 3, }, }, - {req: ðpb.ListValidatorBalancesRequest{PublicKeys: [][]byte{pubKey(10), pubKey(11), pubKey(12)}, QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}}, + {req: ðpb.ListValidatorBalancesRequest{PublicKeys: [][]byte{pubKey(10), pubKey(11), pubKey(12)}}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {Index: 10, PublicKey: pubKey(10), Balance: 10}, @@ -293,7 +314,7 @@ func TestServer_ListValidatorBalances_Pagination_Default(t *testing.T) { NextPageToken: "", TotalSize: 3, }}, - {req: ðpb.ListValidatorBalancesRequest{PublicKeys: [][]byte{pubKey(2), pubKey(3)}, Indices: []uint64{3, 4}, QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}}, // Duplication + {req: ðpb.ListValidatorBalancesRequest{PublicKeys: [][]byte{pubKey(2), pubKey(3)}, Indices: []uint64{3, 4}}, // Duplication res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {Index: 2, PublicKey: pubKey(2), Balance: 2}, @@ -303,7 +324,7 @@ func TestServer_ListValidatorBalances_Pagination_Default(t *testing.T) { NextPageToken: "", TotalSize: 3, }}, - {req: ðpb.ListValidatorBalancesRequest{PublicKeys: [][]byte{{}}, Indices: []uint64{3, 4}, QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}}, // Public key has a blank value + {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: pubKey(3), Balance: 3}, @@ -327,35 +348,26 @@ func TestServer_ListValidatorBalances_Pagination_Default(t *testing.T) { func TestServer_ListValidatorBalances_Pagination_CustomPageSizes(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) - ctx := context.Background() + count := 1000 setupValidators(t, db, count) + headState, err := db.HeadState(context.Background()) if err != nil { t.Fatal(err) } - b := ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{}} - gRoot, err := ssz.HashTreeRoot(b.Block) - if err != nil { - t.Fatal(err) - } - if err := db.SaveGenesisBlockRoot(ctx, gRoot); err != nil { - t.Fatal(err) - } - if err := db.SaveState(ctx, headState, gRoot); err != nil { - t.Fatal(err) - } bs := &Server{ - GenesisTimeFetcher: &mock.ChainService{}, - StateGen: stategen.New(db, cache.NewStateSummaryCache()), + HeadFetcher: &mock.ChainService{ + State: headState, + }, } tests := []struct { req *ethpb.ListValidatorBalancesRequest res *ethpb.ValidatorBalances }{ - {req: ðpb.ListValidatorBalancesRequest{PageToken: strconv.Itoa(1), PageSize: 3, QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}}, + {req: ðpb.ListValidatorBalancesRequest{PageToken: strconv.Itoa(1), PageSize: 3}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {PublicKey: pubKey(3), Index: 3, Balance: uint64(3)}, @@ -363,7 +375,7 @@ func TestServer_ListValidatorBalances_Pagination_CustomPageSizes(t *testing.T) { {PublicKey: pubKey(5), Index: 5, Balance: uint64(5)}}, NextPageToken: strconv.Itoa(2), TotalSize: int32(count)}}, - {req: ðpb.ListValidatorBalancesRequest{PageToken: strconv.Itoa(10), PageSize: 5, QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}}, + {req: ðpb.ListValidatorBalancesRequest{PageToken: strconv.Itoa(10), PageSize: 5}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {PublicKey: pubKey(50), Index: 50, Balance: uint64(50)}, @@ -373,7 +385,7 @@ func TestServer_ListValidatorBalances_Pagination_CustomPageSizes(t *testing.T) { {PublicKey: pubKey(54), Index: 54, Balance: uint64(54)}}, NextPageToken: strconv.Itoa(11), TotalSize: int32(count)}}, - {req: ðpb.ListValidatorBalancesRequest{PageToken: strconv.Itoa(33), PageSize: 3, QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}}, + {req: ðpb.ListValidatorBalancesRequest{PageToken: strconv.Itoa(33), PageSize: 3}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {PublicKey: pubKey(99), Index: 99, Balance: uint64(99)}, @@ -382,7 +394,7 @@ func TestServer_ListValidatorBalances_Pagination_CustomPageSizes(t *testing.T) { }, NextPageToken: "34", TotalSize: int32(count)}}, - {req: ðpb.ListValidatorBalancesRequest{PageSize: 2, QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}}, + {req: ðpb.ListValidatorBalancesRequest{PageSize: 2}, res: ðpb.ValidatorBalances{ Balances: []*ethpb.ValidatorBalances_Balance{ {PublicKey: pubKey(0), Index: 0, Balance: uint64(0)}, @@ -404,34 +416,114 @@ func TestServer_ListValidatorBalances_Pagination_CustomPageSizes(t *testing.T) { func TestServer_ListValidatorBalances_OutOfRange(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) - ctx := context.Background() setupValidators(t, db, 1) headState, err := db.HeadState(context.Background()) if err != nil { t.Fatal(err) } - b := ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{}} - gRoot, err := ssz.HashTreeRoot(b.Block) - if err != nil { + + 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); err == nil || !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) } - if err := db.SaveGenesisBlockRoot(ctx, gRoot); err != nil { + + newerBalances := make([]uint64, len(balances)) + for i := 0; i < len(newerBalances); i++ { + newerBalances[i] = balances[i] * 2 + } + st := testutil.NewBeaconState() + if err := st.SetSlot(params.BeaconConfig().SlotsPerEpoch * 3); err != nil { t.Fatal(err) } - if err := db.SaveState(ctx, headState, gRoot); err != nil { + if err := st.SetValidators(validators); err != nil { t.Fatal(err) } + if err := st.SetBalances(newerBalances); err != nil { + t.Fatal(err) + } + bs := &Server{ + BeaconDB: db, + HeadFetcher: &mock.ChainService{ + State: st, + }, + } + 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) + st := testutil.NewBeaconState() + if err := st.SetSlot(params.BeaconConfig().SlotsPerEpoch * 3); err != nil { + t.Fatal(err) + } + if err := st.SetValidators(newValidators); err != nil { + t.Fatal(err) + } + if err := st.SetBalances(newBalances); err != nil { + t.Fatal(err) + } bs := &Server{ - GenesisTimeFetcher: &mock.ChainService{}, - StateGen: stategen.New(db, cache.NewStateSummaryCache()), + BeaconDB: db, + HeadFetcher: &mock.ChainService{ + State: st, + }, } - req := ðpb.ListValidatorBalancesRequest{Indices: []uint64{uint64(1)}, QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}} - wanted := "Validator index 1 >= balance list 1" - if _, err := bs.ListValidatorBalances(context.Background(), req); err == nil || !strings.Contains(err.Error(), wanted) { - t.Errorf("Expected error %v, received %v", wanted, err) + req := ðpb.ListValidatorBalancesRequest{ + QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{Epoch: 0}, + Indices: []uint64{1, 150, 161}, + } + if _, err := bs.ListValidatorBalances(context.Background(), req); err == nil || !strings.Contains(err.Error(), "does not exist") { + t.Errorf("Wanted out of range error for including newer validators in the arguments, received %v", err) } } @@ -1516,6 +1608,58 @@ func BenchmarkListValidatorBalances(b *testing.B) { } } +func BenchmarkListValidatorBalances_FromArchive(b *testing.B) { + b.StopTimer() + db := dbTest.SetupDB(b) + defer dbTest.TeardownDB(b, db) + + ctx := context.Background() + currentNumValidators := 1000 + numOldBalances := 50 + validators := make([]*ethpb.Validator, currentNumValidators) + oldBalances := make([]uint64, numOldBalances) + for i := 0; i < currentNumValidators; i++ { + validators[i] = ðpb.Validator{ + PublicKey: []byte(strconv.Itoa(i)), + } + } + for i := 0; i < numOldBalances; i++ { + oldBalances[i] = params.BeaconConfig().MaxEffectiveBalance + } + // We archive old balances for epoch 50. + if err := db.SaveArchivedBalances(ctx, 50, oldBalances); err != nil { + b.Fatal(err) + } + headState := testutil.NewBeaconState() + if err := headState.SetSlot(helpers.StartSlot(100 /* epoch 100 */)); err != nil { + b.Fatal(err) + } + if err := headState.SetValidators(validators); err != nil { + b.Fatal(err) + } + bs := &Server{ + BeaconDB: db, + HeadFetcher: &mock.ChainService{ + State: headState, + }, + } + + b.StartTimer() + for i := 0; i < b.N; i++ { + if _, err := bs.ListValidatorBalances( + ctx, + ðpb.ListValidatorBalancesRequest{ + QueryFilter: ðpb.ListValidatorBalancesRequest_Epoch{ + Epoch: 50, + }, + PageSize: 100, + }, + ); err != nil { + b.Fatal(err) + } + } +} + func setupValidators(t testing.TB, db db.Database, count int) ([]*ethpb.Validator, []uint64) { ctx := context.Background() balances := make([]uint64, count)