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

858 lines
26 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/accounts/abi"
"github.com/harmony-one/harmony/core"
"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"
eth "github.com/harmony-one/harmony/rpc/eth"
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, Eth:
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 := EstimateGas(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)
case Eth:
tx, err := eth.NewTransaction(tx.ConvertToEth(), 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, Eth:
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, Eth:
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)
case Eth:
tx, err := eth.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)
case Eth:
tx, err := eth.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, Eth:
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, Eth:
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, Eth:
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)
case Eth:
if tx != nil {
RPCReceipt, err = eth.NewReceipt(tx.ConvertToEth(), 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, Eth:
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]
}
// EstimateGas - estimate gas cost for a given operation
func EstimateGas(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 uint64 = params.TxGas - 1
hi uint64
cap uint64
)
blockNum := rpc.LatestBlockNumber
// 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 block to act as the gas ceiling
blk, err := hmy.BlockByNumber(ctx, blockNum)
if err != nil {
return 0, err
}
hi = blk.GasLimit()
}
// Recap the highest gas limit with account's available balance.
if args.GasPrice != nil && args.GasPrice.ToInt().BitLen() != 0 {
state, _, err := hmy.StateAndHeaderByNumber(ctx, blockNum)
if err != nil {
return 0, err
}
balance := state.GetBalance(*args.From) // from can't be nil
available := new(big.Int).Set(balance)
if args.Value != nil {
if args.Value.ToInt().Cmp(available) >= 0 {
return 0, errors.New("insufficient funds for transfer")
}
available.Sub(available, args.Value.ToInt())
}
allowance := new(big.Int).Div(available, args.GasPrice.ToInt())
// If the allowance is larger than maximum uint64, skip checking
if allowance.IsUint64() && hi > allowance.Uint64() {
transfer := args.Value
if transfer == nil {
transfer = new(hexutil.Big)
}
utils.Logger().Warn().Uint64("original", hi).Uint64("balance", balance.Uint64()).Uint64("sent", transfer.ToInt().Uint64()).Uint64("gasprice", args.GasPrice.ToInt().Uint64()).Uint64("fundable", allowance.Uint64()).Msg("Gas estimation capped by limited funds")
hi = allowance.Uint64()
}
}
// Recap the highest gas allowance with specified gascap.
if gasCap != nil && hi > gasCap.Uint64() {
utils.Logger().Warn().Uint64("requested", hi).Uint64("cap", gasCap.Uint64()).Msg("Caller gas above allowance, capping")
hi = gasCap.Uint64()
}
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 := DoEVMCall(ctx, hmy, args, blockNum, 0)
if err != nil {
if 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
}
// 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 && result.VMErr != vm.ErrOutOfGas {
if len(result.Revert()) > 0 {
return 0, newRevertError(result)
}
return 0, result.VMErr
}
// Otherwise, the specified gas cap is too low
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
}
}
return hi, nil
}
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
}