2019-11-12 17:01:27 +00:00
|
|
|
package beacon
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2019-11-18 23:24:33 +00:00
|
|
|
"strconv"
|
2019-11-12 17:01:27 +00:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2019-11-27 05:08:18 +00:00
|
|
|
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
2019-11-12 17:01:27 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
2019-11-27 05:08:18 +00:00
|
|
|
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
|
2019-11-12 17:01:27 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
|
|
|
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
|
|
|
"github.com/prysmaticlabs/prysm/shared/pagination"
|
|
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ListValidatorAssignments retrieves the validator assignments for a given epoch,
|
|
|
|
// optional validator indices or public keys may be included to filter validator assignments.
|
|
|
|
func (bs *Server) ListValidatorAssignments(
|
|
|
|
ctx context.Context, req *ethpb.ListValidatorAssignmentsRequest,
|
|
|
|
) (*ethpb.ValidatorAssignments, error) {
|
|
|
|
if int(req.PageSize) > params.BeaconConfig().MaxPageSize {
|
2019-11-20 00:36:45 +00:00
|
|
|
return nil, status.Errorf(
|
|
|
|
codes.InvalidArgument,
|
|
|
|
"Requested page size %d can not be greater than max size %d",
|
|
|
|
req.PageSize,
|
|
|
|
params.BeaconConfig().MaxPageSize,
|
|
|
|
)
|
2019-11-12 17:01:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var res []*ethpb.ValidatorAssignments_CommitteeAssignment
|
2019-11-19 16:12:50 +00:00
|
|
|
headState, err := bs.HeadFetcher.HeadState(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, "Could not get head state")
|
|
|
|
}
|
2019-11-12 17:01:27 +00:00
|
|
|
filtered := map[uint64]bool{} // track filtered validators to prevent duplication in the response.
|
|
|
|
filteredIndices := make([]uint64, 0)
|
|
|
|
requestedEpoch := helpers.CurrentEpoch(headState)
|
|
|
|
|
|
|
|
switch q := req.QueryFilter.(type) {
|
|
|
|
case *ethpb.ListValidatorAssignmentsRequest_Genesis:
|
|
|
|
if q.Genesis {
|
|
|
|
requestedEpoch = 0
|
|
|
|
}
|
|
|
|
case *ethpb.ListValidatorAssignmentsRequest_Epoch:
|
|
|
|
requestedEpoch = q.Epoch
|
|
|
|
}
|
|
|
|
|
2019-11-18 23:24:33 +00:00
|
|
|
if requestedEpoch > helpers.CurrentEpoch(headState) {
|
|
|
|
return nil, status.Errorf(
|
|
|
|
codes.InvalidArgument,
|
|
|
|
"Cannot retrieve information about an epoch in the future, current epoch %d, requesting %d",
|
|
|
|
helpers.CurrentEpoch(headState),
|
|
|
|
requestedEpoch,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-11-12 17:01:27 +00:00
|
|
|
// Filter out assignments by public keys.
|
|
|
|
for _, pubKey := range req.PublicKeys {
|
|
|
|
index, ok, err := bs.BeaconDB.ValidatorIndex(ctx, bytesutil.ToBytes48(pubKey))
|
|
|
|
if err != nil {
|
2019-11-13 21:03:12 +00:00
|
|
|
return nil, status.Errorf(codes.Internal, "Could not retrieve validator index: %v", err)
|
2019-11-12 17:01:27 +00:00
|
|
|
}
|
|
|
|
if !ok {
|
2019-11-13 21:03:12 +00:00
|
|
|
return nil, status.Errorf(codes.NotFound, "Could not find validator index for public key %#x", pubKey)
|
2019-11-12 17:01:27 +00:00
|
|
|
}
|
|
|
|
filtered[index] = true
|
|
|
|
filteredIndices = append(filteredIndices, index)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter out assignments by validator indices.
|
|
|
|
for _, index := range req.Indices {
|
|
|
|
if !filtered[index] {
|
|
|
|
filteredIndices = append(filteredIndices, index)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
activeIndices, err := helpers.ActiveValidatorIndices(headState, requestedEpoch)
|
|
|
|
if err != nil {
|
2019-11-13 21:03:12 +00:00
|
|
|
return nil, status.Errorf(codes.Internal, "Could not retrieve active validator indices: %v", err)
|
2019-11-12 17:01:27 +00:00
|
|
|
}
|
|
|
|
if len(filteredIndices) == 0 {
|
2019-11-18 23:24:33 +00:00
|
|
|
if len(activeIndices) == 0 {
|
|
|
|
return ðpb.ValidatorAssignments{
|
|
|
|
Assignments: make([]*ethpb.ValidatorAssignments_CommitteeAssignment, 0),
|
|
|
|
TotalSize: int32(0),
|
|
|
|
NextPageToken: strconv.Itoa(0),
|
|
|
|
}, nil
|
|
|
|
}
|
2019-11-12 17:01:27 +00:00
|
|
|
// If no filter was specified, return assignments from active validator indices with pagination.
|
|
|
|
filteredIndices = activeIndices
|
|
|
|
}
|
|
|
|
|
|
|
|
start, end, nextPageToken, err := pagination.StartAndEndPage(req.PageToken, int(req.PageSize), len(filteredIndices))
|
|
|
|
if err != nil {
|
2019-11-13 21:03:12 +00:00
|
|
|
return nil, status.Errorf(codes.Internal, "Could not paginate results: %v", err)
|
2019-11-12 17:01:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
shouldFetchFromArchive := requestedEpoch < bs.FinalizationFetcher.FinalizedCheckpt().Epoch
|
|
|
|
|
|
|
|
for _, index := range filteredIndices[start:end] {
|
|
|
|
if int(index) >= len(headState.Validators) {
|
2019-11-13 21:03:12 +00:00
|
|
|
return nil, status.Errorf(codes.OutOfRange, "Validator index %d >= validator count %d",
|
2019-11-12 17:01:27 +00:00
|
|
|
index, len(headState.Validators))
|
|
|
|
}
|
|
|
|
if shouldFetchFromArchive {
|
|
|
|
archivedInfo, err := bs.BeaconDB.ArchivedCommitteeInfo(ctx, requestedEpoch)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(
|
|
|
|
codes.Internal,
|
2019-11-13 21:03:12 +00:00
|
|
|
"Could not retrieve archived committee info for epoch %d",
|
2019-11-12 17:01:27 +00:00
|
|
|
requestedEpoch,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if archivedInfo == nil {
|
|
|
|
return nil, status.Errorf(
|
|
|
|
codes.NotFound,
|
2019-11-13 21:03:12 +00:00
|
|
|
"Could not retrieve data for epoch %d, perhaps --archive in the running beacon node is disabled",
|
2019-11-12 17:01:27 +00:00
|
|
|
requestedEpoch,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
archivedBalances, err := bs.BeaconDB.ArchivedBalances(ctx, requestedEpoch)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(
|
|
|
|
codes.Internal,
|
2019-11-13 21:03:12 +00:00
|
|
|
"Could not retrieve archived balances for epoch %d",
|
2019-11-12 17:01:27 +00:00
|
|
|
requestedEpoch,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if archivedBalances == nil {
|
|
|
|
return nil, status.Errorf(
|
|
|
|
codes.NotFound,
|
2019-11-13 21:03:12 +00:00
|
|
|
"Could not retrieve data for epoch %d, perhaps --archive in the running beacon node is disabled",
|
2019-11-12 17:01:27 +00:00
|
|
|
requestedEpoch,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
committee, committeeIndex, attesterSlot, proposerSlot, err := archivedValidatorCommittee(
|
|
|
|
requestedEpoch,
|
|
|
|
index,
|
|
|
|
archivedInfo,
|
|
|
|
activeIndices,
|
|
|
|
archivedBalances,
|
|
|
|
)
|
|
|
|
if err != nil {
|
2019-11-13 21:03:12 +00:00
|
|
|
return nil, status.Errorf(codes.Internal, "Could not retrieve archived assignment for validator %d: %v", index, err)
|
2019-11-12 17:01:27 +00:00
|
|
|
}
|
|
|
|
assign := ðpb.ValidatorAssignments_CommitteeAssignment{
|
|
|
|
BeaconCommittees: committee,
|
|
|
|
CommitteeIndex: committeeIndex,
|
|
|
|
AttesterSlot: attesterSlot,
|
|
|
|
ProposerSlot: proposerSlot,
|
|
|
|
PublicKey: headState.Validators[index].PublicKey,
|
|
|
|
}
|
|
|
|
res = append(res, assign)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
committee, committeeIndex, attesterSlot, proposerSlot, err := helpers.CommitteeAssignment(headState, requestedEpoch, index)
|
|
|
|
if err != nil {
|
2019-11-13 21:03:12 +00:00
|
|
|
return nil, status.Errorf(codes.Internal, "Could not retrieve assignment for validator %d: %v", index, err)
|
2019-11-12 17:01:27 +00:00
|
|
|
}
|
|
|
|
assign := ðpb.ValidatorAssignments_CommitteeAssignment{
|
|
|
|
BeaconCommittees: committee,
|
|
|
|
CommitteeIndex: committeeIndex,
|
|
|
|
AttesterSlot: attesterSlot,
|
|
|
|
ProposerSlot: proposerSlot,
|
|
|
|
PublicKey: headState.Validators[index].PublicKey,
|
|
|
|
}
|
|
|
|
res = append(res, assign)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ðpb.ValidatorAssignments{
|
|
|
|
Epoch: requestedEpoch,
|
|
|
|
Assignments: res,
|
|
|
|
NextPageToken: nextPageToken,
|
|
|
|
TotalSize: int32(len(filteredIndices)),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Computes validator assignments for an epoch and validator index using archived committee
|
|
|
|
// information, archived balances, and a set of active validators.
|
|
|
|
func archivedValidatorCommittee(
|
|
|
|
epoch uint64,
|
|
|
|
validatorIndex uint64,
|
2019-11-27 05:08:18 +00:00
|
|
|
archivedInfo *pb.ArchivedCommitteeInfo,
|
2019-11-12 17:01:27 +00:00
|
|
|
activeIndices []uint64,
|
|
|
|
archivedBalances []uint64,
|
|
|
|
) ([]uint64, uint64, uint64, uint64, error) {
|
|
|
|
proposerSeed := bytesutil.ToBytes32(archivedInfo.ProposerSeed)
|
|
|
|
attesterSeed := bytesutil.ToBytes32(archivedInfo.AttesterSeed)
|
|
|
|
|
|
|
|
startSlot := helpers.StartSlot(epoch)
|
|
|
|
proposerIndexToSlot := make(map[uint64]uint64)
|
|
|
|
for slot := startSlot; slot < startSlot+params.BeaconConfig().SlotsPerEpoch; slot++ {
|
|
|
|
seedWithSlot := append(proposerSeed[:], bytesutil.Bytes8(slot)...)
|
|
|
|
seedWithSlotHash := hashutil.Hash(seedWithSlot)
|
|
|
|
i, err := archivedProposerIndex(activeIndices, archivedBalances, seedWithSlotHash)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, 0, 0, errors.Wrapf(err, "could not check proposer at slot %d", slot)
|
|
|
|
}
|
|
|
|
proposerIndexToSlot[i] = slot
|
|
|
|
}
|
|
|
|
for slot := startSlot; slot < startSlot+params.BeaconConfig().SlotsPerEpoch; slot++ {
|
|
|
|
var countAtSlot = uint64(len(activeIndices)) / params.BeaconConfig().SlotsPerEpoch / params.BeaconConfig().TargetCommitteeSize
|
|
|
|
if countAtSlot > params.BeaconConfig().MaxCommitteesPerSlot {
|
|
|
|
countAtSlot = params.BeaconConfig().MaxCommitteesPerSlot
|
|
|
|
}
|
|
|
|
if countAtSlot == 0 {
|
|
|
|
countAtSlot = 1
|
|
|
|
}
|
|
|
|
for i := uint64(0); i < countAtSlot; i++ {
|
|
|
|
epochOffset := i + (slot%params.BeaconConfig().SlotsPerEpoch)*countAtSlot
|
2019-11-13 05:32:42 +00:00
|
|
|
totalCount := countAtSlot * params.BeaconConfig().SlotsPerEpoch
|
|
|
|
committee, err := helpers.ComputeCommittee(activeIndices, attesterSeed, epochOffset, totalCount)
|
2019-11-12 17:01:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, 0, 0, 0, errors.Wrap(err, "could not compute committee")
|
|
|
|
}
|
|
|
|
for _, index := range committee {
|
|
|
|
if validatorIndex == index {
|
|
|
|
proposerSlot, _ := proposerIndexToSlot[validatorIndex]
|
|
|
|
return committee, i, slot, proposerSlot, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, 0, 0, 0, fmt.Errorf("could not find committee for validator index %d", validatorIndex)
|
|
|
|
}
|
|
|
|
|
|
|
|
func archivedProposerIndex(activeIndices []uint64, activeBalances []uint64, seed [32]byte) (uint64, error) {
|
|
|
|
length := uint64(len(activeIndices))
|
|
|
|
if length == 0 {
|
|
|
|
return 0, errors.New("empty indices list")
|
|
|
|
}
|
|
|
|
maxRandomByte := uint64(1<<8 - 1)
|
|
|
|
for i := uint64(0); ; i++ {
|
|
|
|
candidateIndex, err := helpers.ComputeShuffledIndex(i%length, length, seed, true)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
b := append(seed[:], bytesutil.Bytes8(i/32)...)
|
|
|
|
randomByte := hashutil.Hash(b)[i%32]
|
|
|
|
effectiveBalance := activeBalances[candidateIndex]
|
|
|
|
if effectiveBalance >= params.BeaconConfig().MaxEffectiveBalance {
|
|
|
|
// if the actual balance is greater than or equal to the max effective balance,
|
|
|
|
// we just determine the proposer index using config.MaxEffectiveBalance.
|
|
|
|
effectiveBalance = params.BeaconConfig().MaxEffectiveBalance
|
|
|
|
}
|
|
|
|
if effectiveBalance*maxRandomByte >= params.BeaconConfig().MaxEffectiveBalance*uint64(randomByte) {
|
|
|
|
return candidateIndex, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|