go-ethereum/internal/ethapi/api.go
2025-08-26 11:54:01 +08:00

3373 lines
115 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package ethapi
import (
"bytes"
"context"
"errors"
"fmt"
gomath "math"
"math/big"
"strings"
"time"
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
"github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
math "github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/common/sort"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559"
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)
const (
defaultGasPrice = 50 * params.Shannon
// statuses of candidates
statusMasternode = "MASTERNODE"
statusSlashed = "SLASHED"
statusProposed = "PROPOSED"
fieldStatus = "status"
fieldCapacity = "capacity"
fieldCandidates = "candidates"
fieldSuccess = "success"
fieldEpoch = "epoch"
)
var errEmptyHeader = errors.New("empty header")
// EthereumAPI provides an API to access Ethereum related information.
// It offers only methods that operate on public data that is freely available to anyone.
type EthereumAPI struct {
b Backend
}
// NewEthereumAPI creates a new Ethereum protocol API.
func NewEthereumAPI(b Backend) *EthereumAPI {
return &EthereumAPI{b}
}
// GasPrice returns a suggestion for a gas price for legacy transactions.
func (s *EthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
tipcap, err := s.b.SuggestGasTipCap(ctx)
if err != nil {
return nil, err
}
if head := s.b.CurrentHeader(); head.BaseFee != nil {
tipcap.Add(tipcap, head.BaseFee)
}
return (*hexutil.Big)(tipcap), err
}
// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic transactions.
func (s *EthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
tipcap, err := s.b.SuggestGasTipCap(ctx)
if err != nil {
return nil, err
}
return (*hexutil.Big)(tipcap), err
}
type feeHistoryResult struct {
OldestBlock *hexutil.Big `json:"oldestBlock"`
Reward [][]*hexutil.Big `json:"reward,omitempty"`
BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
GasUsedRatio []float64 `json:"gasUsedRatio"`
}
// FeeHistory returns the fee market history.
func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecimal64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) {
oldest, reward, baseFee, gasUsed, err := s.b.FeeHistory(ctx, uint64(blockCount), lastBlock, rewardPercentiles)
if err != nil {
return nil, err
}
results := &feeHistoryResult{
OldestBlock: (*hexutil.Big)(oldest),
GasUsedRatio: gasUsed,
}
if reward != nil {
results.Reward = make([][]*hexutil.Big, len(reward))
for i, w := range reward {
results.Reward[i] = make([]*hexutil.Big, len(w))
for j, v := range w {
results.Reward[i][j] = (*hexutil.Big)(v)
}
}
}
if baseFee != nil {
results.BaseFee = make([]*hexutil.Big, len(baseFee))
for i, v := range baseFee {
results.BaseFee[i] = (*hexutil.Big)(v)
}
}
return results, nil
}
// BlobBaseFee returns the base fee for blob gas at the current head.
func (s *EthereumAPI) BlobBaseFee(ctx context.Context) *hexutil.Big {
return (*hexutil.Big)(new(big.Int))
}
// ProtocolVersion returns the current Ethereum protocol version this node supports
func (s *EthereumAPI) ProtocolVersion() hexutil.Uint {
return hexutil.Uint(s.b.ProtocolVersion())
}
// Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not
// yet received the latest block headers from its pears. In case it is synchronizing:
// - startingBlock: block number this node started to synchronise from
// - currentBlock: block number this node is currently importing
// - highestBlock: block number of the highest block header this node has received from peers
// - pulledStates: number of state entries processed until now
// - knownStates: number of known state entries that still need to be pulled
func (s *EthereumAPI) Syncing() (interface{}, error) {
progress := s.b.Downloader().Progress()
// Return not syncing if the synchronisation already completed
if progress.CurrentBlock >= progress.HighestBlock {
return false, nil
}
// Otherwise gather the block sync stats
return map[string]interface{}{
"startingBlock": hexutil.Uint64(progress.StartingBlock),
"currentBlock": hexutil.Uint64(progress.CurrentBlock),
"highestBlock": hexutil.Uint64(progress.HighestBlock),
"pulledStates": hexutil.Uint64(progress.PulledStates),
"knownStates": hexutil.Uint64(progress.KnownStates),
}, nil
}
// TxPoolAPI offers and API for the transaction pool. It only operates on data that is non confidential.
type TxPoolAPI struct {
b Backend
}
// NewTxPoolAPI creates a new tx pool service that gives information about the transaction pool.
func NewTxPoolAPI(b Backend) *TxPoolAPI {
return &TxPoolAPI{b}
}
// Content returns the transactions contained within the transaction pool.
func (s *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction {
content := map[string]map[string]map[string]*RPCTransaction{
"pending": make(map[string]map[string]*RPCTransaction),
"queued": make(map[string]map[string]*RPCTransaction),
}
pending, queue := s.b.TxPoolContent()
curHeader := s.b.CurrentHeader()
// Flatten the pending transactions
for account, txs := range pending {
dump := make(map[string]*RPCTransaction)
for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
}
content["pending"][account.Hex()] = dump
}
// Flatten the queued transactions
for account, txs := range queue {
dump := make(map[string]*RPCTransaction)
for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
}
content["queued"][account.Hex()] = dump
}
return content
}
// ContentFrom returns the transactions contained within the transaction pool.
func (s *TxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCTransaction {
content := make(map[string]map[string]*RPCTransaction, 2)
pending, queue := s.b.TxPoolContentFrom(addr)
curHeader := s.b.CurrentHeader()
// Build the pending transactions
dump := make(map[string]*RPCTransaction, len(pending))
for _, tx := range pending {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
}
content["pending"] = dump
// Build the queued transactions
dump = make(map[string]*RPCTransaction, len(queue))
for _, tx := range queue {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
}
content["queued"] = dump
return content
}
// Status returns the number of pending and queued transaction in the pool.
func (s *TxPoolAPI) Status() map[string]hexutil.Uint {
pending, queue := s.b.Stats()
return map[string]hexutil.Uint{
"pending": hexutil.Uint(pending),
"queued": hexutil.Uint(queue),
}
}
// Inspect retrieves the content of the transaction pool and flattens it into an
// easily inspectable list.
func (s *TxPoolAPI) Inspect() map[string]map[string]map[string]string {
content := map[string]map[string]map[string]string{
"pending": make(map[string]map[string]string),
"queued": make(map[string]map[string]string),
}
pending, queue := s.b.TxPoolContent()
// Define a formatter to flatten a transaction into a string
var format = func(tx *types.Transaction) string {
if to := tx.To(); to != nil {
return fmt.Sprintf("%s: %v wei + %v gas × %v wei", to, tx.Value(), tx.Gas(), tx.GasPrice())
}
return fmt.Sprintf("contract creation: %v wei + %v gas × %v wei", tx.Value(), tx.Gas(), tx.GasPrice())
}
// Flatten the pending transactions
for account, txs := range pending {
dump := make(map[string]string)
for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx)
}
content["pending"][account.Hex()] = dump
}
// Flatten the queued transactions
for account, txs := range queue {
dump := make(map[string]string)
for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx)
}
content["queued"][account.Hex()] = dump
}
return content
}
// EthereumAccountAPI provides an API to access accounts managed by this node.
// It offers only methods that can retrieve accounts.
type EthereumAccountAPI struct {
am *accounts.Manager
}
// NewEthereumAccountAPI creates a new EthereumAccountAPI.
func NewEthereumAccountAPI(am *accounts.Manager) *EthereumAccountAPI {
return &EthereumAccountAPI{am: am}
}
// Accounts returns the collection of accounts this node manages
func (s *EthereumAccountAPI) Accounts() []common.Address {
return s.am.Accounts()
}
// BlockChainAPI provides an API to access Ethereum blockchain data.
type BlockChainAPI struct {
b Backend
chainReader consensus.ChainReader
}
// NewBlockChainAPI creates a new Ethereum blockchain API.
func NewBlockChainAPI(b Backend, chainReader consensus.ChainReader) *BlockChainAPI {
return &BlockChainAPI{
b,
chainReader,
}
}
// ChainId returns the chainID value for transaction replay protection.
func (s *BlockChainAPI) ChainId() *hexutil.Big {
return (*hexutil.Big)(s.b.ChainConfig().ChainId)
}
// BlockNumber returns the block number of the chain head.
func (s *BlockChainAPI) BlockNumber() hexutil.Uint64 {
header, _ := s.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available
return hexutil.Uint64(header.Number.Uint64())
}
// BlockNumber returns the block number of the chain head.
func (s *BlockChainAPI) GetRewardByHash(hash common.Hash) map[string]map[string]map[string]*big.Int {
return s.b.GetRewardByHash(hash)
}
// GetBalance returns the amount of wei 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 *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
return (*hexutil.Big)(state.GetBalance(address)), state.Error()
}
// GetTransactionAndReceiptProof returns the Trie transaction and receipt proof of the given transaction hash.
func (s *BlockChainAPI) GetTransactionAndReceiptProof(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
tx, blockHash, _, index := rawdb.ReadTransaction(s.b.ChainDb(), hash)
if tx == nil {
return nil, nil
}
block, err := s.b.GetBlock(ctx, blockHash)
if err != nil {
return nil, err
}
tx_tr := deriveTrie(block.Transactions())
keybuf := new(bytes.Buffer)
rlp.Encode(keybuf, uint(index))
var tx_proof proofPairList
if err := tx_tr.Prove(keybuf.Bytes(), 0, &tx_proof); err != nil {
return nil, err
}
receipts, err := s.b.GetReceipts(ctx, blockHash)
if err != nil {
return nil, err
}
if len(receipts) <= int(index) {
return nil, nil
}
receipt_tr := deriveTrie(receipts)
var receipt_proof proofPairList
if err := receipt_tr.Prove(keybuf.Bytes(), 0, &receipt_proof); err != nil {
return nil, err
}
fields := map[string]interface{}{
"blockHash": blockHash,
"txRoot": tx_tr.Hash(),
"receiptRoot": receipt_tr.Hash(),
"key": hexutil.Encode(keybuf.Bytes()),
"txProofKeys": tx_proof.keys,
"txProofValues": tx_proof.values,
"receiptProofKeys": receipt_proof.keys,
"receiptProofValues": receipt_proof.values,
}
return fields, 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 *BlockChainAPI) GetBlockByNumber(ctx context.Context, blockNr rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
block, err := s.b.BlockByNumber(ctx, blockNr)
if block != nil {
response, err := s.rpcOutputBlock(block, true, fullTx, ctx)
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 *BlockChainAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (map[string]interface{}, error) {
block, err := s.b.GetBlock(ctx, blockHash)
if block != nil {
return s.rpcOutputBlock(block, true, fullTx, ctx)
}
return nil, err
}
// GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index. When fullTx is true
// all transactions in the block are returned in full detail, otherwise only the transaction hash is returned.
func (s *BlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (map[string]interface{}, error) {
block, err := s.b.BlockByNumber(ctx, blockNr)
if block != nil {
uncles := block.Uncles()
if index >= hexutil.Uint(len(uncles)) {
log.Debug("Requested uncle not found", "number", blockNr, "hash", block.Hash(), "index", index)
return nil, nil
}
block = types.NewBlockWithHeader(uncles[index])
return s.rpcOutputBlock(block, false, false, ctx)
}
return nil, err
}
// GetUncleByBlockHashAndIndex returns the uncle block for the given block hash and index. When fullTx is true
// all transactions in the block are returned in full detail, otherwise only the transaction hash is returned.
// DEPRECATED SINCE 1.0
func (s *BlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (map[string]interface{}, error) {
block, err := s.b.GetBlock(ctx, blockHash)
if block != nil {
uncles := block.Uncles()
if index >= hexutil.Uint(len(uncles)) {
log.Debug("Requested uncle not found", "number", block.Number(), "hash", blockHash, "index", index)
return nil, nil
}
block = types.NewBlockWithHeader(uncles[index])
return s.rpcOutputBlock(block, false, false, ctx)
}
return nil, err
}
// GetUncleCountByBlockNumber returns number of uncles in the block for the given block number
// DEPRECATED SINCE 1.0
func (s *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
n := hexutil.Uint(len(block.Uncles()))
return &n
}
return nil
}
// GetUncleCountByBlockHash returns number of uncles in the block for the given block hash
// DEPRECATED SINCE 1.0
func (s *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
if block, _ := s.b.GetBlock(ctx, blockHash); block != nil {
n := hexutil.Uint(len(block.Uncles()))
return &n
}
return nil
}
// GetCode returns the code stored at the given address in the state for the given block number.
func (s *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
code := state.GetCode(address)
return code, state.Error()
}
// GetAccountInfo returns the information at the given address in the state for the given block number.
func (s *BlockChainAPI) GetAccountInfo(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (map[string]interface{}, error) {
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
info := state.GetAccountInfo(address)
result := map[string]interface{}{
"address": address,
"balance": (*hexutil.Big)(info.Balance),
"codeSize": info.CodeSize,
"codeHash": info.CodeHash,
"nonce": info.Nonce,
"storageHash": info.StorageHash,
}
return result, nil
}
// 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 *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, key string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
res := state.GetState(address, common.HexToHash(key))
return res[:], state.Error()
}
// GetBlockReceipts returns the block receipts for the given block hash or number or tag.
func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
block, err := s.b.BlockByNumberOrHash(ctx, blockNrOrHash)
if block == nil || err != nil {
// When the block doesn't exist, the RPC method should return JSON null
// as per specification.
return nil, nil
}
receipts, err := s.b.GetReceipts(ctx, block.Hash())
if err != nil {
return nil, err
}
txs := block.Transactions()
if len(txs) != len(receipts) {
return nil, fmt.Errorf("receipts length mismatch: %d vs %d", len(txs), len(receipts))
}
// Derive the sender.
signer := types.MakeSigner(s.b.ChainConfig(), block.Number())
result := make([]map[string]interface{}, len(receipts))
for i, receipt := range receipts {
result[i] = marshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i)
}
return result, nil
}
// OverrideAccount indicates the overriding fields of account during the execution
// of a message call.
// Note, state and stateDiff can't be specified at the same time. If state is
// set, message execution will only use the data in the given state. Otherwise
// if statDiff is set, all diff will be applied first and then execute the call
// message.
type OverrideAccount struct {
Nonce *hexutil.Uint64 `json:"nonce"`
Code *hexutil.Bytes `json:"code"`
Balance **hexutil.Big `json:"balance"`
State *map[common.Hash]common.Hash `json:"state"`
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
}
// StateOverride is the collection of overridden accounts.
type StateOverride map[common.Address]OverrideAccount
// Apply overrides the fields of specified accounts into the given state.
func (diff *StateOverride) Apply(state *state.StateDB) error {
if diff == nil {
return nil
}
for addr, account := range *diff {
// Override account nonce.
if account.Nonce != nil {
state.SetNonce(addr, uint64(*account.Nonce))
}
// Override account(contract) code.
if account.Code != nil {
state.SetCode(addr, *account.Code)
}
// Override account balance.
if account.Balance != nil {
state.SetBalance(addr, (*big.Int)(*account.Balance))
}
if account.State != nil && account.StateDiff != nil {
return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
}
// Replace entire state if caller requires.
if account.State != nil {
state.SetStorage(addr, *account.State)
}
// Apply state diff into specified accounts.
if account.StateDiff != nil {
for key, value := range *account.StateDiff {
state.SetState(addr, key, value)
}
}
}
return nil
}
func (s *BlockChainAPI) GetBlockSignersByHash(ctx context.Context, blockHash common.Hash) ([]common.Address, error) {
block, err := s.b.GetBlock(ctx, blockHash)
if err != nil || block == nil {
return []common.Address{}, err
}
masternodes, err := s.GetMasternodes(ctx, block)
if err != nil || len(masternodes) == 0 {
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes))
return []common.Address{}, err
}
return s.rpcOutputBlockSigners(block, ctx, masternodes)
}
func (s *BlockChainAPI) GetBlockSignersByNumber(ctx context.Context, blockNumber rpc.BlockNumber) ([]common.Address, error) {
block, err := s.b.BlockByNumber(ctx, blockNumber)
if err != nil || block == nil {
return []common.Address{}, err
}
masternodes, err := s.GetMasternodes(ctx, block)
if err != nil || len(masternodes) == 0 {
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes))
return []common.Address{}, err
}
return s.rpcOutputBlockSigners(block, ctx, masternodes)
}
func (s *BlockChainAPI) GetBlockFinalityByHash(ctx context.Context, blockHash common.Hash) (uint, error) {
block, err := s.b.GetBlock(ctx, blockHash)
if err != nil || block == nil {
return uint(0), err
}
masternodes, err := s.GetMasternodes(ctx, block)
if err != nil || len(masternodes) == 0 {
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes))
return uint(0), err
}
return s.findFinalityOfBlock(ctx, block, masternodes)
}
func (s *BlockChainAPI) GetBlockFinalityByNumber(ctx context.Context, blockNumber rpc.BlockNumber) (uint, error) {
block, err := s.b.BlockByNumber(ctx, blockNumber)
if err != nil || block == nil {
return uint(0), err
}
masternodes, err := s.GetMasternodes(ctx, block)
if err != nil || len(masternodes) == 0 {
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes))
return uint(0), err
}
return s.findFinalityOfBlock(ctx, block, masternodes)
}
// GetMasternodes returns masternodes set at the starting block of epoch of the given block
func (s *BlockChainAPI) GetMasternodes(ctx context.Context, b *types.Block) ([]common.Address, error) {
var masternodes []common.Address
if b.Number().Int64() >= 0 {
curBlockNumber := b.Number().Uint64()
prevBlockNumber := curBlockNumber + (common.MergeSignRange - (curBlockNumber % common.MergeSignRange))
latestBlockNumber := s.b.CurrentBlock().Number().Uint64()
if prevBlockNumber >= latestBlockNumber || !s.b.ChainConfig().IsTIP2019(b.Number()) {
prevBlockNumber = curBlockNumber
}
if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
// Get block epoc latest.
return engine.GetMasternodesByNumber(s.chainReader, prevBlockNumber), nil
} else {
log.Error("Undefined XDPoS consensus engine")
}
}
return masternodes, nil
}
// GetCandidateStatus returns status of the given candidate at a specified epochNumber
func (s *BlockChainAPI) GetCandidateStatus(ctx context.Context, coinbaseAddress common.Address, epoch rpc.EpochNumber) (map[string]interface{}, error) {
var (
block *types.Block
header *types.Header
checkpointNumber rpc.BlockNumber
epochNumber rpc.EpochNumber // if epoch == "latest", print the latest epoch number to epochNumber
masternodes, penaltyList []common.Address
candidates []utils.Masternode
penalties []byte
err error
)
result := map[string]interface{}{
fieldStatus: "",
fieldCapacity: 0,
fieldSuccess: true,
}
epochConfig := s.b.ChainConfig().XDPoS.Epoch
// checkpoint block
checkpointNumber, epochNumber = s.GetCheckpointFromEpoch(ctx, epoch)
result[fieldEpoch] = epochNumber.Int64()
block, err = s.b.BlockByNumber(ctx, checkpointNumber)
if err != nil || block == nil { // || checkpointNumber == 0 {
result[fieldSuccess] = false
return result, err
}
header = block.Header()
if header == nil {
log.Error("Empty header at checkpoint ", "num", checkpointNumber)
return result, errEmptyHeader
}
// list of candidates (masternode, slash, propose) at block checkpoint
if epoch == rpc.LatestEpochNumber {
candidates, err = s.getCandidatesFromSmartContract()
} else {
statedb, _, err := s.b.StateAndHeaderByNumber(ctx, checkpointNumber)
if err != nil {
result[fieldSuccess] = false
return result, err
}
if statedb == nil {
result[fieldSuccess] = false
return result, errors.New("nil statedb in GetCandidateStatus")
}
candidatesAddresses := state.GetCandidates(statedb)
candidates = make([]utils.Masternode, 0, len(candidatesAddresses))
for _, address := range candidatesAddresses {
v := state.GetCandidateCap(statedb, address)
candidates = append(candidates, utils.Masternode{Address: address, Stake: v})
}
}
if err != nil || len(candidates) == 0 {
log.Debug("Candidates list cannot be found", "len(candidates)", len(candidates), "err", err)
result[fieldSuccess] = false
return result, err
}
var maxMasternodes int
if header.Number.Cmp(s.b.ChainConfig().XDPoS.V2.SwitchBlock) == 1 {
if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
round, err := engine.EngineV2.GetRoundNumber(header)
if err != nil {
return result, err
}
maxMasternodes = s.b.ChainConfig().XDPoS.V2.Config(uint64(round)).MaxMasternodes
} else {
return result, errors.New("undefined XDPoS consensus engine")
}
} else if s.b.ChainConfig().IsTIPIncreaseMasternodes(block.Number()) {
maxMasternodes = common.MaxMasternodesV2
} else {
maxMasternodes = common.MaxMasternodes
}
// check penalties from checkpoint headers and modify status of a node to SLASHED if it's in top maxMasternodes candidates.
// if it's SLASHED but it's out of top maxMasternodes, the status should be still PROPOSED.
isCandidate := false
for i := 0; i < len(candidates); i++ {
if coinbaseAddress == candidates[i].Address {
isCandidate = true
result[fieldStatus] = statusProposed
result[fieldCapacity] = candidates[i].Stake
break
}
}
// Get masternode list
if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
masternodes = engine.GetMasternodesFromCheckpointHeader(header)
if len(masternodes) == 0 {
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64())
result[fieldSuccess] = false
return result, err
}
} else {
log.Error("Undefined XDPoS consensus engine")
}
// Set to statusMasternode if it is masternode
for _, masternode := range masternodes {
if coinbaseAddress == masternode {
result[fieldStatus] = statusMasternode
if !isCandidate {
result[fieldCapacity] = -1
log.Warn("Find non-candidate masternode", "masternode", masternode.String(), "checkpointNumber", checkpointNumber, "epoch", epoch, "epochNumber", epochNumber)
}
return result, nil
}
}
if !isCandidate || len(masternodes) >= maxMasternodes {
return result, nil
}
if len(candidates) > maxMasternodes {
sort.Slice(candidates, func(i, j int) bool {
return candidates[i].Stake.Cmp(candidates[j].Stake) > 0
})
}
// Get penalties list
penalties = append(penalties, header.Penalties...)
// check last 5 epochs to find penalize masternodes
for i := 1; i <= common.LimitPenaltyEpoch; i++ {
if header.Number.Uint64() < epochConfig*uint64(i) {
break
}
blockNum := header.Number.Uint64() - epochConfig*uint64(i)
checkpointHeader, err := s.b.HeaderByNumber(ctx, rpc.BlockNumber(blockNum))
if checkpointHeader == nil || err != nil {
log.Error("Failed to get header by number", "num", blockNum, "err", err)
continue
}
penalties = append(penalties, checkpointHeader.Penalties...)
}
penaltyList = common.ExtractAddressFromBytes(penalties)
// map slashing status
total := len(masternodes)
for _, candidate := range candidates {
for _, pen := range penaltyList {
if candidate.Address == pen {
if coinbaseAddress == pen {
result[fieldStatus] = statusSlashed
return result, nil
}
total++
if total >= maxMasternodes {
return result, nil
}
}
}
}
return result, nil
}
// GetCandidates returns status of all candidates at a specified epochNumber
func (s *BlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.EpochNumber) (map[string]interface{}, error) {
var (
block *types.Block
header *types.Header
checkpointNumber rpc.BlockNumber
epochNumber rpc.EpochNumber
masternodes []common.Address
penaltyList []common.Address
candidates []utils.Masternode
penalties []byte
err error
)
result := map[string]interface{}{
fieldSuccess: true,
}
epochConfig := s.b.ChainConfig().XDPoS.Epoch
checkpointNumber, epochNumber = s.GetCheckpointFromEpoch(ctx, epoch)
result[fieldEpoch] = epochNumber.Int64()
block, err = s.b.BlockByNumber(ctx, checkpointNumber)
if err != nil || block == nil { // || checkpointNumber == 0 {
result[fieldSuccess] = false
return result, err
}
header = block.Header()
if header == nil {
log.Error("Empty header at checkpoint", "num", checkpointNumber)
return result, errEmptyHeader
}
// list of candidates (masternode, slash, propose) at block checkpoint
if epoch == rpc.LatestEpochNumber {
candidates, err = s.getCandidatesFromSmartContract()
} else {
statedb, _, err := s.b.StateAndHeaderByNumber(ctx, checkpointNumber)
if err != nil {
result[fieldSuccess] = false
return result, err
}
if statedb == nil {
result[fieldSuccess] = false
return result, errors.New("nil statedb in GetCandidates")
}
candidatesAddresses := state.GetCandidates(statedb)
candidates = make([]utils.Masternode, 0, len(candidatesAddresses))
for _, address := range candidatesAddresses {
v := state.GetCandidateCap(statedb, address)
candidates = append(candidates, utils.Masternode{Address: address, Stake: v})
}
}
if err != nil || len(candidates) == 0 {
log.Debug("Candidates list cannot be found", "len(candidates)", len(candidates), "err", err)
result[fieldSuccess] = false
return result, err
}
// Find candidates that have masternode status
if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
masternodes = engine.GetMasternodesFromCheckpointHeader(header)
if len(masternodes) == 0 {
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64())
result[fieldSuccess] = false
return result, err
}
} else {
log.Error("Undefined XDPoS consensus engine")
}
// Set all candidate to statusProposed
candidatesStatusMap := make(map[string]map[string]interface{}, len(candidates))
for _, candidate := range candidates {
candidatesStatusMap[candidate.Address.String()] = map[string]interface{}{
fieldStatus: statusProposed,
fieldCapacity: candidate.Stake,
}
}
// Set masternodes to statusMasternode
for _, masternode := range masternodes {
key := masternode.String()
if candidatesStatusMap[key] != nil {
candidatesStatusMap[key][fieldStatus] = statusMasternode
} else {
candidatesStatusMap[key] = map[string]interface{}{
fieldStatus: statusMasternode,
fieldCapacity: -1,
}
log.Warn("Masternode is not candidate", "masternode", key, "checkpointNumber", checkpointNumber, "epoch", epoch, "epochNumber", epochNumber)
}
}
var maxMasternodes int
if header.Number.Cmp(s.b.ChainConfig().XDPoS.V2.SwitchBlock) == 1 {
if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
round, err := engine.EngineV2.GetRoundNumber(header)
if err != nil {
return result, err
}
maxMasternodes = s.b.ChainConfig().XDPoS.V2.Config(uint64(round)).MaxMasternodes
} else {
return result, errors.New("undefined XDPoS consensus engine")
}
} else if s.b.ChainConfig().IsTIPIncreaseMasternodes(block.Number()) {
maxMasternodes = common.MaxMasternodesV2
} else {
maxMasternodes = common.MaxMasternodes
}
if len(masternodes) >= maxMasternodes {
result[fieldCandidates] = candidatesStatusMap
return result, nil
}
if len(candidates) > maxMasternodes {
sort.Slice(candidates, func(i, j int) bool {
return candidates[i].Stake.Cmp(candidates[j].Stake) > 0
})
}
// Get penalties list
penalties = append(penalties, header.Penalties...)
// check last 5 epochs to find penalize masternodes
for i := 1; i <= common.LimitPenaltyEpoch; i++ {
if header.Number.Uint64() < epochConfig*uint64(i) {
break
}
blockNum := header.Number.Uint64() - epochConfig*uint64(i)
checkpointHeader, err := s.b.HeaderByNumber(ctx, rpc.BlockNumber(blockNum))
if checkpointHeader == nil || err != nil {
log.Error("Failed to get header by number", "num", blockNum, "err", err)
continue
}
penalties = append(penalties, checkpointHeader.Penalties...)
}
// map slashing status
if len(penalties) == 0 {
result[fieldCandidates] = candidatesStatusMap
return result, nil
}
penaltyList = common.ExtractAddressFromBytes(penalties)
// check penalties from checkpoint headers and modify status of a node to SLASHED if it's in top maxMasternodes candidates.
// if it's SLASHED but it's out of top maxMasternodes, the status should be still PROPOSED.
total := len(masternodes)
for _, candidate := range candidates {
for _, pen := range penaltyList {
if candidate.Address == pen {
candidatesStatusMap[pen.String()][fieldStatus] = statusSlashed
total++
if total >= maxMasternodes {
result[fieldCandidates] = candidatesStatusMap
return result, nil
}
}
}
}
// update result
result[fieldCandidates] = candidatesStatusMap
return result, nil
}
// GetCheckpointFromEpoch returns header of the previous checkpoint
func (s *BlockChainAPI) GetCheckpointFromEpoch(ctx context.Context, epochNum rpc.EpochNumber) (rpc.BlockNumber, rpc.EpochNumber) {
var checkpointNumber uint64
epoch := s.b.ChainConfig().XDPoS.Epoch
if epochNum == rpc.LatestEpochNumber {
blockNumer := s.b.CurrentBlock().Number()
if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
var err error
var currentEpoch uint64
checkpointNumber, currentEpoch, err = engine.GetCurrentEpochSwitchBlock(s.chainReader, blockNumer)
if err != nil {
log.Error("[GetCheckpointFromEpoch] Fail to get GetCurrentEpochSwitchBlock for current checkpoint block", "block", blockNumer, "err", err)
return 0, epochNum
}
epochNum = rpc.EpochNumber(currentEpoch)
}
} else if epochNum < 2 {
checkpointNumber = 0
} else {
// TODO this checkpointNumber needs to be recalculated for v2 blocks
checkpointNumber = epoch * (uint64(epochNum) - 1)
}
return rpc.BlockNumber(checkpointNumber), epochNum
}
// getCandidatesFromSmartContract returns all candidates with their capacities at the current time
func (s *BlockChainAPI) getCandidatesFromSmartContract() ([]utils.Masternode, error) {
client, err := s.b.GetIPCClient()
if err != nil {
return []utils.Masternode{}, err
}
addr := common.MasternodeVotingSMCBinary
validator, err := contractValidator.NewXDCValidator(addr, client)
if err != nil {
return []utils.Masternode{}, err
}
opts := new(bind.CallOpts)
candidates, err := validator.GetCandidates(opts)
if err != nil {
return []utils.Masternode{}, err
}
candidatesWithStakeInfo := make([]utils.Masternode, 0, len(candidates))
for _, candidate := range candidates {
if !candidate.IsZero() {
v, err := validator.GetCandidateCap(opts, candidate)
if err != nil {
return []utils.Masternode{}, err
}
candidatesWithStakeInfo = append(candidatesWithStakeInfo, utils.Masternode{Address: candidate, Stake: v})
}
}
return candidatesWithStakeInfo, nil
}
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
statedb, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if statedb == nil || err != nil {
return nil, err
}
if header == nil {
return nil, errors.New("nil header in DoCall")
}
if err := overrides.Apply(statedb); err != nil {
return nil, err
}
// 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()
block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return nil, err
}
if block == nil {
return nil, fmt.Errorf("nil block in DoCall: number=%d, hash=%s", header.Number.Uint64(), header.Hash().Hex())
}
author, err := b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
XDCxState, err := b.XDCxService().GetTradingState(block, author)
if err != nil {
return nil, err
}
// TODO: replace header.BaseFee with blockCtx.BaseFee
// reference: https://github.com/ethereum/go-ethereum/pull/29051
msg, err := args.ToMessage(b, header.Number, globalGasCap, header.BaseFee)
if err != nil {
return nil, err
}
msg.SetBalanceTokenFeeForCall()
// Get a new instance of the EVM.
evm, vmError, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &vm.Config{NoBaseFee: true})
if err != nil {
return nil, 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()
}()
// Execute the message.
gp := new(core.GasPool).AddGas(gomath.MaxUint64)
owner := common.Address{}
result, err := core.ApplyMessage(evm, msg, gp, owner)
if err := vmError(); err != nil {
return nil, err
}
// If the timer caused an abort, return an appropriate error message
if evm.Cancelled() {
return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout)
}
if err != nil {
return result, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas())
}
return result, err
}
func newRevertError(result *core.ExecutionResult) *revertError {
reason, errUnpack := abi.UnpackRevert(result.Revert())
err := errors.New("execution reverted")
if errUnpack == nil {
err = fmt.Errorf("execution reverted: %v", reason)
}
return &revertError{
error: err,
reason: hexutil.Encode(result.Revert()),
}
}
// revertError is an API error that encompassas an EVM revertal with JSON error
// code and a binary data blob.
type revertError struct {
error
reason string // revert reason hex encoded
}
// ErrorCode returns the JSON error code for a revertal.
// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
func (e *revertError) ErrorCode() int {
return 3
}
// ErrorData returns the hex encoded revert reason.
func (e *revertError) ErrorData() interface{} {
return e.reason
}
// 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 *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) {
if blockNrOrHash == nil {
latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
blockNrOrHash = &latest
}
timeout := 5 * time.Second
if args.To != nil && *args.To == common.MasternodeVotingSMCBinary {
timeout = 0
}
result, err := DoCall(ctx, s.b, args, *blockNrOrHash, overrides, timeout, s.b.RPCGasCap())
if err != nil {
return nil, err
}
// If the result contains a revert reason, try to unpack and return it.
if len(result.Revert()) > 0 {
return nil, newRevertError(result)
}
return result.Return(), result.Err
}
type estimateGasError struct {
error string // Concrete error type if it's failed to estimate gas usage
vmerr error // Additional field, it's non-nil if the given transaction is invalid
revert string // Additional field, it's non-empty if the transaction is reverted and reason is provided
}
func (e estimateGasError) Error() string {
errMsg := e.error
if e.vmerr != nil {
errMsg += fmt.Sprintf(" (%v)", e.vmerr)
}
if e.revert != "" {
errMsg += fmt.Sprintf(" (%s)", e.revert)
}
return errMsg
}
func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, gasCap uint64) (hexutil.Uint64, error) {
// Retrieve the base state and mutate it with any overrides
state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return 0, err
}
if err = overrides.Apply(state); err != nil {
return 0, err
}
// Binary search the gas requirement, as it may be higher than the amount used
var (
lo uint64 = params.TxGas - 1
hi uint64
cap uint64
)
// Use zero address if sender unspecified.
if args.From == nil {
args.From = new(common.Address)
}
// Determine the highest gas limit can be used during the estimation.
if args.Gas != nil && uint64(*args.Gas) >= params.TxGas {
hi = uint64(*args.Gas)
} else {
// Retrieve the current pending block to act as the gas ceiling
block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return 0, err
}
if block == nil {
return 0, errors.New("block not found")
}
hi = block.GasLimit()
}
// Recap the highest gas allowance with specified gascap.
if gasCap != 0 && hi > gasCap {
log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
hi = gasCap
}
cap = hi
// Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
args.Gas = (*hexutil.Uint64)(&gas)
result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap)
if err != nil {
if errors.Is(err, vm.ErrOutOfGas) || errors.Is(err, core.ErrIntrinsicGas) {
return true, nil, nil // Special case, raise gas limit
}
return true, nil, err // Bail out
}
return result.Failed(), result, nil
}
// If the transaction is a plain value transfer, short circuit estimation and
// directly try 21000. Returning 21000 without any execution is dangerous as
// some tx field combos might bump the price up even for plain transfers (e.g.
// unused access list items). Ever so slightly wasteful, but safer overall.
if args.Data == nil || len(*args.Data) == 0 {
if args.To != nil && state.GetCodeSize(*args.To) == 0 {
ok, _, err := executable(params.TxGas)
if ok && err == nil {
return hexutil.Uint64(params.TxGas), nil
}
}
}
// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi {
mid := (hi + lo) / 2
failed, _, err := executable(mid)
// If the error is not nil(consensus error), it means the provided message
// call or transaction will never be accepted no matter how much gas it is
// assigned. Return the error directly, don't struggle any more.
if err != nil {
return 0, err
}
if failed {
lo = mid
} else {
hi = mid
}
}
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap {
failed, result, err := executable(hi)
if err != nil {
return 0, err
}
if failed {
if result != nil && !errors.Is(result.Err, vm.ErrOutOfGas) {
if len(result.Revert()) > 0 {
return 0, newRevertError(result)
}
return 0, result.Err
}
// Otherwise, the specified gas cap is too low
return 0, fmt.Errorf("gas required exceeds allowance (%d)", 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 *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Uint64, error) {
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
if blockNrOrHash != nil {
bNrOrHash = *blockNrOrHash
}
return DoEstimateGas(ctx, s.b, args, bNrOrHash, overrides, s.b.RPCGasCap())
}
// ExecutionResult groups all structured logs emitted by the EVM
// while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value
type ExecutionResult struct {
Gas uint64 `json:"gas"`
Failed bool `json:"failed"`
ReturnValue string `json:"returnValue"`
StructLogs []StructLogRes `json:"structLogs"`
}
// StructLogRes stores a structured log emitted by the EVM while replaying a
// transaction in debug mode
type StructLogRes struct {
Pc uint64 `json:"pc"`
Op string `json:"op"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error error `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"`
Memory *[]string `json:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
}
// formatLogs formats EVM returned structured logs for json output
func FormatLogs(logs []vm.StructLog) []StructLogRes {
formatted := make([]StructLogRes, len(logs))
for index, trace := range logs {
formatted[index] = StructLogRes{
Pc: trace.Pc,
Op: trace.Op.String(),
Gas: trace.Gas,
GasCost: trace.GasCost,
Depth: trace.Depth,
Error: trace.Err,
}
if trace.Stack != nil {
stack := make([]string, len(trace.Stack))
for i, stackValue := range trace.Stack {
stack[i] = stackValue.Hex()
}
formatted[index].Stack = &stack
}
if trace.Memory != nil {
memory := make([]string, 0, (len(trace.Memory)+31)/32)
for i := 0; i+32 <= len(trace.Memory); i += 32 {
memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
}
formatted[index].Memory = &memory
}
if trace.Storage != nil {
storage := make(map[string]string)
for i, storageValue := range trace.Storage {
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
}
formatted[index].Storage = &storage
}
}
return formatted
}
// RPCMarshalHeader converts the given header to the RPC output .
func RPCMarshalHeader(head *types.Header) map[string]interface{} {
result := map[string]interface{}{
"number": (*hexutil.Big)(head.Number),
"hash": head.Hash(),
"parentHash": head.ParentHash,
"nonce": head.Nonce,
"mixHash": head.MixDigest,
"sha3Uncles": head.UncleHash,
"logsBloom": head.Bloom,
"stateRoot": head.Root,
"miner": head.Coinbase,
"difficulty": (*hexutil.Big)(head.Difficulty),
"extraData": hexutil.Bytes(head.Extra),
"size": hexutil.Uint64(head.Size()),
"gasLimit": hexutil.Uint64(head.GasLimit),
"gasUsed": hexutil.Uint64(head.GasUsed),
"timestamp": hexutil.Uint64(head.Time),
"transactionsRoot": head.TxHash,
"receiptsRoot": head.ReceiptHash,
"validators": hexutil.Bytes(head.Validators),
"validator": hexutil.Bytes(head.Validator),
"penalties": hexutil.Bytes(head.Penalties),
}
if head.BaseFee != nil {
result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee)
}
return result
}
// rpcOutputBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are
// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain
// transaction hashes.
func (s *BlockChainAPI) rpcOutputBlock(b *types.Block, inclTx bool, fullTx bool, ctx context.Context) (map[string]interface{}, error) {
fields := RPCMarshalHeader(b.Header())
fields["size"] = hexutil.Uint64(b.Size())
fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(context.Background(), b.Hash()))
if inclTx {
formatTx := func(tx *types.Transaction) (interface{}, error) {
return tx.Hash(), nil
}
if fullTx {
formatTx = func(tx *types.Transaction) (interface{}, error) {
return newRPCTransactionFromBlockHash(b, tx.Hash()), nil
}
}
txs := b.Transactions()
transactions := make([]interface{}, len(txs))
var err error
for i, tx := range b.Transactions() {
if transactions[i], err = formatTx(tx); err != nil {
return nil, err
}
}
fields["transactions"] = transactions
}
uncles := b.Uncles()
uncleHashes := make([]common.Hash, len(uncles))
for i, uncle := range uncles {
uncleHashes[i] = uncle.Hash()
}
fields["uncles"] = uncleHashes
return fields, nil
}
// findNearestSignedBlock finds the nearest checkpoint from input block
func (s *BlockChainAPI) findNearestSignedBlock(ctx context.Context, b *types.Block) *types.Block {
if b.Number().Int64() <= 0 {
return nil
}
blockNumber := b.Number().Uint64()
signedBlockNumber := blockNumber + (common.MergeSignRange - (blockNumber % common.MergeSignRange))
latestBlockNumber := s.b.CurrentBlock().Number()
if signedBlockNumber >= latestBlockNumber.Uint64() || !s.b.ChainConfig().IsTIPSigning(b.Number()) {
signedBlockNumber = blockNumber
}
// Get block epoc latest
checkpointNumber, _, err := s.b.Engine().(*XDPoS.XDPoS).GetCurrentEpochSwitchBlock(s.chainReader, big.NewInt(int64(signedBlockNumber)))
if err != nil {
log.Error("[findNearestSignedBlock] Error while trying to get current Epoch switch block", "Number", signedBlockNumber)
}
checkpointBlock, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(checkpointNumber))
if checkpointBlock != nil {
signedBlock, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(signedBlockNumber))
return signedBlock
}
return nil
}
/*
findFinalityOfBlock return finality of a block
Use blocksHashCache for to keep track - refer core/blockchain.go for more detail
*/
func (s *BlockChainAPI) findFinalityOfBlock(ctx context.Context, b *types.Block, masternodes []common.Address) (uint, error) {
engine, _ := s.b.Engine().(*XDPoS.XDPoS)
signedBlock := s.findNearestSignedBlock(ctx, b)
if signedBlock == nil {
return 0, nil
}
signedBlocksHash := s.b.GetBlocksHashCache(signedBlock.Number().Uint64())
// there is no cache for this block's number
// return the number(signers) / number(masternode) * 100 if this block is on canonical path
// else return 0 for fork path
if signedBlocksHash == nil {
if !s.b.AreTwoBlockSamePath(signedBlock.Hash(), b.Hash()) {
return 0, nil
}
blockSigners, err := s.getSigners(ctx, signedBlock, engine)
if blockSigners == nil {
return 0, err
}
return uint(100 * len(blockSigners) / len(masternodes)), nil
}
/*
With Hashes cache - we can track all chain's path
back to current's block number by parent's Hash
If found the current block so the finality = signedBlock's finality
else return 0
*/
var signedBlockSamePath common.Hash
for count := 0; count < len(signedBlocksHash); count++ {
blockHash := signedBlocksHash[count]
if s.b.AreTwoBlockSamePath(blockHash, b.Hash()) {
signedBlockSamePath = blockHash
break
}
}
// return 0 if not same path with any signed block
if len(signedBlockSamePath) == 0 {
return 0, nil
}
// get signers and return finality
samePathSignedBlock, err := s.b.GetBlock(ctx, signedBlockSamePath)
if samePathSignedBlock == nil {
return 0, err
}
blockSigners, err := s.getSigners(ctx, samePathSignedBlock, engine)
if blockSigners == nil {
return 0, err
}
return uint(100 * len(blockSigners) / len(masternodes)), nil
}
/*
Extract signers from block
*/
func (s *BlockChainAPI) getSigners(ctx context.Context, block *types.Block, engine *XDPoS.XDPoS) ([]common.Address, error) {
var err error
var filterSigners []common.Address
var signers []common.Address
masternodes := engine.GetMasternodes(s.chainReader, block.Header())
signers, err = GetSignersFromBlocks(s.b, block.NumberU64(), block.Hash(), masternodes)
if err != nil {
log.Error("Fail to get signers from block signer SC.", "error", err)
return nil, err
}
validator, _ := engine.RecoverValidator(block.Header())
creator, _ := engine.RecoverSigner(block.Header())
signers = append(signers, validator)
signers = append(signers, creator)
for _, masternode := range masternodes {
for _, signer := range signers {
if signer == masternode {
filterSigners = append(filterSigners, masternode)
break
}
}
}
return filterSigners, nil
}
func (s *BlockChainAPI) rpcOutputBlockSigners(b *types.Block, ctx context.Context, masternodes []common.Address) ([]common.Address, error) {
_, err := s.b.GetIPCClient()
if err != nil {
log.Error("Fail to connect IPC client for block status", "error", err)
return []common.Address{}, err
}
engine, ok := s.b.Engine().(*XDPoS.XDPoS)
if !ok {
log.Error("Undefined XDPoS consensus engine")
return []common.Address{}, nil
}
signedBlock := s.findNearestSignedBlock(ctx, b)
if signedBlock == nil {
return []common.Address{}, nil
}
return s.getSigners(ctx, signedBlock, engine)
}
// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
type RPCTransaction struct {
BlockHash *common.Hash `json:"blockHash"`
BlockNumber *hexutil.Big `json:"blockNumber"`
From common.Address `json:"from"`
Gas hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
Hash common.Hash `json:"hash"`
Input hexutil.Bytes `json:"input"`
Nonce hexutil.Uint64 `json:"nonce"`
To *common.Address `json:"to"`
TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
Value *hexutil.Big `json:"value"`
Type hexutil.Uint64 `json:"type"`
Accesses *types.AccessList `json:"accessList,omitempty"`
ChainID *hexutil.Big `json:"chainId,omitempty"`
V *hexutil.Big `json:"v"`
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
}
// newRPCTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64, baseFee *big.Int) *RPCTransaction {
// Determine the signer. For replay-protected transactions, use the most permissive
// signer, because we assume that signers are backwards-compatible with old
// transactions. For non-protected transactions, the homestead signer signer is used
// because the return value of ChainId is zero for those transactions.
var signer types.Signer
if tx.Protected() {
signer = types.LatestSignerForChainID(tx.ChainId())
} else {
signer = types.HomesteadSigner{}
}
from, _ := types.Sender(signer, tx)
v, r, s := tx.RawSignatureValues()
result := &RPCTransaction{
Type: hexutil.Uint64(tx.Type()),
From: from,
Gas: hexutil.Uint64(tx.Gas()),
GasPrice: (*hexutil.Big)(tx.GasPrice()),
Hash: tx.Hash(),
Input: hexutil.Bytes(tx.Data()),
Nonce: hexutil.Uint64(tx.Nonce()),
To: tx.To(),
Value: (*hexutil.Big)(tx.Value()),
V: (*hexutil.Big)(v),
R: (*hexutil.Big)(r),
S: (*hexutil.Big)(s),
}
if blockHash != (common.Hash{}) {
result.BlockHash = &blockHash
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
result.TransactionIndex = (*hexutil.Uint64)(&index)
}
switch tx.Type() {
case types.AccessListTxType:
al := tx.AccessList()
result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId())
case types.DynamicFeeTxType:
al := tx.AccessList()
result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId())
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
// if the transaction has been mined, compute the effective gas price
if baseFee != nil && blockHash != (common.Hash{}) {
// price = min(tip, gasFeeCap - baseFee) + baseFee
price := new(big.Int).Add(tx.GasTipCap(), baseFee)
txGasFeeCap := tx.GasFeeCap()
if price.Cmp(txGasFeeCap) > 0 {
price = txGasFeeCap
}
result.GasPrice = (*hexutil.Big)(price)
} else {
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
}
}
return result
}
// newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation
func newRPCPendingTransaction(tx *types.Transaction, current *types.Header, config *params.ChainConfig) *RPCTransaction {
var baseFee *big.Int
if current != nil {
baseFee = eip1559.CalcBaseFee(config, current)
}
return newRPCTransaction(tx, common.Hash{}, 0, 0, baseFee)
}
// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation.
func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction {
txs := b.Transactions()
if index >= uint64(len(txs)) {
return nil
}
return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index, b.BaseFee())
}
// newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index.
func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) hexutil.Bytes {
txs := b.Transactions()
if index >= uint64(len(txs)) {
return nil
}
blob, _ := txs[index].MarshalBinary()
return blob
}
// newRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation.
func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction {
for idx, tx := range b.Transactions() {
if tx.Hash() == hash {
return newRPCTransactionFromBlockIndex(b, uint64(idx))
}
}
return nil
}
// accessListResult returns an optional accesslist
// Its the result of the `debug_createAccessList` RPC call.
// It contains an error if the transaction itself failed.
type accessListResult struct {
Accesslist *types.AccessList `json:"accessList"`
Error string `json:"error,omitempty"`
GasUsed hexutil.Uint64 `json:"gasUsed"`
}
// CreateAccessList creates a EIP-2930 type AccessList for the given transaction.
// Reexec and BlockNrOrHash can be specified to create the accessList on top of a certain state.
func (s *BlockChainAPI) CreateAccessList(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) {
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
if blockNrOrHash != nil {
bNrOrHash = *blockNrOrHash
}
acl, gasUsed, vmerr, err := AccessList(ctx, s.b, bNrOrHash, args)
if err != nil {
return nil, err
}
result := &accessListResult{Accesslist: &acl, GasUsed: hexutil.Uint64(gasUsed)}
if vmerr != nil {
result.Error = vmerr.Error()
}
return result, nil
}
// AccessList creates an access list for the given transaction.
// If the accesslist creation fails an error is returned.
// If the transaction itself fails, an vmErr is returned.
func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash, args TransactionArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) {
// Retrieve the execution context
db, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if db == nil || err != nil {
return nil, 0, nil, err
}
block, err := b.BlockByHash(ctx, header.Hash())
if err != nil {
return nil, 0, nil, err
}
if block == nil {
return nil, 0, nil, fmt.Errorf("nil block in AccessList: number=%d, hash=%s", header.Number.Uint64(), header.Hash().Hex())
}
author, err := b.Engine().Author(block.Header())
if err != nil {
return nil, 0, nil, err
}
XDCxState, err := b.XDCxService().GetTradingState(block, author)
if err != nil {
return nil, 0, nil, err
}
owner := common.Address{}
// Ensure any missing fields are filled, extract the recipient and input data
if err := args.setDefaults(ctx, b, true); err != nil {
return nil, 0, nil, err
}
var to common.Address
if args.To != nil {
to = *args.To
} else {
to = crypto.CreateAddress(args.from(), uint64(*args.Nonce))
}
// Retrieve the precompiles since they don't need to be added to the access list
precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number))
// Create an initial tracer
prevTracer := vm.NewAccessListTracer(nil, args.from(), to, precompiles)
if args.AccessList != nil {
prevTracer = vm.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles)
}
for {
// Retrieve the current access list to expand
accessList := prevTracer.AccessList()
log.Trace("Creating access list", "input", accessList)
// Copy the original db so we don't modify it
statedb := db.Copy()
// Set the accesslist to the last al
args.AccessList = &accessList
msg, err := args.ToMessage(b, block.Number(), b.RPCGasCap(), header.BaseFee)
if err != nil {
return nil, 0, nil, err
}
feeCapacity := state.GetTRC21FeeCapacityFromState(statedb)
var balanceTokenFee *big.Int
if value, ok := feeCapacity[to]; ok {
balanceTokenFee = value
}
msg.SetBalanceTokenFee(balanceTokenFee)
// Apply the transaction with the access list tracer
tracer := vm.NewAccessListTracer(accessList, args.from(), to, precompiles)
config := vm.Config{Tracer: tracer, NoBaseFee: true}
vmenv, _, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &config)
if err != nil {
return nil, 0, nil, err
}
// TODO: determine the value of owner
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), owner)
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err)
}
if tracer.Equal(prevTracer) {
return accessList, res.UsedGas, res.Err, nil
}
prevTracer = tracer
}
}
// TransactionAPI exposes methods for reading and creating transaction data.
type TransactionAPI struct {
b Backend
nonceLock *AddrLocker
signer types.Signer
}
// PublicTransactionPoolAPI exposes methods for the RPC interface
type PublicXDCXTransactionPoolAPI struct {
b Backend
nonceLock *AddrLocker
}
// NewTransactionAPI creates a new RPC service with methods specific for the transaction pool.
func NewTransactionAPI(b Backend, nonceLock *AddrLocker) *TransactionAPI {
// The signer used by the API should always be the 'latest' known one because we expect
// signers to be backwards-compatible with old transactions.
signer := types.LatestSigner(b.ChainConfig())
return &TransactionAPI{b, nonceLock, signer}
}
// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool.
func NewPublicXDCXTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicXDCXTransactionPoolAPI {
return &PublicXDCXTransactionPoolAPI{b, nonceLock}
}
// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
func (s *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
n := hexutil.Uint(len(block.Transactions()))
return &n
}
return nil
}
// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash.
func (s *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
if block, _ := s.b.GetBlock(ctx, blockHash); block != nil {
n := hexutil.Uint(len(block.Transactions()))
return &n
}
return nil
}
// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index.
func (s *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction {
if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
return newRPCTransactionFromBlockIndex(block, uint64(index))
}
return nil
}
// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index.
func (s *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction {
if block, _ := s.b.GetBlock(ctx, blockHash); block != nil {
return newRPCTransactionFromBlockIndex(block, uint64(index))
}
return nil
}
// GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index.
func (s *TransactionAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) hexutil.Bytes {
if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
return newRPCRawTransactionFromBlockIndex(block, uint64(index))
}
return nil
}
// GetRawTransactionByBlockHashAndIndex returns the bytes of the transaction for the given block hash and index.
func (s *TransactionAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) hexutil.Bytes {
if block, _ := s.b.GetBlock(ctx, blockHash); block != nil {
return newRPCRawTransactionFromBlockIndex(block, uint64(index))
}
return nil
}
// GetTransactionCount returns the number of transactions the given address has sent for the given block number
func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) {
// Ask transaction pool for the nonce which includes pending transactions
if blockNr, ok := blockNrOrHash.Number(); ok && blockNr == rpc.PendingBlockNumber {
nonce, err := s.b.GetPoolNonce(ctx, address)
if err != nil {
return nil, err
}
return (*hexutil.Uint64)(&nonce), nil
}
// Resolve block number and use its state to ask for the nonce
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
nonce := state.GetNonce(address)
return (*hexutil.Uint64)(&nonce), state.Error()
}
// GetTransactionByHash returns the transaction for the given hash
func (s *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
// Try to return an already finalized transaction
tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash)
if tx != nil {
header, err := s.b.HeaderByHash(ctx, blockHash)
if err != nil {
return nil, err
}
return newRPCTransaction(tx, blockHash, blockNumber, index, header.BaseFee), nil
}
// No finalized transaction, try to retrieve it from the pool
if tx := s.b.GetPoolTransaction(hash); tx != nil {
return newRPCPendingTransaction(tx, s.b.CurrentHeader(), s.b.ChainConfig()), nil
}
// Transaction unknown, return as such
return nil, nil
}
// GetRawTransactionByHash returns the bytes of the transaction for the given hash.
func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
// Retrieve a finalized transaction, or a pooled otherwise
tx, _, _, _ := rawdb.ReadTransaction(s.b.ChainDb(), hash)
if tx == nil {
if tx = s.b.GetPoolTransaction(hash); tx == nil {
// Transaction not found anywhere, abort
return nil, nil
}
}
// Serialize to RLP and return
return tx.MarshalBinary()
}
// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash)
if tx == nil {
// When the transaction doesn't exist, the RPC method should return JSON null
// as per specification.
return nil, nil
}
receipts, err := s.b.GetReceipts(ctx, blockHash)
if err != nil {
return nil, err
}
if uint64(len(receipts)) <= index {
return nil, nil
}
receipt := receipts[index]
// Derive the sender.
bigblock := new(big.Int).SetUint64(blockNumber)
signer := types.MakeSigner(s.b.ChainConfig(), bigblock)
return marshalReceipt(receipt, blockHash, blockNumber, signer, tx, int(index)), nil
}
// marshalReceipt marshals a transaction receipt into a JSON object.
func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} {
from, _ := types.Sender(signer, tx)
fields := map[string]interface{}{
"blockHash": blockHash,
"blockNumber": hexutil.Uint64(blockNumber),
"transactionHash": tx.Hash(),
"transactionIndex": hexutil.Uint64(txIndex),
"from": from,
"to": tx.To(),
"gasUsed": hexutil.Uint64(receipt.GasUsed),
"cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed),
"contractAddress": nil,
"logs": receipt.Logs,
"logsBloom": receipt.Bloom,
"type": hexutil.Uint(tx.Type()),
"effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice),
}
// Assign receipt status or post state.
if len(receipt.PostState) > 0 {
fields["root"] = hexutil.Bytes(receipt.PostState)
} else {
fields["status"] = hexutil.Uint(receipt.Status)
}
if receipt.Logs == nil {
fields["logs"] = []*types.Log{}
}
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
if receipt.ContractAddress != (common.Address{}) {
fields["contractAddress"] = receipt.ContractAddress
}
return fields
}
// sign is a helper function that signs a transaction with the private key of the given address.
func (s *TransactionAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
// Look up the wallet containing the requested signer
account := accounts.Account{Address: addr}
wallet, err := s.b.AccountManager().Find(account)
if err != nil {
return nil, err
}
// Request the wallet to sign the transaction
var chainID *big.Int
if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
chainID = config.ChainId
}
return wallet.SignTx(account, tx, chainID)
}
// SubmitTransaction is a helper function that submits tx to txPool and logs a message.
func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
if tx.IsSpecialTransaction() {
return common.Hash{}, errors.New("don't allow transaction sent to BlockSigners & RandomizeSMC smart contract via API")
}
// If the transaction fee cap is already specified, ensure the
// fee of the given transaction is _reasonable_.
if err := checkTxFee(tx.GasPrice(), tx.Gas(), b.RPCTxFeeCap()); err != nil {
return common.Hash{}, err
}
if !b.UnprotectedAllowed() && !tx.Protected() {
// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
}
if err := b.SendTx(ctx, tx); err != nil {
return common.Hash{}, err
}
// Print a log with full tx details for manual investigations and interventions
signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())
from, err := types.Sender(signer, tx)
if err != nil {
return common.Hash{}, err
}
if tx.To() == nil {
addr := crypto.CreateAddress(from, tx.Nonce())
log.Info("Submitted contract creation", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "contract", addr.Hex(), "value", tx.Value())
} else {
log.Info("Submitted transaction", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "recipient", tx.To(), "value", tx.Value())
}
return tx.Hash(), nil
}
// SubmitTransaction is a helper function that submits tx to txPool and logs a message.
func submitOrderTransaction(ctx context.Context, b Backend, tx *types.OrderTransaction) (common.Hash, error) {
if err := b.SendOrderTx(ctx, tx); err != nil {
return common.Hash{}, err
}
return tx.Hash(), nil
}
// submitLendingTransaction is a helper function that submits tx to txPool and logs a message.
func submitLendingTransaction(ctx context.Context, b Backend, tx *types.LendingTransaction) (common.Hash, error) {
if err := b.SendLendingTx(ctx, tx); err != nil {
return common.Hash{}, err
}
return tx.Hash(), nil
}
// SendTransaction creates a transaction for the given argument, sign it and submit it to the
// transaction pool.
func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionArgs) (common.Hash, error) {
// Look up the wallet containing the requested signer
account := accounts.Account{Address: args.from()}
wallet, err := s.b.AccountManager().Find(account)
if err != nil {
return common.Hash{}, err
}
if args.Nonce == nil {
// Hold the addresse's mutex around signing to prevent concurrent assignment of
// the same nonce to multiple accounts.
s.nonceLock.LockAddr(args.from())
defer s.nonceLock.UnlockAddr(args.from())
}
// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b, false); err != nil {
return common.Hash{}, err
}
// Assemble the transaction and sign with the wallet
tx := args.toTransaction()
var chainID *big.Int
if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
chainID = config.ChainId
}
signed, err := wallet.SignTx(account, tx, chainID)
if err != nil {
return common.Hash{}, err
}
return SubmitTransaction(ctx, s.b, signed)
}
// FillTransaction fills the defaults (nonce, gas, gasPrice or 1559 fields)
// on a given unsigned transaction, and returns it to the caller for further
// processing (signing + broadcast).
func (s *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b, false); err != nil {
return nil, err
}
// Assemble the transaction and obtain rlp
tx := args.toTransaction()
data, err := tx.MarshalBinary()
if err != nil {
return nil, err
}
return &SignTransactionResult{data, tx}, nil
}
// SendRawTransaction will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce.
func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
tx := new(types.Transaction)
if err := tx.UnmarshalBinary(input); err != nil {
return common.Hash{}, err
}
return SubmitTransaction(ctx, s.b, tx)
}
// SendOrderRawTransaction will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce.
func (s *PublicXDCXTransactionPoolAPI) SendOrderRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) {
tx := new(types.OrderTransaction)
if err := rlp.DecodeBytes(encodedTx, tx); err != nil {
return common.Hash{}, err
}
return submitOrderTransaction(ctx, s.b, tx)
}
// SendLendingRawTransaction will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce.
func (s *PublicXDCXTransactionPoolAPI) SendLendingRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) {
tx := new(types.LendingTransaction)
if err := rlp.DecodeBytes(encodedTx, tx); err != nil {
return common.Hash{}, err
}
return submitLendingTransaction(ctx, s.b, tx)
}
// GetOrderTxMatchByHash returns the bytes of the transaction for the given hash.
func (s *PublicXDCXTransactionPoolAPI) GetOrderTxMatchByHash(ctx context.Context, hash common.Hash) ([]*tradingstate.OrderItem, error) {
var tx *types.Transaction
orders := []*tradingstate.OrderItem{}
if tx, _, _, _ = rawdb.ReadTransaction(s.b.ChainDb(), hash); tx == nil {
if tx = s.b.GetPoolTransaction(hash); tx == nil {
return []*tradingstate.OrderItem{}, nil
}
}
batch, err := tradingstate.DecodeTxMatchesBatch(tx.Data())
if err != nil {
return []*tradingstate.OrderItem{}, err
}
for _, txMatch := range batch.Data {
order, err := txMatch.DecodeOrder()
if err != nil {
return []*tradingstate.OrderItem{}, err
}
orders = append(orders, order)
}
return orders, nil
}
// GetOrderPoolContent return pending, queued content
func (s *PublicXDCXTransactionPoolAPI) GetOrderPoolContent(ctx context.Context) interface{} {
pendingOrders := []*tradingstate.OrderItem{}
queuedOrders := []*tradingstate.OrderItem{}
pending, queued := s.b.OrderTxPoolContent()
for _, txs := range pending {
for _, tx := range txs {
V, R, S := tx.Signature()
order := &tradingstate.OrderItem{
Nonce: big.NewInt(int64(tx.Nonce())),
Quantity: tx.Quantity(),
Price: tx.Price(),
ExchangeAddress: tx.ExchangeAddress(),
UserAddress: tx.UserAddress(),
BaseToken: tx.BaseToken(),
QuoteToken: tx.QuoteToken(),
Status: tx.Status(),
Side: tx.Side(),
Type: tx.Type(),
Hash: tx.OrderHash(),
OrderID: tx.OrderID(),
Signature: &tradingstate.Signature{
V: byte(V.Uint64()),
R: common.BigToHash(R),
S: common.BigToHash(S),
},
}
pendingOrders = append(pendingOrders, order)
}
}
for _, txs := range queued {
for _, tx := range txs {
V, R, S := tx.Signature()
order := &tradingstate.OrderItem{
Nonce: big.NewInt(int64(tx.Nonce())),
Quantity: tx.Quantity(),
Price: tx.Price(),
ExchangeAddress: tx.ExchangeAddress(),
UserAddress: tx.UserAddress(),
BaseToken: tx.BaseToken(),
QuoteToken: tx.QuoteToken(),
Status: tx.Status(),
Side: tx.Side(),
Type: tx.Type(),
Hash: tx.OrderHash(),
OrderID: tx.OrderID(),
Signature: &tradingstate.Signature{
V: byte(V.Uint64()),
R: common.BigToHash(R),
S: common.BigToHash(S),
},
}
queuedOrders = append(pendingOrders, order)
}
}
return map[string]interface{}{
"pending": pendingOrders,
"queued": queuedOrders,
}
}
// GetOrderStats return pending, queued length
func (s *PublicXDCXTransactionPoolAPI) GetOrderStats(ctx context.Context) interface{} {
pending, queued := s.b.OrderStats()
return map[string]interface{}{
"pending": pending,
"queued": queued,
}
}
// OrderMsg struct
type OrderMsg struct {
AccountNonce hexutil.Uint64 `json:"nonce" gencodec:"required"`
Quantity hexutil.Big `json:"quantity,omitempty"`
Price hexutil.Big `json:"price,omitempty"`
ExchangeAddress common.Address `json:"exchangeAddress,omitempty"`
UserAddress common.Address `json:"userAddress,omitempty"`
BaseToken common.Address `json:"baseToken,omitempty"`
QuoteToken common.Address `json:"quoteToken,omitempty"`
Status string `json:"status,omitempty"`
Side string `json:"side,omitempty"`
Type string `json:"type,omitempty"`
OrderID hexutil.Uint64 `json:"orderid,omitempty"`
// Signature values
V hexutil.Big `json:"v" gencodec:"required"`
R hexutil.Big `json:"r" gencodec:"required"`
S hexutil.Big `json:"s" gencodec:"required"`
// This is only used when marshaling to JSON.
Hash common.Hash `json:"hash" rlp:"-"`
}
// LendingMsg api message for lending
type LendingMsg struct {
AccountNonce hexutil.Uint64 `json:"nonce" gencodec:"required"`
Quantity hexutil.Big `json:"quantity,omitempty"`
RelayerAddress common.Address `json:"relayerAddress,omitempty"`
UserAddress common.Address `json:"userAddress,omitempty"`
CollateralToken common.Address `json:"collateralToken,omitempty"`
AutoTopUp bool `json:"autoTopUp,omitempty"`
LendingToken common.Address `json:"lendingToken,omitempty"`
Term hexutil.Uint64 `json:"term,omitempty"`
Interest hexutil.Uint64 `json:"interest,omitempty"`
Status string `json:"status,omitempty"`
Side string `json:"side,omitempty"`
Type string `json:"type,omitempty"`
LendingId hexutil.Uint64 `json:"lendingId,omitempty"`
LendingTradeId hexutil.Uint64 `json:"tradeId,omitempty"`
ExtraData string `json:"extraData,omitempty"`
// Signature values
V hexutil.Big `json:"v" gencodec:"required"`
R hexutil.Big `json:"r" gencodec:"required"`
S hexutil.Big `json:"s" gencodec:"required"`
// This is only used when marshaling to JSON.
Hash common.Hash `json:"hash" rlp:"-"`
}
type PriceVolume struct {
Price *big.Int `json:"price,omitempty"`
Volume *big.Int `json:"volume,omitempty"`
}
type InterestVolume struct {
Interest *big.Int `json:"interest,omitempty"`
Volume *big.Int `json:"volume,omitempty"`
}
// SendOrder will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce.
func (s *PublicXDCXTransactionPoolAPI) SendOrder(ctx context.Context, msg OrderMsg) (common.Hash, error) {
tx := types.NewOrderTransaction(uint64(msg.AccountNonce), msg.Quantity.ToInt(), msg.Price.ToInt(), msg.ExchangeAddress, msg.UserAddress, msg.BaseToken, msg.QuoteToken, msg.Status, msg.Side, msg.Type, msg.Hash, uint64(msg.OrderID))
tx = tx.ImportSignature(msg.V.ToInt(), msg.R.ToInt(), msg.S.ToInt())
return submitOrderTransaction(ctx, s.b, tx)
}
// SendLending will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce.
func (s *PublicXDCXTransactionPoolAPI) SendLending(ctx context.Context, msg LendingMsg) (common.Hash, error) {
tx := types.NewLendingTransaction(uint64(msg.AccountNonce), msg.Quantity.ToInt(), uint64(msg.Interest), uint64(msg.Term), msg.RelayerAddress, msg.UserAddress, msg.LendingToken, msg.CollateralToken, msg.AutoTopUp, msg.Status, msg.Side, msg.Type, msg.Hash, uint64(msg.LendingId), uint64(msg.LendingTradeId), msg.ExtraData)
tx = tx.ImportSignature(msg.V.ToInt(), msg.R.ToInt(), msg.S.ToInt())
return submitLendingTransaction(ctx, s.b, tx)
}
// GetOrderCount returns the number of transactions the given address has sent for the given block number
func (s *PublicXDCXTransactionPoolAPI) GetOrderCount(ctx context.Context, addr common.Address) (*hexutil.Uint64, error) {
nonce, err := s.b.GetOrderNonce(addr.Hash())
if err != nil {
return (*hexutil.Uint64)(&nonce), err
}
return (*hexutil.Uint64)(&nonce), err
}
func (s *PublicXDCXTransactionPoolAPI) GetBestBid(ctx context.Context, baseToken, quoteToken common.Address) (PriceVolume, error) {
result := PriceVolume{}
block := s.b.CurrentBlock()
if block == nil {
return result, errors.New("current block not found")
}
XDCxService := s.b.XDCxService()
if XDCxService == nil {
return result, errors.New("not find XDCX service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return result, err
}
XDCxState, err := XDCxService.GetTradingState(block, author)
if err != nil {
return result, err
}
result.Price, result.Volume = XDCxState.GetBestBidPrice(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
if result.Price.Sign() == 0 {
return result, errors.New("not found bid tree")
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetBestAsk(ctx context.Context, baseToken, quoteToken common.Address) (PriceVolume, error) {
result := PriceVolume{}
block := s.b.CurrentBlock()
if block == nil {
return result, errors.New("not found current block")
}
XDCxService := s.b.XDCxService()
if XDCxService == nil {
return result, errors.New("not found XDCX service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return result, err
}
XDCxState, err := XDCxService.GetTradingState(block, author)
if err != nil {
return result, err
}
result.Price, result.Volume = XDCxState.GetBestAskPrice(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
if result.Price.Sign() == 0 {
return result, errors.New("not find ask tree")
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetBidTree(ctx context.Context, baseToken, quoteToken common.Address) (map[*big.Int]tradingstate.DumpOrderList, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
XDCxService := s.b.XDCxService()
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
XDCxState, err := XDCxService.GetTradingState(block, author)
if err != nil {
return nil, err
}
result, err := XDCxState.DumpBidTrie(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetPrice(ctx context.Context, baseToken, quoteToken common.Address) (*big.Int, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
XDCxService := s.b.XDCxService()
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
XDCxState, err := XDCxService.GetTradingState(block, author)
if err != nil {
return nil, err
}
price := XDCxState.GetLastPrice(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
if price == nil || price.Sign() == 0 {
return common.Big0, errors.New("not find order book's price")
}
return price, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetLastEpochPrice(ctx context.Context, baseToken, quoteToken common.Address) (*big.Int, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
XDCxService := s.b.XDCxService()
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
XDCxState, err := XDCxService.GetTradingState(block, author)
if err != nil {
return nil, err
}
price := XDCxState.GetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
if price == nil || price.Sign() == 0 {
return common.Big0, errors.New("not find order book's price")
}
return price, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetCurrentEpochPrice(ctx context.Context, baseToken, quoteToken common.Address) (*big.Int, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
XDCxService := s.b.XDCxService()
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
XDCxState, err := XDCxService.GetTradingState(block, author)
if err != nil {
return nil, err
}
price, _ := XDCxState.GetMediumPriceAndTotalAmount(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
if price == nil || price.Sign() == 0 {
return common.Big0, errors.New("not find order book's price")
}
return price, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetAskTree(ctx context.Context, baseToken, quoteToken common.Address) (map[*big.Int]tradingstate.DumpOrderList, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
XDCxService := s.b.XDCxService()
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
XDCxState, err := XDCxService.GetTradingState(block, author)
if err != nil {
return nil, err
}
result, err := XDCxState.DumpAskTrie(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetOrderById(ctx context.Context, baseToken, quoteToken common.Address, orderId uint64) (interface{}, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
XDCxService := s.b.XDCxService()
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
XDCxState, err := XDCxService.GetTradingState(block, author)
if err != nil {
return nil, err
}
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderId))
orderitem := XDCxState.GetOrder(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken), orderIdHash)
if orderitem.Quantity == nil || orderitem.Quantity.Sign() == 0 {
return nil, errors.New("not found order")
}
return orderitem, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetTradingOrderBookInfo(ctx context.Context, baseToken, quoteToken common.Address) (*tradingstate.DumpOrderBookInfo, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
XDCxService := s.b.XDCxService()
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
XDCxState, err := XDCxService.GetTradingState(block, author)
if err != nil {
return nil, err
}
result, err := XDCxState.DumpOrderBookInfo(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetLiquidationPriceTree(ctx context.Context, baseToken, quoteToken common.Address) (map[*big.Int]tradingstate.DumpLendingBook, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
XDCxService := s.b.XDCxService()
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
XDCxState, err := XDCxService.GetTradingState(block, author)
if err != nil {
return nil, err
}
result, err := XDCxState.DumpLiquidationPriceTrie(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetInvestingTree(ctx context.Context, lendingToken common.Address, term uint64) (map[*big.Int]lendingstate.DumpOrderList, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return nil, errors.New("XDCX Lending service not found")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return nil, err
}
result, err := lendingState.DumpInvestingTrie(lendingstate.GetLendingOrderBookHash(lendingToken, term))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetBorrowingTree(ctx context.Context, lendingToken common.Address, term uint64) (map[*big.Int]lendingstate.DumpOrderList, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return nil, errors.New("XDCX Lending service not found")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return nil, err
}
result, err := lendingState.DumpBorrowingTrie(lendingstate.GetLendingOrderBookHash(lendingToken, term))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetLendingOrderBookInfo(tx context.Context, lendingToken common.Address, term uint64) (*lendingstate.DumpOrderBookInfo, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return nil, errors.New("XDCX Lending service not found")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return nil, err
}
result, err := lendingState.DumpOrderBookInfo(lendingstate.GetLendingOrderBookHash(lendingToken, term))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) getLendingOrderTree(ctx context.Context, lendingToken common.Address, term uint64) (map[*big.Int]lendingstate.LendingItem, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return nil, errors.New("not find XDCX Lending service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return nil, err
}
result, err := lendingState.DumpLendingOrderTrie(lendingstate.GetLendingOrderBookHash(lendingToken, term))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetLendingTradeTree(ctx context.Context, lendingToken common.Address, term uint64) (map[*big.Int]lendingstate.LendingTrade, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return nil, errors.New("not find XDCX lending service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return nil, err
}
result, err := lendingState.DumpLendingTradeTrie(lendingstate.GetLendingOrderBookHash(lendingToken, term))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetLiquidationTimeTree(ctx context.Context, lendingToken common.Address, term uint64) (map[*big.Int]lendingstate.DumpOrderList, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return nil, errors.New("not find XDCX Lending service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return nil, err
}
result, err := lendingState.DumpLiquidationTimeTrie(lendingstate.GetLendingOrderBookHash(lendingToken, term))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetLendingOrderCount(ctx context.Context, addr common.Address) (*hexutil.Uint64, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return nil, errors.New("not find XDCX Lending service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return nil, err
}
nonce := lendingState.GetNonce(addr.Hash())
return (*hexutil.Uint64)(&nonce), err
}
func (s *PublicXDCXTransactionPoolAPI) GetBestInvesting(ctx context.Context, lendingToken common.Address, term uint64) (InterestVolume, error) {
result := InterestVolume{}
block := s.b.CurrentBlock()
if block == nil {
return result, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return result, errors.New("not find XDCX Lending service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return result, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return result, err
}
result.Interest, result.Volume = lendingState.GetBestInvestingRate(lendingstate.GetLendingOrderBookHash(lendingToken, term))
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetBestBorrowing(ctx context.Context, lendingToken common.Address, term uint64) (InterestVolume, error) {
result := InterestVolume{}
block := s.b.CurrentBlock()
if block == nil {
return result, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return result, errors.New("not find XDCX Lending service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return result, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return result, err
}
result.Interest, result.Volume = lendingState.GetBestBorrowRate(lendingstate.GetLendingOrderBookHash(lendingToken, term))
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetBids(ctx context.Context, baseToken, quoteToken common.Address) (map[*big.Int]*big.Int, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
XDCxService := s.b.XDCxService()
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
XDCxState, err := XDCxService.GetTradingState(block, author)
if err != nil {
return nil, err
}
result, err := XDCxState.GetBids(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetAsks(ctx context.Context, baseToken, quoteToken common.Address) (map[*big.Int]*big.Int, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
XDCxService := s.b.XDCxService()
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
XDCxState, err := XDCxService.GetTradingState(block, author)
if err != nil {
return nil, err
}
result, err := XDCxState.GetAsks(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetInvests(ctx context.Context, lendingToken common.Address, term uint64) (map[*big.Int]*big.Int, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return nil, errors.New("XDCX Lending service not found")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return nil, err
}
result, err := lendingState.GetInvestings(lendingstate.GetLendingOrderBookHash(lendingToken, term))
if err != nil {
return nil, err
}
return result, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetBorrows(ctx context.Context, lendingToken common.Address, term uint64) (map[*big.Int]*big.Int, error) {
block := s.b.CurrentBlock()
if block == nil {
return nil, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return nil, errors.New("XDCX Lending service not found")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return nil, err
}
result, err := lendingState.GetBorrowings(lendingstate.GetLendingOrderBookHash(lendingToken, term))
if err != nil {
return nil, err
}
return result, nil
}
// GetLendingTxMatchByHash returns lendingItems which have been processed at tx of the given txhash
func (s *PublicXDCXTransactionPoolAPI) GetLendingTxMatchByHash(ctx context.Context, hash common.Hash) ([]*lendingstate.LendingItem, error) {
var tx *types.Transaction
if tx, _, _, _ = rawdb.ReadTransaction(s.b.ChainDb(), hash); tx == nil {
if tx = s.b.GetPoolTransaction(hash); tx == nil {
return []*lendingstate.LendingItem{}, nil
}
}
batch, err := lendingstate.DecodeTxLendingBatch(tx.Data())
if err != nil {
return []*lendingstate.LendingItem{}, err
}
return batch.Data, nil
}
// GetLiquidatedTradesByTxHash returns trades which closed by XDCX protocol at the tx of the give hash
func (s *PublicXDCXTransactionPoolAPI) GetLiquidatedTradesByTxHash(ctx context.Context, hash common.Hash) (lendingstate.FinalizedResult, error) {
var tx *types.Transaction
if tx, _, _, _ = rawdb.ReadTransaction(s.b.ChainDb(), hash); tx == nil {
if tx = s.b.GetPoolTransaction(hash); tx == nil {
return lendingstate.FinalizedResult{}, nil
}
}
finalizedResult, err := lendingstate.DecodeFinalizedResult(tx.Data())
if err != nil {
return lendingstate.FinalizedResult{}, err
}
finalizedResult.TxHash = hash
return finalizedResult, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetLendingOrderById(ctx context.Context, lendingToken common.Address, term uint64, orderId uint64) (lendingstate.LendingItem, error) {
lendingItem := lendingstate.LendingItem{}
block := s.b.CurrentBlock()
if block == nil {
return lendingItem, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return lendingItem, errors.New("not find XDCX lending service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return lendingItem, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return lendingItem, err
}
lendingOrderBook := lendingstate.GetLendingOrderBookHash(lendingToken, term)
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderId))
lendingItem = lendingState.GetLendingOrder(lendingOrderBook, orderIdHash)
if lendingItem.LendingId != orderId {
return lendingItem, errors.New("not find lending item")
}
return lendingItem, nil
}
func (s *PublicXDCXTransactionPoolAPI) GetLendingTradeById(ctx context.Context, lendingToken common.Address, term uint64, tradeId uint64) (lendingstate.LendingTrade, error) {
lendingItem := lendingstate.LendingTrade{}
block := s.b.CurrentBlock()
if block == nil {
return lendingItem, errors.New("not find current block")
}
lendingService := s.b.LendingService()
if lendingService == nil {
return lendingItem, errors.New("not find XDCX Lending service")
}
author, err := s.b.Engine().Author(block.Header())
if err != nil {
return lendingItem, err
}
lendingState, err := lendingService.GetLendingState(block, author)
if err != nil {
return lendingItem, err
}
lendingOrderBook := lendingstate.GetLendingOrderBookHash(lendingToken, term)
tradeIdHash := common.BigToHash(new(big.Int).SetUint64(tradeId))
lendingItem = lendingState.GetLendingTrade(lendingOrderBook, tradeIdHash)
if lendingItem.TradeId != tradeId {
return lendingItem, errors.New("not find lending item")
}
return lendingItem, nil
}
// Sign calculates an ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message).
//
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
// where the V value will be 27 or 28 for legacy reasons.
//
// The account associated with addr must be unlocked.
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
func (s *TransactionAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
// Look up the wallet containing the requested signer
account := accounts.Account{Address: addr}
wallet, err := s.b.AccountManager().Find(account)
if err != nil {
return nil, err
}
// Sign the requested hash with the wallet
signature, err := wallet.SignText(account, data)
if err == nil {
signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
}
return signature, err
}
// SignTransactionResult represents a RLP encoded signed transaction.
type SignTransactionResult struct {
Raw hexutil.Bytes `json:"raw"`
Tx *types.Transaction `json:"tx"`
}
// SignTransaction will sign the given transaction with the from account.
// The node needs to have the private key of the account corresponding with
// the given from address and it needs to be unlocked.
func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
if args.Gas == nil {
return nil, errors.New("not specify Gas")
}
if args.GasPrice == nil && (args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil) {
return nil, errors.New("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas")
}
if args.Nonce == nil {
return nil, errors.New("not specify Nonce")
}
if err := args.setDefaults(ctx, s.b, false); err != nil {
return nil, err
}
// Before actually sign the transaction, ensure the transaction fee is reasonable.
tx := args.toTransaction()
if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil {
return nil, err
}
signed, err := s.sign(args.from(), tx)
if err != nil {
return nil, err
}
data, err := signed.MarshalBinary()
if err != nil {
return nil, err
}
return &SignTransactionResult{data, tx}, nil
}
// PendingTransactions returns the transactions that are in the transaction pool
// and have a from address that is one of the accounts this node manages.
func (s *TransactionAPI) PendingTransactions() ([]*RPCTransaction, error) {
pending, err := s.b.GetPoolTransactions()
if err != nil {
return nil, err
}
accounts := make(map[common.Address]struct{})
for _, wallet := range s.b.AccountManager().Wallets() {
for _, account := range wallet.Accounts() {
accounts[account.Address] = struct{}{}
}
}
curHeader := s.b.CurrentHeader()
transactions := make([]*RPCTransaction, 0, len(pending))
for _, tx := range pending {
from, _ := types.Sender(s.signer, tx)
if _, exists := accounts[from]; exists {
transactions = append(transactions, newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig()))
}
}
return transactions, nil
}
// Resend accepts an existing transaction and a new gas price and limit. It will remove
// the given transaction from the pool and reinsert it with the new gas price and limit.
func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) {
if sendArgs.Nonce == nil {
return common.Hash{}, errors.New("missing transaction nonce in transaction spec")
}
if err := sendArgs.setDefaults(ctx, s.b, false); err != nil {
return common.Hash{}, err
}
matchTx := sendArgs.toTransaction()
// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
var price = matchTx.GasPrice()
if gasPrice != nil {
price = gasPrice.ToInt()
}
var gas = matchTx.Gas()
if gasLimit != nil {
gas = uint64(*gasLimit)
}
if err := checkTxFee(price, gas, s.b.RPCTxFeeCap()); err != nil {
return common.Hash{}, err
}
// Iterate the pending list for replacement
pending, err := s.b.GetPoolTransactions()
if err != nil {
return common.Hash{}, err
}
for _, p := range pending {
wantSigHash := s.signer.Hash(matchTx)
pFrom, err := types.Sender(s.signer, p)
if err == nil && pFrom == sendArgs.from() && s.signer.Hash(p) == wantSigHash {
// Match. Re-sign and send the transaction.
if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 {
sendArgs.GasPrice = gasPrice
}
if gasLimit != nil && *gasLimit != 0 {
sendArgs.Gas = gasLimit
}
signedTx, err := s.sign(sendArgs.from(), sendArgs.toTransaction())
if err != nil {
return common.Hash{}, err
}
if err = s.b.SendTx(ctx, signedTx); err != nil {
return common.Hash{}, err
}
return signedTx.Hash(), nil
}
}
return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash())
}
// DebugAPI is the collection of Ethereum APIs exposed over the debugging
// namespace.
type DebugAPI struct {
b Backend
}
// NewDebugAPI creates a new instance of DebugAPI.
func NewDebugAPI(b Backend) *DebugAPI {
return &DebugAPI{b: b}
}
// GetBlockRlp retrieves the RLP encoded for of a single block.
func (api *DebugAPI) GetBlockRlp(ctx context.Context, number uint64) (string, error) {
block, _ := api.b.BlockByNumber(ctx, rpc.BlockNumber(number))
if block == nil {
return "", fmt.Errorf("block #%d not found", number)
}
encoded, err := rlp.EncodeToBytes(block)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", encoded), nil
}
// PrintBlock retrieves a block and returns its pretty printed form.
func (api *DebugAPI) PrintBlock(ctx context.Context, number uint64) (string, error) {
block, _ := api.b.BlockByNumber(ctx, rpc.BlockNumber(number))
if block == nil {
return "", fmt.Errorf("block #%d not found", number)
}
return block.String(), nil
}
// ChaindbProperty returns leveldb properties of the chain database.
func (api *DebugAPI) ChaindbProperty(property string) (string, error) {
ldb, ok := api.b.ChainDb().(interface {
LDB() *leveldb.DB
})
if !ok {
return "", errors.New("chaindbProperty does not work for memory databases")
}
if property == "" {
property = "leveldb.stats"
} else if !strings.HasPrefix(property, "leveldb.") {
property = "leveldb." + property
}
return ldb.LDB().GetProperty(property)
}
func (api *DebugAPI) ChaindbCompact() error {
ldb, ok := api.b.ChainDb().(interface {
LDB() *leveldb.DB
})
if !ok {
return errors.New("chaindbCompact does not work for memory databases")
}
for b := byte(0); b < 255; b++ {
log.Info("Compacting chain database", "range", fmt.Sprintf("0x%0.2X-0x%0.2X", b, b+1))
err := ldb.LDB().CompactRange(util.Range{Start: []byte{b}, Limit: []byte{b + 1}})
if err != nil {
log.Error("Database compaction failed", "err", err)
return err
}
}
return nil
}
// SetHead rewinds the head of the blockchain to a previous block.
func (api *DebugAPI) SetHead(number hexutil.Uint64) {
api.b.SetHead(uint64(number))
}
// DbGet returns the raw value of a key stored in the database.
func (api *DebugAPI) DbGet(key string) (hexutil.Bytes, error) {
blob, err := common.ParseHexOrString(key)
if err != nil {
return nil, err
}
return api.b.ChainDb().Get(blob)
}
// NetAPI offers network related RPC methods
type NetAPI struct {
net *p2p.Server
networkVersion uint64
}
// NewNetAPI creates a new net API instance.
func NewNetAPI(net *p2p.Server, networkVersion uint64) *NetAPI {
return &NetAPI{net, networkVersion}
}
// Listening returns an indication if the node is listening for network connections.
func (s *NetAPI) Listening() bool {
return true // always listening
}
// PeerCount returns the number of connected peers
func (s *NetAPI) PeerCount() hexutil.Uint {
return hexutil.Uint(s.net.PeerCount())
}
// Version returns the current ethereum protocol version.
func (s *NetAPI) Version() string {
return fmt.Sprintf("%d", s.networkVersion)
}
// checkTxFee is an internal function used to check whether the fee of
// the given transaction is _reasonable_(under the cap).
func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
// Short circuit if there is no cap for transaction fee at all.
if cap == 0 {
return nil
}
feeEth := new(big.Float).Quo(new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas))), new(big.Float).SetInt(big.NewInt(params.Ether)))
feeFloat, _ := feeEth.Float64()
if feeFloat > cap {
return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap)
}
return nil
}
func GetSignersFromBlocks(b Backend, blockNumber uint64, blockHash common.Hash, masternodes []common.Address) ([]common.Address, error) {
var addrs []common.Address
mapMN := map[common.Address]bool{}
for _, node := range masternodes {
mapMN[node] = true
}
signer := types.MakeSigner(b.ChainConfig(), new(big.Int).SetUint64(blockNumber))
if engine, ok := b.Engine().(*XDPoS.XDPoS); ok {
limitNumber := blockNumber + common.LimitTimeFinality
currentNumber := b.CurrentBlock().NumberU64()
if limitNumber > currentNumber {
limitNumber = currentNumber
}
for i := blockNumber + 1; i <= limitNumber; i++ {
header, err := b.HeaderByNumber(context.TODO(), rpc.BlockNumber(i))
if err != nil {
return addrs, err
}
if header == nil {
return addrs, errors.New("nil header in GetSignersFromBlocks")
}
blockData, err := b.BlockByNumber(context.TODO(), rpc.BlockNumber(i))
if err != nil {
return addrs, err
}
if blockData == nil {
return addrs, errors.New("nil blockData in GetSignersFromBlocks")
}
signTxs := engine.CacheSigningTxs(header.Hash(), blockData.Transactions())
for _, signtx := range signTxs {
blkHash := common.BytesToHash(signtx.Data()[len(signtx.Data())-32:])
from, _ := types.Sender(signer, signtx)
if blkHash == blockHash && mapMN[from] {
addrs = append(addrs, from)
delete(mapMN, from)
}
}
if len(mapMN) == 0 {
break
}
}
}
return addrs, nil
}
// GetStakerROI Estimate ROI for stakers using the last epoc reward
// then multiple by epoch per year, if the address is not masternode of last epoch - return 0
// Formular:
//
// ROI = average_latest_epoch_reward_for_voters*number_of_epoch_per_year/latest_total_cap*100
func (s *BlockChainAPI) GetStakerROI() float64 {
blockNumber := s.b.CurrentBlock().Number().Uint64()
lastCheckpointNumber := blockNumber - (blockNumber % s.b.ChainConfig().XDPoS.Epoch) - s.b.ChainConfig().XDPoS.Epoch // calculate for 2 epochs ago
totalCap := new(big.Int).SetUint64(0)
mastersCap := s.b.GetMasternodesCap(lastCheckpointNumber)
if mastersCap == nil {
return 0
}
masternodeReward := new(big.Int).Mul(new(big.Int).SetUint64(s.b.ChainConfig().XDPoS.Reward), new(big.Int).SetUint64(params.Ether))
for _, cap := range mastersCap {
totalCap.Add(totalCap, cap)
}
holderReward := new(big.Int).Div(masternodeReward, new(big.Int).SetUint64(2))
EpochPerYear := 365 * 86400 / s.b.GetEpochDuration().Uint64()
voterRewardAYear := new(big.Int).Mul(holderReward, new(big.Int).SetUint64(EpochPerYear))
return 100.0 / float64(totalCap.Div(totalCap, voterRewardAYear).Uint64())
}
// GetStakerROIMasternode Estimate ROI for stakers of a specific masternode using the last epoc reward
// then multiple by epoch per year, if the address is not masternode of last epoch - return 0
// Formular:
//
// ROI = latest_epoch_reward_for_voters*number_of_epoch_per_year/latest_total_cap*100
func (s *BlockChainAPI) GetStakerROIMasternode(masternode common.Address) float64 {
votersReward := s.b.GetVotersRewards(masternode)
if votersReward == nil {
return 0
}
masternodeReward := new(big.Int).SetUint64(0) // this includes all reward for this masternode
voters := []common.Address{}
for voter, reward := range votersReward {
voters = append(voters, voter)
masternodeReward.Add(masternodeReward, reward)
}
blockNumber := s.b.CurrentBlock().Number().Uint64()
lastCheckpointNumber := blockNumber - blockNumber%s.b.ChainConfig().XDPoS.Epoch
totalCap := new(big.Int).SetUint64(0)
votersCap := s.b.GetVotersCap(new(big.Int).SetUint64(lastCheckpointNumber), masternode, voters)
for _, cap := range votersCap {
totalCap.Add(totalCap, cap)
}
// holder reward = 50% total reward of a masternode
holderReward := new(big.Int).Div(masternodeReward, new(big.Int).SetUint64(2))
EpochPerYear := 365 * 86400 / s.b.GetEpochDuration().Uint64()
voterRewardAYear := new(big.Int).Mul(holderReward, new(big.Int).SetUint64(EpochPerYear))
return 100.0 / float64(totalCap.Div(totalCap, voterRewardAYear).Uint64())
}
type currentTotalMinted struct {
TotalMinted *hexutil.Big `json:"totalMinted"`
LastEpochNum *hexutil.Big `json:"lastEpochNum"`
BlockHash common.Hash `json:"blockHash"`
BlockNumber *hexutil.Big `json:"blockNumber"`
}
func (s *BlockChainAPI) GetCurrentTotalMinted(ctx context.Context) (*currentTotalMinted, error) {
statedb, header, err := s.b.StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber)
if err != nil {
return nil, err
}
totalMinted := state.GetTotalMinted(statedb).Big()
lastEpochNum := state.GetLastEpochNum(statedb).Big()
result := &currentTotalMinted{
TotalMinted: (*hexutil.Big)(totalMinted),
LastEpochNum: (*hexutil.Big)(lastEpochNum),
BlockHash: header.Hash(),
BlockNumber: (*hexutil.Big)(header.Number),
}
return result, nil
}