package eth import ( "context" "math/big" "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/ethereum/go-ethereum/rpc" "github.com/tendermint/tendermint/libs/log" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "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" ) // The Ethereum API allows applications to connect to an Evmos node that is // part of the Evmos blockchain. Developers can interact with on-chain EVM data // and send different types of transactions to the network by utilizing the // endpoints provided by the API. The API follows a JSON-RPC standard. If not // otherwise specified, the interface is derived from the Alchemy Ethereum API: // https://docs.alchemy.com/alchemy/apis/ethereum type EthereumAPI interface { // Getting Blocks // // Retrieves information from a particular block in the blockchain. BlockNumber() (hexutil.Uint64, error) GetBlockByNumber(ethBlockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) // Reading Transactions // // Retrieves information on the state data for addresses regardless of whether // it is a user or a smart contract. GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransaction, error) GetTransactionCount(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Uint64, error) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) // eth_getBlockReceipts // Writing Transactions // // Allows developers to both send ETH from one address to another, write data // on-chain, and interact with smart contracts. SendRawTransaction(data hexutil.Bytes) (common.Hash, error) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) // eth_sendPrivateTransaction // eth_cancel PrivateTransaction // Account Information // // Returns information regarding an address's stored on-chain data. Accounts() ([]common.Address, error) GetBalance(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Big, error) GetStorageAt(address common.Address, key string, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) GetCode(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) GetProof(address common.Address, storageKeys []string, blockNrOrHash rpctypes.BlockNumberOrHash) (*rpctypes.AccountResult, error) // EVM/Smart Contract Execution // // Allows developers to read data from the blockchain which includes executing // smart contracts. However, no data is published to the Ethereum network. Call(args evmtypes.TransactionArgs, blockNrOrHash rpctypes.BlockNumberOrHash, _ *rpctypes.StateOverride) (hexutil.Bytes, error) // Chain Information // // Returns information on the Ethereum network and internal settings. ProtocolVersion() hexutil.Uint GasPrice() (*hexutil.Big, error) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error) FeeHistory(blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*rpctypes.FeeHistoryResult, error) MaxPriorityFeePerGas() (*hexutil.Big, error) ChainId() (*hexutil.Big, error) // Getting Uncles // // Returns information on uncle blocks are which are network rejected blocks and replaced by a canonical block instead. GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) map[string]interface{} GetUncleByBlockNumberAndIndex(number, idx hexutil.Uint) map[string]interface{} GetUncleCountByBlockHash(hash common.Hash) hexutil.Uint GetUncleCountByBlockNumber(blockNum rpctypes.BlockNumber) hexutil.Uint // Proof of Work Hashrate() hexutil.Uint64 Mining() bool // Other Syncing() (interface{}, error) Coinbase() (string, error) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) SignTypedData(address common.Address, typedData apitypes.TypedData) (hexutil.Bytes, error) FillTransaction(args evmtypes.TransactionArgs) (*rpctypes.SignTransactionResult, error) Resend(ctx context.Context, args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) GetPendingTransactions() ([]*rpctypes.RPCTransaction, error) // eth_signTransaction (on Ethereum.org) // eth_getCompilers (on Ethereum.org) // eth_compileSolidity (on Ethereum.org) // eth_compileLLL (on Ethereum.org) // eth_compileSerpent (on Ethereum.org) // eth_getWork (on Ethereum.org) // eth_submitWork (on Ethereum.org) // eth_submitHashrate (on Ethereum.org) } var _ EthereumAPI = (*PublicAPI)(nil) // PublicAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. type PublicAPI struct { ctx context.Context logger log.Logger backend backend.EVMBackend } // NewPublicAPI creates an instance of the public ETH Web3 API. func NewPublicAPI( logger log.Logger, backend backend.EVMBackend, ) *PublicAPI { api := &PublicAPI{ ctx: context.Background(), logger: logger.With("client", "json-rpc"), backend: backend, } return api } /////////////////////////////////////////////////////////////////////////////// /// Blocks /// /////////////////////////////////////////////////////////////////////////////// // BlockNumber returns the current block number. func (e *PublicAPI) BlockNumber() (hexutil.Uint64, error) { e.logger.Debug("eth_blockNumber") return e.backend.BlockNumber() } // 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) } // 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) } /////////////////////////////////////////////////////////////////////////////// /// Read Txs /// /////////////////////////////////////////////////////////////////////////////// // 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) } // 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.backend.GetBlockNumber(blockNrOrHash) if err != nil { return nil, err } return e.backend.GetTransactionCount(address, blockNum) } // 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) return e.backend.GetTransactionReceipt(hash) } // 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()) return e.backend.GetBlockTransactionCountByHash(hash) } // 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()) return e.backend.GetBlockTransactionCountByNumber(blockNum) } // 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) return e.backend.GetTransactionByBlockHashAndIndex(hash, 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.backend.GetTransactionByBlockAndIndex(block, idx) } /////////////////////////////////////////////////////////////////////////////// /// Write Txs /// /////////////////////////////////////////////////////////////////////////////// // SendRawTransaction send a raw Ethereum transaction. func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { e.logger.Debug("eth_sendRawTransaction", "length", len(data)) return e.backend.SendRawTransaction(data) } // 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) } /////////////////////////////////////////////////////////////////////////////// /// Account Information /// /////////////////////////////////////////////////////////////////////////////// // Accounts returns the list of accounts available to this node. func (e *PublicAPI) Accounts() ([]common.Address, error) { e.logger.Debug("eth_accounts") return e.backend.Accounts() } // 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) return e.backend.GetBalance(address, blockNrOrHash) } // 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) return e.backend.GetStorageAt(address, key, blockNrOrHash) } // 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) return e.backend.GetCode(address, blockNrOrHash) } // 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) return e.backend.GetProof(address, storageKeys, blockNrOrHash) } /////////////////////////////////////////////////////////////////////////////// /// EVM/Smart Contract Execution /// /////////////////////////////////////////////////////////////////////////////// // 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.backend.GetBlockNumber(blockNrOrHash) if err != nil { return nil, err } data, err := e.backend.DoCall(args, blockNum) if err != nil { return []byte{}, err } return (hexutil.Bytes)(data.Ret), nil } /////////////////////////////////////////////////////////////////////////////// /// Event Logs /// /////////////////////////////////////////////////////////////////////////////// // FILTER API /////////////////////////////////////////////////////////////////////////////// /// Chain Information /// /////////////////////////////////////////////////////////////////////////////// // ProtocolVersion returns the supported Ethereum protocol version. func (e *PublicAPI) ProtocolVersion() hexutil.Uint { e.logger.Debug("eth_protocolVersion") return hexutil.Uint(ethermint.ProtocolVersion) } // 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 } // 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) } 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) } // 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 } // 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") return e.backend.ChainID() } /////////////////////////////////////////////////////////////////////////////// /// Uncles /// /////////////////////////////////////////////////////////////////////////////// // 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 } // 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 } /////////////////////////////////////////////////////////////////////////////// /// Proof of Work /// /////////////////////////////////////////////////////////////////////////////// // Hashrate returns the current node's hashrate. Always 0. func (e *PublicAPI) Hashrate() hexutil.Uint64 { e.logger.Debug("eth_hashrate") return 0 } // Mining returns whether or not this node is currently mining. Always false. func (e *PublicAPI) Mining() bool { e.logger.Debug("eth_mining") return false } /////////////////////////////////////////////////////////////////////////////// /// Other /// /////////////////////////////////////////////////////////////////////////////// // 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") return e.backend.Syncing() } // 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 } // 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)) return e.backend.Sign(address, data) } // 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.Failed { // failed, return empty logs return nil, nil } resBlockResult, err := e.backend.GetTendermintBlockResultByNumber(&res.Height) if err != nil { e.logger.Debug("block result not found", "number", res.Height, "error", err.Error()) return nil, nil } // parse tx logs from events return backend.TxLogsFromEvents(resBlockResult.TxsResults[res.TxIndex].Events, int(res.MsgIndex)) } // 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) return e.backend.SignTypedData(address, typedData) } // 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 } // 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(_ context.Context, args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64, ) (common.Hash, error) { e.logger.Debug("eth_resend", "args", args.String()) return e.backend.Resend(args, gasPrice, gasLimit) } // 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 }