|
|
|
package rpc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"reflect"
|
|
|
|
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"golang.org/x/time/rate"
|
|
|
|
|
|
|
|
lru "github.com/hashicorp/golang-lru"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
|
|
"github.com/harmony-one/harmony/hmy"
|
|
|
|
internal_common "github.com/harmony-one/harmony/internal/common"
|
|
|
|
"github.com/harmony-one/harmony/shard"
|
|
|
|
staking "github.com/harmony-one/harmony/staking/types"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
validatorsPageSize = 100
|
|
|
|
|
|
|
|
validatorInfoCacheSize = 128
|
|
|
|
)
|
|
|
|
|
|
|
|
// PublicStakingService provides an API to access Harmony's staking services.
|
|
|
|
// It offers only methods that operate on public data that is freely available to anyone.
|
|
|
|
type PublicStakingService struct {
|
|
|
|
hmy *hmy.Harmony
|
|
|
|
version Version
|
|
|
|
|
|
|
|
validatorInfoCache *lru.Cache // cache for detailed validator information per page and block
|
|
|
|
// TEMP SOLUTION to rpc node spamming issue
|
|
|
|
limiterGetAllValidatorInformation *rate.Limiter
|
|
|
|
limiterGetAllDelegationInformation *rate.Limiter
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewPublicStakingAPI creates a new API for the RPC interface
|
|
|
|
func NewPublicStakingAPI(hmy *hmy.Harmony, version Version) rpc.API {
|
|
|
|
viCache, _ := lru.New(validatorInfoCacheSize)
|
|
|
|
return rpc.API{
|
|
|
|
Namespace: version.Namespace(),
|
|
|
|
Version: APIVersion,
|
|
|
|
Service: &PublicStakingService{
|
|
|
|
hmy: hmy,
|
|
|
|
version: version,
|
|
|
|
validatorInfoCache: viCache,
|
|
|
|
limiterGetAllValidatorInformation: rate.NewLimiter(1, 3),
|
|
|
|
limiterGetAllDelegationInformation: rate.NewLimiter(1, 3),
|
|
|
|
},
|
|
|
|
Public: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PublicStakingService) wait(limiter *rate.Limiter, ctx context.Context) error {
|
|
|
|
if limiter != nil {
|
|
|
|
deadlineCtx, cancel := context.WithTimeout(ctx, DefaultRateLimiterWaitTimeout)
|
|
|
|
defer cancel()
|
|
|
|
if !limiter.Allow() {
|
|
|
|
strLimit := fmt.Sprintf("%d", int64(limiter.Limit()))
|
|
|
|
|
|
|
|
name := reflect.TypeOf(limiter).Elem().Name()
|
|
|
|
rpcRateLimitCounterVec.With(prometheus.Labels{
|
|
|
|
name: strLimit,
|
|
|
|
}).Inc()
|
|
|
|
}
|
|
|
|
|
|
|
|
return limiter.Wait(deadlineCtx)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getBalanceByBlockNumber returns balance by block number at given eth blockNum without checks
|
|
|
|
func (s *PublicStakingService) getBalanceByBlockNumber(
|
|
|
|
ctx context.Context, address string, blockNum rpc.BlockNumber,
|
|
|
|
) (*big.Int, error) {
|
|
|
|
addr, err := internal_common.ParseAddr(address)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
balance, err := s.hmy.GetBalance(ctx, addr, blockNum)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return balance, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTotalStaking returns total staking by validators, only meant to be called on beaconchain
|
|
|
|
// explorer node
|
|
|
|
func (s *PublicStakingService) GetTotalStaking(
|
|
|
|
ctx context.Context,
|
|
|
|
) (*big.Int, error) {
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
|
|
|
|
// Response output is the same for all versions
|
|
|
|
return s.hmy.GetTotalStakingSnapshot(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetMedianRawStakeSnapshot returns the raw median stake, only meant to be called on beaconchain
|
|
|
|
// explorer node
|
|
|
|
func (s *PublicStakingService) GetMedianRawStakeSnapshot(
|
|
|
|
ctx context.Context,
|
|
|
|
) (StructuredResponse, error) {
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch snapshot
|
|
|
|
snapshot, err := s.hmy.GetMedianRawStakeSnapshot()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Response output is the same for all versions
|
|
|
|
return NewStructuredResponse(snapshot)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetElectedValidatorAddresses returns elected validator addresses.
|
|
|
|
func (s *PublicStakingService) GetElectedValidatorAddresses(
|
|
|
|
ctx context.Context,
|
|
|
|
) ([]string, error) {
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch elected validators
|
|
|
|
electedAddresses := s.hmy.GetElectedValidatorAddresses()
|
|
|
|
addresses := make([]string, len(electedAddresses))
|
|
|
|
for i, addr := range electedAddresses {
|
|
|
|
oneAddr, _ := internal_common.AddressToBech32(addr)
|
|
|
|
// Response output is the same for all versions
|
|
|
|
addresses[i] = oneAddr
|
|
|
|
}
|
|
|
|
return addresses, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetValidators returns validators list for a particular epoch.
|
|
|
|
func (s *PublicStakingService) GetValidators(
|
|
|
|
ctx context.Context, epoch int64,
|
|
|
|
) (StructuredResponse, error) {
|
|
|
|
// Fetch the Committee
|
|
|
|
cmt, err := s.hmy.GetValidators(big.NewInt(epoch))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
balanceQueryBlock := shard.Schedule.EpochLastBlock(uint64(epoch))
|
|
|
|
if balanceQueryBlock > s.hmy.CurrentBlock().NumberU64() {
|
|
|
|
balanceQueryBlock = s.hmy.CurrentBlock().NumberU64()
|
|
|
|
}
|
|
|
|
|
|
|
|
validators := []StructuredResponse{}
|
|
|
|
for _, validator := range cmt.Slots {
|
|
|
|
// Fetch the balance of the validator
|
|
|
|
oneAddress, err := internal_common.AddressToBech32(validator.EcdsaAddress)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
validatorBalance, err := s.getBalanceByBlockNumber(ctx, oneAddress, rpc.BlockNumber(balanceQueryBlock))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Format the response according to the version
|
|
|
|
var validatorsFields StructuredResponse
|
|
|
|
switch s.version {
|
|
|
|
case V1:
|
|
|
|
validatorsFields = StructuredResponse{
|
|
|
|
"address": oneAddress,
|
|
|
|
"balance": (*hexutil.Big)(validatorBalance),
|
|
|
|
}
|
|
|
|
case V2:
|
|
|
|
validatorsFields = StructuredResponse{
|
|
|
|
"address": oneAddress,
|
|
|
|
"balance": validatorBalance,
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return nil, ErrUnknownRPCVersion
|
|
|
|
}
|
|
|
|
validators = append(validators, validatorsFields)
|
|
|
|
}
|
|
|
|
result := StructuredResponse{
|
|
|
|
"shardID": cmt.ShardID,
|
|
|
|
"validators": validators,
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAllValidatorAddresses returns all validator addresses.
|
|
|
|
func (s *PublicStakingService) GetAllValidatorAddresses(
|
|
|
|
ctx context.Context,
|
|
|
|
) ([]string, error) {
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch all validator addresses
|
|
|
|
validatorAddresses := s.hmy.GetAllValidatorAddresses()
|
|
|
|
addresses := make([]string, len(validatorAddresses))
|
|
|
|
for i, addr := range validatorAddresses {
|
|
|
|
oneAddr, _ := internal_common.AddressToBech32(addr)
|
|
|
|
// Response output is the same for all versions
|
|
|
|
addresses[i] = oneAddr
|
|
|
|
}
|
|
|
|
return addresses, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetValidatorKeys returns list of bls public keys in the committee for a particular epoch.
|
|
|
|
func (s *PublicStakingService) GetValidatorKeys(
|
|
|
|
ctx context.Context, epoch int64,
|
|
|
|
) ([]string, error) {
|
|
|
|
// Fetch the Committee
|
|
|
|
cmt, err := s.hmy.GetValidators(big.NewInt(epoch))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Response output is the same for all versions
|
|
|
|
validators := make([]string, len(cmt.Slots))
|
|
|
|
for i, v := range cmt.Slots {
|
|
|
|
validators[i] = v.BLSPublicKey.Hex()
|
|
|
|
}
|
|
|
|
return validators, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAllValidatorInformation returns information about all validators.
|
|
|
|
// If page is -1, return all instead of `validatorsPageSize` elements.
|
|
|
|
func (s *PublicStakingService) GetAllValidatorInformation(
|
|
|
|
ctx context.Context, page int,
|
|
|
|
) (interface{}, error) {
|
|
|
|
timer := DoMetricRPCRequest(GetAllValidatorInformation)
|
|
|
|
defer DoRPCRequestDuration(GetAllValidatorInformation, timer)
|
|
|
|
|
|
|
|
err := s.wait(s.limiterGetAllValidatorInformation, ctx)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetAllValidatorInformation, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
DoMetricRPCQueryInfo(GetAllValidatorInformation, FailedNumber)
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err := s.getPagedValidatorInformationCached(ctx, page, LatestBlockNumber)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetAllValidatorInformation, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Response output is the same for all versions
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAllValidatorInformationByBlockNumber returns information about all validators.
|
|
|
|
// If page is -1, return all instead of `validatorsPageSize` elements.
|
|
|
|
func (s *PublicStakingService) GetAllValidatorInformationByBlockNumber(
|
|
|
|
ctx context.Context, page int, blockNumber BlockNumber,
|
|
|
|
) (interface{}, error) {
|
|
|
|
timer := DoMetricRPCRequest(GetAllValidatorInformationByBlockNumber)
|
|
|
|
defer DoRPCRequestDuration(GetAllValidatorInformationByBlockNumber, timer)
|
|
|
|
|
|
|
|
err := s.wait(s.limiterGetAllValidatorInformation, ctx)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetAllValidatorInformationByBlockNumber, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process number based on version
|
|
|
|
blockNum := blockNumber.EthBlockNumber()
|
|
|
|
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
DoMetricRPCQueryInfo(GetAllValidatorInformationByBlockNumber, FailedNumber)
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
if isBlockGreaterThanLatest(s.hmy, blockNum) {
|
|
|
|
DoMetricRPCQueryInfo(GetAllValidatorInformationByBlockNumber, FailedNumber)
|
|
|
|
return nil, ErrRequestedBlockTooHigh
|
|
|
|
}
|
|
|
|
|
|
|
|
// Response output is the same for all versions
|
|
|
|
res, err := s.getPagedValidatorInformationCached(ctx, page, blockNumber)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetAllValidatorInformationByBlockNumber, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PublicStakingService) getPagedValidatorInformationCached(ctx context.Context, page int, blockNumber BlockNumber) (interface{}, error) {
|
|
|
|
type cacheKey struct {
|
|
|
|
bn uint64
|
|
|
|
page int
|
|
|
|
}
|
|
|
|
var bn uint64
|
|
|
|
if blockNumber == LatestBlockNumber {
|
|
|
|
bn = s.hmy.CurrentBlock().NumberU64()
|
|
|
|
} else {
|
|
|
|
bn = uint64(blockNumber)
|
|
|
|
}
|
|
|
|
|
|
|
|
key := cacheKey{bn, page}
|
|
|
|
val, ok := s.validatorInfoCache.Get(key)
|
|
|
|
if ok && val != nil {
|
|
|
|
return val, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err := s.getAllValidatorInformation(ctx, page, bn)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
s.validatorInfoCache.Add(key, res)
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getAllValidatorInformation is the helper function to get all validator information for a given eth block number
|
|
|
|
func (s *PublicStakingService) getAllValidatorInformation(
|
|
|
|
ctx context.Context, page int, blockNum uint64,
|
|
|
|
) (interface{}, error) {
|
|
|
|
if page < -1 {
|
|
|
|
return nil, errors.Errorf("page given %d cannot be less than -1", page)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get all validators
|
|
|
|
addresses := s.hmy.GetAllValidatorAddresses()
|
|
|
|
if page != -1 && len(addresses) <= page*validatorsPageSize {
|
|
|
|
return []StructuredResponse{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set page start
|
|
|
|
validatorsNum := len(addresses)
|
|
|
|
start := 0
|
|
|
|
if page != -1 {
|
|
|
|
validatorsNum = validatorsPageSize
|
|
|
|
start = page * validatorsPageSize
|
|
|
|
if len(addresses)-start < validatorsPageSize {
|
|
|
|
validatorsNum = len(addresses) - start
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch block
|
|
|
|
blk, err := s.hmy.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "could not retrieve the blk information for blk number: %d", blockNum)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch validator information for block
|
|
|
|
validators := make([]*staking.ValidatorRPCEnhanced, 0, validatorsNum)
|
|
|
|
for i := start; i < start+validatorsNum; i++ {
|
|
|
|
validatorInfo, err := s.hmy.GetValidatorInformation(addresses[i], blk)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Response output is the same for all versions
|
|
|
|
validators = append(validators, validatorInfo)
|
|
|
|
}
|
|
|
|
return validators, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetValidatorInformation returns information about a validator.
|
|
|
|
func (s *PublicStakingService) GetValidatorInformation(
|
|
|
|
ctx context.Context, address string,
|
|
|
|
) (StructuredResponse, error) {
|
|
|
|
timer := DoMetricRPCRequest(GetValidatorInformation)
|
|
|
|
defer DoRPCRequestDuration(GetValidatorInformation, timer)
|
|
|
|
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
DoMetricRPCQueryInfo(GetValidatorInformation, FailedNumber)
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch latest block
|
|
|
|
blk, err := s.hmy.BlockByNumber(ctx, rpc.LatestBlockNumber)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetValidatorInformation, FailedNumber)
|
|
|
|
return nil, errors.Wrapf(err, "could not retrieve the latest blk information")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch validator information
|
|
|
|
addr, err := internal_common.ParseAddr(address)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetValidatorInformation, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
validatorInfo, err := s.hmy.GetValidatorInformation(addr, blk)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetValidatorInformation, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Response output is the same for all versions
|
|
|
|
return NewStructuredResponse(validatorInfo)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetValidatorInformationByBlockNumber returns information about a validator.
|
|
|
|
func (s *PublicStakingService) GetValidatorInformationByBlockNumber(
|
|
|
|
ctx context.Context, address string, blockNumber BlockNumber,
|
|
|
|
) (StructuredResponse, error) {
|
|
|
|
timer := DoMetricRPCRequest(GetValidatorInformationByBlockNumber)
|
|
|
|
defer DoRPCRequestDuration(GetValidatorInformationByBlockNumber, timer)
|
|
|
|
|
|
|
|
// Process number based on version
|
|
|
|
blockNum := blockNumber.EthBlockNumber()
|
|
|
|
|
|
|
|
// Fetch block
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
DoMetricRPCQueryInfo(GetValidatorInformationByBlockNumber, FailedNumber)
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
if isBlockGreaterThanLatest(s.hmy, blockNum) {
|
|
|
|
DoMetricRPCQueryInfo(GetValidatorInformationByBlockNumber, FailedNumber)
|
|
|
|
return nil, ErrRequestedBlockTooHigh
|
|
|
|
}
|
|
|
|
blk, err := s.hmy.BlockByNumber(ctx, blockNum)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetValidatorInformationByBlockNumber, FailedNumber)
|
|
|
|
return nil, errors.Wrapf(err, "could not retrieve the blk information for blk number: %d", blockNum)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch validator info
|
|
|
|
addr, err := internal_common.ParseAddr(address)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetValidatorInformationByBlockNumber, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
validatorInfo, err := s.hmy.GetValidatorInformation(addr, blk)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetValidatorInformationByBlockNumber, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Response output is the same for all versions
|
|
|
|
return NewStructuredResponse(validatorInfo)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetValidatorSelfDelegation returns validator stake.
|
|
|
|
func (s *PublicStakingService) GetValidatorSelfDelegation(
|
|
|
|
ctx context.Context, address string,
|
|
|
|
) (interface{}, error) {
|
|
|
|
// Ensure node is for beacon shard
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch self delegation
|
|
|
|
addr, err := internal_common.ParseAddr(address)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
selfDelegation := s.hmy.GetValidatorSelfDelegation(addr).Uint64()
|
|
|
|
|
|
|
|
// Format the response according to the version
|
|
|
|
switch s.version {
|
|
|
|
case V1:
|
|
|
|
return hexutil.Uint64(selfDelegation), nil
|
|
|
|
case V2:
|
|
|
|
return selfDelegation, nil
|
|
|
|
default:
|
|
|
|
return nil, ErrUnknownRPCVersion
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetValidatorTotalDelegation returns total balance stacking for validator with delegation.
|
|
|
|
func (s *PublicStakingService) GetValidatorTotalDelegation(
|
|
|
|
ctx context.Context, address string,
|
|
|
|
) (interface{}, error) {
|
|
|
|
// Ensure node is for beacon shard
|
|
|
|
if s.hmy.ShardID != shard.BeaconChainShardID {
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch delegations & sum
|
|
|
|
addr, err := internal_common.ParseAddr(address)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
delegations := s.hmy.GetDelegationsByValidator(addr)
|
|
|
|
totalStake := big.NewInt(0)
|
|
|
|
for _, delegation := range delegations {
|
|
|
|
totalStake.Add(totalStake, delegation.Amount)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Format the response according to the version
|
|
|
|
switch s.version {
|
|
|
|
case V1:
|
|
|
|
return hexutil.Uint64(totalStake.Uint64()), nil
|
|
|
|
case V2:
|
|
|
|
return totalStake, nil
|
|
|
|
default:
|
|
|
|
return nil, ErrUnknownRPCVersion
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAllDelegationInformation returns delegation information about `validatorsPageSize` validators,
|
|
|
|
// starting at `page*validatorsPageSize`.
|
|
|
|
// If page is -1, return all instead of `validatorsPageSize` elements.
|
|
|
|
// TODO(dm): optimize with single flight
|
|
|
|
func (s *PublicStakingService) GetAllDelegationInformation(
|
|
|
|
ctx context.Context, page int,
|
|
|
|
) ([][]StructuredResponse, error) {
|
|
|
|
timer := DoMetricRPCRequest(GetAllDelegationInformation)
|
|
|
|
defer DoRPCRequestDuration(GetAllDelegationInformation, timer)
|
|
|
|
|
|
|
|
err := s.wait(s.limiterGetAllDelegationInformation, ctx)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetAllDelegationInformation, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
DoMetricRPCQueryInfo(GetAllDelegationInformation, FailedNumber)
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
if page < -1 {
|
|
|
|
return make([][]StructuredResponse, 0), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get all validators
|
|
|
|
addresses := s.hmy.GetAllValidatorAddresses()
|
|
|
|
|
|
|
|
// Return nothing if no delegation on page
|
|
|
|
if page != -1 && len(addresses) <= page*validatorsPageSize {
|
|
|
|
return make([][]StructuredResponse, 0), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set page start
|
|
|
|
validatorsNum := len(addresses)
|
|
|
|
start := 0
|
|
|
|
if page != -1 {
|
|
|
|
validatorsNum = validatorsPageSize
|
|
|
|
start = page * validatorsPageSize
|
|
|
|
if len(addresses)-start < validatorsPageSize {
|
|
|
|
validatorsNum = len(addresses) - start
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch all delegations
|
|
|
|
validators := make([][]StructuredResponse, validatorsNum)
|
|
|
|
for i := start; i < start+validatorsNum; i++ {
|
|
|
|
validators[i-start], err = s.GetDelegationsByValidator(ctx, addresses[i].String())
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetAllDelegationInformation, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Response output is the same for all versions
|
|
|
|
return validators, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetDelegationsByDelegator returns list of delegations for a delegator address.
|
|
|
|
func (s *PublicStakingService) GetDelegationsByDelegator(
|
|
|
|
ctx context.Context, address string,
|
|
|
|
) ([]StructuredResponse, error) {
|
|
|
|
timer := DoMetricRPCRequest(GetDelegationsByDelegator)
|
|
|
|
defer DoRPCRequestDuration(GetDelegationsByDelegator, timer)
|
|
|
|
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
DoMetricRPCQueryInfo(GetDelegationsByDelegator, FailedNumber)
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch delegation
|
|
|
|
delegatorAddress, err := internal_common.ParseAddr(address)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetDelegationsByDelegator, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
validators, delegations := s.hmy.GetDelegationsByDelegator(delegatorAddress)
|
|
|
|
|
|
|
|
// Format response
|
|
|
|
result := []StructuredResponse{}
|
|
|
|
for i := range delegations {
|
|
|
|
delegation := delegations[i]
|
|
|
|
undelegations := make([]Undelegation, len(delegation.Undelegations))
|
|
|
|
|
|
|
|
for j := range delegation.Undelegations {
|
|
|
|
undelegations[j] = Undelegation{
|
|
|
|
Amount: delegation.Undelegations[j].Amount,
|
|
|
|
Epoch: delegation.Undelegations[j].Epoch,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
valAddr, _ := internal_common.AddressToBech32(validators[i])
|
|
|
|
delAddr, _ := internal_common.AddressToBech32(delegatorAddress)
|
|
|
|
|
|
|
|
// Response output is the same for all versions
|
|
|
|
del, err := NewStructuredResponse(Delegation{
|
|
|
|
ValidatorAddress: valAddr,
|
|
|
|
DelegatorAddress: delAddr,
|
|
|
|
Amount: delegation.Amount,
|
|
|
|
Reward: delegation.Reward,
|
|
|
|
Undelegations: undelegations,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetDelegationsByDelegator, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
result = append(result, del)
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetDelegationsByDelegatorByBlockNumber returns list of delegations for a delegator address at given block number
|
|
|
|
func (s *PublicStakingService) GetDelegationsByDelegatorByBlockNumber(
|
|
|
|
ctx context.Context, aol AddressOrList, blockNumber BlockNumber,
|
|
|
|
) (interface{}, error) {
|
|
|
|
// Process number based on version
|
|
|
|
blockNum := blockNumber.EthBlockNumber()
|
|
|
|
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
if isBlockGreaterThanLatest(s.hmy, blockNum) {
|
|
|
|
return nil, ErrRequestedBlockTooHigh
|
|
|
|
}
|
|
|
|
blk, err := s.hmy.BlockByNumber(ctx, blockNum)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "could not retrieve the blk information for blk number: %d", blockNum)
|
|
|
|
}
|
|
|
|
|
|
|
|
if aol.Address != nil { // single address
|
|
|
|
delegatorAddress := *aol.Address
|
|
|
|
validators, delegations := s.hmy.GetDelegationsByDelegatorByBlock(delegatorAddress, blk)
|
|
|
|
return s.parseGetDelegationsByDelegatorResp(delegatorAddress, validators, delegations)
|
|
|
|
|
|
|
|
} else { // multiple address
|
|
|
|
srs := make([][]StructuredResponse, 0, len(aol.AddressList))
|
|
|
|
for _, delegatorAddress := range aol.AddressList {
|
|
|
|
validators, delegations := s.hmy.GetDelegationsByDelegatorByBlock(delegatorAddress, blk)
|
|
|
|
res, err := s.parseGetDelegationsByDelegatorResp(delegatorAddress, validators, delegations)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
srs = append(srs, res)
|
|
|
|
}
|
|
|
|
return srs, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PublicStakingService) parseGetDelegationsByDelegatorResp(
|
|
|
|
delegator common.Address, validators []common.Address, delegations []*staking.Delegation,
|
|
|
|
) ([]StructuredResponse, error) {
|
|
|
|
var result []StructuredResponse
|
|
|
|
for i := range delegations {
|
|
|
|
delegation := delegations[i]
|
|
|
|
undelegations := make([]Undelegation, len(delegation.Undelegations))
|
|
|
|
|
|
|
|
for j := range delegation.Undelegations {
|
|
|
|
undelegations[j] = Undelegation{
|
|
|
|
Amount: delegation.Undelegations[j].Amount,
|
|
|
|
Epoch: delegation.Undelegations[j].Epoch,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
valAddr, _ := internal_common.AddressToBech32(validators[i])
|
|
|
|
delAddr, _ := internal_common.AddressToBech32(delegator)
|
|
|
|
|
|
|
|
// Response output is the same for all versions
|
|
|
|
del, err := NewStructuredResponse(Delegation{
|
|
|
|
ValidatorAddress: valAddr,
|
|
|
|
DelegatorAddress: delAddr,
|
|
|
|
Amount: delegation.Amount,
|
|
|
|
Reward: delegation.Reward,
|
|
|
|
Undelegations: undelegations,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
result = append(result, del)
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetDelegationsByValidator returns list of delegations for a validator address.
|
|
|
|
func (s *PublicStakingService) GetDelegationsByValidator(
|
|
|
|
ctx context.Context, address string,
|
|
|
|
) ([]StructuredResponse, error) {
|
|
|
|
timer := DoMetricRPCRequest(GetDelegationsByValidator)
|
|
|
|
defer DoRPCRequestDuration(GetDelegationsByValidator, timer)
|
|
|
|
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
DoMetricRPCQueryInfo(GetDelegationsByValidator, FailedNumber)
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch delegations
|
|
|
|
validatorAddress, err := internal_common.ParseAddr(address)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
delegations := s.hmy.GetDelegationsByValidator(validatorAddress)
|
|
|
|
|
|
|
|
// Format response
|
|
|
|
result := []StructuredResponse{}
|
|
|
|
for i := range delegations {
|
|
|
|
delegation := delegations[i]
|
|
|
|
undelegations := make([]Undelegation, len(delegation.Undelegations))
|
|
|
|
|
|
|
|
for j := range delegation.Undelegations {
|
|
|
|
undelegations[j] = Undelegation{
|
|
|
|
Amount: delegation.Undelegations[j].Amount,
|
|
|
|
Epoch: delegation.Undelegations[j].Epoch,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
valAddr, _ := internal_common.AddressToBech32(validatorAddress)
|
|
|
|
delAddr, _ := internal_common.AddressToBech32(delegation.DelegatorAddress)
|
|
|
|
|
|
|
|
// Skip delegations with zero amount and empty undelegation
|
|
|
|
if delegation.Amount.Cmp(common.Big0) == 0 && len(undelegations) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Response output is the same for all versions
|
|
|
|
del, err := NewStructuredResponse(Delegation{
|
|
|
|
ValidatorAddress: valAddr,
|
|
|
|
DelegatorAddress: delAddr,
|
|
|
|
Amount: delegation.Amount,
|
|
|
|
Reward: delegation.Reward,
|
|
|
|
Undelegations: undelegations,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetDelegationsByValidator, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
result = append(result, del)
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetDelegationByDelegatorAndValidator returns a delegation for delegator and validator.
|
|
|
|
func (s *PublicStakingService) GetDelegationByDelegatorAndValidator(
|
|
|
|
ctx context.Context, address string, validator string,
|
|
|
|
) (StructuredResponse, error) {
|
|
|
|
timer := DoMetricRPCRequest(GetDelegationByDelegatorAndValidator)
|
|
|
|
defer DoRPCRequestDuration(GetDelegationByDelegatorAndValidator, timer)
|
|
|
|
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
DoMetricRPCQueryInfo(GetDelegationByDelegatorAndValidator, FailedNumber)
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch delegations
|
|
|
|
delegatorAddress, err := internal_common.ParseAddr(address)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetDelegationByDelegatorAndValidator, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
validatorAddress, err := internal_common.ParseAddr(validator)
|
|
|
|
if err != nil {
|
|
|
|
DoMetricRPCQueryInfo(GetDelegationByDelegatorAndValidator, FailedNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
validators, delegations := s.hmy.GetDelegationsByDelegator(delegatorAddress)
|
|
|
|
|
|
|
|
// Format response
|
|
|
|
for i := range delegations {
|
|
|
|
if validators[i] != validatorAddress {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
delegation := delegations[i]
|
|
|
|
undelegations := make([]Undelegation, len(delegation.Undelegations))
|
|
|
|
|
|
|
|
for j := range delegation.Undelegations {
|
|
|
|
undelegations[j] = Undelegation{
|
|
|
|
Amount: delegation.Undelegations[j].Amount,
|
|
|
|
Epoch: delegation.Undelegations[j].Epoch,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
valAddr, _ := internal_common.AddressToBech32(validatorAddress)
|
|
|
|
delAddr, _ := internal_common.AddressToBech32(delegatorAddress)
|
|
|
|
|
|
|
|
// Response output is the same for all versions
|
|
|
|
return NewStructuredResponse(Delegation{
|
|
|
|
ValidatorAddress: valAddr,
|
|
|
|
DelegatorAddress: delAddr,
|
|
|
|
Amount: delegation.Amount,
|
|
|
|
Reward: delegation.Reward,
|
|
|
|
Undelegations: undelegations,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAvailableRedelegationBalance returns the amount of locked undelegated tokens
|
|
|
|
func (s *PublicStakingService) GetAvailableRedelegationBalance(
|
|
|
|
ctx context.Context, address string,
|
|
|
|
) (*big.Int, error) {
|
|
|
|
if !isBeaconShard(s.hmy) {
|
|
|
|
return nil, ErrNotBeaconShard
|
|
|
|
}
|
|
|
|
|
|
|
|
currEpoch := s.hmy.BlockChain.CurrentHeader().Epoch()
|
|
|
|
|
|
|
|
delegatorAddr, err := internal_common.ParseAddr(address)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, delegations := s.hmy.GetDelegationsByDelegator(delegatorAddr)
|
|
|
|
|
|
|
|
redelegationTotal := big.NewInt(0)
|
|
|
|
for _, d := range delegations {
|
|
|
|
for _, u := range d.Undelegations {
|
|
|
|
if u.Epoch.Cmp(currEpoch) < 1 { // Undelegation.Epoch < currentEpoch
|
|
|
|
redelegationTotal.Add(redelegationTotal, u.Amount)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return redelegationTotal, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func isBeaconShard(hmy *hmy.Harmony) bool {
|
|
|
|
return hmy.ShardID == shard.BeaconChainShardID
|
|
|
|
}
|