mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-27 05:38:55 +00:00
7cbef104b0
Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
1008 lines
34 KiB
Go
1008 lines
34 KiB
Go
package beacon
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strconv"
|
|
|
|
types "github.com/prysmaticlabs/eth2-types"
|
|
"github.com/prysmaticlabs/prysm/api/pagination"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/altair"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/epoch/precompute"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
|
coreTime "github.com/prysmaticlabs/prysm/beacon-chain/core/time"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/transition"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/validators"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
|
"github.com/prysmaticlabs/prysm/cmd"
|
|
"github.com/prysmaticlabs/prysm/config/features"
|
|
"github.com/prysmaticlabs/prysm/config/params"
|
|
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
|
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
|
"github.com/prysmaticlabs/prysm/runtime/version"
|
|
"github.com/prysmaticlabs/prysm/time/slots"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
)
|
|
|
|
// ListValidatorBalances retrieves the validator balances for a given set of public keys.
|
|
// An optional Epoch parameter is provided to request historical validator balances from
|
|
// archived, persistent data.
|
|
func (bs *Server) ListValidatorBalances(
|
|
ctx context.Context,
|
|
req *ethpb.ListValidatorBalancesRequest,
|
|
) (*ethpb.ValidatorBalances, error) {
|
|
if int(req.PageSize) > cmd.Get().MaxRPCPageSize {
|
|
return nil, status.Errorf(codes.InvalidArgument, "Requested page size %d can not be greater than max size %d",
|
|
req.PageSize, cmd.Get().MaxRPCPageSize)
|
|
}
|
|
|
|
if bs.GenesisTimeFetcher == nil {
|
|
return nil, status.Errorf(codes.Internal, "Nil genesis time fetcher")
|
|
}
|
|
currentEpoch := slots.ToEpoch(bs.GenesisTimeFetcher.CurrentSlot())
|
|
requestedEpoch := currentEpoch
|
|
switch q := req.QueryFilter.(type) {
|
|
case *ethpb.ListValidatorBalancesRequest_Epoch:
|
|
requestedEpoch = q.Epoch
|
|
case *ethpb.ListValidatorBalancesRequest_Genesis:
|
|
requestedEpoch = 0
|
|
}
|
|
|
|
if requestedEpoch > currentEpoch {
|
|
return nil, status.Errorf(
|
|
codes.InvalidArgument,
|
|
errEpoch,
|
|
currentEpoch,
|
|
requestedEpoch,
|
|
)
|
|
}
|
|
res := make([]*ethpb.ValidatorBalances_Balance, 0)
|
|
filtered := map[types.ValidatorIndex]bool{} // Track filtered validators to prevent duplication in the response.
|
|
|
|
startSlot, err := slots.EpochStart(requestedEpoch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
requestedState, err := bs.StateGen.StateBySlot(ctx, startSlot)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
|
|
}
|
|
|
|
vals := requestedState.Validators()
|
|
balances := requestedState.Balances()
|
|
balancesCount := len(balances)
|
|
for _, pubKey := range req.PublicKeys {
|
|
// Skip empty public key.
|
|
if len(pubKey) == 0 {
|
|
continue
|
|
}
|
|
pubkeyBytes := bytesutil.ToBytes48(pubKey)
|
|
index, ok := requestedState.ValidatorIndexByPubkey(pubkeyBytes)
|
|
if !ok {
|
|
// We continue the loop if one validator in the request is not found.
|
|
res = append(res, ðpb.ValidatorBalances_Balance{
|
|
Status: "UNKNOWN",
|
|
})
|
|
balancesCount = len(res)
|
|
continue
|
|
}
|
|
filtered[index] = true
|
|
|
|
if uint64(index) >= uint64(len(balances)) {
|
|
return nil, status.Errorf(codes.OutOfRange, "Validator index %d >= balance list %d",
|
|
index, len(balances))
|
|
}
|
|
|
|
val := vals[index]
|
|
st := validatorStatus(val, requestedEpoch)
|
|
res = append(res, ðpb.ValidatorBalances_Balance{
|
|
PublicKey: pubKey,
|
|
Index: index,
|
|
Balance: balances[index],
|
|
Status: st.String(),
|
|
})
|
|
balancesCount = len(res)
|
|
}
|
|
|
|
for _, index := range req.Indices {
|
|
if uint64(index) >= uint64(len(balances)) {
|
|
return nil, status.Errorf(codes.OutOfRange, "Validator index %d >= balance list %d",
|
|
index, len(balances))
|
|
}
|
|
|
|
if !filtered[index] {
|
|
val := vals[index]
|
|
st := validatorStatus(val, requestedEpoch)
|
|
res = append(res, ðpb.ValidatorBalances_Balance{
|
|
PublicKey: vals[index].PublicKey,
|
|
Index: index,
|
|
Balance: balances[index],
|
|
Status: st.String(),
|
|
})
|
|
}
|
|
balancesCount = len(res)
|
|
}
|
|
// Depending on the indices and public keys given, results might not be sorted.
|
|
sort.Slice(res, func(i, j int) bool {
|
|
return res[i].Index < res[j].Index
|
|
})
|
|
|
|
// If there are no balances, we simply return a response specifying this.
|
|
// Otherwise, attempting to paginate 0 balances below would result in an error.
|
|
if balancesCount == 0 {
|
|
return ðpb.ValidatorBalances{
|
|
Epoch: requestedEpoch,
|
|
Balances: make([]*ethpb.ValidatorBalances_Balance, 0),
|
|
TotalSize: int32(0),
|
|
NextPageToken: strconv.Itoa(0),
|
|
}, nil
|
|
}
|
|
|
|
start, end, nextPageToken, err := pagination.StartAndEndPage(req.PageToken, int(req.PageSize), balancesCount)
|
|
if err != nil {
|
|
return nil, status.Errorf(
|
|
codes.Internal,
|
|
"Could not paginate results: %v",
|
|
err,
|
|
)
|
|
}
|
|
|
|
if len(req.Indices) == 0 && len(req.PublicKeys) == 0 {
|
|
// Return everything.
|
|
for i := start; i < end; i++ {
|
|
pubkey := requestedState.PubkeyAtIndex(types.ValidatorIndex(i))
|
|
val := vals[i]
|
|
st := validatorStatus(val, requestedEpoch)
|
|
res = append(res, ðpb.ValidatorBalances_Balance{
|
|
PublicKey: pubkey[:],
|
|
Index: types.ValidatorIndex(i),
|
|
Balance: balances[i],
|
|
Status: st.String(),
|
|
})
|
|
}
|
|
return ðpb.ValidatorBalances{
|
|
Epoch: requestedEpoch,
|
|
Balances: res,
|
|
TotalSize: int32(balancesCount),
|
|
NextPageToken: nextPageToken,
|
|
}, nil
|
|
}
|
|
|
|
if end > len(res) || end < start {
|
|
return nil, status.Error(codes.OutOfRange, "Request exceeds response length")
|
|
}
|
|
|
|
return ðpb.ValidatorBalances{
|
|
Epoch: requestedEpoch,
|
|
Balances: res[start:end],
|
|
TotalSize: int32(balancesCount),
|
|
NextPageToken: nextPageToken,
|
|
}, nil
|
|
}
|
|
|
|
// ListValidators retrieves the current list of active validators with an optional historical epoch flag to
|
|
// to retrieve validator set in time.
|
|
func (bs *Server) ListValidators(
|
|
ctx context.Context,
|
|
req *ethpb.ListValidatorsRequest,
|
|
) (*ethpb.Validators, error) {
|
|
if int(req.PageSize) > cmd.Get().MaxRPCPageSize {
|
|
return nil, status.Errorf(codes.InvalidArgument, "Requested page size %d can not be greater than max size %d",
|
|
req.PageSize, cmd.Get().MaxRPCPageSize)
|
|
}
|
|
|
|
currentEpoch := slots.ToEpoch(bs.GenesisTimeFetcher.CurrentSlot())
|
|
requestedEpoch := currentEpoch
|
|
|
|
switch q := req.QueryFilter.(type) {
|
|
case *ethpb.ListValidatorsRequest_Genesis:
|
|
if q.Genesis {
|
|
requestedEpoch = 0
|
|
}
|
|
case *ethpb.ListValidatorsRequest_Epoch:
|
|
if q.Epoch > currentEpoch {
|
|
return nil, status.Errorf(
|
|
codes.InvalidArgument,
|
|
errEpoch,
|
|
currentEpoch,
|
|
q.Epoch,
|
|
)
|
|
}
|
|
requestedEpoch = q.Epoch
|
|
}
|
|
var reqState state.BeaconState
|
|
var err error
|
|
if requestedEpoch != currentEpoch {
|
|
var s types.Slot
|
|
s, err = slots.EpochStart(requestedEpoch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
reqState, err = bs.StateGen.StateBySlot(ctx, s)
|
|
} else {
|
|
reqState, err = bs.HeadFetcher.HeadState(ctx)
|
|
}
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get requested state: %v", err)
|
|
}
|
|
if reqState == nil || reqState.IsNil() {
|
|
return nil, status.Error(codes.Internal, "Requested state is nil")
|
|
}
|
|
|
|
s, err := slots.EpochStart(requestedEpoch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if s > reqState.Slot() {
|
|
reqState = reqState.Copy()
|
|
reqState, err = transition.ProcessSlots(ctx, reqState, s)
|
|
if err != nil {
|
|
return nil, status.Errorf(
|
|
codes.Internal,
|
|
"Could not process slots up to epoch %d: %v",
|
|
requestedEpoch,
|
|
err,
|
|
)
|
|
}
|
|
}
|
|
|
|
validatorList := make([]*ethpb.Validators_ValidatorContainer, 0)
|
|
|
|
for _, index := range req.Indices {
|
|
val, err := reqState.ValidatorAtIndex(index)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get validator: %v", err)
|
|
}
|
|
validatorList = append(validatorList, ðpb.Validators_ValidatorContainer{
|
|
Index: index,
|
|
Validator: val,
|
|
})
|
|
}
|
|
|
|
for _, pubKey := range req.PublicKeys {
|
|
// Skip empty public key.
|
|
if len(pubKey) == 0 {
|
|
continue
|
|
}
|
|
pubkeyBytes := bytesutil.ToBytes48(pubKey)
|
|
index, ok := reqState.ValidatorIndexByPubkey(pubkeyBytes)
|
|
if !ok {
|
|
continue
|
|
}
|
|
val, err := reqState.ValidatorAtIndex(index)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get validator: %v", err)
|
|
}
|
|
validatorList = append(validatorList, ðpb.Validators_ValidatorContainer{
|
|
Index: index,
|
|
Validator: val,
|
|
})
|
|
}
|
|
// Depending on the indices and public keys given, results might not be sorted.
|
|
sort.Slice(validatorList, func(i, j int) bool {
|
|
return validatorList[i].Index < validatorList[j].Index
|
|
})
|
|
|
|
if len(req.PublicKeys) == 0 && len(req.Indices) == 0 {
|
|
for i := types.ValidatorIndex(0); uint64(i) < uint64(reqState.NumValidators()); i++ {
|
|
val, err := reqState.ValidatorAtIndex(i)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get validator: %v", err)
|
|
}
|
|
validatorList = append(validatorList, ðpb.Validators_ValidatorContainer{
|
|
Index: i,
|
|
Validator: val,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Filter active validators if the request specifies it.
|
|
res := validatorList
|
|
if req.Active {
|
|
filteredValidators := make([]*ethpb.Validators_ValidatorContainer, 0)
|
|
for _, item := range validatorList {
|
|
if helpers.IsActiveValidator(item.Validator, requestedEpoch) {
|
|
filteredValidators = append(filteredValidators, item)
|
|
}
|
|
}
|
|
res = filteredValidators
|
|
}
|
|
|
|
validatorCount := len(res)
|
|
// If there are no items, we simply return a response specifying this.
|
|
// Otherwise, attempting to paginate 0 validators below would result in an error.
|
|
if validatorCount == 0 {
|
|
return ðpb.Validators{
|
|
ValidatorList: make([]*ethpb.Validators_ValidatorContainer, 0),
|
|
TotalSize: int32(0),
|
|
NextPageToken: strconv.Itoa(0),
|
|
}, nil
|
|
}
|
|
|
|
start, end, nextPageToken, err := pagination.StartAndEndPage(req.PageToken, int(req.PageSize), validatorCount)
|
|
if err != nil {
|
|
return nil, status.Errorf(
|
|
codes.Internal,
|
|
"Could not paginate results: %v",
|
|
err,
|
|
)
|
|
}
|
|
|
|
return ðpb.Validators{
|
|
ValidatorList: res[start:end],
|
|
TotalSize: int32(validatorCount),
|
|
NextPageToken: nextPageToken,
|
|
}, nil
|
|
}
|
|
|
|
// GetValidator information from any validator in the registry by index or public key.
|
|
func (bs *Server) GetValidator(
|
|
ctx context.Context, req *ethpb.GetValidatorRequest,
|
|
) (*ethpb.Validator, error) {
|
|
var requestingIndex bool
|
|
var index types.ValidatorIndex
|
|
var pubKey []byte
|
|
switch q := req.QueryFilter.(type) {
|
|
case *ethpb.GetValidatorRequest_Index:
|
|
index = q.Index
|
|
requestingIndex = true
|
|
case *ethpb.GetValidatorRequest_PublicKey:
|
|
pubKey = q.PublicKey
|
|
default:
|
|
return nil, status.Error(
|
|
codes.InvalidArgument,
|
|
"Need to specify either validator index or public key in request",
|
|
)
|
|
}
|
|
headState, err := bs.HeadFetcher.HeadState(ctx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err)
|
|
}
|
|
if requestingIndex {
|
|
if uint64(index) >= uint64(headState.NumValidators()) {
|
|
return nil, status.Errorf(
|
|
codes.OutOfRange,
|
|
"Requesting index %d, but there are only %d validators",
|
|
index,
|
|
headState.NumValidators(),
|
|
)
|
|
}
|
|
return headState.ValidatorAtIndex(index)
|
|
}
|
|
pk48 := bytesutil.ToBytes48(pubKey)
|
|
for i := types.ValidatorIndex(0); uint64(i) < uint64(headState.NumValidators()); i++ {
|
|
keyFromState := headState.PubkeyAtIndex(i)
|
|
if keyFromState == pk48 {
|
|
return headState.ValidatorAtIndex(i)
|
|
}
|
|
}
|
|
return nil, status.Error(codes.NotFound, "No validator matched filter criteria")
|
|
}
|
|
|
|
// GetValidatorActiveSetChanges retrieves the active set changes for a given epoch.
|
|
//
|
|
// This data includes any activations, voluntary exits, and involuntary
|
|
// ejections.
|
|
func (bs *Server) GetValidatorActiveSetChanges(
|
|
ctx context.Context, req *ethpb.GetValidatorActiveSetChangesRequest,
|
|
) (*ethpb.ActiveSetChanges, error) {
|
|
currentEpoch := slots.ToEpoch(bs.GenesisTimeFetcher.CurrentSlot())
|
|
|
|
var requestedEpoch types.Epoch
|
|
switch q := req.QueryFilter.(type) {
|
|
case *ethpb.GetValidatorActiveSetChangesRequest_Genesis:
|
|
requestedEpoch = 0
|
|
case *ethpb.GetValidatorActiveSetChangesRequest_Epoch:
|
|
requestedEpoch = q.Epoch
|
|
default:
|
|
requestedEpoch = currentEpoch
|
|
}
|
|
if requestedEpoch > currentEpoch {
|
|
return nil, status.Errorf(
|
|
codes.InvalidArgument,
|
|
errEpoch,
|
|
currentEpoch,
|
|
requestedEpoch,
|
|
)
|
|
}
|
|
|
|
s, err := slots.EpochStart(requestedEpoch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
requestedState, err := bs.StateGen.StateBySlot(ctx, s)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
|
|
}
|
|
|
|
activeValidatorCount, err := helpers.ActiveValidatorCount(ctx, requestedState, coreTime.CurrentEpoch(requestedState))
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get active validator count: %v", err)
|
|
}
|
|
vs := requestedState.Validators()
|
|
activatedIndices := validators.ActivatedValidatorIndices(coreTime.CurrentEpoch(requestedState), vs)
|
|
exitedIndices, err := validators.ExitedValidatorIndices(coreTime.CurrentEpoch(requestedState), vs, activeValidatorCount)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not determine exited validator indices: %v", err)
|
|
}
|
|
slashedIndices := validators.SlashedValidatorIndices(coreTime.CurrentEpoch(requestedState), vs)
|
|
ejectedIndices, err := validators.EjectedValidatorIndices(coreTime.CurrentEpoch(requestedState), vs, activeValidatorCount)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not determine ejected validator indices: %v", err)
|
|
}
|
|
|
|
// Retrieve public keys for the indices.
|
|
activatedKeys := make([][]byte, len(activatedIndices))
|
|
exitedKeys := make([][]byte, len(exitedIndices))
|
|
slashedKeys := make([][]byte, len(slashedIndices))
|
|
ejectedKeys := make([][]byte, len(ejectedIndices))
|
|
for i, idx := range activatedIndices {
|
|
pubkey := requestedState.PubkeyAtIndex(idx)
|
|
activatedKeys[i] = pubkey[:]
|
|
}
|
|
for i, idx := range exitedIndices {
|
|
pubkey := requestedState.PubkeyAtIndex(idx)
|
|
exitedKeys[i] = pubkey[:]
|
|
}
|
|
for i, idx := range slashedIndices {
|
|
pubkey := requestedState.PubkeyAtIndex(idx)
|
|
slashedKeys[i] = pubkey[:]
|
|
}
|
|
for i, idx := range ejectedIndices {
|
|
pubkey := requestedState.PubkeyAtIndex(idx)
|
|
ejectedKeys[i] = pubkey[:]
|
|
}
|
|
return ðpb.ActiveSetChanges{
|
|
Epoch: requestedEpoch,
|
|
ActivatedPublicKeys: activatedKeys,
|
|
ActivatedIndices: activatedIndices,
|
|
ExitedPublicKeys: exitedKeys,
|
|
ExitedIndices: exitedIndices,
|
|
SlashedPublicKeys: slashedKeys,
|
|
SlashedIndices: slashedIndices,
|
|
EjectedPublicKeys: ejectedKeys,
|
|
EjectedIndices: ejectedIndices,
|
|
}, nil
|
|
}
|
|
|
|
// GetValidatorParticipation retrieves the validator participation information for a given epoch,
|
|
// it returns the information about validator's participation rate in voting on the proof of stake
|
|
// rules based on their balance compared to the total active validator balance.
|
|
func (bs *Server) GetValidatorParticipation(
|
|
ctx context.Context, req *ethpb.GetValidatorParticipationRequest,
|
|
) (*ethpb.ValidatorParticipationResponse, error) {
|
|
currentSlot := bs.GenesisTimeFetcher.CurrentSlot()
|
|
currentEpoch := slots.ToEpoch(currentSlot)
|
|
|
|
var requestedEpoch types.Epoch
|
|
switch q := req.QueryFilter.(type) {
|
|
case *ethpb.GetValidatorParticipationRequest_Genesis:
|
|
requestedEpoch = 0
|
|
case *ethpb.GetValidatorParticipationRequest_Epoch:
|
|
requestedEpoch = q.Epoch
|
|
default:
|
|
requestedEpoch = currentEpoch
|
|
}
|
|
|
|
if requestedEpoch > currentEpoch {
|
|
return nil, status.Errorf(
|
|
codes.InvalidArgument,
|
|
"Cannot retrieve information about an epoch greater than current epoch, current epoch %d, requesting %d",
|
|
currentEpoch,
|
|
requestedEpoch,
|
|
)
|
|
}
|
|
|
|
// Get current slot state for current epoch attestations.
|
|
startSlot, err := slots.EpochStart(requestedEpoch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Use the last slot of requested epoch to obtain current and previous epoch attestations.
|
|
// This ensures that we don't miss previous attestations when input requested epochs.
|
|
startSlot += params.BeaconConfig().SlotsPerEpoch - 1
|
|
// The start slot should be a canonical slot.
|
|
canonical, err := bs.isSlotCanonical(ctx, startSlot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Keep looking back until there's a canonical slot.
|
|
for i := int(startSlot - 1); !canonical && i >= 0; i-- {
|
|
canonical, err = bs.isSlotCanonical(ctx, types.Slot(i))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
startSlot = types.Slot(i)
|
|
}
|
|
beaconState, err := bs.StateGen.StateBySlot(ctx, startSlot)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
|
|
}
|
|
var v []*precompute.Validator
|
|
var b *precompute.Balance
|
|
switch beaconState.Version() {
|
|
case version.Phase0:
|
|
v, b, err = precompute.New(ctx, beaconState)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not set up pre compute instance: %v", err)
|
|
}
|
|
_, b, err = precompute.ProcessAttestations(ctx, beaconState, v, b)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err)
|
|
}
|
|
case version.Altair:
|
|
v, b, err = altair.InitializePrecomputeValidators(ctx, beaconState)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not set up altair pre compute instance: %v", err)
|
|
}
|
|
_, b, err = altair.ProcessEpochParticipation(ctx, beaconState, b, v)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err)
|
|
}
|
|
default:
|
|
return nil, status.Errorf(codes.Internal, "Invalid state type retrieved with a version of %d", beaconState.Version())
|
|
}
|
|
|
|
p := ðpb.ValidatorParticipationResponse{
|
|
Epoch: requestedEpoch,
|
|
Finalized: requestedEpoch <= bs.FinalizationFetcher.FinalizedCheckpt().Epoch,
|
|
Participation: ðpb.ValidatorParticipation{
|
|
// TODO(7130): Remove these three deprecated fields.
|
|
GlobalParticipationRate: float32(b.PrevEpochTargetAttested) / float32(b.ActivePrevEpoch),
|
|
VotedEther: b.PrevEpochTargetAttested,
|
|
EligibleEther: b.ActivePrevEpoch,
|
|
CurrentEpochActiveGwei: b.ActiveCurrentEpoch,
|
|
CurrentEpochAttestingGwei: b.CurrentEpochAttested,
|
|
CurrentEpochTargetAttestingGwei: b.CurrentEpochTargetAttested,
|
|
PreviousEpochActiveGwei: b.ActivePrevEpoch,
|
|
PreviousEpochAttestingGwei: b.PrevEpochAttested,
|
|
PreviousEpochTargetAttestingGwei: b.PrevEpochTargetAttested,
|
|
PreviousEpochHeadAttestingGwei: b.PrevEpochHeadAttested,
|
|
},
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// GetValidatorQueue retrieves the current validator queue information.
|
|
func (bs *Server) GetValidatorQueue(
|
|
ctx context.Context, _ *emptypb.Empty,
|
|
) (*ethpb.ValidatorQueue, error) {
|
|
headState, err := bs.HeadFetcher.HeadState(ctx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err)
|
|
}
|
|
// Queue the validators whose eligible to activate and sort them by activation eligibility epoch number.
|
|
// Additionally, determine those validators queued to exit
|
|
awaitingExit := make([]types.ValidatorIndex, 0)
|
|
exitEpochs := make([]types.Epoch, 0)
|
|
activationQ := make([]types.ValidatorIndex, 0)
|
|
vals := headState.Validators()
|
|
for idx, validator := range vals {
|
|
eligibleActivated := validator.ActivationEligibilityEpoch != params.BeaconConfig().FarFutureEpoch
|
|
canBeActive := validator.ActivationEpoch >= helpers.ActivationExitEpoch(headState.FinalizedCheckpointEpoch())
|
|
if eligibleActivated && canBeActive {
|
|
activationQ = append(activationQ, types.ValidatorIndex(idx))
|
|
}
|
|
if validator.ExitEpoch != params.BeaconConfig().FarFutureEpoch {
|
|
exitEpochs = append(exitEpochs, validator.ExitEpoch)
|
|
awaitingExit = append(awaitingExit, types.ValidatorIndex(idx))
|
|
}
|
|
}
|
|
sort.Slice(activationQ, func(i, j int) bool {
|
|
return vals[i].ActivationEligibilityEpoch < vals[j].ActivationEligibilityEpoch
|
|
})
|
|
sort.Slice(awaitingExit, func(i, j int) bool {
|
|
return vals[i].WithdrawableEpoch < vals[j].WithdrawableEpoch
|
|
})
|
|
|
|
// Only activate just enough validators according to the activation churn limit.
|
|
activeValidatorCount, err := helpers.ActiveValidatorCount(ctx, headState, coreTime.CurrentEpoch(headState))
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get active validator count: %v", err)
|
|
}
|
|
churnLimit, err := helpers.ValidatorChurnLimit(activeValidatorCount)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not compute churn limit: %v", err)
|
|
}
|
|
|
|
exitQueueEpoch := types.Epoch(0)
|
|
for _, i := range exitEpochs {
|
|
if exitQueueEpoch < i {
|
|
exitQueueEpoch = i
|
|
}
|
|
}
|
|
exitQueueChurn := uint64(0)
|
|
for _, val := range vals {
|
|
if val.ExitEpoch == exitQueueEpoch {
|
|
exitQueueChurn++
|
|
}
|
|
}
|
|
// Prevent churn limit from causing index out of bound issues.
|
|
if churnLimit < exitQueueChurn {
|
|
// If we are above the churn limit, we simply increase the churn by one.
|
|
exitQueueEpoch++
|
|
}
|
|
|
|
// We use the exit queue churn to determine if we have passed a churn limit.
|
|
minEpoch := exitQueueEpoch + params.BeaconConfig().MinValidatorWithdrawabilityDelay
|
|
exitQueueIndices := make([]types.ValidatorIndex, 0)
|
|
for _, valIdx := range awaitingExit {
|
|
val := vals[valIdx]
|
|
// Ensure the validator has not yet exited before adding its index to the exit queue.
|
|
if val.WithdrawableEpoch < minEpoch && !validatorHasExited(val, coreTime.CurrentEpoch(headState)) {
|
|
exitQueueIndices = append(exitQueueIndices, valIdx)
|
|
}
|
|
}
|
|
|
|
// Get the public keys for the validators in the queues up to the allowed churn limits.
|
|
activationQueueKeys := make([][]byte, len(activationQ))
|
|
exitQueueKeys := make([][]byte, len(exitQueueIndices))
|
|
for i, idx := range activationQ {
|
|
activationQueueKeys[i] = vals[idx].PublicKey
|
|
}
|
|
for i, idx := range exitQueueIndices {
|
|
exitQueueKeys[i] = vals[idx].PublicKey
|
|
}
|
|
|
|
return ðpb.ValidatorQueue{
|
|
ChurnLimit: churnLimit,
|
|
ActivationPublicKeys: activationQueueKeys,
|
|
ExitPublicKeys: exitQueueKeys,
|
|
ActivationValidatorIndices: activationQ,
|
|
ExitValidatorIndices: exitQueueIndices,
|
|
}, nil
|
|
}
|
|
|
|
// GetValidatorPerformance reports the validator's latest balance along with other important metrics on
|
|
// rewards and penalties throughout its lifecycle in the beacon chain.
|
|
func (bs *Server) GetValidatorPerformance(
|
|
ctx context.Context, req *ethpb.ValidatorPerformanceRequest,
|
|
) (*ethpb.ValidatorPerformanceResponse, error) {
|
|
if bs.SyncChecker.Syncing() {
|
|
return nil, status.Errorf(codes.Unavailable, "Syncing to latest head, not ready to respond")
|
|
}
|
|
|
|
headState, err := bs.HeadFetcher.HeadState(ctx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err)
|
|
}
|
|
currSlot := bs.GenesisTimeFetcher.CurrentSlot()
|
|
|
|
if currSlot > headState.Slot() {
|
|
if features.Get().EnableNextSlotStateCache {
|
|
headRoot, err := bs.HeadFetcher.HeadRoot(ctx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not retrieve head root: %v", err)
|
|
}
|
|
headState, err = transition.ProcessSlotsUsingNextSlotCache(ctx, headState, headRoot, currSlot)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not process slots up to %d: %v", currSlot, err)
|
|
}
|
|
} else {
|
|
headState, err = transition.ProcessSlots(ctx, headState, currSlot)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not process slots: %v", err)
|
|
}
|
|
}
|
|
}
|
|
var validatorSummary []*precompute.Validator
|
|
switch headState.Version() {
|
|
case version.Phase0:
|
|
vp, bp, err := precompute.New(ctx, headState)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vp, bp, err = precompute.ProcessAttestations(ctx, headState, vp, bp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
headState, err = precompute.ProcessRewardsAndPenaltiesPrecompute(headState, bp, vp, precompute.AttestationsDelta, precompute.ProposersDelta)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
validatorSummary = vp
|
|
case version.Altair:
|
|
vp, bp, err := altair.InitializePrecomputeValidators(ctx, headState)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vp, bp, err = altair.ProcessEpochParticipation(ctx, headState, bp, vp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
headState, vp, err = altair.ProcessInactivityScores(ctx, headState, vp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
headState, err = altair.ProcessRewardsAndPenaltiesPrecompute(headState, bp, vp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
validatorSummary = vp
|
|
}
|
|
|
|
responseCap := len(req.Indices) + len(req.PublicKeys)
|
|
validatorIndices := make([]types.ValidatorIndex, 0, responseCap)
|
|
missingValidators := make([][]byte, 0, responseCap)
|
|
|
|
filtered := map[types.ValidatorIndex]bool{} // Track filtered validators to prevent duplication in the response.
|
|
// Convert the list of validator public keys to validator indices and add to the indices set.
|
|
for _, pubKey := range req.PublicKeys {
|
|
// Skip empty public key.
|
|
if len(pubKey) == 0 {
|
|
continue
|
|
}
|
|
pubkeyBytes := bytesutil.ToBytes48(pubKey)
|
|
idx, ok := headState.ValidatorIndexByPubkey(pubkeyBytes)
|
|
if !ok {
|
|
// Validator index not found, track as missing.
|
|
missingValidators = append(missingValidators, pubKey)
|
|
continue
|
|
}
|
|
if !filtered[idx] {
|
|
validatorIndices = append(validatorIndices, idx)
|
|
filtered[idx] = true
|
|
}
|
|
}
|
|
// Add provided indices to the indices set.
|
|
for _, idx := range req.Indices {
|
|
if !filtered[idx] {
|
|
validatorIndices = append(validatorIndices, idx)
|
|
filtered[idx] = true
|
|
}
|
|
}
|
|
// Depending on the indices and public keys given, results might not be sorted.
|
|
sort.Slice(validatorIndices, func(i, j int) bool {
|
|
return validatorIndices[i] < validatorIndices[j]
|
|
})
|
|
|
|
currentEpoch := coreTime.CurrentEpoch(headState)
|
|
responseCap = len(validatorIndices)
|
|
pubKeys := make([][]byte, 0, responseCap)
|
|
beforeTransitionBalances := make([]uint64, 0, responseCap)
|
|
afterTransitionBalances := make([]uint64, 0, responseCap)
|
|
effectiveBalances := make([]uint64, 0, responseCap)
|
|
inclusionSlots := make([]types.Slot, 0, responseCap)
|
|
inclusionDistances := make([]types.Slot, 0, responseCap)
|
|
correctlyVotedSource := make([]bool, 0, responseCap)
|
|
correctlyVotedTarget := make([]bool, 0, responseCap)
|
|
correctlyVotedHead := make([]bool, 0, responseCap)
|
|
inactivityScores := make([]uint64, 0, responseCap)
|
|
// Append performance summaries.
|
|
// Also track missing validators using public keys.
|
|
for _, idx := range validatorIndices {
|
|
val, err := headState.ValidatorAtIndexReadOnly(idx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "could not get validator: %v", err)
|
|
}
|
|
pubKey := val.PublicKey()
|
|
if uint64(idx) >= uint64(len(validatorSummary)) {
|
|
// Not listed in validator summary yet; treat it as missing.
|
|
missingValidators = append(missingValidators, pubKey[:])
|
|
continue
|
|
}
|
|
if !helpers.IsActiveValidatorUsingTrie(val, currentEpoch) {
|
|
// Inactive validator; treat it as missing.
|
|
missingValidators = append(missingValidators, pubKey[:])
|
|
continue
|
|
}
|
|
|
|
summary := validatorSummary[idx]
|
|
pubKeys = append(pubKeys, pubKey[:])
|
|
effectiveBalances = append(effectiveBalances, summary.CurrentEpochEffectiveBalance)
|
|
beforeTransitionBalances = append(beforeTransitionBalances, summary.BeforeEpochTransitionBalance)
|
|
afterTransitionBalances = append(afterTransitionBalances, summary.AfterEpochTransitionBalance)
|
|
correctlyVotedTarget = append(correctlyVotedTarget, summary.IsPrevEpochTargetAttester)
|
|
correctlyVotedHead = append(correctlyVotedHead, summary.IsPrevEpochHeadAttester)
|
|
|
|
if headState.Version() == version.Phase0 {
|
|
correctlyVotedSource = append(correctlyVotedSource, summary.IsPrevEpochAttester)
|
|
inclusionSlots = append(inclusionSlots, summary.InclusionSlot)
|
|
inclusionDistances = append(inclusionDistances, summary.InclusionDistance)
|
|
} else {
|
|
correctlyVotedSource = append(correctlyVotedSource, summary.IsPrevEpochSourceAttester)
|
|
inactivityScores = append(inactivityScores, summary.InactivityScore)
|
|
}
|
|
}
|
|
|
|
return ðpb.ValidatorPerformanceResponse{
|
|
PublicKeys: pubKeys,
|
|
CorrectlyVotedSource: correctlyVotedSource,
|
|
CorrectlyVotedTarget: correctlyVotedTarget, // In altair, when this is true then the attestation was definitely included.
|
|
CorrectlyVotedHead: correctlyVotedHead,
|
|
CurrentEffectiveBalances: effectiveBalances,
|
|
BalancesBeforeEpochTransition: beforeTransitionBalances,
|
|
BalancesAfterEpochTransition: afterTransitionBalances,
|
|
MissingValidators: missingValidators,
|
|
InclusionSlots: inclusionSlots, // Only populated in phase0
|
|
InclusionDistances: inclusionDistances, // Only populated in phase 0
|
|
InactivityScores: inactivityScores, // Only populated in Altair
|
|
}, nil
|
|
}
|
|
|
|
// GetIndividualVotes retrieves individual voting status of validators.
|
|
func (bs *Server) GetIndividualVotes(
|
|
ctx context.Context,
|
|
req *ethpb.IndividualVotesRequest,
|
|
) (*ethpb.IndividualVotesRespond, error) {
|
|
currentEpoch := slots.ToEpoch(bs.GenesisTimeFetcher.CurrentSlot())
|
|
if req.Epoch > currentEpoch {
|
|
return nil, status.Errorf(
|
|
codes.InvalidArgument,
|
|
errEpoch,
|
|
currentEpoch,
|
|
req.Epoch,
|
|
)
|
|
}
|
|
|
|
s, err := slots.EpochEnd(req.Epoch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
requestedState, err := bs.StateGen.StateBySlot(ctx, s)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not retrieve archived state for epoch %d: %v", req.Epoch, err)
|
|
}
|
|
// Track filtered validators to prevent duplication in the response.
|
|
filtered := map[types.ValidatorIndex]bool{}
|
|
filteredIndices := make([]types.ValidatorIndex, 0)
|
|
votes := make([]*ethpb.IndividualVotesRespond_IndividualVote, 0, len(req.Indices)+len(req.PublicKeys))
|
|
// Filter out assignments by public keys.
|
|
for _, pubKey := range req.PublicKeys {
|
|
index, ok := requestedState.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey))
|
|
if !ok {
|
|
votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{PublicKey: pubKey, ValidatorIndex: types.ValidatorIndex(^uint64(0))})
|
|
continue
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
sort.Slice(filteredIndices, func(i, j int) bool {
|
|
return filteredIndices[i] < filteredIndices[j]
|
|
})
|
|
|
|
var v []*precompute.Validator
|
|
var bal *precompute.Balance
|
|
switch requestedState.Version() {
|
|
case version.Phase0:
|
|
v, bal, err = precompute.New(ctx, requestedState)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not set up pre compute instance: %v", err)
|
|
}
|
|
v, _, err = precompute.ProcessAttestations(ctx, requestedState, v, bal)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err)
|
|
}
|
|
case version.Altair:
|
|
v, bal, err = altair.InitializePrecomputeValidators(ctx, requestedState)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not set up altair pre compute instance: %v", err)
|
|
}
|
|
v, _, err = altair.ProcessEpochParticipation(ctx, requestedState, bal, v)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err)
|
|
}
|
|
default:
|
|
return nil, status.Errorf(codes.Internal, "Invalid state type retrieved with a version of %d", requestedState.Version())
|
|
}
|
|
|
|
for _, index := range filteredIndices {
|
|
if uint64(index) >= uint64(len(v)) {
|
|
votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{ValidatorIndex: index})
|
|
continue
|
|
}
|
|
val, err := requestedState.ValidatorAtIndexReadOnly(index)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not retrieve validator: %v", err)
|
|
|
|
}
|
|
pb := val.PublicKey()
|
|
votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{
|
|
Epoch: req.Epoch,
|
|
PublicKey: pb[:],
|
|
ValidatorIndex: index,
|
|
IsSlashed: v[index].IsSlashed,
|
|
IsWithdrawableInCurrentEpoch: v[index].IsWithdrawableCurrentEpoch,
|
|
IsActiveInCurrentEpoch: v[index].IsActiveCurrentEpoch,
|
|
IsActiveInPreviousEpoch: v[index].IsActivePrevEpoch,
|
|
IsCurrentEpochAttester: v[index].IsCurrentEpochAttester,
|
|
IsCurrentEpochTargetAttester: v[index].IsCurrentEpochTargetAttester,
|
|
IsPreviousEpochAttester: v[index].IsPrevEpochAttester,
|
|
IsPreviousEpochTargetAttester: v[index].IsPrevEpochTargetAttester,
|
|
IsPreviousEpochHeadAttester: v[index].IsPrevEpochHeadAttester,
|
|
CurrentEpochEffectiveBalanceGwei: v[index].CurrentEpochEffectiveBalance,
|
|
InclusionSlot: v[index].InclusionSlot,
|
|
InclusionDistance: v[index].InclusionDistance,
|
|
InactivityScore: v[index].InactivityScore,
|
|
})
|
|
}
|
|
|
|
return ðpb.IndividualVotesRespond{
|
|
IndividualVotes: votes,
|
|
}, nil
|
|
}
|
|
|
|
// isSlotCanonical returns true if the input slot has a canonical block in the chain,
|
|
// if the input slot has a skip block, false is returned,
|
|
// if the input slot has more than one block, an error is returned.
|
|
func (bs *Server) isSlotCanonical(ctx context.Context, slot types.Slot) (bool, error) {
|
|
if slot == 0 {
|
|
return true, nil
|
|
}
|
|
|
|
hasBlockRoots, roots, err := bs.BeaconDB.BlockRootsBySlot(ctx, slot)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if !hasBlockRoots {
|
|
return false, nil
|
|
}
|
|
|
|
// Loop through all roots in slot, and
|
|
// check which one is canonical.
|
|
for _, rt := range roots {
|
|
canonical, err := bs.CanonicalFetcher.IsCanonical(ctx, rt)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if canonical {
|
|
return true, nil
|
|
}
|
|
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// Determines whether a validator has already exited.
|
|
func validatorHasExited(validator *ethpb.Validator, currentEpoch types.Epoch) bool {
|
|
farFutureEpoch := params.BeaconConfig().FarFutureEpoch
|
|
if currentEpoch < validator.ActivationEligibilityEpoch {
|
|
return false
|
|
}
|
|
if currentEpoch < validator.ActivationEpoch {
|
|
return false
|
|
}
|
|
if validator.ExitEpoch == farFutureEpoch {
|
|
return false
|
|
}
|
|
if currentEpoch < validator.ExitEpoch {
|
|
if validator.Slashed {
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func validatorStatus(validator *ethpb.Validator, epoch types.Epoch) ethpb.ValidatorStatus {
|
|
farFutureEpoch := params.BeaconConfig().FarFutureEpoch
|
|
if validator == nil {
|
|
return ethpb.ValidatorStatus_UNKNOWN_STATUS
|
|
}
|
|
if epoch < validator.ActivationEligibilityEpoch {
|
|
return ethpb.ValidatorStatus_DEPOSITED
|
|
}
|
|
if epoch < validator.ActivationEpoch {
|
|
return ethpb.ValidatorStatus_PENDING
|
|
}
|
|
if validator.ExitEpoch == farFutureEpoch {
|
|
return ethpb.ValidatorStatus_ACTIVE
|
|
}
|
|
if epoch < validator.ExitEpoch {
|
|
if validator.Slashed {
|
|
return ethpb.ValidatorStatus_SLASHING
|
|
}
|
|
return ethpb.ValidatorStatus_EXITING
|
|
}
|
|
return ethpb.ValidatorStatus_EXITED
|
|
}
|