mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-31 16:21:21 +00:00
544 lines
17 KiB
Go
544 lines
17 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
libcommon "github.com/ledgerwatch/erigon-lib/common"
|
|
"github.com/ledgerwatch/erigon-lib/kv"
|
|
"github.com/ledgerwatch/erigon/cl/beacon/beaconhttp"
|
|
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
|
|
"github.com/ledgerwatch/erigon/cl/persistence/beacon_indicies"
|
|
state_accessors "github.com/ledgerwatch/erigon/cl/persistence/state"
|
|
"github.com/ledgerwatch/erigon/cl/phase1/core/state"
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
type validatorStatus int
|
|
|
|
var validatorJsonTemplate = "{\"index\":\"%d\",\"status\":\"%s\",\"balance\":\"%d\",\"validator\":{\"pubkey\":\"0x%x\",\"withdrawal_credentials\":\"0x%x\",\"effective_balance\":\"%d\",\"slashed\":%t,\"activation_eligibility_epoch\":\"%d\",\"activation_epoch\":\"%d\",\"exit_epoch\":\"%d\",\"withdrawable_epoch\":\"%d\"}}"
|
|
|
|
const (
|
|
validatorPendingInitialized validatorStatus = 1 //"pending_initialized"
|
|
validatorPendingQueued validatorStatus = 2 //"pending_queued"
|
|
validatorActiveOngoing validatorStatus = 3 //"active_ongoing"
|
|
validatorActiveExiting validatorStatus = 4 //"active_exiting"
|
|
validatorActiveSlashed validatorStatus = 5 //"active_slashed"
|
|
validatorExitedUnslashed validatorStatus = 6 //"exited_unslashed"
|
|
validatorExitedSlashed validatorStatus = 7 //"exited_slashed"
|
|
validatorWithdrawalPossible validatorStatus = 8 //"withdrawal_possible"
|
|
validatorWithdrawalDone validatorStatus = 9 //"withdrawal_done"
|
|
validatorActive validatorStatus = 10 //"active"
|
|
validatorPending validatorStatus = 11 //"pending"
|
|
validatorExited validatorStatus = 12 //"exited"
|
|
validatorWithdrawal validatorStatus = 13 //"withdrawal"
|
|
)
|
|
|
|
func validatorStatusFromString(s string) (validatorStatus, error) {
|
|
switch s {
|
|
case "pending_initialized":
|
|
return validatorPendingInitialized, nil
|
|
case "pending_queued":
|
|
return validatorPendingQueued, nil
|
|
case "active_ongoing":
|
|
return validatorActiveOngoing, nil
|
|
case "active_exiting":
|
|
return validatorActiveExiting, nil
|
|
case "active_slashed":
|
|
return validatorActiveSlashed, nil
|
|
case "exited_unslashed":
|
|
return validatorExitedUnslashed, nil
|
|
case "exited_slashed":
|
|
return validatorExitedSlashed, nil
|
|
case "withdrawal_possible":
|
|
return validatorWithdrawalPossible, nil
|
|
case "withdrawal_done":
|
|
return validatorWithdrawalDone, nil
|
|
case "active":
|
|
return validatorActive, nil
|
|
case "pending":
|
|
return validatorPending, nil
|
|
case "exited":
|
|
return validatorExited, nil
|
|
case "withdrawal":
|
|
return validatorWithdrawal, nil
|
|
default:
|
|
return 0, fmt.Errorf("invalid validator status %s", s)
|
|
}
|
|
}
|
|
|
|
func validatorStatusFromValidator(v solid.Validator, currentEpoch uint64, balance uint64) validatorStatus {
|
|
activationEpoch := v.ActivationEpoch()
|
|
// pending section
|
|
if activationEpoch > currentEpoch {
|
|
activationEligibilityEpoch := v.ActivationEligibilityEpoch()
|
|
if activationEligibilityEpoch == math.MaxUint64 {
|
|
return validatorPendingInitialized
|
|
}
|
|
return validatorPendingQueued
|
|
}
|
|
|
|
exitEpoch := v.ExitEpoch()
|
|
// active section
|
|
if activationEpoch <= currentEpoch && currentEpoch < exitEpoch {
|
|
if exitEpoch == math.MaxUint64 {
|
|
return validatorActiveOngoing
|
|
}
|
|
slashed := v.Slashed()
|
|
if slashed {
|
|
return validatorActiveSlashed
|
|
}
|
|
return validatorActiveExiting
|
|
}
|
|
|
|
withdrawableEpoch := v.WithdrawableEpoch()
|
|
// exited section
|
|
if exitEpoch <= currentEpoch && currentEpoch < withdrawableEpoch {
|
|
if v.Slashed() {
|
|
return validatorExitedSlashed
|
|
}
|
|
return validatorExitedUnslashed
|
|
}
|
|
|
|
if balance == 0 {
|
|
return validatorWithdrawalDone
|
|
}
|
|
return validatorWithdrawalPossible
|
|
|
|
}
|
|
|
|
func (s validatorStatus) String() string {
|
|
switch s {
|
|
case validatorPendingInitialized:
|
|
return "pending_initialized"
|
|
case validatorPendingQueued:
|
|
return "pending_queued"
|
|
case validatorActiveOngoing:
|
|
return "active_ongoing"
|
|
case validatorActiveExiting:
|
|
return "active_exiting"
|
|
case validatorActiveSlashed:
|
|
return "active_slashed"
|
|
case validatorExitedUnslashed:
|
|
return "exited_unslashed"
|
|
case validatorExitedSlashed:
|
|
return "exited_slashed"
|
|
case validatorWithdrawalPossible:
|
|
return "withdrawal_possible"
|
|
case validatorWithdrawalDone:
|
|
return "withdrawal_done"
|
|
case validatorActive:
|
|
return "active"
|
|
case validatorPending:
|
|
return "pending"
|
|
case validatorExited:
|
|
return "exited"
|
|
case validatorWithdrawal:
|
|
return "withdrawal"
|
|
default:
|
|
panic("invalid validator status")
|
|
}
|
|
}
|
|
|
|
const maxValidatorsLookupFilter = 32
|
|
|
|
func parseStatuses(s []string) ([]validatorStatus, error) {
|
|
seenAlready := make(map[validatorStatus]struct{})
|
|
statuses := make([]validatorStatus, 0, len(s))
|
|
|
|
if len(s) > maxValidatorsLookupFilter {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, "too many statuses requested")
|
|
}
|
|
|
|
for _, status := range s {
|
|
s, err := validatorStatusFromString(status)
|
|
if err != nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
|
|
}
|
|
if _, ok := seenAlready[s]; ok {
|
|
continue
|
|
}
|
|
seenAlready[s] = struct{}{}
|
|
statuses = append(statuses, s)
|
|
}
|
|
return statuses, nil
|
|
}
|
|
|
|
func checkValidValidatorId(s string) (bool, error) {
|
|
// If it starts with 0x, then it must a 48bytes 0x prefixed string
|
|
if len(s) == 98 && s[:2] == "0x" {
|
|
// check if it is a valid hex string
|
|
if _, err := hex.DecodeString(s[2:]); err != nil {
|
|
return false, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
|
|
}
|
|
return true, nil
|
|
}
|
|
// If it is not 0x prefixed, then it must be a number, check if it is a base-10 number
|
|
if _, err := strconv.ParseUint(s, 10, 64); err != nil {
|
|
return false, beaconhttp.NewEndpointError(http.StatusBadRequest, "invalid validator id")
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func (a *ApiHandler) getAllValidators(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
|
|
ctx := r.Context()
|
|
|
|
tx, err := a.indiciesDB.BeginRo(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
blockId, err := beaconhttp.StateIdFromRequest(r)
|
|
if err != nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
|
|
}
|
|
|
|
blockRoot, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId)
|
|
if err != nil {
|
|
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
|
|
}
|
|
|
|
queryFilters, err := beaconhttp.StringListFromQueryParams(r, "status")
|
|
if err != nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
|
|
}
|
|
|
|
validatorIds, err := beaconhttp.StringListFromQueryParams(r, "id")
|
|
if err != nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
|
|
}
|
|
|
|
if len(validatorIds) > maxValidatorsLookupFilter {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, "too many validators requested")
|
|
}
|
|
filterIndicies, err := parseQueryValidatorIndicies(tx, validatorIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Check the filters' validity
|
|
statusFilters, err := parseStatuses(queryFilters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if blockId.Head() { // Lets see if we point to head, if yes then we need to look at the head state we always keep.
|
|
s, cn := a.syncedData.HeadState()
|
|
defer cn()
|
|
if s == nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, "node is not synced")
|
|
}
|
|
return responseValidators(filterIndicies, statusFilters, state.Epoch(s), s.Balances(), s.Validators(), false)
|
|
}
|
|
slot, err := beacon_indicies.ReadBlockSlotByBlockRoot(tx, blockRoot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if slot == nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, "state not found")
|
|
}
|
|
stateEpoch := *slot / a.beaconChainCfg.SlotsPerEpoch
|
|
state, err := a.forkchoiceStore.GetStateAtBlockRoot(blockRoot, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if state == nil {
|
|
validatorSet, err := a.stateReader.ReadValidatorsForHistoricalState(tx, *slot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
balances, err := a.stateReader.ReadValidatorsBalances(tx, *slot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return responseValidators(filterIndicies, statusFilters, stateEpoch, balances, validatorSet, true)
|
|
}
|
|
return responseValidators(filterIndicies, statusFilters, stateEpoch, state.Balances(), state.Validators(), *slot <= a.forkchoiceStore.FinalizedSlot())
|
|
}
|
|
|
|
func parseQueryValidatorIndex(tx kv.Tx, id string) (uint64, error) {
|
|
isPublicKey, err := checkValidValidatorId(id)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if isPublicKey {
|
|
var b48 libcommon.Bytes48
|
|
if err := b48.UnmarshalText([]byte(id)); err != nil {
|
|
return 0, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
|
|
}
|
|
has, err := tx.Has(kv.InvertedValidatorPublicKeys, b48[:])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if !has {
|
|
return math.MaxUint64, nil
|
|
}
|
|
idx, ok, err := state_accessors.ReadValidatorIndexByPublicKey(tx, b48)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if !ok {
|
|
return 0, beaconhttp.NewEndpointError(http.StatusNotFound, "validator not found")
|
|
}
|
|
return idx, nil
|
|
}
|
|
idx, err := strconv.ParseUint(id, 10, 64)
|
|
if err != nil {
|
|
return 0, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
|
|
}
|
|
return idx, nil
|
|
|
|
}
|
|
|
|
func parseQueryValidatorIndicies(tx kv.Tx, ids []string) ([]uint64, error) {
|
|
filterIndicies := make([]uint64, 0, len(ids))
|
|
|
|
for _, id := range ids {
|
|
idx, err := parseQueryValidatorIndex(tx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
filterIndicies = append(filterIndicies, idx)
|
|
}
|
|
return filterIndicies, nil
|
|
}
|
|
|
|
func (a *ApiHandler) getSingleValidator(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
|
|
ctx := r.Context()
|
|
|
|
tx, err := a.indiciesDB.BeginRo(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
blockId, err := beaconhttp.StateIdFromRequest(r)
|
|
if err != nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
|
|
}
|
|
|
|
blockRoot, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId)
|
|
if err != nil {
|
|
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
|
|
}
|
|
|
|
validatorId, err := beaconhttp.StringFromRequest(r, "validator_id")
|
|
if err != nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
|
|
}
|
|
|
|
validatorIndex, err := parseQueryValidatorIndex(tx, validatorId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if blockId.Head() { // Lets see if we point to head, if yes then we need to look at the head state we always keep.
|
|
s, cn := a.syncedData.HeadState()
|
|
defer cn()
|
|
if s.ValidatorLength() <= int(validatorIndex) {
|
|
return newBeaconResponse([]int{}).WithFinalized(false), nil
|
|
}
|
|
if s == nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, "node is not synced")
|
|
}
|
|
return responseValidator(validatorIndex, state.Epoch(s), s.Balances(), s.Validators(), false)
|
|
}
|
|
slot, err := beacon_indicies.ReadBlockSlotByBlockRoot(tx, blockRoot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if slot == nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, "state not found")
|
|
}
|
|
stateEpoch := *slot / a.beaconChainCfg.SlotsPerEpoch
|
|
state, err := a.forkchoiceStore.GetStateAtBlockRoot(blockRoot, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if state == nil {
|
|
validatorSet, err := a.stateReader.ReadValidatorsForHistoricalState(tx, *slot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
balances, err := a.stateReader.ReadValidatorsBalances(tx, *slot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return responseValidator(validatorIndex, stateEpoch, balances, validatorSet, true)
|
|
}
|
|
return responseValidator(validatorIndex, stateEpoch, state.Balances(), state.Validators(), *slot <= a.forkchoiceStore.FinalizedSlot())
|
|
}
|
|
|
|
func (a *ApiHandler) getAllValidatorsBalances(w http.ResponseWriter, r *http.Request) (*beaconhttp.BeaconResponse, error) {
|
|
ctx := r.Context()
|
|
|
|
tx, err := a.indiciesDB.BeginRo(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
blockId, err := beaconhttp.StateIdFromRequest(r)
|
|
if err != nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
|
|
}
|
|
|
|
blockRoot, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId)
|
|
if err != nil {
|
|
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
|
|
}
|
|
|
|
validatorIds, err := beaconhttp.StringListFromQueryParams(r, "id")
|
|
if err != nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
|
|
}
|
|
|
|
if len(validatorIds) > maxValidatorsLookupFilter {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, "too many validators requested")
|
|
}
|
|
filterIndicies, err := parseQueryValidatorIndicies(tx, validatorIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if blockId.Head() { // Lets see if we point to head, if yes then we need to look at the head state we always keep.
|
|
s, cn := a.syncedData.HeadState()
|
|
defer cn()
|
|
if s == nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, "node is not synced")
|
|
}
|
|
return responseValidatorsBalances(filterIndicies, state.Epoch(s), s.Balances(), false)
|
|
}
|
|
slot, err := beacon_indicies.ReadBlockSlotByBlockRoot(tx, blockRoot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if slot == nil {
|
|
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, "state not found")
|
|
}
|
|
stateEpoch := *slot / a.beaconChainCfg.SlotsPerEpoch
|
|
state, err := a.forkchoiceStore.GetStateAtBlockRoot(blockRoot, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if state == nil {
|
|
balances, err := a.stateReader.ReadValidatorsBalances(tx, *slot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return responseValidatorsBalances(filterIndicies, stateEpoch, balances, true)
|
|
}
|
|
return responseValidatorsBalances(filterIndicies, stateEpoch, state.Balances(), *slot <= a.forkchoiceStore.FinalizedSlot())
|
|
}
|
|
|
|
type directString string
|
|
|
|
func (d directString) MarshalJSON() ([]byte, error) {
|
|
return []byte(d), nil
|
|
}
|
|
|
|
func responseValidators(filterIndicies []uint64, filterStatuses []validatorStatus, stateEpoch uint64, balances solid.Uint64ListSSZ, validators *solid.ValidatorSet, finalized bool) (*beaconhttp.BeaconResponse, error) {
|
|
var b strings.Builder
|
|
b.WriteString("[")
|
|
first := true
|
|
var err error
|
|
validators.Range(func(i int, v solid.Validator, l int) bool {
|
|
if len(filterIndicies) > 0 && !slices.Contains(filterIndicies, uint64(i)) {
|
|
return true
|
|
}
|
|
status := validatorStatusFromValidator(v, stateEpoch, balances.Get(i))
|
|
if shouldStatusBeFiltered(status, filterStatuses) {
|
|
return true
|
|
}
|
|
if !first {
|
|
if _, err = b.WriteString(","); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
first = false
|
|
if _, err = b.WriteString(fmt.Sprintf(validatorJsonTemplate, i, status.String(), balances.Get(i), v.PublicKey(), v.WithdrawalCredentials(), v.EffectiveBalance(), v.Slashed(), v.ActivationEligibilityEpoch(), v.ActivationEpoch(), v.ExitEpoch(), v.WithdrawableEpoch())); err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = b.WriteString("]\n")
|
|
|
|
return newBeaconResponse(directString(b.String())).WithFinalized(finalized), err
|
|
}
|
|
|
|
func responseValidator(idx uint64, stateEpoch uint64, balances solid.Uint64ListSSZ, validators *solid.ValidatorSet, finalized bool) (*beaconhttp.BeaconResponse, error) {
|
|
var b strings.Builder
|
|
var err error
|
|
if validators.Length() <= int(idx) {
|
|
return newBeaconResponse([]int{}).WithFinalized(finalized), nil
|
|
}
|
|
|
|
v := validators.Get(int(idx))
|
|
status := validatorStatusFromValidator(v, stateEpoch, balances.Get(int(idx)))
|
|
|
|
if _, err = b.WriteString(fmt.Sprintf(validatorJsonTemplate, idx, status.String(), balances.Get(int(idx)), v.PublicKey(), v.WithdrawalCredentials(), v.EffectiveBalance(), v.Slashed(), v.ActivationEligibilityEpoch(), v.ActivationEpoch(), v.ExitEpoch(), v.WithdrawableEpoch())); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = b.WriteString("\n")
|
|
|
|
return newBeaconResponse(directString(b.String())).WithFinalized(finalized), err
|
|
}
|
|
|
|
func responseValidatorsBalances(filterIndicies []uint64, stateEpoch uint64, balances solid.Uint64ListSSZ, finalized bool) (*beaconhttp.BeaconResponse, error) {
|
|
var b strings.Builder
|
|
b.WriteString("[")
|
|
jsonTemplate := "{\"index\":\"%d\",\"balance\":\"%d\"}"
|
|
first := true
|
|
var err error
|
|
balances.Range(func(i int, v uint64, l int) bool {
|
|
if len(filterIndicies) > 0 && !slices.Contains(filterIndicies, uint64(i)) {
|
|
return true
|
|
}
|
|
|
|
if !first {
|
|
if _, err = b.WriteString(","); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
first = false
|
|
if _, err = b.WriteString(fmt.Sprintf(jsonTemplate, i, v)); err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = b.WriteString("]\n")
|
|
|
|
return newBeaconResponse(directString(b.String())).WithFinalized(finalized), err
|
|
}
|
|
|
|
func shouldStatusBeFiltered(status validatorStatus, statuses []validatorStatus) bool {
|
|
if len(statuses) == 0 {
|
|
return false
|
|
}
|
|
for _, s := range statuses {
|
|
if (s == status) || (s == validatorActive && (status == validatorActiveOngoing || status == validatorActiveExiting || status == validatorActiveSlashed)) ||
|
|
(s == validatorPending && (status == validatorPendingInitialized || status == validatorPendingQueued)) ||
|
|
(s == validatorExited && (status == validatorExitedUnslashed || status == validatorExitedSlashed)) ||
|
|
(s == validatorWithdrawal && (status == validatorWithdrawalPossible || status == validatorWithdrawalDone)) {
|
|
return false
|
|
}
|
|
}
|
|
return true // filter if no filter condition is met
|
|
}
|