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

751 lines
23 KiB

package rpc
import (
"context"
"fmt"
"math/big"
"strings"
"github.com/pkg/errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
"github.com/harmony-one/harmony/core/rawdb"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/hmy"
internal_common "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/internal/utils"
v1 "github.com/harmony-one/harmony/rpc/v1"
v2 "github.com/harmony-one/harmony/rpc/v2"
staking "github.com/harmony-one/harmony/staking/types"
)
const (
defaultPageSize = uint32(100)
)
// PublicTransactionService provides an API to access Harmony's transaction service.
// It offers only methods that operate on public data that is freely available to anyone.
type PublicTransactionService struct {
hmy *hmy.Harmony
version Version
}
// NewPublicTransactionAPI creates a new API for the RPC interface
func NewPublicTransactionAPI(hmy *hmy.Harmony, version Version) rpc.API {
return rpc.API{
Namespace: version.Namespace(),
Version: APIVersion,
Service: &PublicTransactionService{hmy, version},
Public: true,
}
}
// GetAccountNonce returns the nonce value of the given address for the given block number
func (s *PublicTransactionService) GetAccountNonce(
ctx context.Context, address string, blockNumber BlockNumber,
) (uint64, error) {
// Process number based on version
blockNum := blockNumber.EthBlockNumber()
// Response output is the same for all versions
addr := internal_common.ParseAddr(address)
return s.hmy.GetAccountNonce(ctx, addr, blockNum)
}
// GetTransactionCount returns the number of transactions the given address has sent for the given block number.
// Legacy for apiv1. For apiv2, please use getAccountNonce/getPoolNonce/getTransactionsCount/getStakingTransactionsCount apis for
// more granular transaction counts queries
// Note that the return type is an interface to account for the different versions
func (s *PublicTransactionService) GetTransactionCount(
ctx context.Context, addr string, blockNumber BlockNumber,
) (response interface{}, err error) {
// Process arguments based on version
blockNum := blockNumber.EthBlockNumber()
address := internal_common.ParseAddr(addr)
// Fetch transaction count
var nonce uint64
if blockNum == rpc.PendingBlockNumber {
// Ask transaction pool for the nonce which includes pending transactions
nonce, err = s.hmy.GetPoolNonce(ctx, address)
if err != nil {
return nil, err
}
} else {
// Resolve block number and use its state to ask for the nonce
state, _, err := s.hmy.StateAndHeaderByNumber(ctx, blockNum)
if err != nil {
return nil, err
}
if state == nil {
return nil, fmt.Errorf("state not found")
}
if state.Error() != nil {
return nil, state.Error()
}
nonce = state.GetNonce(address)
}
// Format response according to version
switch s.version {
case V1:
return (hexutil.Uint64)(nonce), nil
case V2:
return nonce, nil
default:
return nil, ErrUnknownRPCVersion
}
}
// GetTransactionsCount returns the number of regular transactions from genesis of input type ("SENT", "RECEIVED", "ALL")
func (s *PublicTransactionService) GetTransactionsCount(
ctx context.Context, address, txType string,
) (count uint64, err error) {
if !strings.HasPrefix(address, "one1") {
// Handle hex address
addr := internal_common.ParseAddr(address)
address, err = internal_common.AddressToBech32(addr)
if err != nil {
return 0, err
}
}
// Response output is the same for all versions
return s.hmy.GetTransactionsCount(address, txType)
}
// GetStakingTransactionsCount returns the number of staking transactions from genesis of input type ("SENT", "RECEIVED", "ALL")
func (s *PublicTransactionService) GetStakingTransactionsCount(
ctx context.Context, address, txType string,
) (count uint64, err error) {
if !strings.HasPrefix(address, "one1") {
// Handle hex address
addr := internal_common.ParseAddr(address)
address, err = internal_common.AddressToBech32(addr)
if err != nil {
return 0, err
}
}
// Response output is the same for all versions
return s.hmy.GetStakingTransactionsCount(address, txType)
}
// EstimateGas returns an estimate of the amount of gas needed to execute the
// given transaction against the current pending block.
func (s *PublicTransactionService) EstimateGas(
ctx context.Context, args CallArgs,
) (hexutil.Uint64, error) {
gas, err := doEstimateGas(ctx, s.hmy, args, nil)
if err != nil {
return 0, err
}
// Response output is the same for all versions
return (hexutil.Uint64)(gas), nil
}
// GetTransactionByHash returns the plain transaction for the given hash
func (s *PublicTransactionService) GetTransactionByHash(
ctx context.Context, hash common.Hash,
) (StructuredResponse, error) {
// Try to return an already finalized transaction
tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.hmy.ChainDb(), hash)
if tx == nil {
utils.Logger().Debug().
Err(errors.Wrapf(ErrTransactionNotFound, "hash %v", hash.String())).
Msgf("%v error at %v", LogTag, "GetTransactionByHash")
// Legacy behavior is to not return RPC errors
return nil, nil
}
block, err := s.hmy.GetBlock(ctx, blockHash)
if err != nil {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetTransactionByHash")
// Legacy behavior is to not return RPC errors
return nil, nil
}
// Format the response according to the version
switch s.version {
case V1:
tx, err := v1.NewTransaction(tx, blockHash, blockNumber, block.Time().Uint64(), index)
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
case V2:
tx, err := v2.NewTransaction(tx, blockHash, blockNumber, block.Time().Uint64(), index)
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
default:
return nil, ErrUnknownRPCVersion
}
}
// GetStakingTransactionByHash returns the staking transaction for the given hash
func (s *PublicTransactionService) GetStakingTransactionByHash(
ctx context.Context, hash common.Hash,
) (StructuredResponse, error) {
// Try to return an already finalized transaction
stx, blockHash, blockNumber, index := rawdb.ReadStakingTransaction(s.hmy.ChainDb(), hash)
if stx == nil {
utils.Logger().Debug().
Err(errors.Wrapf(ErrTransactionNotFound, "hash %v", hash.String())).
Msgf("%v error at %v", LogTag, "GetStakingTransactionByHash")
// Legacy behavior is to not return RPC errors
return nil, nil
}
block, err := s.hmy.GetBlock(ctx, blockHash)
if err != nil {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetStakingTransactionByHash")
// Legacy behavior is to not return RPC errors
return nil, nil
}
switch s.version {
case V1:
tx, err := v1.NewStakingTransaction(stx, blockHash, blockNumber, block.Time().Uint64(), index)
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
case V2:
tx, err := v2.NewStakingTransaction(stx, blockHash, blockNumber, block.Time().Uint64(), index)
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
default:
return nil, ErrUnknownRPCVersion
}
}
// GetTransactionsHistory returns the list of transactions hashes that involve a particular address.
func (s *PublicTransactionService) GetTransactionsHistory(
ctx context.Context, args TxHistoryArgs,
) (StructuredResponse, error) {
// Fetch transaction history
var address string
var result []common.Hash
var err error
if strings.HasPrefix(args.Address, "one1") {
address = args.Address
} else {
addr := internal_common.ParseAddr(args.Address)
address, err = internal_common.AddressToBech32(addr)
if err != nil {
return nil, err
}
}
hashes, err := s.hmy.GetTransactionsHistory(address, args.TxType, args.Order)
if err != nil {
return nil, err
}
result = returnHashesWithPagination(hashes, args.PageIndex, args.PageSize)
// Just hashes have same response format for all versions
if !args.FullTx {
return StructuredResponse{"transactions": result}, nil
}
// Full transactions have different response format
txs := []StructuredResponse{}
for _, hash := range result {
tx, err := s.GetTransactionByHash(ctx, hash)
if err == nil {
txs = append(txs, tx)
} else {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetTransactionsHistory")
// Legacy behavior is to not return RPC errors
}
}
return StructuredResponse{"transactions": txs}, nil
}
// GetStakingTransactionsHistory returns the list of transactions hashes that involve a particular address.
func (s *PublicTransactionService) GetStakingTransactionsHistory(
ctx context.Context, args TxHistoryArgs,
) (StructuredResponse, error) {
// Fetch transaction history
var address string
var result []common.Hash
var err error
if strings.HasPrefix(args.Address, "one1") {
address = args.Address
} else {
addr := internal_common.ParseAddr(args.Address)
address, err = internal_common.AddressToBech32(addr)
if err != nil {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetStakingTransactionsHistory")
// Legacy behavior is to not return RPC errors
return nil, nil
}
}
hashes, err := s.hmy.GetStakingTransactionsHistory(address, args.TxType, args.Order)
if err != nil {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetStakingTransactionsHistory")
// Legacy behavior is to not return RPC errors
return nil, nil
}
result = returnHashesWithPagination(hashes, args.PageIndex, args.PageSize)
// Just hashes have same response format for all versions
if !args.FullTx {
return StructuredResponse{"staking_transactions": result}, nil
}
// Full transactions have different response format
txs := []StructuredResponse{}
for _, hash := range result {
tx, err := s.GetStakingTransactionByHash(ctx, hash)
if err == nil {
txs = append(txs, tx)
} else {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetStakingTransactionsHistory")
// Legacy behavior is to not return RPC errors
}
}
return StructuredResponse{"staking_transactions": txs}, nil
}
// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
// Note that the return type is an interface to account for the different versions
func (s *PublicTransactionService) GetBlockTransactionCountByNumber(
ctx context.Context, blockNumber BlockNumber,
) (interface{}, error) {
// Process arguments based on version
blockNum := blockNumber.EthBlockNumber()
// Fetch block
block, err := s.hmy.BlockByNumber(ctx, blockNum)
if err != nil {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetBlockTransactionCountByNumber")
// Legacy behavior is to not return RPC errors
return nil, nil
}
// Format response according to version
switch s.version {
case V1:
return hexutil.Uint(len(block.Transactions())), nil
case V2:
return len(block.Transactions()), nil
default:
return nil, ErrUnknownRPCVersion
}
}
// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash.
// Note that the return type is an interface to account for the different versions
func (s *PublicTransactionService) GetBlockTransactionCountByHash(
ctx context.Context, blockHash common.Hash,
) (interface{}, error) {
// Fetch block
block, err := s.hmy.GetBlock(ctx, blockHash)
if err != nil {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetBlockTransactionCountByHash")
// Legacy behavior is to not return RPC errors
return nil, nil
}
// Format response according to version
switch s.version {
case V1:
return hexutil.Uint(len(block.Transactions())), nil
case V2:
return len(block.Transactions()), nil
default:
return nil, ErrUnknownRPCVersion
}
}
// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index.
func (s *PublicTransactionService) GetTransactionByBlockNumberAndIndex(
ctx context.Context, blockNumber BlockNumber, index TransactionIndex,
) (StructuredResponse, error) {
// Process arguments based on version
blockNum := blockNumber.EthBlockNumber()
// Fetch Block
block, err := s.hmy.BlockByNumber(ctx, blockNum)
if err != nil {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetTransactionByBlockNumberAndIndex")
// Legacy behavior is to not return RPC errors
return nil, nil
}
// Format response according to version
switch s.version {
case V1:
tx, err := v1.NewTransactionFromBlockIndex(block, uint64(index))
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
case V2:
tx, err := v2.NewTransactionFromBlockIndex(block, uint64(index))
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
default:
return nil, ErrUnknownRPCVersion
}
}
// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index.
func (s *PublicTransactionService) GetTransactionByBlockHashAndIndex(
ctx context.Context, blockHash common.Hash, index TransactionIndex,
) (StructuredResponse, error) {
// Fetch Block
block, err := s.hmy.GetBlock(ctx, blockHash)
if err != nil {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetTransactionByBlockHashAndIndex")
// Legacy behavior is to not return RPC errors
return nil, nil
}
// Format response according to version
switch s.version {
case V1:
tx, err := v1.NewTransactionFromBlockIndex(block, uint64(index))
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
case V2:
tx, err := v2.NewTransactionFromBlockIndex(block, uint64(index))
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
default:
return nil, ErrUnknownRPCVersion
}
}
// GetBlockStakingTransactionCountByNumber returns the number of staking transactions in the block with the given block number.
// Note that the return type is an interface to account for the different versions
func (s *PublicTransactionService) GetBlockStakingTransactionCountByNumber(
ctx context.Context, blockNumber BlockNumber,
) (interface{}, error) {
// Process arguments based on version
blockNum := blockNumber.EthBlockNumber()
// Fetch block
block, err := s.hmy.BlockByNumber(ctx, blockNum)
if err != nil {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetBlockStakingTransactionCountByNumber")
// Legacy behavior is to not return RPC errors
return nil, nil
}
// Format response according to version
switch s.version {
case V1:
return hexutil.Uint(len(block.StakingTransactions())), nil
case V2:
return len(block.StakingTransactions()), nil
default:
return nil, ErrUnknownRPCVersion
}
}
// GetBlockStakingTransactionCountByHash returns the number of staking transactions in the block with the given hash.
// Note that the return type is an interface to account for the different versions
func (s *PublicTransactionService) GetBlockStakingTransactionCountByHash(
ctx context.Context, blockHash common.Hash,
) (interface{}, error) {
// Fetch block
block, err := s.hmy.GetBlock(ctx, blockHash)
if err != nil {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetBlockStakingTransactionCountByHash")
// Legacy behavior is to not return RPC errors
return nil, nil
}
// Format response according to version
switch s.version {
case V1:
return hexutil.Uint(len(block.StakingTransactions())), nil
case V2:
return len(block.StakingTransactions()), nil
default:
return nil, ErrUnknownRPCVersion
}
}
// GetStakingTransactionByBlockNumberAndIndex returns the staking transaction for the given block number and index.
func (s *PublicTransactionService) GetStakingTransactionByBlockNumberAndIndex(
ctx context.Context, blockNumber BlockNumber, index TransactionIndex,
) (StructuredResponse, error) {
// Process arguments based on version
blockNum := blockNumber.EthBlockNumber()
// Fetch Block
block, err := s.hmy.BlockByNumber(ctx, blockNum)
if err != nil {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetStakingTransactionByBlockNumberAndIndex")
// Legacy behavior is to not return RPC errors
return nil, nil
}
// Format response according to version
switch s.version {
case V1:
tx, err := v1.NewStakingTransactionFromBlockIndex(block, uint64(index))
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
case V2:
tx, err := v2.NewStakingTransactionFromBlockIndex(block, uint64(index))
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
default:
return nil, ErrUnknownRPCVersion
}
}
// GetStakingTransactionByBlockHashAndIndex returns the transaction for the given block hash and index.
func (s *PublicTransactionService) GetStakingTransactionByBlockHashAndIndex(
ctx context.Context, blockHash common.Hash, index TransactionIndex,
) (StructuredResponse, error) {
// Fetch Block
block, err := s.hmy.GetBlock(ctx, blockHash)
if err != nil {
utils.Logger().Debug().
Err(err).
Msgf("%v error at %v", LogTag, "GetStakingTransactionByBlockHashAndIndex")
// Legacy behavior is to not return RPC errors
return nil, nil
}
// Format response according to version
switch s.version {
case V1:
tx, err := v1.NewStakingTransactionFromBlockIndex(block, uint64(index))
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
case V2:
tx, err := v2.NewStakingTransactionFromBlockIndex(block, uint64(index))
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
default:
return nil, ErrUnknownRPCVersion
}
}
// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
func (s *PublicTransactionService) GetTransactionReceipt(
ctx context.Context, hash common.Hash,
) (StructuredResponse, error) {
// Fetch receipt for plain & staking transaction
var tx *types.Transaction
var stx *staking.StakingTransaction
var blockHash common.Hash
var blockNumber, index uint64
tx, blockHash, blockNumber, index = rawdb.ReadTransaction(s.hmy.ChainDb(), hash)
if tx == nil {
stx, blockHash, blockNumber, index = rawdb.ReadStakingTransaction(s.hmy.ChainDb(), hash)
if stx == nil {
return nil, nil
}
// if there both normal and staking transactions, add to index
if block, _ := s.hmy.GetBlock(ctx, blockHash); block != nil {
index = index + uint64(block.Transactions().Len())
}
}
receipts, err := s.hmy.GetReceipts(ctx, blockHash)
if err != nil {
return nil, err
}
if len(receipts) <= int(index) {
return nil, nil
}
receipt := receipts[index]
// Format response according to version
var RPCReceipt interface{}
switch s.version {
case V1:
if tx == nil {
RPCReceipt, err = v1.NewReceipt(stx, blockHash, blockNumber, index, receipt)
} else {
RPCReceipt, err = v1.NewReceipt(tx, blockHash, blockNumber, index, receipt)
}
if err != nil {
return nil, err
}
return NewStructuredResponse(RPCReceipt)
case V2:
if tx == nil {
RPCReceipt, err = v2.NewReceipt(stx, blockHash, blockNumber, index, receipt)
} else {
RPCReceipt, err = v2.NewReceipt(tx, blockHash, blockNumber, index, receipt)
}
if err != nil {
return nil, err
}
return NewStructuredResponse(RPCReceipt)
default:
return nil, ErrUnknownRPCVersion
}
}
// GetCXReceiptByHash returns the transaction for the given hash
func (s *PublicTransactionService) GetCXReceiptByHash(
ctx context.Context, hash common.Hash,
) (StructuredResponse, error) {
if cx, blockHash, blockNumber, _ := rawdb.ReadCXReceipt(s.hmy.ChainDb(), hash); cx != nil {
// Format response according to version
switch s.version {
case V1:
tx, err := v1.NewCxReceipt(cx, blockHash, blockNumber)
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
case V2:
tx, err := v2.NewCxReceipt(cx, blockHash, blockNumber)
if err != nil {
return nil, err
}
return NewStructuredResponse(tx)
default:
return nil, ErrUnknownRPCVersion
}
}
utils.Logger().Debug().
Err(fmt.Errorf("unable to found CX receipt for tx %v", hash.String())).
Msgf("%v error at %v", LogTag, "GetCXReceiptByHash")
return nil, nil // Legacy behavior is to not return an error here
}
// ResendCx requests that the egress receipt for the given cross-shard
// transaction be sent to the destination shard for credit. This is used for
// unblocking a half-complete cross-shard transaction whose fund has been
// withdrawn already from the source shard but not credited yet in the
// destination account due to transient failures.
func (s *PublicTransactionService) ResendCx(ctx context.Context, txID common.Hash) (bool, error) {
_, success := s.hmy.ResendCx(ctx, txID)
// Response output is the same for all versions
return success, nil
}
// returnHashesWithPagination returns result with pagination (offset, page in TxHistoryArgs).
func returnHashesWithPagination(hashes []common.Hash, pageIndex uint32, pageSize uint32) []common.Hash {
size := defaultPageSize
if pageSize > 0 {
size = pageSize
}
if uint64(size)*uint64(pageIndex) >= uint64(len(hashes)) {
return make([]common.Hash, 0)
}
if uint64(size)*uint64(pageIndex)+uint64(size) > uint64(len(hashes)) {
return hashes[size*pageIndex:]
}
return hashes[size*pageIndex : size*pageIndex+size]
}
// doEstimateGas ..
func doEstimateGas(
ctx context.Context, hmy *hmy.Harmony, args CallArgs, gasCap *big.Int,
) (uint64, error) {
// Binary search the gas requirement, as it may be higher than the amount used
var (
lo = params.TxGas - 1
hi uint64
max uint64
)
blockNum := rpc.LatestBlockNumber
if args.Gas != nil && uint64(*args.Gas) >= params.TxGas {
hi = uint64(*args.Gas)
} else {
// Retrieve the blk to act as the gas ceiling
blk, err := hmy.BlockByNumber(ctx, blockNum)
if err != nil {
return 0, err
}
hi = blk.GasLimit()
}
if gasCap != nil && hi > gasCap.Uint64() {
hi = gasCap.Uint64()
}
max = hi
// Use zero-address if none other is available
if args.From == nil {
args.From = &common.Address{}
}
// Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) bool {
args.Gas = (*hexutil.Uint64)(&gas)
_, _, failed, err := doCall(ctx, hmy, args, blockNum, vm.Config{}, 0, big.NewInt(int64(max)))
if err != nil || failed {
return false
}
return true
}
// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi {
mid := (hi + lo) / 2
if !executable(mid) {
lo = mid
} else {
hi = mid
}
}
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == max {
if !executable(hi) {
return 0, fmt.Errorf("gas required exceeds allowance (%d) or always failing transaction", max)
}
}
return hi, nil
}