package beacon import ( "context" "fmt" "reflect" "strconv" "strings" "testing" "github.com/prysmaticlabs/go-ssz" mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" dbTest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing" pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" "github.com/prysmaticlabs/prysm/shared/params" ) func TestServer_ListAssignments_InputOutOfRange(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() setupValidators(t, db, 1) headState, err := db.HeadState(ctx) if err != nil { t.Fatal(err) } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: headState, }, } wanted := fmt.Sprintf("page start %d >= list %d", 0, 0) if _, err := bs.ListValidatorAssignments( context.Background(), ðpb.ListValidatorAssignmentsRequest{ QueryFilter: ðpb.ListValidatorAssignmentsRequest_Genesis{Genesis: true}, }, ); err != nil && !strings.Contains(err.Error(), wanted) { t.Errorf("Expected error %v, received %v", wanted, err) } } func TestServer_ListAssignments_Pagination_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.ListValidatorAssignmentsRequest{ PageToken: strconv.Itoa(0), PageSize: exceedsMax, } if _, err := bs.ListValidatorAssignments(context.Background(), req); err != nil && !strings.Contains(err.Error(), wanted) { t.Errorf("Expected error %v, received %v", wanted, err) } } func TestServer_ListAssignments_Pagination_DefaultPageSize_NoArchive(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() count := 1000 validators := make([]*ethpb.Validator, 0, count) for i := 0; i < count; i++ { var pubKey [48]byte copy(pubKey[:], strconv.Itoa(i)) if err := db.SaveValidatorIndex(ctx, pubKey, uint64(i)); err != nil { t.Fatal(err) } // Mark the validators with index divisible by 3 inactive. if i%3 == 0 { validators = append(validators, ðpb.Validator{ PublicKey: pubKey[:], ExitEpoch: 0, ActivationEpoch: 0, EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, }) } else { validators = append(validators, ðpb.Validator{ PublicKey: pubKey[:], ExitEpoch: params.BeaconConfig().FarFutureEpoch, EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, ActivationEpoch: 0, }) } } 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) } s := &pbp2p.BeaconState{ Validators: validators, RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)} if err := db.SaveState(ctx, s, blockRoot); err != nil { t.Fatal(err) } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: s, }, FinalizationFetcher: &mock.ChainService{ FinalizedCheckPoint: ðpb.Checkpoint{ Epoch: 0, }, }, } res, err := bs.ListValidatorAssignments(context.Background(), ðpb.ListValidatorAssignmentsRequest{ QueryFilter: ðpb.ListValidatorAssignmentsRequest_Genesis{Genesis: true}, }) if err != nil { t.Fatal(err) } // Construct the wanted assignments. var wanted []*ethpb.ValidatorAssignments_CommitteeAssignment activeIndices, err := helpers.ActiveValidatorIndices(s, 0) if err != nil { t.Fatal(err) } for _, index := range activeIndices[0:params.BeaconConfig().DefaultPageSize] { committee, committeeIndex, attesterSlot, proposerSlot, err := helpers.CommitteeAssignment(s, 0, index) if err != nil { t.Fatal(err) } wanted = append(wanted, ðpb.ValidatorAssignments_CommitteeAssignment{ BeaconCommittees: committee, CommitteeIndex: committeeIndex, AttesterSlot: attesterSlot, ProposerSlot: proposerSlot, PublicKey: s.Validators[index].PublicKey, }) } if !reflect.DeepEqual(res.Assignments, wanted) { t.Error("Did not receive wanted assignments") } } func TestServer_ListAssignments_Pagination_DefaultPageSize_FromArchive(t *testing.T) { db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() count := 1000 validators := make([]*ethpb.Validator, 0, count) balances := make([]uint64, count) for i := 0; i < count; i++ { var pubKey [48]byte copy(pubKey[:], strconv.Itoa(i)) if err := db.SaveValidatorIndex(ctx, pubKey, uint64(i)); err != nil { t.Fatal(err) } // Mark the validators with index divisible by 3 inactive. if i%3 == 0 { validators = append(validators, ðpb.Validator{ PublicKey: pubKey[:], ExitEpoch: 0, EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, }) } else { validators = append(validators, ðpb.Validator{ PublicKey: pubKey[:], ExitEpoch: params.BeaconConfig().FarFutureEpoch, EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, }) } balances[i] = params.BeaconConfig().MaxEffectiveBalance } 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) } s := &pbp2p.BeaconState{ Validators: validators, Balances: balances, RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), } if err := db.SaveState(ctx, s, blockRoot); err != nil { t.Fatal(err) } // We tell the beacon chain server that our finalized epoch is 10 so that when // we request assignments for epoch 0, it looks within the archived data. bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: s, }, FinalizationFetcher: &mock.ChainService{ FinalizedCheckPoint: ðpb.Checkpoint{ Epoch: 10, }, }, } // We then store archived data into the DB. currentEpoch := helpers.CurrentEpoch(s) proposerSeed, err := helpers.Seed(s, currentEpoch, params.BeaconConfig().DomainBeaconProposer) if err != nil { t.Fatal(err) } attesterSeed, err := helpers.Seed(s, currentEpoch, params.BeaconConfig().DomainBeaconAttester) if err != nil { t.Fatal(err) } if err := db.SaveArchivedCommitteeInfo(context.Background(), 0, ðpb.ArchivedCommitteeInfo{ ProposerSeed: proposerSeed[:], AttesterSeed: attesterSeed[:], }); err != nil { t.Fatal(err) } if err := db.SaveArchivedBalances(context.Background(), 0, balances); err != nil { t.Fatal(err) } // Construct the wanted assignments. var wanted []*ethpb.ValidatorAssignments_CommitteeAssignment activeIndices, err := helpers.ActiveValidatorIndices(s, 0) if err != nil { t.Fatal(err) } for _, index := range activeIndices[0:params.BeaconConfig().DefaultPageSize] { committee, committeeIndex, attesterSlot, proposerSlot, err := helpers.CommitteeAssignment(s, 0, index) if err != nil { t.Fatal(err) } assign := ðpb.ValidatorAssignments_CommitteeAssignment{ BeaconCommittees: committee, CommitteeIndex: committeeIndex, AttesterSlot: attesterSlot, ProposerSlot: proposerSlot, PublicKey: s.Validators[index].PublicKey, } wanted = append(wanted, assign) } res, err := bs.ListValidatorAssignments(context.Background(), ðpb.ListValidatorAssignmentsRequest{ QueryFilter: ðpb.ListValidatorAssignmentsRequest_Genesis{Genesis: true}, }) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(res.Assignments, wanted) { t.Error("Did not receive wanted assignments") } } func TestServer_ListAssignments_FilterPubkeysIndices_NoPagination(t *testing.T) { helpers.ClearAllCaches() db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() count := 100 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) } validators = append(validators, ðpb.Validator{PublicKey: []byte{byte(i)}, ExitEpoch: params.BeaconConfig().FarFutureEpoch}) } 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) } s := &pbp2p.BeaconState{ Validators: validators, RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)} if err := db.SaveState(ctx, s, blockRoot); err != nil { t.Fatal(err) } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: s, }, FinalizationFetcher: &mock.ChainService{ FinalizedCheckPoint: ðpb.Checkpoint{ Epoch: 0, }, }, } req := ðpb.ListValidatorAssignmentsRequest{PublicKeys: [][]byte{{1}, {2}}, Indices: []uint64{2, 3}} res, err := bs.ListValidatorAssignments(context.Background(), req) if err != nil { t.Fatal(err) } // Construct the wanted assignments. var wanted []*ethpb.ValidatorAssignments_CommitteeAssignment activeIndices, err := helpers.ActiveValidatorIndices(s, 0) if err != nil { t.Fatal(err) } for _, index := range activeIndices[1:4] { committee, committeeIndex, attesterSlot, proposerSlot, err := helpers.CommitteeAssignment(s, 0, index) if err != nil { t.Fatal(err) } wanted = append(wanted, ðpb.ValidatorAssignments_CommitteeAssignment{ BeaconCommittees: committee, CommitteeIndex: committeeIndex, AttesterSlot: attesterSlot, ProposerSlot: proposerSlot, PublicKey: s.Validators[index].PublicKey, }) } if !reflect.DeepEqual(res.Assignments, wanted) { t.Error("Did not receive wanted assignments") } } func TestServer_ListAssignments_CanFilterPubkeysIndices_WithPagination(t *testing.T) { helpers.ClearAllCaches() db := dbTest.SetupDB(t) defer dbTest.TeardownDB(t, db) ctx := context.Background() count := 100 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) } validators = append(validators, ðpb.Validator{PublicKey: []byte{byte(i)}, ExitEpoch: params.BeaconConfig().FarFutureEpoch}) } 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) } s := &pbp2p.BeaconState{ Validators: validators, RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)} if err := db.SaveState(ctx, s, blockRoot); err != nil { t.Fatal(err) } bs := &Server{ BeaconDB: db, HeadFetcher: &mock.ChainService{ State: s, }, FinalizationFetcher: &mock.ChainService{ FinalizedCheckPoint: ðpb.Checkpoint{ Epoch: 0, }, }, } req := ðpb.ListValidatorAssignmentsRequest{Indices: []uint64{1, 2, 3, 4, 5, 6}, PageSize: 2, PageToken: "1"} res, err := bs.ListValidatorAssignments(context.Background(), req) if err != nil { t.Fatal(err) } // Construct the wanted assignments. var assignments []*ethpb.ValidatorAssignments_CommitteeAssignment activeIndices, err := helpers.ActiveValidatorIndices(s, 0) if err != nil { t.Fatal(err) } for _, index := range activeIndices[3:5] { committee, committeeIndex, attesterSlot, proposerSlot, err := helpers.CommitteeAssignment(s, 0, index) if err != nil { t.Fatal(err) } assignments = append(assignments, ðpb.ValidatorAssignments_CommitteeAssignment{ BeaconCommittees: committee, CommitteeIndex: committeeIndex, AttesterSlot: attesterSlot, ProposerSlot: proposerSlot, PublicKey: s.Validators[index].PublicKey, }) } wantedRes := ðpb.ValidatorAssignments{ Assignments: assignments, TotalSize: int32(len(req.Indices)), NextPageToken: "2", } if !reflect.DeepEqual(res, wantedRes) { t.Error("Did not get wanted assignments") } // Test the wrap around scenario. assignments = nil req = ðpb.ListValidatorAssignmentsRequest{Indices: []uint64{1, 2, 3, 4, 5, 6}, PageSize: 5, PageToken: "1"} res, err = bs.ListValidatorAssignments(context.Background(), req) if err != nil { t.Fatal(err) } for _, index := range activeIndices[6:7] { committee, committeeIndex, attesterSlot, proposerSlot, err := helpers.CommitteeAssignment(s, 0, index) if err != nil { t.Fatal(err) } assignments = append(assignments, ðpb.ValidatorAssignments_CommitteeAssignment{ BeaconCommittees: committee, CommitteeIndex: committeeIndex, AttesterSlot: attesterSlot, ProposerSlot: proposerSlot, PublicKey: s.Validators[index].PublicKey, }) } wantedRes = ðpb.ValidatorAssignments{ Assignments: assignments, TotalSize: int32(len(req.Indices)), NextPageToken: "2", } if !reflect.DeepEqual(res, wantedRes) { t.Error("Did not receive wanted assignments") } }