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.
896 lines
28 KiB
896 lines
28 KiB
package hmy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/big"
|
|
"sync"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/math"
|
|
"github.com/ethereum/go-ethereum/core/bloombits"
|
|
"github.com/ethereum/go-ethereum/ethdb"
|
|
"github.com/ethereum/go-ethereum/event"
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
"github.com/harmony-one/bls/ffi/go/bls"
|
|
"github.com/harmony-one/harmony/api/proto"
|
|
"github.com/harmony-one/harmony/block"
|
|
"github.com/harmony-one/harmony/consensus/quorum"
|
|
"github.com/harmony-one/harmony/core"
|
|
"github.com/harmony-one/harmony/core/rawdb"
|
|
"github.com/harmony-one/harmony/core/state"
|
|
"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"
|
|
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
|
|
commonRPC "github.com/harmony-one/harmony/internal/hmyapi/common"
|
|
"github.com/harmony-one/harmony/internal/params"
|
|
"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/availability"
|
|
"github.com/harmony-one/harmony/staking/effective"
|
|
"github.com/harmony-one/harmony/staking/network"
|
|
staking "github.com/harmony-one/harmony/staking/types"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/sync/singleflight"
|
|
)
|
|
|
|
var (
|
|
// ErrFinalizedTransaction is returned if the transaction to be submitted is already on-chain
|
|
ErrFinalizedTransaction = errors.New("transaction already finalized")
|
|
)
|
|
|
|
// APIBackend An implementation of internal/hmyapi/Backend. Full client.
|
|
type APIBackend struct {
|
|
hmy *Harmony
|
|
TotalStakingCache struct {
|
|
sync.Mutex
|
|
BlockHeight int64
|
|
TotalStaking *big.Int
|
|
}
|
|
apiCache singleflight.Group
|
|
}
|
|
|
|
// SingleFlightRequest ...
|
|
func (b *APIBackend) SingleFlightRequest(
|
|
key string,
|
|
fn func() (interface{}, error),
|
|
) (interface{}, error) {
|
|
res, err, _ := b.apiCache.Do(key, fn)
|
|
return res, err
|
|
}
|
|
|
|
// SingleFlightForgetKey ...
|
|
func (b *APIBackend) SingleFlightForgetKey(key string) {
|
|
b.apiCache.Forget(key)
|
|
}
|
|
|
|
// ChainDb ...
|
|
func (b *APIBackend) ChainDb() ethdb.Database {
|
|
return b.hmy.chainDb
|
|
}
|
|
|
|
// GetBlock ...
|
|
func (b *APIBackend) GetBlock(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
|
return b.hmy.blockchain.GetBlockByHash(hash), nil
|
|
}
|
|
|
|
// GetPoolTransaction ...
|
|
func (b *APIBackend) GetPoolTransaction(hash common.Hash) types.PoolTransaction {
|
|
return b.hmy.txPool.Get(hash)
|
|
}
|
|
|
|
// BlockByNumber ...
|
|
func (b *APIBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) {
|
|
// Pending block is only known by the miner
|
|
if blockNr == rpc.PendingBlockNumber {
|
|
return nil, errors.New("not implemented")
|
|
}
|
|
// Otherwise resolve and return the block
|
|
if blockNr == rpc.LatestBlockNumber {
|
|
return b.hmy.blockchain.CurrentBlock(), nil
|
|
}
|
|
return b.hmy.blockchain.GetBlockByNumber(uint64(blockNr)), nil
|
|
}
|
|
|
|
// StateAndHeaderByNumber ...
|
|
func (b *APIBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*state.DB, *block.Header, error) {
|
|
// Pending state is only known by the miner
|
|
if blockNr == rpc.PendingBlockNumber {
|
|
return nil, nil, errors.New("not implemented")
|
|
}
|
|
// Otherwise resolve the block number and return its state
|
|
header, err := b.HeaderByNumber(ctx, blockNr)
|
|
if header == nil || err != nil {
|
|
return nil, nil, err
|
|
}
|
|
stateDb, err := b.hmy.blockchain.StateAt(header.Root())
|
|
return stateDb, header, err
|
|
}
|
|
|
|
// HeaderByNumber ...
|
|
func (b *APIBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*block.Header, error) {
|
|
// Pending block is only known by the miner
|
|
if blockNr == rpc.PendingBlockNumber {
|
|
return nil, errors.New("not implemented")
|
|
}
|
|
// Otherwise resolve and return the block
|
|
if blockNr == rpc.LatestBlockNumber {
|
|
return b.hmy.blockchain.CurrentBlock().Header(), nil
|
|
}
|
|
return b.hmy.blockchain.GetHeaderByNumber(uint64(blockNr)), nil
|
|
}
|
|
|
|
// GetPoolNonce ...
|
|
func (b *APIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
|
|
return b.hmy.txPool.State().GetNonce(addr), nil
|
|
}
|
|
|
|
// SendTx ...
|
|
func (b *APIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
|
|
tx, _, _, _ := rawdb.ReadTransaction(b.ChainDb(), signedTx.Hash())
|
|
if tx == nil {
|
|
return b.hmy.nodeAPI.AddPendingTransaction(signedTx)
|
|
}
|
|
return ErrFinalizedTransaction
|
|
}
|
|
|
|
// ChainConfig ...
|
|
func (b *APIBackend) ChainConfig() *params.ChainConfig {
|
|
return b.hmy.blockchain.Config()
|
|
}
|
|
|
|
// CurrentBlock ...
|
|
func (b *APIBackend) CurrentBlock() *types.Block {
|
|
return types.NewBlockWithHeader(b.hmy.blockchain.CurrentHeader())
|
|
}
|
|
|
|
// GetReceipts ...
|
|
func (b *APIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
|
return b.hmy.blockchain.GetReceiptsByHash(hash), nil
|
|
}
|
|
|
|
// EventMux ...
|
|
// TODO: this is not implemented or verified yet for harmony.
|
|
func (b *APIBackend) EventMux() *event.TypeMux { return b.hmy.eventMux }
|
|
|
|
const (
|
|
// BloomBitsBlocks is the number of blocks a single bloom bit section vector
|
|
// contains on the server side.
|
|
BloomBitsBlocks uint64 = 4096
|
|
)
|
|
|
|
// BloomStatus ...
|
|
// TODO: this is not implemented or verified yet for harmony.
|
|
func (b *APIBackend) BloomStatus() (uint64, uint64) {
|
|
sections, _, _ := b.hmy.bloomIndexer.Sections()
|
|
return BloomBitsBlocks, sections
|
|
}
|
|
|
|
// ProtocolVersion ...
|
|
func (b *APIBackend) ProtocolVersion() int {
|
|
return proto.ProtocolVersion
|
|
}
|
|
|
|
// Filter related APIs
|
|
|
|
// GetLogs ...
|
|
func (b *APIBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) {
|
|
receipts := b.hmy.blockchain.GetReceiptsByHash(blockHash)
|
|
if receipts == nil {
|
|
return nil, errors.New("Missing receipts")
|
|
}
|
|
logs := make([][]*types.Log, len(receipts))
|
|
for i, receipt := range receipts {
|
|
logs[i] = receipt.Logs
|
|
}
|
|
return logs, nil
|
|
}
|
|
|
|
// HeaderByHash ...
|
|
func (b *APIBackend) HeaderByHash(ctx context.Context, blockHash common.Hash) (*block.Header, error) {
|
|
header := b.hmy.blockchain.GetHeaderByHash(blockHash)
|
|
if header == nil {
|
|
return nil, errors.New("Header is not found")
|
|
}
|
|
return header, nil
|
|
}
|
|
|
|
// ServiceFilter ...
|
|
func (b *APIBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
|
|
// TODO(ricl): implement
|
|
}
|
|
|
|
// SubscribeNewTxsEvent subcribes new tx event.
|
|
// TODO: this is not implemented or verified yet for harmony.
|
|
func (b *APIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
|
return b.hmy.TxPool().SubscribeNewTxsEvent(ch)
|
|
}
|
|
|
|
// SubscribeChainEvent subcribes chain event.
|
|
// TODO: this is not implemented or verified yet for harmony.
|
|
func (b *APIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
|
return b.hmy.BlockChain().SubscribeChainEvent(ch)
|
|
}
|
|
|
|
// SubscribeChainHeadEvent subcribes chain head event.
|
|
// TODO: this is not implemented or verified yet for harmony.
|
|
func (b *APIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
|
|
return b.hmy.BlockChain().SubscribeChainHeadEvent(ch)
|
|
}
|
|
|
|
// SubscribeChainSideEvent subcribes chain side event.
|
|
// TODO: this is not implemented or verified yet for harmony.
|
|
func (b *APIBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
|
|
return b.hmy.BlockChain().SubscribeChainSideEvent(ch)
|
|
}
|
|
|
|
// SubscribeRemovedLogsEvent subcribes removed logs event.
|
|
// TODO: this is not implemented or verified yet for harmony.
|
|
func (b *APIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
|
|
return b.hmy.BlockChain().SubscribeRemovedLogsEvent(ch)
|
|
}
|
|
|
|
// SubscribeLogsEvent subcribes log event.
|
|
// TODO: this is not implemented or verified yet for harmony.
|
|
func (b *APIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
|
return b.hmy.BlockChain().SubscribeLogsEvent(ch)
|
|
}
|
|
|
|
// GetPoolTransactions returns pool transactions.
|
|
func (b *APIBackend) GetPoolTransactions() (types.PoolTransactions, error) {
|
|
pending, err := b.hmy.txPool.Pending()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
queued, err := b.hmy.txPool.Queued()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var txs types.PoolTransactions
|
|
for _, batch := range pending {
|
|
txs = append(txs, batch...)
|
|
}
|
|
for _, batch := range queued {
|
|
txs = append(txs, batch...)
|
|
}
|
|
return txs, nil
|
|
}
|
|
|
|
// GetPoolStats returns the number of pending and queued transactions
|
|
func (b *APIBackend) GetPoolStats() (pendingCount, queuedCount int) {
|
|
return b.hmy.txPool.Stats()
|
|
}
|
|
|
|
// GetAccountNonce returns the nonce value of the given address for the given block number
|
|
func (b *APIBackend) GetAccountNonce(
|
|
ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (uint64, error) {
|
|
state, _, err := b.StateAndHeaderByNumber(ctx, blockNr)
|
|
if state == nil || err != nil {
|
|
return 0, err
|
|
}
|
|
return state.GetNonce(address), state.Error()
|
|
}
|
|
|
|
// GetBalance returns balance of an given address.
|
|
func (b *APIBackend) GetBalance(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*big.Int, error) {
|
|
state, _, err := b.StateAndHeaderByNumber(ctx, blockNr)
|
|
if state == nil || err != nil {
|
|
return nil, err
|
|
}
|
|
return state.GetBalance(address), state.Error()
|
|
}
|
|
|
|
// GetTransactionsHistory returns list of transactions hashes of address.
|
|
func (b *APIBackend) GetTransactionsHistory(address, txType, order string) ([]common.Hash, error) {
|
|
return b.hmy.nodeAPI.GetTransactionsHistory(address, txType, order)
|
|
}
|
|
|
|
// GetStakingTransactionsHistory returns list of staking transactions hashes of address.
|
|
func (b *APIBackend) GetStakingTransactionsHistory(address, txType, order string) ([]common.Hash, error) {
|
|
return b.hmy.nodeAPI.GetStakingTransactionsHistory(address, txType, order)
|
|
}
|
|
|
|
// GetTransactionsCount returns the number of regular transactions of address.
|
|
func (b *APIBackend) GetTransactionsCount(address, txType string) (uint64, error) {
|
|
return b.hmy.nodeAPI.GetTransactionsCount(address, txType)
|
|
}
|
|
|
|
// GetStakingTransactionsCount returns the number of staking transactions of address.
|
|
func (b *APIBackend) GetStakingTransactionsCount(address, txType string) (uint64, error) {
|
|
return b.hmy.nodeAPI.GetStakingTransactionsCount(address, txType)
|
|
}
|
|
|
|
// NetVersion returns net version
|
|
func (b *APIBackend) NetVersion() uint64 {
|
|
return b.hmy.NetVersion()
|
|
}
|
|
|
|
// GetEVM returns a new EVM entity
|
|
func (b *APIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.DB, header *block.Header) (*vm.EVM, func() error, error) {
|
|
// TODO(ricl): The code is borrowed from [go-ethereum](https://github.com/ethereum/go-ethereum/blob/40cdcf8c47ff094775aca08fd5d94051f9cf1dbb/les/api_backend.go#L114)
|
|
// [question](https://ethereum.stackexchange.com/q/72977/54923)
|
|
// Might need to reconsider the SetBalance behavior
|
|
state.SetBalance(msg.From(), math.MaxBig256)
|
|
vmError := func() error { return nil }
|
|
|
|
context := core.NewEVMContext(msg, header, b.hmy.BlockChain(), nil)
|
|
return vm.NewEVM(context, state, b.hmy.blockchain.Config(), *b.hmy.blockchain.GetVMConfig()), vmError, nil
|
|
}
|
|
|
|
// RPCGasCap returns the gas cap of rpc
|
|
func (b *APIBackend) RPCGasCap() *big.Int {
|
|
return b.hmy.RPCGasCap // TODO(ricl): should be hmy.config.RPCGasCap
|
|
}
|
|
|
|
// GetShardID returns shardID of this node
|
|
func (b *APIBackend) GetShardID() uint32 {
|
|
return b.hmy.shardID
|
|
}
|
|
|
|
// GetValidators returns validators for a particular epoch.
|
|
func (b *APIBackend) GetValidators(epoch *big.Int) (*shard.Committee, error) {
|
|
state, err := b.hmy.BlockChain().ReadShardState(epoch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, committee := range state.Shards {
|
|
if committee.ShardID == b.GetShardID() {
|
|
return &committee, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// ResendCx retrieve blockHash from txID and add blockHash to CxPool for resending
|
|
// Note that cross shard txn is only for regular txns, not for staking txns, so the input txn hash
|
|
// is expected to be regular txn hash
|
|
func (b *APIBackend) ResendCx(ctx context.Context, txID common.Hash) (uint64, bool) {
|
|
blockHash, blockNum, index := b.hmy.BlockChain().ReadTxLookupEntry(txID)
|
|
if blockHash == (common.Hash{}) {
|
|
return 0, false
|
|
}
|
|
|
|
blk := b.hmy.BlockChain().GetBlockByHash(blockHash)
|
|
if blk == nil {
|
|
return 0, false
|
|
}
|
|
|
|
txs := blk.Transactions()
|
|
// a valid index is from 0 to len-1
|
|
if int(index) > len(txs)-1 {
|
|
return 0, false
|
|
}
|
|
tx := txs[int(index)]
|
|
|
|
// check whether it is a valid cross shard tx
|
|
if tx.ShardID() == tx.ToShardID() || blk.Header().ShardID() != tx.ShardID() {
|
|
return 0, false
|
|
}
|
|
entry := core.CxEntry{blockHash, tx.ToShardID()}
|
|
success := b.hmy.CxPool().Add(entry)
|
|
return blockNum, success
|
|
}
|
|
|
|
// IsLeader exposes if node is currently leader
|
|
func (b *APIBackend) IsLeader() bool {
|
|
return b.hmy.nodeAPI.IsCurrentlyLeader()
|
|
}
|
|
|
|
// SendStakingTx adds a staking transaction
|
|
func (b *APIBackend) SendStakingTx(ctx context.Context, signedStakingTx *staking.StakingTransaction) error {
|
|
stx, _, _, _ := rawdb.ReadStakingTransaction(b.ChainDb(), signedStakingTx.Hash())
|
|
if stx == nil {
|
|
return b.hmy.nodeAPI.AddPendingStakingTransaction(signedStakingTx)
|
|
}
|
|
return ErrFinalizedTransaction
|
|
}
|
|
|
|
// GetElectedValidatorAddresses returns the address of elected validators for current epoch
|
|
func (b *APIBackend) GetElectedValidatorAddresses() []common.Address {
|
|
list, _ := b.hmy.BlockChain().ReadShardState(b.hmy.BlockChain().CurrentBlock().Epoch())
|
|
return list.StakedValidators().Addrs
|
|
}
|
|
|
|
// GetAllValidatorAddresses returns the up to date validator candidates for next epoch
|
|
func (b *APIBackend) GetAllValidatorAddresses() []common.Address {
|
|
return b.hmy.BlockChain().ValidatorCandidates()
|
|
}
|
|
|
|
var (
|
|
zero = numeric.ZeroDec()
|
|
)
|
|
|
|
// GetValidatorInformation returns the information of validator
|
|
func (b *APIBackend) GetValidatorInformation(
|
|
addr common.Address, block *types.Block,
|
|
) (*staking.ValidatorRPCEnhanced, error) {
|
|
bc := b.hmy.BlockChain()
|
|
wrapper, err := bc.ReadValidatorInformationAt(addr, block.Root())
|
|
if err != nil {
|
|
s, _ := internal_common.AddressToBech32(addr)
|
|
return nil, errors.Wrapf(err, "not found address in current state %s", s)
|
|
}
|
|
|
|
now := block.Epoch()
|
|
// At the last block of epoch, block epoch is e while val.LastEpochInCommittee
|
|
// is already updated to e+1. So need the >= check rather than ==
|
|
inCommittee := wrapper.LastEpochInCommittee.Cmp(now) >= 0
|
|
defaultReply := &staking.ValidatorRPCEnhanced{
|
|
CurrentlyInCommittee: inCommittee,
|
|
Wrapper: *wrapper,
|
|
Performance: nil,
|
|
ComputedMetrics: nil,
|
|
TotalDelegated: wrapper.TotalDelegation(),
|
|
EPoSStatus: effective.ValidatorStatus(
|
|
inCommittee, wrapper.Status,
|
|
).String(),
|
|
EPoSWinningStake: nil,
|
|
BootedStatus: nil,
|
|
ActiveStatus: wrapper.Validator.Status.String(),
|
|
Lifetime: &staking.AccumulatedOverLifetime{
|
|
wrapper.BlockReward,
|
|
wrapper.Counters,
|
|
zero,
|
|
nil,
|
|
},
|
|
}
|
|
|
|
snapshot, err := bc.ReadValidatorSnapshotAtEpoch(
|
|
now, addr,
|
|
)
|
|
|
|
if err != nil {
|
|
return defaultReply, nil
|
|
}
|
|
|
|
computed := availability.ComputeCurrentSigning(
|
|
snapshot.Validator, wrapper,
|
|
)
|
|
beaconChainBlocks := uint64(
|
|
b.hmy.BeaconChain().CurrentBlock().Header().Number().Int64(),
|
|
) % shard.Schedule.BlocksPerEpoch()
|
|
computed.BlocksLeftInEpoch = shard.Schedule.BlocksPerEpoch() - beaconChainBlocks
|
|
|
|
if defaultReply.CurrentlyInCommittee {
|
|
defaultReply.Performance = &staking.CurrentEpochPerformance{
|
|
CurrentSigningPercentage: *computed,
|
|
}
|
|
}
|
|
|
|
stats, err := bc.ReadValidatorStats(addr)
|
|
if err != nil {
|
|
// when validator has no stats, default boot-status to not booted
|
|
notBooted := effective.NotBooted.String()
|
|
defaultReply.BootedStatus = ¬Booted
|
|
return defaultReply, nil
|
|
}
|
|
|
|
latestAPR := numeric.ZeroDec()
|
|
l := len(stats.APRs)
|
|
if l > 0 {
|
|
latestAPR = stats.APRs[l-1].Value
|
|
}
|
|
defaultReply.Lifetime.APR = latestAPR
|
|
defaultReply.Lifetime.EpochAPRs = stats.APRs
|
|
|
|
// average apr cache keys
|
|
// key := fmt.Sprintf("apr-%s-%d", addr.Hex(), now.Uint64())
|
|
// prevKey := fmt.Sprintf("apr-%s-%d", addr.Hex(), now.Uint64()-1)
|
|
|
|
// delete entry for previous epoch
|
|
// b.apiCache.Forget(prevKey)
|
|
|
|
// calculate last APRHistoryLength epochs for averaging APR
|
|
// epochFrom := bc.Config().StakingEpoch
|
|
// nowMinus := big.NewInt(0).Sub(now, big.NewInt(staking.APRHistoryLength))
|
|
// if nowMinus.Cmp(epochFrom) > 0 {
|
|
// epochFrom = nowMinus
|
|
// }
|
|
|
|
// if len(stats.APRs) > 0 && stats.APRs[0].Epoch.Cmp(epochFrom) > 0 {
|
|
// epochFrom = stats.APRs[0].Epoch
|
|
// }
|
|
|
|
// epochToAPRs := map[int64]numeric.Dec{}
|
|
// for i := 0; i < len(stats.APRs); i++ {
|
|
// entry := stats.APRs[i]
|
|
// epochToAPRs[entry.Epoch.Int64()] = entry.Value
|
|
// }
|
|
|
|
// at this point, validator is active and has apr's for the recent 100 epochs
|
|
// compute average apr over history
|
|
// if avgAPR, err := b.SingleFlightRequest(
|
|
// key, func() (interface{}, error) {
|
|
// total := numeric.ZeroDec()
|
|
// count := 0
|
|
// for i := epochFrom.Int64(); i < now.Int64(); i++ {
|
|
// if apr, ok := epochToAPRs[i]; ok {
|
|
// total = total.Add(apr)
|
|
// }
|
|
// count++
|
|
// }
|
|
// if count == 0 {
|
|
// return nil, errors.New("no apr snapshots available")
|
|
// }
|
|
// return total.QuoInt64(int64(count)), nil
|
|
// },
|
|
// ); err != nil {
|
|
// // could not compute average apr from snapshot
|
|
// // assign the latest apr available from stats
|
|
// defaultReply.Lifetime.APR = numeric.ZeroDec()
|
|
// } else {
|
|
// defaultReply.Lifetime.APR = avgAPR.(numeric.Dec)
|
|
// }
|
|
|
|
if defaultReply.CurrentlyInCommittee {
|
|
defaultReply.ComputedMetrics = stats
|
|
defaultReply.EPoSWinningStake = &stats.TotalEffectiveStake
|
|
}
|
|
|
|
if !defaultReply.CurrentlyInCommittee {
|
|
reason := stats.BootedStatus.String()
|
|
defaultReply.BootedStatus = &reason
|
|
}
|
|
|
|
return defaultReply, nil
|
|
}
|
|
|
|
// GetMedianRawStakeSnapshot ..
|
|
func (b *APIBackend) GetMedianRawStakeSnapshot() (
|
|
*committee.CompletedEPoSRound, error,
|
|
) {
|
|
blockNr := b.CurrentBlock().NumberU64()
|
|
key := fmt.Sprintf("median-%d", blockNr)
|
|
|
|
// delete cache for previous block
|
|
prevKey := fmt.Sprintf("median-%d", blockNr-1)
|
|
b.apiCache.Forget(prevKey)
|
|
|
|
res, err := b.SingleFlightRequest(
|
|
key,
|
|
func() (interface{}, error) {
|
|
// Compute for next epoch
|
|
epoch := big.NewInt(0).Add(b.CurrentBlock().Epoch(), big.NewInt(1))
|
|
return committee.NewEPoSRound(epoch, b.hmy.BlockChain())
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res.(*committee.CompletedEPoSRound), nil
|
|
}
|
|
|
|
// GetLatestChainHeaders ..
|
|
func (b *APIBackend) GetLatestChainHeaders() *block.HeaderPair {
|
|
return &block.HeaderPair{
|
|
BeaconHeader: b.hmy.BeaconChain().CurrentHeader(),
|
|
ShardHeader: b.hmy.BlockChain().CurrentHeader(),
|
|
}
|
|
}
|
|
|
|
// GetTotalStakingSnapshot ..
|
|
func (b *APIBackend) GetTotalStakingSnapshot() *big.Int {
|
|
b.TotalStakingCache.Lock()
|
|
defer b.TotalStakingCache.Unlock()
|
|
if b.TotalStakingCache.BlockHeight != -1 &&
|
|
b.TotalStakingCache.BlockHeight > int64(rpc.LatestBlockNumber)-20 {
|
|
return b.TotalStakingCache.TotalStaking
|
|
}
|
|
b.TotalStakingCache.BlockHeight = int64(rpc.LatestBlockNumber)
|
|
candidates := b.hmy.BlockChain().ValidatorCandidates()
|
|
if len(candidates) == 0 {
|
|
b.TotalStakingCache.TotalStaking = big.NewInt(0)
|
|
return b.TotalStakingCache.TotalStaking
|
|
}
|
|
stakes := big.NewInt(0)
|
|
for i := range candidates {
|
|
snapshot, _ := b.hmy.BlockChain().ReadValidatorSnapshot(candidates[i])
|
|
validator, _ := b.hmy.BlockChain().ReadValidatorInformation(candidates[i])
|
|
if !committee.IsEligibleForEPoSAuction(
|
|
snapshot, validator,
|
|
) {
|
|
continue
|
|
}
|
|
for i := range validator.Delegations {
|
|
stakes.Add(stakes, validator.Delegations[i].Amount)
|
|
}
|
|
}
|
|
b.TotalStakingCache.TotalStaking = stakes
|
|
return b.TotalStakingCache.TotalStaking
|
|
}
|
|
|
|
// GetDelegationsByValidator returns all delegation information of a validator
|
|
func (b *APIBackend) GetDelegationsByValidator(validator common.Address) []*staking.Delegation {
|
|
wrapper, err := b.hmy.BlockChain().ReadValidatorInformation(validator)
|
|
if err != nil || wrapper == nil {
|
|
return nil
|
|
}
|
|
delegations := []*staking.Delegation{}
|
|
for i := range wrapper.Delegations {
|
|
delegations = append(delegations, &wrapper.Delegations[i])
|
|
}
|
|
return delegations
|
|
}
|
|
|
|
// GetDelegationsByDelegatorByBlock returns all delegation information of a delegator
|
|
func (b *APIBackend) GetDelegationsByDelegatorByBlock(
|
|
delegator common.Address, block *types.Block,
|
|
) ([]common.Address, []*staking.Delegation) {
|
|
addresses := []common.Address{}
|
|
delegations := []*staking.Delegation{}
|
|
delegationIndexes, err := b.hmy.BlockChain().
|
|
ReadDelegationsByDelegatorAt(delegator, block.Number())
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
|
|
for i := range delegationIndexes {
|
|
wrapper, err := b.hmy.BlockChain().ReadValidatorInformationAt(
|
|
delegationIndexes[i].ValidatorAddress, block.Root(),
|
|
)
|
|
if err != nil || wrapper == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
if uint64(len(wrapper.Delegations)) > delegationIndexes[i].Index {
|
|
delegations = append(delegations, &wrapper.Delegations[delegationIndexes[i].Index])
|
|
} else {
|
|
delegations = append(delegations, nil)
|
|
}
|
|
addresses = append(addresses, delegationIndexes[i].ValidatorAddress)
|
|
}
|
|
return addresses, delegations
|
|
}
|
|
|
|
// GetDelegationsByDelegator returns all delegation information of a delegator
|
|
func (b *APIBackend) GetDelegationsByDelegator(
|
|
delegator common.Address,
|
|
) ([]common.Address, []*staking.Delegation) {
|
|
block := b.hmy.BlockChain().CurrentBlock()
|
|
return b.GetDelegationsByDelegatorByBlock(delegator, block)
|
|
}
|
|
|
|
// GetValidatorSelfDelegation returns the amount of staking after applying all delegated stakes
|
|
func (b *APIBackend) GetValidatorSelfDelegation(addr common.Address) *big.Int {
|
|
wrapper, err := b.hmy.BlockChain().ReadValidatorInformation(addr)
|
|
if err != nil || wrapper == nil {
|
|
return nil
|
|
}
|
|
if len(wrapper.Delegations) == 0 {
|
|
return nil
|
|
}
|
|
return wrapper.Delegations[0].Amount
|
|
}
|
|
|
|
// GetShardState ...
|
|
func (b *APIBackend) GetShardState() (*shard.State, error) {
|
|
return b.hmy.BlockChain().ReadShardState(b.hmy.BlockChain().CurrentHeader().Epoch())
|
|
}
|
|
|
|
// GetCurrentStakingErrorSink ..
|
|
func (b *APIBackend) GetCurrentStakingErrorSink() types.TransactionErrorReports {
|
|
return b.hmy.nodeAPI.ReportStakingErrorSink()
|
|
}
|
|
|
|
// GetCurrentTransactionErrorSink ..
|
|
func (b *APIBackend) GetCurrentTransactionErrorSink() types.TransactionErrorReports {
|
|
return b.hmy.nodeAPI.ReportPlainErrorSink()
|
|
}
|
|
|
|
// GetPendingCXReceipts ..
|
|
func (b *APIBackend) GetPendingCXReceipts() []*types.CXReceiptsProof {
|
|
return b.hmy.nodeAPI.PendingCXReceipts()
|
|
}
|
|
|
|
// GetCurrentUtilityMetrics ..
|
|
func (b *APIBackend) GetCurrentUtilityMetrics() (*network.UtilityMetric, error) {
|
|
return network.NewUtilityMetricSnapshot(b.hmy.BlockChain())
|
|
}
|
|
|
|
func (b *APIBackend) readAndUpdateRawStakes(
|
|
epoch *big.Int,
|
|
decider quorum.Decider,
|
|
comm shard.Committee,
|
|
rawStakes []effective.SlotPurchase,
|
|
validatorSpreads map[common.Address]numeric.Dec,
|
|
) []effective.SlotPurchase {
|
|
for i := range comm.Slots {
|
|
slot := comm.Slots[i]
|
|
slotAddr := slot.EcdsaAddress
|
|
slotKey := slot.BLSPublicKey
|
|
spread, ok := validatorSpreads[slotAddr]
|
|
if !ok {
|
|
snapshot, err := b.hmy.BlockChain().ReadValidatorSnapshotAtEpoch(epoch, slotAddr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
wrapper := snapshot.Validator
|
|
spread = numeric.NewDecFromBigInt(wrapper.TotalDelegation()).
|
|
QuoInt64(int64(len(wrapper.SlotPubKeys)))
|
|
validatorSpreads[slotAddr] = spread
|
|
}
|
|
|
|
commonRPC.SetRawStake(decider, slotKey, spread)
|
|
// add entry to array for median calculation
|
|
rawStakes = append(rawStakes, effective.SlotPurchase{
|
|
slotAddr,
|
|
slotKey,
|
|
spread,
|
|
spread,
|
|
})
|
|
}
|
|
return rawStakes
|
|
}
|
|
|
|
func (b *APIBackend) getSuperCommittees() (*quorum.Transition, error) {
|
|
nowE := b.hmy.BlockChain().CurrentHeader().Epoch()
|
|
thenE := new(big.Int).Sub(nowE, common.Big1)
|
|
|
|
var (
|
|
nowCommittee, prevCommittee *shard.State
|
|
err error
|
|
)
|
|
nowCommittee, err = b.hmy.BlockChain().ReadShardState(nowE)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
prevCommittee, err = b.hmy.BlockChain().ReadShardState(thenE)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
stakedSlotsNow, stakedSlotsThen :=
|
|
shard.ExternalSlotsAvailableForEpoch(nowE),
|
|
shard.ExternalSlotsAvailableForEpoch(thenE)
|
|
|
|
then, now :=
|
|
quorum.NewRegistry(stakedSlotsThen),
|
|
quorum.NewRegistry(stakedSlotsNow)
|
|
|
|
rawStakes := []effective.SlotPurchase{}
|
|
validatorSpreads := map[common.Address]numeric.Dec{}
|
|
for _, comm := range prevCommittee.Shards {
|
|
decider := quorum.NewDecider(quorum.SuperMajorityStake, comm.ShardID)
|
|
// before staking skip computing
|
|
if b.hmy.BlockChain().Config().IsStaking(prevCommittee.Epoch) {
|
|
if _, err := decider.SetVoters(&comm, prevCommittee.Epoch); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
rawStakes = b.readAndUpdateRawStakes(thenE, decider, comm, rawStakes, validatorSpreads)
|
|
then.Deciders[fmt.Sprintf("shard-%d", comm.ShardID)] = decider
|
|
}
|
|
then.MedianStake = effective.Median(rawStakes)
|
|
|
|
rawStakes = []effective.SlotPurchase{}
|
|
validatorSpreads = map[common.Address]numeric.Dec{}
|
|
for _, comm := range nowCommittee.Shards {
|
|
decider := quorum.NewDecider(quorum.SuperMajorityStake, comm.ShardID)
|
|
if _, err := decider.SetVoters(&comm, nowCommittee.Epoch); err != nil {
|
|
return nil, errors.Wrapf(
|
|
err,
|
|
"committee is only available from staking epoch: %v, current epoch: %v",
|
|
b.hmy.BlockChain().Config().StakingEpoch,
|
|
b.hmy.BlockChain().CurrentHeader().Epoch(),
|
|
)
|
|
}
|
|
rawStakes = b.readAndUpdateRawStakes(nowE, decider, comm, rawStakes, validatorSpreads)
|
|
now.Deciders[fmt.Sprintf("shard-%d", comm.ShardID)] = decider
|
|
}
|
|
now.MedianStake = effective.Median(rawStakes)
|
|
|
|
return &quorum.Transition{then, now}, nil
|
|
}
|
|
|
|
// GetSuperCommittees ..
|
|
func (b *APIBackend) GetSuperCommittees() (*quorum.Transition, error) {
|
|
nowE := b.hmy.BlockChain().CurrentHeader().Epoch()
|
|
key := fmt.Sprintf("sc-%s", nowE.String())
|
|
|
|
res, err := b.SingleFlightRequest(
|
|
key, func() (interface{}, error) {
|
|
thenE := new(big.Int).Sub(nowE, common.Big1)
|
|
thenKey := fmt.Sprintf("sc-%s", thenE.String())
|
|
b.apiCache.Forget(thenKey)
|
|
return b.getSuperCommittees()
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res.(*quorum.Transition), err
|
|
}
|
|
|
|
// GetCurrentBadBlocks ..
|
|
func (b *APIBackend) GetCurrentBadBlocks() []core.BadBlock {
|
|
return b.hmy.BlockChain().BadBlocks()
|
|
}
|
|
|
|
// GetLastCrossLinks ..
|
|
func (b *APIBackend) GetLastCrossLinks() ([]*types.CrossLink, error) {
|
|
crossLinks := []*types.CrossLink{}
|
|
for i := uint32(1); i < shard.Schedule.InstanceForEpoch(b.CurrentBlock().Epoch()).NumShards(); i++ {
|
|
link, err := b.hmy.BlockChain().ReadShardLastCrossLink(i)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
crossLinks = append(crossLinks, link)
|
|
}
|
|
|
|
return crossLinks, nil
|
|
}
|
|
|
|
// GetNodeMetadata ..
|
|
func (b *APIBackend) GetNodeMetadata() commonRPC.NodeMetadata {
|
|
cfg := nodeconfig.GetDefaultConfig()
|
|
header := b.CurrentBlock().Header()
|
|
var blockEpoch *uint64
|
|
|
|
if header.ShardID() == shard.BeaconChainShardID {
|
|
sched := shard.Schedule.InstanceForEpoch(header.Epoch())
|
|
b := sched.BlocksPerEpoch()
|
|
blockEpoch = &b
|
|
}
|
|
|
|
blsKeys := []string{}
|
|
if cfg.ConsensusPubKey != nil {
|
|
for _, key := range cfg.ConsensusPubKey {
|
|
blsKeys = append(blsKeys, key.Bytes.Hex())
|
|
}
|
|
}
|
|
c := commonRPC.C{}
|
|
c.TotalKnownPeers, c.Connected, c.NotConnected = b.hmy.nodeAPI.PeerConnectivity()
|
|
|
|
return commonRPC.NodeMetadata{
|
|
blsKeys,
|
|
nodeconfig.GetVersion(),
|
|
string(cfg.GetNetworkType()),
|
|
*b.ChainConfig(),
|
|
b.IsLeader(),
|
|
b.GetShardID(),
|
|
header.Epoch().Uint64(),
|
|
blockEpoch,
|
|
cfg.Role().String(),
|
|
cfg.DNSZone,
|
|
cfg.GetArchival(),
|
|
b.hmy.nodeAPI.GetNodeBootTime(),
|
|
c,
|
|
}
|
|
}
|
|
|
|
// GetBlockSigners ..
|
|
func (b *APIBackend) GetBlockSigners(ctx context.Context, blockNr rpc.BlockNumber) (shard.SlotList, *internal_bls.Mask, error) {
|
|
block, err := b.BlockByNumber(ctx, blockNr)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
blockWithSigners, err := b.BlockByNumber(ctx, blockNr+1)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
committee, err := b.GetValidators(block.Epoch())
|
|
if err != nil {
|
|
return nil, 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, nil, err
|
|
}
|
|
err = mask.SetMask(blockWithSigners.Header().LastCommitBitmap())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return committee.Slots, mask, nil
|
|
}
|
|
|
|
// IsStakingEpoch ...
|
|
func (b *APIBackend) IsStakingEpoch(epoch *big.Int) bool {
|
|
return b.hmy.BlockChain().Config().IsStaking(epoch)
|
|
}
|
|
|