The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
woop/internal/hmyapi/apiv1/blockchain.go

1033 lines
35 KiB

package apiv1
import (
"context"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/rpc"
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/block"
"github.com/harmony-one/harmony/common/denominations"
"github.com/harmony-one/harmony/consensus/quorum"
"github.com/harmony-one/harmony/consensus/reward"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
internal_bls "github.com/harmony-one/harmony/crypto/bls"
internal_common "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/shard/committee"
"github.com/harmony-one/harmony/staking/network"
staking "github.com/harmony-one/harmony/staking/types"
"github.com/pkg/errors"
)
const (
defaultGasPrice = denominations.Nano
defaultFromAddress = "0x0000000000000000000000000000000000000000"
defaultBlocksPeriod = 15000
validatorsPageSize = 100
initSupply = int64(12600000000)
)
// PublicBlockChainAPI provides an API to access the Harmony blockchain.
// It offers only methods that operate on public data that is freely available to anyone.
type PublicBlockChainAPI struct {
b Backend
}
// NewPublicBlockChainAPI creates a new Harmony blockchain API.
func NewPublicBlockChainAPI(b Backend) *PublicBlockChainAPI {
return &PublicBlockChainAPI{b}
}
// BlockArgs is struct to include optional block formatting params.
type BlockArgs struct {
WithSigners bool `json:"withSigners"`
InclTx bool `json:"inclTx"`
FullTx bool `json:"fullTx"`
Signers []string `json:"signers"`
InclStaking bool `json:"inclStaking"`
}
func (s *PublicBlockChainAPI) isBeaconShard() error {
if s.b.GetShardID() != shard.BeaconChainShardID {
return ErrNotBeaconShard
}
return nil
}
func (s *PublicBlockChainAPI) isBlockGreaterThanLatest(blockNum rpc.BlockNumber) error {
if uint64(blockNum) > s.b.CurrentBlock().NumberU64() {
return ErrRequestedBlockTooHigh
}
return nil
}
// GetBlockByNumber returns the requested block. When blockNr is -1 the chain head is returned. When fullTx is true all
// transactions in the block are returned in full detail, otherwise only the transaction hash is returned.
func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, blockNr rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
if err := s.isBlockGreaterThanLatest(blockNr); err != nil {
return nil, err
}
block, err := s.b.BlockByNumber(ctx, blockNr)
if block != nil {
blockArgs := BlockArgs{WithSigners: false, InclTx: true, FullTx: fullTx, InclStaking: true}
response, err := RPCMarshalBlock(block, blockArgs)
if err == nil && blockNr == rpc.PendingBlockNumber {
// Pending blocks need to nil out a few fields
for _, field := range []string{"hash", "nonce", "miner"} {
response[field] = nil
}
}
return response, err
}
return nil, err
}
// GetBlockByHash returns the requested block. When fullTx is true all transactions in the block are returned in full
// detail, otherwise only the transaction hash is returned.
func (s *PublicBlockChainAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (map[string]interface{}, error) {
block, err := s.b.GetBlock(ctx, blockHash)
if block != nil {
blockArgs := BlockArgs{WithSigners: false, InclTx: true, FullTx: fullTx, InclStaking: true}
return RPCMarshalBlock(block, blockArgs)
}
return nil, err
}
// GetBlockByNumberNew returns the requested block. When blockNr is -1 the chain head is returned. When fullTx in blockArgs is true all
// transactions in the block are returned in full detail, otherwise only the transaction hash is returned. When withSigners in BlocksArgs is true
// it shows block signers for this block in list of one addresses.
func (s *PublicBlockChainAPI) GetBlockByNumberNew(ctx context.Context, blockNr rpc.BlockNumber, blockArgs BlockArgs) (map[string]interface{}, error) {
if err := s.isBlockGreaterThanLatest(blockNr); err != nil {
return nil, err
}
block, err := s.b.BlockByNumber(ctx, blockNr)
blockArgs.InclTx = true
if blockArgs.WithSigners {
blockArgs.Signers, err = s.GetBlockSigners(ctx, blockNr)
if err != nil {
return nil, err
}
}
if block != nil {
response, err := RPCMarshalBlock(block, blockArgs)
if err == nil && blockNr == rpc.PendingBlockNumber {
// Pending blocks need to nil out a few fields
for _, field := range []string{"hash", "nonce", "miner"} {
response[field] = nil
}
}
return response, err
}
return nil, err
}
// GetBlockByHashNew returns the requested block. When fullTx in blockArgs is true all transactions in the block are returned in full
// detail, otherwise only the transaction hash is returned. When withSigners in BlocksArgs is true
// it shows block signers for this block in list of one addresses.
func (s *PublicBlockChainAPI) GetBlockByHashNew(ctx context.Context, blockHash common.Hash, blockArgs BlockArgs) (map[string]interface{}, error) {
block, err := s.b.GetBlock(ctx, blockHash)
blockArgs.InclTx = true
if blockArgs.WithSigners {
blockArgs.Signers, err = s.GetBlockSigners(ctx, rpc.BlockNumber(block.NumberU64()))
if err != nil {
return nil, err
}
}
if block != nil {
return RPCMarshalBlock(block, blockArgs)
}
return nil, err
}
// GetBlocks method returns blocks in range blockStart, blockEnd just like GetBlockByNumber but all at once.
func (s *PublicBlockChainAPI) GetBlocks(ctx context.Context, blockStart rpc.BlockNumber, blockEnd rpc.BlockNumber, blockArgs BlockArgs) ([]map[string]interface{}, error) {
result := make([]map[string]interface{}, 0)
for i := blockStart; i <= blockEnd; i++ {
block, err := s.b.BlockByNumber(ctx, i)
blockArgs.InclTx = true
if blockArgs.WithSigners {
blockArgs.Signers, err = s.GetBlockSigners(ctx, rpc.BlockNumber(i))
if err != nil {
return nil, err
}
}
if block != nil {
rpcBlock, err := RPCMarshalBlock(block, blockArgs)
if err == nil && i == rpc.PendingBlockNumber {
// Pending blocks need to nil out a few fields
for _, field := range []string{"hash", "nonce", "miner"} {
rpcBlock[field] = nil
}
}
result = append(result, rpcBlock)
}
}
return result, nil
}
// GetValidators returns validators list for a particular epoch.
func (s *PublicBlockChainAPI) GetValidators(ctx context.Context, epoch int64) (map[string]interface{}, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
committee, err := s.b.GetValidators(big.NewInt(epoch))
if err != nil {
return nil, err
}
balanceQueryBlock := shard.Schedule.EpochLastBlock(uint64(epoch))
if balanceQueryBlock > s.b.CurrentBlock().NumberU64() {
balanceQueryBlock = s.b.CurrentBlock().NumberU64()
}
validators := make([]map[string]interface{}, 0)
for _, validator := range committee.Slots {
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
}
validatorsFields := map[string]interface{}{
"address": oneAddress,
"balance": (*hexutil.Big)(validatorBalance),
}
validators = append(validators, validatorsFields)
}
result := map[string]interface{}{
"shardID": committee.ShardID,
"validators": validators,
}
return result, nil
}
// IsLastBlock checks if block is last epoch block.
func (s *PublicBlockChainAPI) IsLastBlock(blockNum uint64) (bool, error) {
if err := s.isBeaconShard(); err != nil {
return false, err
}
return shard.Schedule.IsLastBlock(blockNum), nil
}
// EpochLastBlock returns epoch last block.
func (s *PublicBlockChainAPI) EpochLastBlock(epoch uint64) (uint64, error) {
if err := s.isBeaconShard(); err != nil {
return 0, err
}
return shard.Schedule.EpochLastBlock(epoch), nil
}
// GetBlockSigners returns signers for a particular block.
func (s *PublicBlockChainAPI) GetBlockSigners(ctx context.Context, blockNr rpc.BlockNumber) ([]string, error) {
if uint64(blockNr) == 0 {
return []string{}, nil
}
if err := s.isBlockGreaterThanLatest(blockNr); err != nil {
return nil, err
}
block, err := s.b.BlockByNumber(ctx, blockNr)
if err != nil {
return nil, err
}
blockWithSigners, err := s.b.BlockByNumber(ctx, blockNr+1)
if err != nil {
return nil, err
}
committee, err := s.b.GetValidators(block.Epoch())
if err != nil {
return nil, err
}
pubkeys := make([]*bls.PublicKey, len(committee.Slots))
for i, validator := range committee.Slots {
pubkeys[i] = new(bls.PublicKey)
validator.BLSPublicKey.ToLibBLSPublicKey(pubkeys[i])
}
mask, err := internal_bls.NewMask(pubkeys, nil)
if err != nil {
return nil, err
}
err = mask.SetMask(blockWithSigners.Header().LastCommitBitmap())
if err != nil {
return nil, err
}
result := []string{}
for _, validator := range committee.Slots {
oneAddress, err := internal_common.AddressToBech32(validator.EcdsaAddress)
if err != nil {
return nil, err
}
blsPublicKey := new(bls.PublicKey)
validator.BLSPublicKey.ToLibBLSPublicKey(blsPublicKey)
if ok, err := mask.KeyEnabled(blsPublicKey); err == nil && ok {
result = append(result, oneAddress)
}
}
return result, nil
}
// IsBlockSigner returns true if validator with address signed blockNr block.
func (s *PublicBlockChainAPI) IsBlockSigner(ctx context.Context, blockNr rpc.BlockNumber, address string) (bool, error) {
if uint64(blockNr) == 0 {
return false, nil
}
if err := s.isBlockGreaterThanLatest(blockNr); err != nil {
return false, err
}
block, err := s.b.BlockByNumber(ctx, blockNr)
if err != nil {
return false, err
}
blockWithSigners, err := s.b.BlockByNumber(ctx, blockNr+1)
if err != nil {
return false, err
}
committee, err := s.b.GetValidators(block.Epoch())
if err != nil {
return false, err
}
pubkeys := make([]*bls.PublicKey, len(committee.Slots))
for i, validator := range committee.Slots {
pubkeys[i] = new(bls.PublicKey)
validator.BLSPublicKey.ToLibBLSPublicKey(pubkeys[i])
}
mask, err := internal_bls.NewMask(pubkeys, nil)
if err != nil {
return false, err
}
err = mask.SetMask(blockWithSigners.Header().LastCommitBitmap())
if err != nil {
return false, err
}
for _, validator := range committee.Slots {
oneAddress, err := internal_common.AddressToBech32(validator.EcdsaAddress)
if err != nil {
return false, err
}
if oneAddress != address {
continue
}
blsPublicKey := new(bls.PublicKey)
validator.BLSPublicKey.ToLibBLSPublicKey(blsPublicKey)
if ok, err := mask.KeyEnabled(blsPublicKey); err == nil && ok {
return true, nil
}
}
return false, nil
}
// GetSignedBlocks returns how many blocks a particular validator signed for last defaultBlocksPeriod (3 hours ~ 1500 blocks).
func (s *PublicBlockChainAPI) GetSignedBlocks(ctx context.Context, address string) hexutil.Uint64 {
totalSigned := uint64(0)
lastBlock := uint64(0)
blockHeight := uint64(s.BlockNumber())
if blockHeight >= defaultBlocksPeriod {
lastBlock = blockHeight - defaultBlocksPeriod + 1
}
for i := lastBlock; i <= blockHeight; i++ {
signed, err := s.IsBlockSigner(ctx, rpc.BlockNumber(i), address)
if err == nil && signed {
totalSigned++
}
}
return hexutil.Uint64(totalSigned)
}
// GetEpoch returns current epoch.
func (s *PublicBlockChainAPI) GetEpoch(ctx context.Context) hexutil.Uint64 {
return hexutil.Uint64(s.LatestHeader(ctx).Epoch)
}
// GetLeader returns current shard leader.
func (s *PublicBlockChainAPI) GetLeader(ctx context.Context) string {
return s.LatestHeader(ctx).Leader
}
// GetValidatorSelfDelegation returns validator stake.
func (s *PublicBlockChainAPI) GetValidatorSelfDelegation(ctx context.Context, address string) (hexutil.Uint64, error) {
if err := s.isBeaconShard(); err != nil {
return hexutil.Uint64(0), err
}
return hexutil.Uint64(s.b.GetValidatorSelfDelegation(internal_common.ParseAddr(address)).Uint64()), nil
}
// GetValidatorTotalDelegation returns total balace stacking for validator with delegation.
func (s *PublicBlockChainAPI) GetValidatorTotalDelegation(ctx context.Context, address string) (hexutil.Uint64, error) {
if err := s.isBeaconShard(); err != nil {
return hexutil.Uint64(0), err
}
delegations := s.b.GetDelegationsByValidator(internal_common.ParseAddr(address))
totalStake := big.NewInt(0)
for _, delegation := range delegations {
totalStake.Add(totalStake, delegation.Amount)
}
// TODO: return more than uint64
return hexutil.Uint64(totalStake.Uint64()), nil
}
// GetShardingStructure returns an array of sharding structures.
func (s *PublicBlockChainAPI) GetShardingStructure(ctx context.Context) ([]map[string]interface{}, error) {
// Get header and number of shards.
epoch := s.GetEpoch(ctx)
numShard := shard.Schedule.InstanceForEpoch(big.NewInt(int64(epoch))).NumShards()
// Return shareding structure for each case.
return shard.Schedule.GetShardingStructure(int(numShard), int(s.b.GetShardID())), nil
}
// GetShardID returns shard ID of the requested node.
func (s *PublicBlockChainAPI) GetShardID(ctx context.Context) (int, error) {
return int(s.b.GetShardID()), nil
}
// GetCode returns the code stored at the given address in the state for the given block number.
func (s *PublicBlockChainAPI) GetCode(ctx context.Context, addr string, blockNr rpc.BlockNumber) (hexutil.Bytes, error) {
address := internal_common.ParseAddr(addr)
state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
if state == nil || err != nil {
return nil, err
}
code := state.GetCode(address)
return code, state.Error()
}
// GetStorageAt returns the storage from the state at the given address, key and
// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block
// numbers are also allowed.
func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, addr string, key string, blockNr rpc.BlockNumber) (hexutil.Bytes, error) {
address := internal_common.ParseAddr(addr)
state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
if state == nil || err != nil {
return nil, err
}
res := state.GetState(address, common.HexToHash(key))
return res[:], state.Error()
}
func (s *PublicBlockChainAPI) getBalanceByBlockNumber(ctx context.Context, address string, blockNr rpc.BlockNumber) (*hexutil.Big, error) {
addr := internal_common.ParseAddr(address)
balance, err := s.b.GetBalance(ctx, addr, blockNr)
if balance == nil {
return nil, err
}
return (*hexutil.Big)(balance), err
}
// GetBalanceByBlockNumber returns balance by block number.
func (s *PublicBlockChainAPI) GetBalanceByBlockNumber(ctx context.Context, address string, blockNr rpc.BlockNumber) (*hexutil.Big, error) {
if err := s.isBlockGreaterThanLatest(blockNr); err != nil {
return nil, err
}
return s.getBalanceByBlockNumber(ctx, address, blockNr)
}
// GetAccountNonce returns the nonce value of the given address for the given block number
func (s *PublicBlockChainAPI) GetAccountNonce(ctx context.Context, address string, blockNr rpc.BlockNumber) (uint64, error) {
addr := internal_common.ParseAddr(address)
return s.b.GetAccountNonce(ctx, addr, rpc.BlockNumber(blockNr))
}
// GetBalance returns the amount of Atto for the given address in the state of the
// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta
// block numbers are also allowed.
func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address string, blockNr rpc.BlockNumber) (*hexutil.Big, error) {
return s.getBalanceByBlockNumber(ctx, address, rpc.BlockNumber(blockNr))
}
// BlockNumber returns the block number of the chain head.
func (s *PublicBlockChainAPI) BlockNumber() hexutil.Uint64 {
header, _ := s.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available
if header == nil {
return 0
}
return hexutil.Uint64(header.Number().Uint64())
}
// ResendCx requests that the egress receipt for the given cross-shard
// transaction be sent to the destination shard for credit. This is used for
// unblocking a half-complete cross-shard transaction whose fund has been
// withdrawn already from the source shard but not credited yet in the
// destination account due to transient failures.
func (s *PublicBlockChainAPI) ResendCx(ctx context.Context, txID common.Hash) (bool, error) {
_, success := s.b.ResendCx(ctx, txID)
return success, nil
}
// Call executes the given transaction on the state for the given block number.
// It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values.
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) {
result, _, _, err := doCall(ctx, s.b, args, blockNr, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
return (hexutil.Bytes)(result), err
}
func doCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
defer func(start time.Time) {
utils.Logger().Debug().
Dur("runtime", time.Since(start)).
Msg("Executing EVM call finished")
}(time.Now())
state, header, err := b.StateAndHeaderByNumber(ctx, blockNr)
if state == nil || err != nil {
return nil, 0, false, err
}
// Set sender address or use a default if none specified
var addr common.Address
if args.From == nil {
// TODO(ricl): this logic was borrowed from [go-ethereum](https://github.com/ethereum/go-ethereum/blob/f578d41ee6b3087f8021fd561a0b5665aea3dba6/internal/ethapi/api.go#L738)
// [question](https://ethereum.stackexchange.com/questions/72979/why-does-the-docall-function-use-the-first-account-by-default)
// Might need to reconsider the logic
// if wallets := b.AccountManager().Wallets(); len(wallets) > 0 {
// if accounts := wallets[0].Accounts(); len(accounts) > 0 {
// addr = accounts[0].Address
// }
// }
// The logic in ethereum is to pick a random address managed under the account manager.
// Currently Harmony no longers support the account manager.
// Any address does not affect the logic of this call.
addr = common.HexToAddress(defaultFromAddress)
} else {
addr = *args.From
}
// Set default gas & gas price if none were set
gas := uint64(math.MaxUint64 / 2)
if args.Gas != nil {
gas = uint64(*args.Gas)
}
if globalGasCap != nil && globalGasCap.Uint64() < gas {
utils.Logger().Warn().
Uint64("requested", gas).
Uint64("cap", globalGasCap.Uint64()).
Msg("Caller gas above allowance, capping")
gas = globalGasCap.Uint64()
}
gasPrice := new(big.Int).SetUint64(defaultGasPrice)
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
}
value := new(big.Int)
if args.Value != nil {
value = args.Value.ToInt()
}
var data []byte
if args.Data != nil {
data = []byte(*args.Data)
}
// Create new call message
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false)
// Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc
if timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, timeout)
} else {
ctx, cancel = context.WithCancel(ctx)
}
// Make sure the context is cancelled when the call has completed
// this makes sure resources are cleaned up.
defer cancel()
// Get a new instance of the EVM.
evm, vmError, err := b.GetEVM(ctx, msg, state, header)
if err != nil {
return nil, 0, false, err
}
// Wait for the context to be done and cancel the evm. Even if the
// EVM has finished, cancelling may be done (repeatedly)
go func() {
<-ctx.Done()
evm.Cancel()
}()
// Setup the gas pool (also for unmetered requests)
// and apply the message.
gp := new(core.GasPool).AddGas(math.MaxUint64)
res, gas, failed, err := core.ApplyMessage(evm, msg, gp)
if err := vmError(); err != nil {
return nil, 0, false, err
}
// If the timer caused an abort, return an appropriate error message
if evm.Cancelled() {
return nil, 0, false, fmt.Errorf("execution aborted (timeout = %v)", timeout)
}
return res, gas, failed, err
}
// LatestHeader returns the latest header information
func (s *PublicBlockChainAPI) LatestHeader(ctx context.Context) *HeaderInformation {
header, _ := s.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available
return newHeaderInformation(header)
}
// GetTotalStaking returns total staking by validators, only meant to be called on beaconchain
// explorer node
func (s *PublicBlockChainAPI) GetTotalStaking() (*big.Int, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
return s.b.GetTotalStakingSnapshot(), nil
}
// GetMedianRawStakeSnapshot returns the raw median stake, only meant to be called on beaconchain
// explorer node
func (s *PublicBlockChainAPI) GetMedianRawStakeSnapshot() (
*committee.CompletedEPoSRound, error,
) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
return s.b.GetMedianRawStakeSnapshot()
}
// GetLatestChainHeaders ..
func (s *PublicBlockChainAPI) GetLatestChainHeaders() *block.HeaderPair {
return s.b.GetLatestChainHeaders()
}
// GetAllValidatorAddresses returns all validator addresses.
func (s *PublicBlockChainAPI) GetAllValidatorAddresses() ([]string, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
addresses := []string{}
for _, addr := range s.b.GetAllValidatorAddresses() {
oneAddr, _ := internal_common.AddressToBech32(addr)
addresses = append(addresses, oneAddr)
}
return addresses, nil
}
// GetElectedValidatorAddresses returns elected validator addresses.
func (s *PublicBlockChainAPI) GetElectedValidatorAddresses() ([]string, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
addresses := []string{}
for _, addr := range s.b.GetElectedValidatorAddresses() {
oneAddr, _ := internal_common.AddressToBech32(addr)
addresses = append(addresses, oneAddr)
}
return addresses, nil
}
// GetValidatorInformation returns information about a validator.
func (s *PublicBlockChainAPI) GetValidatorInformation(
ctx context.Context, address string,
) (*staking.ValidatorRPCEnchanced, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
block, err := s.b.BlockByNumber(ctx, rpc.BlockNumber(rpc.LatestBlockNumber))
if err != nil {
return nil, errors.Wrapf(err, "could not retrieve the latest block information")
}
return s.b.GetValidatorInformation(
internal_common.ParseAddr(address), block,
)
}
// GetValidatorInformationByBlockNumber returns information about a validator.
func (s *PublicBlockChainAPI) GetValidatorInformationByBlockNumber(
ctx context.Context, address string, blockNr rpc.BlockNumber,
) (*staking.ValidatorRPCEnchanced, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
if err := s.isBlockGreaterThanLatest(blockNr); err != nil {
return nil, err
}
block, err := s.b.BlockByNumber(ctx, rpc.BlockNumber(blockNr))
if err != nil {
return nil, errors.Wrapf(err, "could not retrieve the block information for block number: %d", blockNr)
}
return s.b.GetValidatorInformation(
internal_common.ParseAddr(address), block,
)
}
func (s *PublicBlockChainAPI) getAllValidatorInformation(
ctx context.Context, page int, blockNr rpc.BlockNumber,
) ([]*staking.ValidatorRPCEnchanced, error) {
if page < -1 {
return nil, errors.Errorf("page given %d cannot be less than -1", page)
}
addresses := s.b.GetAllValidatorAddresses()
if page != -1 && len(addresses) <= page*validatorsPageSize {
return make([]*staking.ValidatorRPCEnchanced, 0), nil
}
validatorsNum := len(addresses)
start := 0
if page != -1 {
validatorsNum = validatorsPageSize
start = page * validatorsPageSize
if len(addresses)-start < validatorsPageSize {
validatorsNum = len(addresses) - start
}
}
validators := make([]*staking.ValidatorRPCEnchanced, validatorsNum)
block, err := s.b.BlockByNumber(ctx, rpc.BlockNumber(blockNr))
if err != nil {
return nil, errors.Wrapf(err, "could not retrieve the block information for block number: %d", blockNr)
}
for i := start; i < start+validatorsNum; i++ {
information, err := s.b.GetValidatorInformation(addresses[i], block)
if err != nil {
return nil, err
}
validators[i-start] = information
}
return validators, nil
}
// GetAllValidatorInformation returns information about all validators.
// If page is -1, return all instead of `validatorsPageSize` elements.
func (s *PublicBlockChainAPI) GetAllValidatorInformation(
ctx context.Context, page int,
) ([]*staking.ValidatorRPCEnchanced, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
blockNr := s.b.CurrentBlock().NumberU64()
// delete cache for previous block
prevKey := fmt.Sprintf("all-info-%d", blockNr-1)
s.b.SingleFlightForgetKey(prevKey)
key := fmt.Sprintf("all-info-%d", blockNr)
res, err := s.b.SingleFlightRequest(
key,
func() (interface{}, error) {
return s.getAllValidatorInformation(ctx, page, rpc.LatestBlockNumber)
},
)
if err != nil {
return nil, err
}
return res.([]*staking.ValidatorRPCEnchanced), nil
}
// GetAllValidatorInformationByBlockNumber returns information about all validators.
// If page is -1, return all instead of `validatorsPageSize` elements.
func (s *PublicBlockChainAPI) GetAllValidatorInformationByBlockNumber(
ctx context.Context, page int, blockNr rpc.BlockNumber,
) ([]*staking.ValidatorRPCEnchanced, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
if err := s.isBlockGreaterThanLatest(blockNr); err != nil {
return nil, err
}
return s.getAllValidatorInformation(ctx, page, blockNr)
}
// GetAllDelegationInformation returns delegation information about `validatorsPageSize` validators,
// starting at `page*validatorsPageSize`.
// If page is -1, return all instead of `validatorsPageSize` elements.
func (s *PublicBlockChainAPI) GetAllDelegationInformation(ctx context.Context, page int) ([][]*RPCDelegation, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
if page < -1 {
return make([][]*RPCDelegation, 0), nil
}
addresses := s.b.GetAllValidatorAddresses()
if page != -1 && len(addresses) <= page*validatorsPageSize {
return make([][]*RPCDelegation, 0), nil
}
validatorsNum := len(addresses)
start := 0
if page != -1 {
validatorsNum = validatorsPageSize
start = page * validatorsPageSize
if len(addresses)-start < validatorsPageSize {
validatorsNum = len(addresses) - start
}
}
validators := make([][]*RPCDelegation, validatorsNum)
var err error
for i := start; i < start+validatorsNum; i++ {
validators[i-start], err = s.GetDelegationsByValidator(ctx, addresses[i].String())
if err != nil {
return nil, err
}
}
return validators, nil
}
// GetDelegationsByDelegator returns list of delegations for a delegator address.
func (s *PublicBlockChainAPI) GetDelegationsByDelegator(ctx context.Context, address string) ([]*RPCDelegation, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
delegatorAddress := internal_common.ParseAddr(address)
validators, delegations := s.b.GetDelegationsByDelegator(delegatorAddress)
result := []*RPCDelegation{}
for i := range delegations {
delegation := delegations[i]
undelegations := make([]RPCUndelegation, len(delegation.Undelegations))
for j := range delegation.Undelegations {
undelegations = append(undelegations, RPCUndelegation{
delegation.Undelegations[j].Amount,
delegation.Undelegations[j].Epoch,
})
}
valAddr, _ := internal_common.AddressToBech32(validators[i])
delAddr, _ := internal_common.AddressToBech32(delegatorAddress)
result = append(result, &RPCDelegation{
valAddr,
delAddr,
delegation.Amount,
delegation.Reward,
undelegations,
})
}
return result, nil
}
// GetDelegationsByDelegatorByBlockNumber returns list of delegations for a delegator address at given block number
func (s *PublicBlockChainAPI) GetDelegationsByDelegatorByBlockNumber(
ctx context.Context, address string, blockNum rpc.BlockNumber,
) ([]*RPCDelegation, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
if err := s.isBlockGreaterThanLatest(blockNum); err != nil {
return nil, err
}
delegatorAddress := internal_common.ParseAddr(address)
block, err := s.b.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
if err != nil {
return nil, errors.Wrapf(err, "could not retrieve the block information for block number: %d", blockNum)
}
validators, delegations := s.b.GetDelegationsByDelegatorByBlock(delegatorAddress, block)
result := make([]*RPCDelegation, len(delegations))
for i := range delegations {
delegation := delegations[i]
undelegations := make([]RPCUndelegation, len(delegation.Undelegations))
for j := range delegation.Undelegations {
undelegations[j] = RPCUndelegation{
delegation.Undelegations[j].Amount,
delegation.Undelegations[j].Epoch,
}
}
valAddr, _ := internal_common.AddressToBech32(validators[i])
delAddr, _ := internal_common.AddressToBech32(delegatorAddress)
result[i] = &RPCDelegation{
valAddr,
delAddr,
delegation.Amount,
delegation.Reward,
undelegations,
}
}
return result, nil
}
// GetDelegationsByValidator returns list of delegations for a validator address.
func (s *PublicBlockChainAPI) GetDelegationsByValidator(ctx context.Context, address string) ([]*RPCDelegation, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
validatorAddress := internal_common.ParseAddr(address)
delegations := s.b.GetDelegationsByValidator(validatorAddress)
result := make([]*RPCDelegation, 0)
for _, delegation := range delegations {
undelegations := []RPCUndelegation{}
for j := range delegation.Undelegations {
undelegations = append(undelegations, RPCUndelegation{
delegation.Undelegations[j].Amount,
delegation.Undelegations[j].Epoch,
})
}
valAddr, _ := internal_common.AddressToBech32(validatorAddress)
delAddr, _ := internal_common.AddressToBech32(delegation.DelegatorAddress)
result = append(result, &RPCDelegation{
valAddr,
delAddr,
delegation.Amount,
delegation.Reward,
undelegations,
})
}
return result, nil
}
// GetDelegationByDelegatorAndValidator returns a delegation for delegator and validator.
func (s *PublicBlockChainAPI) GetDelegationByDelegatorAndValidator(ctx context.Context, address string, validator string) (*RPCDelegation, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
delegatorAddress := internal_common.ParseAddr(address)
validatorAddress := internal_common.ParseAddr(validator)
validators, delegations := s.b.GetDelegationsByDelegator(delegatorAddress)
for i := range delegations {
if validators[i] != validatorAddress {
continue
}
delegation := delegations[i]
undelegations := []RPCUndelegation{}
for j := range delegation.Undelegations {
undelegations = append(undelegations, RPCUndelegation{
delegation.Undelegations[j].Amount,
delegation.Undelegations[j].Epoch,
})
}
valAddr, _ := internal_common.AddressToBech32(validatorAddress)
delAddr, _ := internal_common.AddressToBech32(delegatorAddress)
return &RPCDelegation{
valAddr,
delAddr,
delegation.Amount,
delegation.Reward,
undelegations,
}, nil
}
return nil, nil
}
// doEstimateGas ..
func doEstimateGas(ctx context.Context, b Backend, args CallArgs, gasCap *big.Int) (hexutil.Uint64, error) {
// Binary search the gas requirement, as it may be higher than the amount used
var (
lo = params.TxGas - 1
hi uint64
cap uint64
)
blockNum := rpc.LatestBlockNumber
if args.Gas != nil && uint64(*args.Gas) >= params.TxGas {
hi = uint64(*args.Gas)
} else {
// Retrieve the block to act as the gas ceiling
block, err := b.BlockByNumber(ctx, blockNum)
if err != nil {
return 0, err
}
hi = block.GasLimit()
}
if gasCap != nil && hi > gasCap.Uint64() {
// log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
hi = gasCap.Uint64()
}
cap = hi
// Use zero-address if none other is available
if args.From == nil {
args.From = &common.Address{}
}
// Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) bool {
args.Gas = (*hexutil.Uint64)(&gas)
_, _, failed, err := doCall(ctx, b, args, blockNum, vm.Config{}, 0, big.NewInt(int64(cap)))
if err != nil || failed {
return false
}
return true
}
// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi {
mid := (hi + lo) / 2
if !executable(mid) {
lo = mid
} else {
hi = mid
}
}
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap {
if !executable(hi) {
return 0, fmt.Errorf("gas required exceeds allowance (%d) or always failing transaction", cap)
}
}
return hexutil.Uint64(hi), nil
}
// EstimateGas returns an estimate of the amount of gas needed to execute the
// given transaction against the current pending block.
func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) {
return doEstimateGas(ctx, s.b, args, nil)
}
// GetCurrentUtilityMetrics ..
func (s *PublicBlockChainAPI) GetCurrentUtilityMetrics() (*network.UtilityMetric, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
return s.b.GetCurrentUtilityMetrics()
}
// GetSuperCommittees ..
func (s *PublicBlockChainAPI) GetSuperCommittees() (*quorum.Transition, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
return s.b.GetSuperCommittees()
}
// GetCurrentBadBlocks ..
func (s *PublicBlockChainAPI) GetCurrentBadBlocks() []core.BadBlock {
return s.b.GetCurrentBadBlocks()
}
// GetTotalSupply ..
func (s *PublicBlockChainAPI) GetTotalSupply() (numeric.Dec, error) {
return numeric.NewDec(initSupply), nil
}
// GetCirculatingSupply ..
func (s *PublicBlockChainAPI) GetCirculatingSupply() (numeric.Dec, error) {
timestamp := time.Now()
return numeric.NewDec(initSupply).Mul(reward.PercentageForTimeStamp(timestamp.Unix())), nil
}
// GetStakingNetworkInfo ..
func (s *PublicBlockChainAPI) GetStakingNetworkInfo(
ctx context.Context,
) (*StakingNetworkInfo, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
totalStaking, _ := s.GetTotalStaking()
round, _ := s.GetMedianRawStakeSnapshot()
epoch := s.LatestHeader(ctx).Epoch
epochLastBlock, _ := s.EpochLastBlock(epoch)
totalSupply, _ := s.GetTotalSupply()
circulatingSupply, _ := s.GetCirculatingSupply()
return &StakingNetworkInfo{
TotalSupply: totalSupply,
CirculatingSupply: circulatingSupply,
EpochLastBlock: epochLastBlock,
TotalStaking: totalStaking,
MedianRawStake: round.MedianStake,
}, nil
}
// GetLastCrossLinks ..
func (s *PublicBlockChainAPI) GetLastCrossLinks() ([]*types.CrossLink, error) {
if err := s.isBeaconShard(); err != nil {
return nil, err
}
return s.b.GetLastCrossLinks()
}