aboutsummaryrefslogtreecommitdiff
path: root/internal/ethapi/api.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/ethapi/api.go')
-rw-r--r--internal/ethapi/api.go394
1 files changed, 283 insertions, 111 deletions
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 8bc2f94..9915ad4 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -30,26 +30,22 @@ import (
"github.com/ava-labs/coreth/accounts/scwallet"
"github.com/ava-labs/coreth/consensus/ethash"
"github.com/ava-labs/coreth/core"
- "github.com/ava-labs/coreth/core/rawdb"
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/core/vm"
"github.com/ava-labs/coreth/params"
"github.com/ava-labs/coreth/rpc"
- "github.com/ava-labs/go-ethereum/common"
- "github.com/ava-labs/go-ethereum/common/hexutil"
- "github.com/ava-labs/go-ethereum/common/math"
- "github.com/ava-labs/go-ethereum/crypto"
- "github.com/ava-labs/go-ethereum/log"
- "github.com/ava-labs/go-ethereum/p2p"
- "github.com/ava-labs/go-ethereum/rlp"
"github.com/davecgh/go-spew/spew"
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/rlp"
"github.com/tyler-smith/go-bip39"
)
-const (
- defaultGasPrice = params.GWei
-)
-
// PublicEthereumAPI provides an API to access Ethereum related information.
// It offers only methods that operate on public data that is freely available to anyone.
type PublicEthereumAPI struct {
@@ -279,7 +275,11 @@ func (s *PrivateAccountAPI) DeriveAccount(url string, path string, pin *bool) (a
// NewAccount will create a new account and returns the address for the new account.
func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
- acc, err := fetchKeystore(s.am).NewAccount(password)
+ ks, err := fetchKeystore(s.am)
+ if err != nil {
+ return common.Address{}, err
+ }
+ acc, err := ks.NewAccount(password)
if err == nil {
log.Info("Your new key was generated", "address", acc.Address)
log.Warn("Please backup your key file!", "path", acc.URL.Path)
@@ -289,9 +289,12 @@ func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error)
return common.Address{}, err
}
-// fetchKeystore retrives the encrypted keystore from the account manager.
-func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
- return am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+// fetchKeystore retrieves the encrypted keystore from the account manager.
+func fetchKeystore(am *accounts.Manager) (*keystore.KeyStore, error) {
+ if ks := am.Backends(keystore.KeyStoreType); len(ks) > 0 {
+ return ks[0].(*keystore.KeyStore), nil
+ }
+ return nil, errors.New("local keystore not used")
}
// ImportRawKey stores the given hex encoded ECDSA key into the key directory,
@@ -301,7 +304,11 @@ func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (commo
if err != nil {
return common.Address{}, err
}
- acc, err := fetchKeystore(s.am).ImportECDSA(key, password)
+ ks, err := fetchKeystore(s.am)
+ if err != nil {
+ return common.Address{}, err
+ }
+ acc, err := ks.ImportECDSA(key, password)
return acc.Address, err
}
@@ -325,7 +332,11 @@ func (s *PrivateAccountAPI) UnlockAccount(ctx context.Context, addr common.Addre
} else {
d = time.Duration(*duration) * time.Second
}
- err := fetchKeystore(s.am).TimedUnlock(accounts.Account{Address: addr}, password, d)
+ ks, err := fetchKeystore(s.am)
+ if err != nil {
+ return false, err
+ }
+ err = ks.TimedUnlock(accounts.Account{Address: addr}, password, d)
if err != nil {
log.Warn("Failed account unlock attempt", "address", addr, "err", err)
}
@@ -334,7 +345,10 @@ func (s *PrivateAccountAPI) UnlockAccount(ctx context.Context, addr common.Addre
// LockAccount will lock the account associated with the given address when it's unlocked.
func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool {
- return fetchKeystore(s.am).Lock(addr) == nil
+ if ks, err := fetchKeystore(s.am); err == nil {
+ return ks.Lock(addr) == nil
+ }
+ return false
}
// signTransaction sets defaults and signs the given transaction
@@ -391,6 +405,10 @@ func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs
if args.Nonce == nil {
return nil, fmt.Errorf("nonce not specified")
}
+ // Before actually sign the transaction, ensure the transaction fee is reasonable.
+ if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil {
+ return nil, err
+ }
signed, err := s.signTransaction(ctx, &args, passwd)
if err != nil {
log.Warn("Failed transaction sign attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err)
@@ -485,7 +503,7 @@ func (s *PrivateAccountAPI) InitializeWallet(ctx context.Context, url string) (s
case *scwallet.Wallet:
return mnemonic, wallet.Initialize(seed)
default:
- return "", fmt.Errorf("Specified wallet does not support initialization")
+ return "", fmt.Errorf("specified wallet does not support initialization")
}
}
@@ -500,7 +518,7 @@ func (s *PrivateAccountAPI) Unpair(ctx context.Context, url string, pin string)
case *scwallet.Wallet:
return wallet.Unpair([]byte(pin))
default:
- return fmt.Errorf("Specified wallet does not support pairing")
+ return fmt.Errorf("specified wallet does not support pairing")
}
}
@@ -529,8 +547,8 @@ func (s *PublicBlockChainAPI) BlockNumber() hexutil.Uint64 {
// 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 *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*hexutil.Big, error) {
- state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
+func (s *PublicBlockChainAPI) 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
}
@@ -554,8 +572,8 @@ type StorageResult struct {
}
// GetProof returns the Merkle-proof for a given account and optionally some storage keys.
-func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNr rpc.BlockNumber) (*AccountResult, error) {
- state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
+func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) {
+ state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
@@ -609,7 +627,7 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre
func (s *PublicBlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) {
header, err := s.b.HeaderByNumber(ctx, number)
if header != nil && err == nil {
- response := s.rpcMarshalHeader(header)
+ response := s.rpcMarshalHeader(ctx, header)
if number == rpc.PendingBlockNumber {
// Pending header need to nil out a few fields
for _, field := range []string{"hash", "nonce", "miner"} {
@@ -625,7 +643,7 @@ func (s *PublicBlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.
func (s *PublicBlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) map[string]interface{} {
header, _ := s.b.HeaderByHash(ctx, hash)
if header != nil {
- return s.rpcMarshalHeader(header)
+ return s.rpcMarshalHeader(ctx, header)
}
return nil
}
@@ -638,7 +656,7 @@ func (s *PublicBlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.H
func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
block, err := s.b.BlockByNumber(ctx, number)
if block != nil && err == nil {
- response, err := s.rpcMarshalBlock(block, true, fullTx)
+ response, err := s.rpcMarshalBlock(ctx, block, true, fullTx)
if err == nil && number == rpc.PendingBlockNumber {
// Pending blocks need to nil out a few fields
for _, field := range []string{"hash", "nonce", "miner"} {
@@ -655,7 +673,7 @@ func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.B
func (s *PublicBlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) {
block, err := s.b.BlockByHash(ctx, hash)
if block != nil {
- return s.rpcMarshalBlock(block, true, fullTx)
+ return s.rpcMarshalBlock(ctx, block, true, fullTx)
}
return nil, err
}
@@ -671,7 +689,7 @@ func (s *PublicBlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context,
return nil, nil
}
block = types.NewBlockWithHeader(uncles[index])
- return s.rpcMarshalBlock(block, false, false)
+ return s.rpcMarshalBlock(ctx, block, false, false)
}
return nil, err
}
@@ -687,7 +705,7 @@ func (s *PublicBlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, b
return nil, nil
}
block = types.NewBlockWithHeader(uncles[index])
- return s.rpcMarshalBlock(block, false, false)
+ return s.rpcMarshalBlock(ctx, block, false, false)
}
return nil, err
}
@@ -711,8 +729,8 @@ func (s *PublicBlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, bloc
}
// GetCode returns the code stored at the given address in the state for the given block number.
-func (s *PublicBlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (hexutil.Bytes, error) {
- state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
+func (s *PublicBlockChainAPI) 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
}
@@ -723,8 +741,8 @@ func (s *PublicBlockChainAPI) GetCode(ctx context.Context, address common.Addres
// 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 *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, key string, blockNr rpc.BlockNumber) (hexutil.Bytes, error) {
- state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
+func (s *PublicBlockChainAPI) 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
}
@@ -742,6 +760,45 @@ type CallArgs struct {
Data *hexutil.Bytes `json:"data"`
}
+// ToMessage converts CallArgs to the Message type used by the core evm
+func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message {
+ // Set sender address or use zero address if none specified.
+ var addr common.Address
+ if args.From != nil {
+ addr = *args.From
+ }
+
+ // Set default gas & gas price if none were set
+ gas := globalGasCap
+ if gas == 0 {
+ gas = uint64(math.MaxUint64 / 2)
+ }
+ if args.Gas != nil {
+ gas = uint64(*args.Gas)
+ }
+ if globalGasCap != 0 && globalGasCap < gas {
+ log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
+ gas = globalGasCap
+ }
+ gasPrice := new(big.Int)
+ if args.GasPrice != nil {
+ gasPrice = args.GasPrice.ToInt()
+ }
+
+ value := new(big.Int)
+ if args.Value != nil {
+ value = args.Value.ToInt()
+ }
+
+ var data []byte
+ if args.Data != nil {
+ data = []byte(*args.Data)
+ }
+
+ msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false)
+ return msg
+}
+
// account 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
@@ -756,23 +813,12 @@ type account struct {
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
}
-func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
+func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, 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())
- state, header, err := b.StateAndHeaderByNumber(ctx, blockNr)
+ state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
- return nil, 0, false, err
- }
- // Set sender address or use a default if none specified
- var addr common.Address
- if args.From == nil {
- if wallets := b.AccountManager().Wallets(); len(wallets) > 0 {
- if accounts := wallets[0].Accounts(); len(accounts) > 0 {
- addr = accounts[0].Address
- }
- }
- } else {
- addr = *args.From
+ return nil, err
}
// Override the fields of specified contracts before execution.
for addr, account := range overrides {
@@ -789,7 +835,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb
state.SetBalance(addr, (*big.Int)(*account.Balance))
}
if account.State != nil && account.StateDiff != nil {
- return nil, 0, false, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
+ return nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
}
// Replace entire state if caller requires.
if account.State != nil {
@@ -802,33 +848,6 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb
}
}
}
- // Set default gas & gas price if none were set
- gas := uint64(math.MaxUint64 / 2)
- if args.Gas != nil {
- gas = uint64(*args.Gas)
- }
- if globalGasCap != nil && globalGasCap.Uint64() < gas {
- log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
- gas = globalGasCap.Uint64()
- }
- gasPrice := new(big.Int).SetUint64(defaultGasPrice)
- if args.GasPrice != nil {
- gasPrice = args.GasPrice.ToInt()
- }
-
- value := new(big.Int)
- if args.Value != nil {
- value = args.Value.ToInt()
- }
-
- var data []byte
- if args.Data != nil {
- data = []byte(*args.Data)
- }
-
- // Create new call message
- msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false)
-
// 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
@@ -842,9 +861,10 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb
defer cancel()
// Get a new instance of the EVM.
+ msg := args.ToMessage(globalGasCap)
evm, vmError, err := b.GetEVM(ctx, msg, state, header)
if err != nil {
- return nil, 0, false, err
+ 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)
@@ -856,15 +876,48 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb
// Setup the gas pool (also for unmetered requests)
// and apply the message.
gp := new(core.GasPool).AddGas(math.MaxUint64)
- res, gas, failed, err := core.ApplyMessage(evm, msg, gp)
+ result, err := core.ApplyMessage(evm, msg, gp)
if err := vmError(); err != nil {
- return nil, 0, false, err
+ return nil, err
}
// If the timer caused an abort, return an appropriate error message
if evm.Cancelled() {
- return nil, 0, false, fmt.Errorf("execution aborted (timeout = %v)", timeout)
+ return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout)
}
- return res, gas, failed, err
+ if err != nil {
+ return result, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas())
+ }
+ return result, 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
}
// Call executes the given transaction on the state for the given block number.
@@ -873,52 +926,103 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb
//
// Note, this function doesn't make and changes in the state/blockchain and is
// useful to execute and retrieve values.
-func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, overrides *map[common.Address]account) (hexutil.Bytes, error) {
+func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *map[common.Address]account) (hexutil.Bytes, error) {
var accounts map[common.Address]account
if overrides != nil {
accounts = *overrides
}
- result, _, _, err := DoCall(ctx, s.b, args, blockNr, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
- return (hexutil.Bytes)(result), err
+ result, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, 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
}
-func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, gasCap *big.Int) (hexutil.Uint64, error) {
+func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.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
)
+ // 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
- block, err := b.BlockByNumber(ctx, blockNr)
+ block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return 0, err
}
hi = block.GasLimit()
}
- if gasCap != nil && hi > gasCap.Uint64() {
+ // Recap the highest gas limit with account's available balance.
+ if args.GasPrice != nil && args.GasPrice.ToInt().BitLen() != 0 {
+ state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
+ 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)
+ }
+ 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.
+ if gasCap != 0 && hi > gasCap {
log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
- hi = gasCap.Uint64()
+ hi = gasCap
}
cap = hi
// Create a helper to check if a gas allowance results in an executable transaction
- executable := func(gas uint64) bool {
+ executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
args.Gas = (*hexutil.Uint64)(&gas)
- _, _, failed, err := DoCall(ctx, b, args, rpc.PendingBlockNumber, nil, vm.Config{}, 0, gasCap)
- if err != nil || failed {
- return false
+ result, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap)
+ 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 true
+ 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
- if !executable(mid) {
+ 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
@@ -926,8 +1030,19 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl
}
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap {
- if !executable(hi) {
- return 0, fmt.Errorf("gas required exceeds allowance (%d) or always failing transaction", cap)
+ failed, result, err := executable(hi)
+ if err != nil {
+ return 0, err
+ }
+ if failed {
+ if result != nil && 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
@@ -936,7 +1051,8 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl
// EstimateGas returns an estimate of the amount of gas needed to execute the
// given transaction against the current pending block.
func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) {
- return DoEstimateGas(ctx, s.b, args, rpc.PendingBlockNumber, s.b.RPCGasCap())
+ blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
+ return DoEstimateGas(ctx, s.b, args, blockNrOrHash, s.b.RPCGasCap())
}
// ExecutionResult groups all structured logs emitted by the EVM
@@ -1061,20 +1177,22 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool) (map[string]i
// rpcMarshalHeader uses the generalized output filler, then adds the total difficulty field, which requires
// a `PublicBlockchainAPI`.
-func (s *PublicBlockChainAPI) rpcMarshalHeader(header *types.Header) map[string]interface{} {
+func (s *PublicBlockChainAPI) rpcMarshalHeader(ctx context.Context, header *types.Header) map[string]interface{} {
fields := RPCMarshalHeader(header)
- fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(header.Hash()))
+ fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, header.Hash()))
return fields
}
// rpcMarshalBlock uses the generalized output filler, then adds the total difficulty field, which requires
// a `PublicBlockchainAPI`.
-func (s *PublicBlockChainAPI) rpcMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
+func (s *PublicBlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
fields, err := RPCMarshalBlock(b, inclTx, fullTx)
if err != nil {
return nil, err
}
- fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(b.Hash()))
+ if inclTx {
+ fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, b.Hash()))
+ }
return fields, err
}
@@ -1223,9 +1341,9 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByBlockHashAndIndex(ctx cont
}
// GetTransactionCount returns the number of transactions the given address has sent for the given block number
-func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*hexutil.Uint64, error) {
+func (s *PublicTransactionPoolAPI) 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 == rpc.PendingBlockNumber {
+ if blockNr, ok := blockNrOrHash.Number(); ok && blockNr == rpc.PendingBlockNumber {
nonce, err := s.b.GetPoolNonce(ctx, address)
if err != nil {
return nil, err
@@ -1233,7 +1351,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, addr
return (*hexutil.Uint64)(&nonce), nil
}
// Resolve block number and use its state to ask for the nonce
- state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
+ state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
@@ -1279,8 +1397,8 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context,
// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
- tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash)
- if tx == nil {
+ tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash)
+ if err != nil {
return nil, nil
}
receipts, err := s.b.GetReceipts(ctx, blockHash)
@@ -1375,7 +1493,7 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
args.Nonce = (*hexutil.Uint64)(&nonce)
}
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
- return errors.New(`Both "data" and "input" are set and not equal. Please use "input" to pass transaction call data.`)
+ return errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`)
}
if args.To == nil {
// Contract creation
@@ -1404,7 +1522,8 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
Value: args.Value,
Data: input,
}
- estimated, err := DoEstimateGas(ctx, b, callArgs, rpc.PendingBlockNumber, b.RPCGasCap())
+ pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
+ estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap())
if err != nil {
return err
}
@@ -1429,6 +1548,11 @@ func (args *SendTxArgs) toTransaction() *types.Transaction {
// 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 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 err := b.SendTx(ctx, tx); err != nil {
return common.Hash{}, err
}
@@ -1478,6 +1602,22 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
return SubmitTransaction(ctx, s.b, signed)
}
+// FillTransaction fills the defaults (nonce, gas, gasPrice) on a given unsigned transaction,
+// and returns it to the caller for further processing (signing + broadcast)
+func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) {
+ // Set some sanity defaults and terminate on failure
+ if err := args.setDefaults(ctx, s.b); err != nil {
+ return nil, err
+ }
+ // Assemble the transaction and obtain rlp
+ tx := args.toTransaction()
+ data, err := rlp.EncodeToBytes(tx)
+ 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 *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) {
@@ -1535,6 +1675,10 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen
if err := args.setDefaults(ctx, s.b); err != nil {
return nil, err
}
+ // Before actually sign the transaction, ensure the transaction fee is reasonable.
+ if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil {
+ return nil, err
+ }
tx, err := s.sign(args.From, args.toTransaction())
if err != nil {
return nil, err
@@ -1583,11 +1727,24 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
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 {
var signer types.Signer = types.HomesteadSigner{}
if p.Protected() {
@@ -1614,7 +1771,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
}
}
- return common.Hash{}, fmt.Errorf("Transaction %#x not found", matchTx.Hash())
+ return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash())
}
// PublicDebugAPI is the collection of Ethereum APIs exposed over the public
@@ -1725,3 +1882,18 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint {
func (s *PublicNetAPI) 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
+}