2020-07-29 16:21:34 +00:00
package commands
import (
"context"
"errors"
"fmt"
2020-10-12 08:39:04 +00:00
"math/big"
2020-07-29 16:21:34 +00:00
"github.com/ledgerwatch/turbo-geth/common"
"github.com/ledgerwatch/turbo-geth/common/hexutil"
"github.com/ledgerwatch/turbo-geth/core"
"github.com/ledgerwatch/turbo-geth/core/rawdb"
"github.com/ledgerwatch/turbo-geth/core/state"
"github.com/ledgerwatch/turbo-geth/core/vm"
2020-10-25 08:38:55 +00:00
"github.com/ledgerwatch/turbo-geth/ethdb"
2020-07-29 16:21:34 +00:00
"github.com/ledgerwatch/turbo-geth/internal/ethapi"
"github.com/ledgerwatch/turbo-geth/log"
"github.com/ledgerwatch/turbo-geth/params"
"github.com/ledgerwatch/turbo-geth/rpc"
2020-08-19 11:46:20 +00:00
"github.com/ledgerwatch/turbo-geth/turbo/rpchelper"
"github.com/ledgerwatch/turbo-geth/turbo/transactions"
2020-07-29 16:21:34 +00:00
)
2020-10-24 17:03:52 +00:00
// Call implements eth_call. Executes a new message call immediately without creating a transaction on the block chain.
2020-08-12 13:46:35 +00:00
func ( api * APIImpl ) Call ( ctx context . Context , args ethapi . CallArgs , blockNrOrHash rpc . BlockNumberOrHash , overrides * map [ common . Address ] ethapi . Account ) ( hexutil . Bytes , error ) {
2020-11-08 05:46:53 +00:00
dbtx , err := api . dbReader . Begin ( ctx , ethdb . RO )
if err != nil {
return nil , err
}
defer dbtx . Rollback ( )
tx := dbtx . ( ethdb . HasTx ) . Tx ( )
chainConfig , err := getChainConfig ( dbtx )
if err != nil {
return nil , err
2020-10-12 08:39:04 +00:00
}
2020-11-08 05:46:53 +00:00
result , err := transactions . DoCall ( ctx , args , tx , api . dbReader , blockNrOrHash , overrides , api . GasCap , chainConfig )
2020-07-29 16:21:34 +00:00
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 , ethapi . NewRevertError ( result )
}
return result . Return ( ) , result . Err
}
2020-10-24 17:03:52 +00:00
// EstimateGas implements eth_estimateGas. Returns an estimate of how much gas is necessary to allow the transaction to complete. The transaction will not be added to the blockchain.
2020-07-29 16:21:34 +00:00
func ( api * APIImpl ) EstimateGas ( ctx context . Context , args ethapi . CallArgs ) ( hexutil . Uint64 , error ) {
2020-10-24 17:03:52 +00:00
// TODO: fixme: blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
2020-07-29 16:21:34 +00:00
hash := rawdb . ReadHeadBlockHash ( api . dbReader )
2020-08-19 11:46:20 +00:00
return api . DoEstimateGas ( ctx , args , rpc . BlockNumberOrHash { BlockHash : & hash } , big . NewInt ( 0 ) . SetUint64 ( api . GasCap ) )
2020-07-29 16:21:34 +00:00
}
func ( api * APIImpl ) DoEstimateGas ( ctx context . Context , args ethapi . CallArgs , blockNrOrHash rpc . BlockNumberOrHash , gasCap * big . Int ) ( hexutil . Uint64 , error ) {
2020-11-08 05:46:53 +00:00
dbtx , err := api . dbReader . Begin ( ctx , ethdb . RO )
if err != nil {
return 0 , err
2020-10-12 08:39:04 +00:00
}
2020-11-08 05:46:53 +00:00
defer dbtx . Rollback ( )
tx := dbtx . ( ethdb . HasTx ) . Tx ( )
2020-07-29 16:21:34 +00:00
// 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 )
}
2020-08-19 11:46:20 +00:00
blockNumber , hash , err := rpchelper . GetBlockNumber ( blockNrOrHash , api . dbReader )
2020-07-29 16:21:34 +00:00
if err != nil {
return 0 , err
}
2020-11-08 05:46:53 +00:00
chainConfig , err := getChainConfig ( dbtx )
if err != nil {
return 0 , err
}
2020-07-29 16:21:34 +00:00
// 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
header := rawdb . ReadHeader ( api . dbReader , hash , blockNumber )
hi = header . GasLimit
}
// Recap the highest gas limit with account's available balance.
if args . GasPrice != nil && args . GasPrice . ToInt ( ) . Uint64 ( ) != 0 {
2020-10-12 08:39:04 +00:00
ds := state . NewPlainDBState ( tx , blockNumber )
2020-07-29 16:21:34 +00:00
state := state . New ( ds )
if state == nil {
return 0 , fmt . Errorf ( "can't get the state for %d" , blockNumber )
}
balance := state . GetBalance ( * args . From ) // from can't be nil
available := balance . ToBig ( )
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 hi > allowance . Uint64 ( ) {
transfer := args . Value
if transfer == nil {
transfer = new ( hexutil . Big )
}
log . Warn ( "Gas estimation capped by limited funds" , "original" , hi , "balance" , balance ,
"sent" , transfer . ToInt ( ) , "gasprice" , args . GasPrice . ToInt ( ) , "fundable" , allowance )
hi = allowance . Uint64 ( )
}
}
// Recap the highest gas allowance with specified gascap.
2020-10-25 08:19:59 +00:00
if gasCap != nil && gasCap . Sign ( ) > 0 && hi > gasCap . Uint64 ( ) {
2020-07-29 16:21:34 +00:00
log . Warn ( "Caller gas above allowance, capping" , "requested" , hi , "cap" , gasCap )
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 )
2020-11-08 05:46:53 +00:00
result , err := transactions . DoCall ( ctx , args , tx , api . dbReader , blockNrOrHash , nil , api . GasCap , chainConfig )
2020-07-29 16:21:34 +00:00
if err != nil {
if errors . Is ( err , core . ErrIntrinsicGas ) {
// Special case, raise gas limit
return true , nil , nil
}
// Bail out
return true , nil , err
}
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
// assigened. 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 , ethapi . 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
}