laconicd-deprecated/rpc/namespaces/ethereum/eth/api.go
Federico Kunze Küllmer 3b852f723e
imp(rpc): restrict unprotected txs on the node RPC (#1143)
* imp(rpc): restrict unprotected txs on the node RPC

* lint

* send raw transaction

* c++
2022-06-22 12:50:36 +02:00

1149 lines
36 KiB
Go

package eth
import (
"context"
"encoding/json"
"fmt"
"math"
"math/big"
"github.com/evmos/ethermint/ethereum/eip712"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/log"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/evmos/ethermint/crypto/hd"
"github.com/evmos/ethermint/rpc/backend"
rpctypes "github.com/evmos/ethermint/rpc/types"
ethermint "github.com/evmos/ethermint/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
)
// PublicAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PublicAPI struct {
ctx context.Context
clientCtx client.Context
queryClient *rpctypes.QueryClient
chainIDEpoch *big.Int
logger log.Logger
backend backend.EVMBackend
nonceLock *rpctypes.AddrLocker
signer ethtypes.Signer
}
// NewPublicAPI creates an instance of the public ETH Web3 API.
func NewPublicAPI(
logger log.Logger,
clientCtx client.Context,
backend backend.EVMBackend,
nonceLock *rpctypes.AddrLocker,
) *PublicAPI {
eip155ChainID, err := ethermint.ParseChainID(clientCtx.ChainID)
if err != nil {
panic(err)
}
algos, _ := clientCtx.Keyring.SupportedAlgorithms()
if !algos.Contains(hd.EthSecp256k1) {
kr, err := keyring.New(
sdk.KeyringServiceName(),
viper.GetString(flags.FlagKeyringBackend),
clientCtx.KeyringDir,
clientCtx.Input,
hd.EthSecp256k1Option(),
)
if err != nil {
panic(err)
}
clientCtx = clientCtx.WithKeyring(kr)
}
// The signer used by the API should always be the 'latest' known one because we expect
// signers to be backwards-compatible with old transactions.
cfg := backend.ChainConfig()
if cfg == nil {
cfg = evmtypes.DefaultChainConfig().EthereumConfig(eip155ChainID)
}
signer := ethtypes.LatestSigner(cfg)
api := &PublicAPI{
ctx: context.Background(),
clientCtx: clientCtx,
queryClient: rpctypes.NewQueryClient(clientCtx),
chainIDEpoch: eip155ChainID,
logger: logger.With("client", "json-rpc"),
backend: backend,
nonceLock: nonceLock,
signer: signer,
}
return api
}
// ClientCtx returns client context
func (e *PublicAPI) ClientCtx() client.Context {
return e.clientCtx
}
func (e *PublicAPI) QueryClient() *rpctypes.QueryClient {
return e.queryClient
}
func (e *PublicAPI) Ctx() context.Context {
return e.ctx
}
// ProtocolVersion returns the supported Ethereum protocol version.
func (e *PublicAPI) ProtocolVersion() hexutil.Uint {
e.logger.Debug("eth_protocolVersion")
return hexutil.Uint(ethermint.ProtocolVersion)
}
// ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config.
func (e *PublicAPI) ChainId() (*hexutil.Big, error) { // nolint
e.logger.Debug("eth_chainId")
// if current block is at or past the EIP-155 replay-protection fork block, return chainID from config
bn, err := e.backend.BlockNumber()
if err != nil {
e.logger.Debug("failed to fetch latest block number", "error", err.Error())
return (*hexutil.Big)(e.chainIDEpoch), nil
}
if config := e.backend.ChainConfig(); config.IsEIP155(new(big.Int).SetUint64(uint64(bn))) {
return (*hexutil.Big)(config.ChainID), nil
}
return nil, fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block")
}
// 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 synchronize 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 (e *PublicAPI) Syncing() (interface{}, error) {
e.logger.Debug("eth_syncing")
status, err := e.clientCtx.Client.Status(e.ctx)
if err != nil {
return false, err
}
if !status.SyncInfo.CatchingUp {
return false, nil
}
return map[string]interface{}{
"startingBlock": hexutil.Uint64(status.SyncInfo.EarliestBlockHeight),
"currentBlock": hexutil.Uint64(status.SyncInfo.LatestBlockHeight),
// "highestBlock": nil, // NA
// "pulledStates": nil, // NA
// "knownStates": nil, // NA
}, nil
}
// Coinbase is the address that staking rewards will be send to (alias for Etherbase).
func (e *PublicAPI) Coinbase() (string, error) {
e.logger.Debug("eth_coinbase")
coinbase, err := e.backend.GetCoinbase()
if err != nil {
return "", err
}
ethAddr := common.BytesToAddress(coinbase.Bytes())
return ethAddr.Hex(), nil
}
// Mining returns whether or not this node is currently mining. Always false.
func (e *PublicAPI) Mining() bool {
e.logger.Debug("eth_mining")
return false
}
// Hashrate returns the current node's hashrate. Always 0.
func (e *PublicAPI) Hashrate() hexutil.Uint64 {
e.logger.Debug("eth_hashrate")
return 0
}
// GasPrice returns the current gas price based on Ethermint's gas price oracle.
func (e *PublicAPI) GasPrice() (*hexutil.Big, error) {
e.logger.Debug("eth_gasPrice")
var (
result *big.Int
err error
)
if head := e.backend.CurrentHeader(); head.BaseFee != nil {
result, err = e.backend.SuggestGasTipCap(head.BaseFee)
if err != nil {
return nil, err
}
result = result.Add(result, head.BaseFee)
} else {
result = big.NewInt(e.backend.RPCMinGasPrice())
}
// return at least GlobalMinGasPrice from FeeMarket module
minGasPrice, err := e.backend.GlobalMinGasPrice()
if err != nil {
return nil, err
}
minGasPriceInt := minGasPrice.TruncateInt().BigInt()
if result.Cmp(minGasPriceInt) < 0 {
result = minGasPriceInt
}
return (*hexutil.Big)(result), nil
}
// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic fee transactions.
func (e *PublicAPI) MaxPriorityFeePerGas() (*hexutil.Big, error) {
e.logger.Debug("eth_maxPriorityFeePerGas")
head := e.backend.CurrentHeader()
tipcap, err := e.backend.SuggestGasTipCap(head.BaseFee)
if err != nil {
return nil, err
}
return (*hexutil.Big)(tipcap), nil
}
func (e *PublicAPI) FeeHistory(blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*rpctypes.FeeHistoryResult, error) {
e.logger.Debug("eth_feeHistory")
return e.backend.FeeHistory(blockCount, lastBlock, rewardPercentiles)
}
// Accounts returns the list of accounts available to this node.
func (e *PublicAPI) Accounts() ([]common.Address, error) {
e.logger.Debug("eth_accounts")
addresses := make([]common.Address, 0) // return [] instead of nil if empty
infos, err := e.clientCtx.Keyring.List()
if err != nil {
return addresses, err
}
for _, info := range infos {
addressBytes := info.GetPubKey().Address().Bytes()
addresses = append(addresses, common.BytesToAddress(addressBytes))
}
return addresses, nil
}
// BlockNumber returns the current block number.
func (e *PublicAPI) BlockNumber() (hexutil.Uint64, error) {
e.logger.Debug("eth_blockNumber")
return e.backend.BlockNumber()
}
// GetBalance returns the provided account's balance up to the provided block number.
func (e *PublicAPI) GetBalance(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Big, error) {
e.logger.Debug("eth_getBalance", "address", address.String(), "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
return nil, err
}
req := &evmtypes.QueryBalanceRequest{
Address: address.String(),
}
res, err := e.queryClient.Balance(rpctypes.ContextWithHeight(blockNum.Int64()), req)
if err != nil {
return nil, err
}
val, ok := sdk.NewIntFromString(res.Balance)
if !ok {
return nil, errors.New("invalid balance")
}
return (*hexutil.Big)(val.BigInt()), nil
}
// GetStorageAt returns the contract storage at the given address, block number, and key.
func (e *PublicAPI) GetStorageAt(address common.Address, key string, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) {
e.logger.Debug("eth_getStorageAt", "address", address.Hex(), "key", key, "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
return nil, err
}
req := &evmtypes.QueryStorageRequest{
Address: address.String(),
Key: key,
}
res, err := e.queryClient.Storage(rpctypes.ContextWithHeight(blockNum.Int64()), req)
if err != nil {
return nil, err
}
value := common.HexToHash(res.Value)
return value.Bytes(), nil
}
// GetTransactionCount returns the number of transactions at the given address up to the given block number.
func (e *PublicAPI) GetTransactionCount(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Uint64, error) {
e.logger.Debug("eth_getTransactionCount", "address", address.Hex(), "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
return nil, err
}
return e.backend.GetTransactionCount(address, blockNum)
}
// GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash.
func (e *PublicAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint {
e.logger.Debug("eth_getBlockTransactionCountByHash", "hash", hash.Hex())
block, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes())
if err != nil {
e.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error())
return nil
}
if block.Block == nil {
e.logger.Debug("block not found", "hash", hash.Hex())
return nil
}
blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
if err != nil {
return nil
}
ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(block, blockRes)
n := hexutil.Uint(len(ethMsgs))
return &n
}
// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number.
func (e *PublicAPI) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint {
e.logger.Debug("eth_getBlockTransactionCountByNumber", "height", blockNum.Int64())
block, err := e.backend.GetTendermintBlockByNumber(blockNum)
if err != nil {
e.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error())
return nil
}
if block.Block == nil {
e.logger.Debug("block not found", "height", blockNum.Int64())
return nil
}
blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
if err != nil {
return nil
}
ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(block, blockRes)
n := hexutil.Uint(len(ethMsgs))
return &n
}
// GetUncleCountByBlockHash returns the number of uncles in the block identified by hash. Always zero.
func (e *PublicAPI) GetUncleCountByBlockHash(hash common.Hash) hexutil.Uint {
return 0
}
// GetUncleCountByBlockNumber returns the number of uncles in the block identified by number. Always zero.
func (e *PublicAPI) GetUncleCountByBlockNumber(blockNum rpctypes.BlockNumber) hexutil.Uint {
return 0
}
// GetCode returns the contract code at the given address and block number.
func (e *PublicAPI) GetCode(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) {
e.logger.Debug("eth_getCode", "address", address.Hex(), "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
return nil, err
}
req := &evmtypes.QueryCodeRequest{
Address: address.String(),
}
res, err := e.queryClient.Code(rpctypes.ContextWithHeight(blockNum.Int64()), req)
if err != nil {
return nil, err
}
return res.Code, nil
}
// GetTransactionLogs returns the logs given a transaction hash.
func (e *PublicAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
e.logger.Debug("eth_getTransactionLogs", "hash", txHash)
hexTx := txHash.Hex()
res, err := e.backend.GetTxByEthHash(txHash)
if err != nil {
e.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
return nil, nil
}
if res.TxResult.Code != 0 {
// failed, return empty logs
return nil, nil
}
parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult)
if err != nil {
return nil, fmt.Errorf("failed to parse tx events: %s, %v", hexTx, err)
}
parsedTx := parsedTxs.GetTxByHash(txHash)
if parsedTx == nil {
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
}
// parse tx logs from events
return parsedTx.ParseTxLogs()
}
// Sign signs the provided data using the private key of address via Geth's signature standard.
func (e *PublicAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
e.logger.Debug("eth_sign", "address", address.Hex(), "data", common.Bytes2Hex(data))
from := sdk.AccAddress(address.Bytes())
_, err := e.clientCtx.Keyring.KeyByAddress(from)
if err != nil {
e.logger.Error("failed to find key in keyring", "address", address.String())
return nil, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error())
}
// Sign the requested hash with the wallet
signature, _, err := e.clientCtx.Keyring.SignByAddress(from, data)
if err != nil {
e.logger.Error("keyring.SignByAddress failed", "address", address.Hex())
return nil, err
}
signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
return signature, nil
}
// SignTypedData signs EIP-712 conformant typed data
func (e *PublicAPI) SignTypedData(address common.Address, typedData apitypes.TypedData) (hexutil.Bytes, error) {
e.logger.Debug("eth_signTypedData", "address", address.Hex(), "data", typedData)
from := sdk.AccAddress(address.Bytes())
_, err := e.clientCtx.Keyring.KeyByAddress(from)
if err != nil {
e.logger.Error("failed to find key in keyring", "address", address.String())
return nil, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error())
}
sigHash, err := eip712.ComputeTypedDataHash(typedData)
if err != nil {
return nil, err
}
// Sign the requested hash with the wallet
signature, _, err := e.clientCtx.Keyring.SignByAddress(from, sigHash)
if err != nil {
e.logger.Error("keyring.SignByAddress failed", "address", address.Hex())
return nil, err
}
signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
return signature, nil
}
// SendTransaction sends an Ethereum transaction.
func (e *PublicAPI) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) {
e.logger.Debug("eth_sendTransaction", "args", args.String())
return e.backend.SendTransaction(args)
}
// 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 (e *PublicAPI) FillTransaction(args evmtypes.TransactionArgs) (*rpctypes.SignTransactionResult, error) {
// Set some sanity defaults and terminate on failure
args, err := e.backend.SetTxDefaults(args)
if err != nil {
return nil, err
}
// Assemble the transaction and obtain rlp
tx := args.ToTransaction().AsTransaction()
data, err := tx.MarshalBinary()
if err != nil {
return nil, err
}
return &rpctypes.SignTransactionResult{
Raw: data,
Tx: tx,
}, nil
}
// SendRawTransaction send a raw Ethereum transaction.
func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) {
e.logger.Debug("eth_sendRawTransaction", "length", len(data))
// RLP decode raw transaction bytes
tx := &ethtypes.Transaction{}
if err := tx.UnmarshalBinary(data); err != nil {
e.logger.Error("transaction decoding failed", "error", err.Error())
return common.Hash{}, err
}
// check the local node config in case unprotected txs are disabled
if !e.backend.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")
}
ethereumTx := &evmtypes.MsgEthereumTx{}
if err := ethereumTx.FromEthereumTx(tx); err != nil {
e.logger.Error("transaction converting failed", "error", err.Error())
return common.Hash{}, err
}
if err := ethereumTx.ValidateBasic(); err != nil {
e.logger.Debug("tx failed basic validation", "error", err.Error())
return common.Hash{}, err
}
// Query params to use the EVM denomination
res, err := e.queryClient.QueryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{})
if err != nil {
e.logger.Error("failed to query evm params", "error", err.Error())
return common.Hash{}, err
}
cosmosTx, err := ethereumTx.BuildTx(e.clientCtx.TxConfig.NewTxBuilder(), res.Params.EvmDenom)
if err != nil {
e.logger.Error("failed to build cosmos tx", "error", err.Error())
return common.Hash{}, err
}
// Encode transaction by default Tx encoder
txBytes, err := e.clientCtx.TxConfig.TxEncoder()(cosmosTx)
if err != nil {
e.logger.Error("failed to encode eth tx using default encoder", "error", err.Error())
return common.Hash{}, err
}
txHash := ethereumTx.AsTransaction().Hash()
syncCtx := e.clientCtx.WithBroadcastMode(flags.BroadcastSync)
rsp, err := syncCtx.BroadcastTx(txBytes)
if rsp != nil && rsp.Code != 0 {
err = sdkerrors.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog)
}
if err != nil {
e.logger.Error("failed to broadcast tx", "error", err.Error())
return txHash, err
}
return txHash, nil
}
// 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
}
totalfee := new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas)))
// 1 photon in 10^18 aphoton
oneToken := new(big.Float).SetInt(big.NewInt(params.Ether))
// quo = rounded(x/y)
feeEth := new(big.Float).Quo(totalfee, oneToken)
// no need to check error from parsing
feeFloat, _ := feeEth.Float64()
if feeFloat > cap {
return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap)
}
return 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 (e *PublicAPI) Resend(ctx context.Context, args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) {
e.logger.Debug("eth_resend", "args", args.String())
if args.Nonce == nil {
return common.Hash{}, fmt.Errorf("missing transaction nonce in transaction spec")
}
args, err := e.backend.SetTxDefaults(args)
if err != nil {
return common.Hash{}, err
}
matchTx := args.ToTransaction().AsTransaction()
// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
price := matchTx.GasPrice()
if gasPrice != nil {
price = gasPrice.ToInt()
}
gas := matchTx.Gas()
if gasLimit != nil {
gas = uint64(*gasLimit)
}
if err := checkTxFee(price, gas, e.backend.RPCTxFeeCap()); err != nil {
return common.Hash{}, err
}
pending, err := e.backend.PendingTransactions()
if err != nil {
return common.Hash{}, err
}
for _, tx := range pending {
// FIXME does Resend api possible at all? https://github.com/evmos/ethermint/issues/905
p, err := evmtypes.UnwrapEthereumMsg(tx, common.Hash{})
if err != nil {
// not valid ethereum tx
continue
}
pTx := p.AsTransaction()
wantSigHash := e.signer.Hash(matchTx)
pFrom, err := ethtypes.Sender(e.signer, pTx)
if err != nil {
continue
}
if pFrom == *args.From && e.signer.Hash(pTx) == wantSigHash {
// Match. Re-sign and send the transaction.
if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 {
args.GasPrice = gasPrice
}
if gasLimit != nil && *gasLimit != 0 {
args.Gas = gasLimit
}
return e.backend.SendTransaction(args) // TODO: this calls SetTxDefaults again, refactor to avoid calling it twice
}
}
return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash())
}
// Call performs a raw contract call.
func (e *PublicAPI) Call(args evmtypes.TransactionArgs, blockNrOrHash rpctypes.BlockNumberOrHash, _ *rpctypes.StateOverride) (hexutil.Bytes, error) {
e.logger.Debug("eth_call", "args", args.String(), "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
return nil, err
}
data, err := e.doCall(args, blockNum)
if err != nil {
return []byte{}, err
}
return (hexutil.Bytes)(data.Ret), nil
}
// DoCall performs a simulated call operation through the evmtypes. It returns the
// estimated gas used on the operation or an error if fails.
func (e *PublicAPI) doCall(
args evmtypes.TransactionArgs, blockNr rpctypes.BlockNumber,
) (*evmtypes.MsgEthereumTxResponse, error) {
bz, err := json.Marshal(&args)
if err != nil {
return nil, err
}
req := evmtypes.EthCallRequest{
Args: bz,
GasCap: e.backend.RPCGasCap(),
}
// From ContextWithHeight: if the provided height is 0,
// it will return an empty context and the gRPC query will use
// the latest block height for querying.
ctx := rpctypes.ContextWithHeight(blockNr.Int64())
timeout := e.backend.RPCEVMTimeout()
// Setup context so it may be canceled 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 canceled when the call has completed
// this makes sure resources are cleaned up.
defer cancel()
res, err := e.queryClient.EthCall(ctx, &req)
if err != nil {
return nil, err
}
if res.Failed() {
if res.VmError != vm.ErrExecutionReverted.Error() {
return nil, status.Error(codes.Internal, res.VmError)
}
return nil, evmtypes.NewExecErrorWithReason(res.Ret)
}
return res, nil
}
// EstimateGas returns an estimate of gas usage for the given smart contract call.
func (e *PublicAPI) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error) {
e.logger.Debug("eth_estimateGas")
return e.backend.EstimateGas(args, blockNrOptional)
}
// GetBlockByHash returns the block identified by hash.
func (e *PublicAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
e.logger.Debug("eth_getBlockByHash", "hash", hash.Hex(), "full", fullTx)
return e.backend.GetBlockByHash(hash, fullTx)
}
// GetBlockByNumber returns the block identified by number.
func (e *PublicAPI) GetBlockByNumber(ethBlockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) {
e.logger.Debug("eth_getBlockByNumber", "number", ethBlockNum, "full", fullTx)
return e.backend.GetBlockByNumber(ethBlockNum, fullTx)
}
// GetTransactionByHash returns the transaction identified by hash.
func (e *PublicAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransaction, error) {
e.logger.Debug("eth_getTransactionByHash", "hash", hash.Hex())
return e.backend.GetTransactionByHash(hash)
}
// getTransactionByBlockAndIndex is the common code shared by `GetTransactionByBlockNumberAndIndex` and `GetTransactionByBlockHashAndIndex`.
func (e *PublicAPI) getTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
var msg *evmtypes.MsgEthereumTx
// try /tx_search first
res, err := e.backend.GetTxByTxIndex(block.Block.Height, uint(idx))
if err == nil {
tx, err := e.clientCtx.TxConfig.TxDecoder()(res.Tx)
if err != nil {
e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
return nil, nil
}
parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult)
if err != nil {
return nil, fmt.Errorf("failed to parse tx events: %d, %v", idx, err)
}
parsedTx := parsedTxs.GetTxByTxIndex(int(idx))
if parsedTx == nil {
return nil, fmt.Errorf("ethereum tx not found in msgs: %d", idx)
}
var ok bool
// msgIndex is inferred from tx events, should be within bound.
msg, ok = tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
if !ok {
e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
return nil, nil
}
} else {
blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
if err != nil {
return nil, nil
}
i := int(idx)
ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(block, blockRes)
if i >= len(ethMsgs) {
e.logger.Debug("block txs index out of bound", "index", i)
return nil, nil
}
msg = ethMsgs[i]
}
baseFee, err := e.backend.BaseFee(block.Block.Height)
if err != nil {
return nil, err
}
return rpctypes.NewTransactionFromMsg(
msg,
common.BytesToHash(block.Block.Hash()),
uint64(block.Block.Height),
uint64(idx),
baseFee,
)
}
// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index.
func (e *PublicAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
e.logger.Debug("eth_getTransactionByBlockHashAndIndex", "hash", hash.Hex(), "index", idx)
block, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes())
if err != nil {
e.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error())
return nil, nil
}
if block.Block == nil {
e.logger.Debug("block not found", "hash", hash.Hex())
return nil, nil
}
return e.getTransactionByBlockAndIndex(block, idx)
}
// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index.
func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
e.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx)
block, err := e.backend.GetTendermintBlockByNumber(blockNum)
if err != nil {
e.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error())
return nil, nil
}
if block.Block == nil {
e.logger.Debug("block not found", "height", blockNum.Int64())
return nil, nil
}
return e.getTransactionByBlockAndIndex(block, idx)
}
// GetTransactionReceipt returns the transaction receipt identified by hash.
func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
hexTx := hash.Hex()
e.logger.Debug("eth_getTransactionReceipt", "hash", hexTx)
res, err := e.backend.GetTxByEthHash(hash)
if err != nil {
e.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
return nil, nil
}
// don't ignore the txs which exceed block gas limit.
if !backend.TxSuccessOrExceedsBlockGasLimit(&res.TxResult) {
return nil, nil
}
parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult)
if err != nil {
return nil, fmt.Errorf("failed to parse tx events: %s, %v", hexTx, err)
}
parsedTx := parsedTxs.GetTxByHash(hash)
if parsedTx == nil {
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
}
resBlock, err := e.clientCtx.Client.Block(e.ctx, &res.Height)
if err != nil {
e.logger.Debug("block not found", "height", res.Height, "error", err.Error())
return nil, nil
}
tx, err := e.clientCtx.TxConfig.TxDecoder()(res.Tx)
if err != nil {
e.logger.Debug("decoding failed", "error", err.Error())
return nil, fmt.Errorf("failed to decode tx: %w", err)
}
if res.TxResult.Code != 0 {
// tx failed, we should return gas limit as gas used, because that's how the fee get deducted.
for i := 0; i <= parsedTx.MsgIndex; i++ {
gasLimit := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx).GetGas()
parsedTxs.Txs[i].GasUsed = gasLimit
}
}
// the `msgIndex` is inferred from tx events, should be within the bound,
// and the tx is found by eth tx hash, so the msg type must be correct.
ethMsg := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
txData, err := evmtypes.UnpackTxData(ethMsg.Data)
if err != nil {
e.logger.Error("failed to unpack tx data", "error", err.Error())
return nil, err
}
cumulativeGasUsed := uint64(0)
blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &res.Height)
if err != nil {
e.logger.Debug("failed to retrieve block results", "height", res.Height, "error", err.Error())
return nil, nil
}
for i := 0; i < int(res.Index) && i < len(blockRes.TxsResults); i++ {
cumulativeGasUsed += uint64(blockRes.TxsResults[i].GasUsed)
}
cumulativeGasUsed += parsedTxs.AccumulativeGasUsed(parsedTx.MsgIndex)
// Get the transaction result from the log
var status hexutil.Uint
if res.TxResult.Code != 0 || parsedTx.Failed {
status = hexutil.Uint(ethtypes.ReceiptStatusFailed)
} else {
status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful)
}
from, err := ethMsg.GetSender(e.chainIDEpoch)
if err != nil {
return nil, err
}
// parse tx logs from events
logs, err := parsedTx.ParseTxLogs()
if err != nil {
e.logger.Debug("failed to parse logs", "hash", hexTx, "error", err.Error())
}
if parsedTx.EthTxIndex == -1 {
// Fallback to find tx index by iterating all valid eth transactions
msgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes)
for i := range msgs {
if msgs[i].Hash == hexTx {
parsedTx.EthTxIndex = int64(i)
break
}
}
}
if parsedTx.EthTxIndex == -1 {
return nil, errors.New("can't find index of ethereum tx")
}
receipt := map[string]interface{}{
// Consensus fields: These fields are defined by the Yellow Paper
"status": status,
"cumulativeGasUsed": hexutil.Uint64(cumulativeGasUsed),
"logsBloom": ethtypes.BytesToBloom(ethtypes.LogsBloom(logs)),
"logs": logs,
// Implementation fields: These fields are added by geth when processing a transaction.
// They are stored in the chain database.
"transactionHash": hash,
"contractAddress": nil,
"gasUsed": hexutil.Uint64(parsedTx.GasUsed),
"type": hexutil.Uint(txData.TxType()),
// Inclusion information: These fields provide information about the inclusion of the
// transaction corresponding to this receipt.
"blockHash": common.BytesToHash(resBlock.Block.Header.Hash()).Hex(),
"blockNumber": hexutil.Uint64(res.Height),
"transactionIndex": hexutil.Uint64(parsedTx.EthTxIndex),
// sender and receiver (contract or EOA) addreses
"from": from,
"to": txData.GetTo(),
}
if logs == nil {
receipt["logs"] = [][]*ethtypes.Log{}
}
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
if txData.GetTo() == nil {
receipt["contractAddress"] = crypto.CreateAddress(from, txData.GetNonce())
}
if dynamicTx, ok := txData.(*evmtypes.DynamicFeeTx); ok {
baseFee, err := e.backend.BaseFee(res.Height)
if err != nil {
return nil, err
}
receipt["effectiveGasPrice"] = hexutil.Big(*dynamicTx.GetEffectiveGasPrice(baseFee))
}
return receipt, nil
}
// GetPendingTransactions returns the transactions that are in the transaction pool
// and have a from address that is one of the accounts this node manages.
func (e *PublicAPI) GetPendingTransactions() ([]*rpctypes.RPCTransaction, error) {
e.logger.Debug("eth_getPendingTransactions")
txs, err := e.backend.PendingTransactions()
if err != nil {
return nil, err
}
result := make([]*rpctypes.RPCTransaction, 0, len(txs))
for _, tx := range txs {
for _, msg := range (*tx).GetMsgs() {
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
// not valid ethereum tx
break
}
rpctx, err := rpctypes.NewTransactionFromMsg(
ethMsg,
common.Hash{},
uint64(0),
uint64(0),
nil,
)
if err != nil {
return nil, err
}
result = append(result, rpctx)
}
}
return result, nil
}
// GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil.
func (e *PublicAPI) GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) map[string]interface{} {
return nil
}
// GetUncleByBlockNumberAndIndex returns the uncle identified by number and index. Always returns nil.
func (e *PublicAPI) GetUncleByBlockNumberAndIndex(number, idx hexutil.Uint) map[string]interface{} {
return nil
}
// GetProof returns an account object with proof and any storage proofs
func (e *PublicAPI) GetProof(address common.Address, storageKeys []string, blockNrOrHash rpctypes.BlockNumberOrHash) (*rpctypes.AccountResult, error) {
e.logger.Debug("eth_getProof", "address", address.Hex(), "keys", storageKeys, "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
return nil, err
}
height := blockNum.Int64()
ctx := rpctypes.ContextWithHeight(height)
// if the height is equal to zero, meaning the query condition of the block is either "pending" or "latest"
if height == 0 {
bn, err := e.backend.BlockNumber()
if err != nil {
return nil, err
}
if bn > math.MaxInt64 {
return nil, fmt.Errorf("not able to query block number greater than MaxInt64")
}
height = int64(bn)
}
clientCtx := e.clientCtx.WithHeight(height)
// query storage proofs
storageProofs := make([]rpctypes.StorageResult, len(storageKeys))
for i, key := range storageKeys {
hexKey := common.HexToHash(key)
valueBz, proof, err := e.queryClient.GetProof(clientCtx, evmtypes.StoreKey, evmtypes.StateKey(address, hexKey.Bytes()))
if err != nil {
return nil, err
}
// check for proof
var proofStr string
if proof != nil {
proofStr = proof.String()
}
storageProofs[i] = rpctypes.StorageResult{
Key: key,
Value: (*hexutil.Big)(new(big.Int).SetBytes(valueBz)),
Proof: []string{proofStr},
}
}
// query EVM account
req := &evmtypes.QueryAccountRequest{
Address: address.String(),
}
res, err := e.queryClient.Account(ctx, req)
if err != nil {
return nil, err
}
// query account proofs
accountKey := authtypes.AddressStoreKey(sdk.AccAddress(address.Bytes()))
_, proof, err := e.queryClient.GetProof(clientCtx, authtypes.StoreKey, accountKey)
if err != nil {
return nil, err
}
// check for proof
var accProofStr string
if proof != nil {
accProofStr = proof.String()
}
balance, ok := sdk.NewIntFromString(res.Balance)
if !ok {
return nil, errors.New("invalid balance")
}
return &rpctypes.AccountResult{
Address: address,
AccountProof: []string{accProofStr},
Balance: (*hexutil.Big)(balance.BigInt()),
CodeHash: common.HexToHash(res.CodeHash),
Nonce: hexutil.Uint64(res.Nonce),
StorageHash: common.Hash{}, // NOTE: Ethermint doesn't have a storage hash. TODO: implement?
StorageProof: storageProofs,
}, nil
}
// getBlockNumber returns the BlockNumber from BlockNumberOrHash
func (e *PublicAPI) getBlockNumber(blockNrOrHash rpctypes.BlockNumberOrHash) (rpctypes.BlockNumber, error) {
switch {
case blockNrOrHash.BlockHash == nil && blockNrOrHash.BlockNumber == nil:
return rpctypes.EthEarliestBlockNumber, fmt.Errorf("types BlockHash and BlockNumber cannot be both nil")
case blockNrOrHash.BlockHash != nil:
blockHeader, err := e.backend.HeaderByHash(*blockNrOrHash.BlockHash)
if err != nil {
return rpctypes.EthEarliestBlockNumber, err
}
return rpctypes.NewBlockNumber(blockHeader.Number), nil
case blockNrOrHash.BlockNumber != nil:
return *blockNrOrHash.BlockNumber, nil
default:
return rpctypes.EthEarliestBlockNumber, nil
}
}