394 lines
12 KiB
Go
394 lines
12 KiB
Go
|
package backend
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
|
||
|
"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"
|
||
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||
|
"github.com/pkg/errors"
|
||
|
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||
|
)
|
||
|
|
||
|
// GetTransactionByHash returns the Ethereum format transaction identified by Ethereum transaction hash
|
||
|
func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransaction, error) {
|
||
|
res, err := b.GetTxByEthHash(txHash)
|
||
|
hexTx := txHash.Hex()
|
||
|
|
||
|
if err != nil {
|
||
|
// try to find tx in mempool
|
||
|
txs, err := b.PendingTransactions()
|
||
|
if err != nil {
|
||
|
b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
for _, tx := range txs {
|
||
|
msg, err := evmtypes.UnwrapEthereumMsg(tx, txHash)
|
||
|
if err != nil {
|
||
|
// not ethereum tx
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if msg.Hash == hexTx {
|
||
|
rpctx, err := rpctypes.NewTransactionFromMsg(
|
||
|
msg,
|
||
|
common.Hash{},
|
||
|
uint64(0),
|
||
|
uint64(0),
|
||
|
nil,
|
||
|
)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return rpctx, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
b.logger.Debug("tx not found", "hash", hexTx)
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
if !TxSuccessOrExceedsBlockGasLimit(&res.TxResult) {
|
||
|
return nil, errors.New("invalid ethereum tx")
|
||
|
}
|
||
|
|
||
|
parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("failed to parse tx events: %s", hexTx)
|
||
|
}
|
||
|
|
||
|
parsedTx := parsedTxs.GetTxByHash(txHash)
|
||
|
if parsedTx == nil {
|
||
|
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
|
||
|
}
|
||
|
|
||
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(res.Tx)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// the `msgIndex` is inferred from tx events, should be within the bound.
|
||
|
msg, ok := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
|
||
|
if !ok {
|
||
|
return nil, errors.New("invalid ethereum tx")
|
||
|
}
|
||
|
|
||
|
block, err := b.clientCtx.Client.Block(b.ctx, &res.Height)
|
||
|
if err != nil {
|
||
|
b.logger.Debug("block not found", "height", res.Height, "error", err.Error())
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&block.Block.Height)
|
||
|
if err != nil {
|
||
|
b.logger.Debug("block result not found", "height", block.Block.Height, "error", err.Error())
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
if parsedTx.EthTxIndex == -1 {
|
||
|
// Fallback to find tx index by iterating all valid eth transactions
|
||
|
msgs := b.GetEthereumMsgsFromTendermintBlock(block, 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")
|
||
|
}
|
||
|
|
||
|
baseFee, err := b.BaseFee(blockRes)
|
||
|
if err != nil {
|
||
|
// handle the error for pruned node.
|
||
|
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", blockRes.Height, "error", err)
|
||
|
}
|
||
|
|
||
|
return rpctypes.NewTransactionFromMsg(
|
||
|
msg,
|
||
|
common.BytesToHash(block.BlockID.Hash.Bytes()),
|
||
|
uint64(res.Height),
|
||
|
uint64(parsedTx.EthTxIndex),
|
||
|
baseFee,
|
||
|
)
|
||
|
}
|
||
|
|
||
|
// GetTransactionReceipt returns the transaction receipt identified by hash.
|
||
|
func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
|
||
|
hexTx := hash.Hex()
|
||
|
b.logger.Debug("eth_getTransactionReceipt", "hash", hexTx)
|
||
|
|
||
|
res, err := b.GetTxByEthHash(hash)
|
||
|
if err != nil {
|
||
|
b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
// don't ignore the txs which exceed block gas limit.
|
||
|
if !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 := b.clientCtx.Client.Block(b.ctx, &res.Height)
|
||
|
if err != nil {
|
||
|
b.logger.Debug("block not found", "height", res.Height, "error", err.Error())
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(res.Tx)
|
||
|
if err != nil {
|
||
|
b.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 {
|
||
|
b.logger.Error("failed to unpack tx data", "error", err.Error())
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
cumulativeGasUsed := uint64(0)
|
||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&res.Height)
|
||
|
if err != nil {
|
||
|
b.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(b.chainID)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// parse tx logs from events
|
||
|
logs, err := parsedTx.ParseTxLogs()
|
||
|
if err != nil {
|
||
|
b.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 := b.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),
|
||
|
|
||
|
// 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 := b.BaseFee(blockRes)
|
||
|
if err != nil {
|
||
|
// tolerate the error for pruned node.
|
||
|
b.logger.Error("fetch basefee failed, node is pruned?", "height", res.Height, "error", err)
|
||
|
} else {
|
||
|
receipt["effectiveGasPrice"] = hexutil.Big(*dynamicTx.EffectiveGasPrice(baseFee))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return receipt, nil
|
||
|
}
|
||
|
|
||
|
// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index.
|
||
|
func (b *Backend) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
|
||
|
b.logger.Debug("eth_getTransactionByBlockHashAndIndex", "hash", hash.Hex(), "index", idx)
|
||
|
|
||
|
block, err := b.clientCtx.Client.BlockByHash(b.ctx, hash.Bytes())
|
||
|
if err != nil {
|
||
|
b.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error())
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
if block.Block == nil {
|
||
|
b.logger.Debug("block not found", "hash", hash.Hex())
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
return b.GetTransactionByBlockAndIndex(block, idx)
|
||
|
}
|
||
|
|
||
|
// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index.
|
||
|
func (b *Backend) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
|
||
|
b.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx)
|
||
|
|
||
|
block, err := b.GetTendermintBlockByNumber(blockNum)
|
||
|
if err != nil {
|
||
|
b.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error())
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
if block.Block == nil {
|
||
|
b.logger.Debug("block not found", "height", blockNum.Int64())
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
return b.GetTransactionByBlockAndIndex(block, idx)
|
||
|
}
|
||
|
|
||
|
// GetTxByEthHash uses `/tx_query` to find transaction by ethereum tx hash
|
||
|
// TODO: Don't need to convert once hashing is fixed on Tendermint
|
||
|
// https://github.com/tendermint/tendermint/issues/6539
|
||
|
func (b *Backend) GetTxByEthHash(hash common.Hash) (*tmrpctypes.ResultTx, error) {
|
||
|
query := fmt.Sprintf("%s.%s='%s'", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash, hash.Hex())
|
||
|
resTxs, err := b.clientCtx.Client.TxSearch(b.ctx, query, false, nil, nil, "")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if len(resTxs.Txs) == 0 {
|
||
|
return nil, errors.Errorf("ethereum tx not found for hash %s", hash.Hex())
|
||
|
}
|
||
|
return resTxs.Txs[0], nil
|
||
|
}
|
||
|
|
||
|
// GetTxByTxIndex uses `/tx_query` to find transaction by tx index of valid ethereum txs
|
||
|
func (b *Backend) GetTxByTxIndex(height int64, index uint) (*tmrpctypes.ResultTx, error) {
|
||
|
query := fmt.Sprintf("tx.height=%d AND %s.%s=%d",
|
||
|
height, evmtypes.TypeMsgEthereumTx,
|
||
|
evmtypes.AttributeKeyTxIndex, index,
|
||
|
)
|
||
|
resTxs, err := b.clientCtx.Client.TxSearch(b.ctx, query, false, nil, nil, "")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if len(resTxs.Txs) == 0 {
|
||
|
return nil, errors.Errorf("ethereum tx not found for block %d index %d", height, index)
|
||
|
}
|
||
|
return resTxs.Txs[0], nil
|
||
|
}
|
||
|
|
||
|
// getTransactionByBlockAndIndex is the common code shared by `GetTransactionByBlockNumberAndIndex` and `GetTransactionByBlockHashAndIndex`.
|
||
|
func (b *Backend) GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
|
||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&block.Block.Height)
|
||
|
if err != nil {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
var msg *evmtypes.MsgEthereumTx
|
||
|
// try /tx_search first
|
||
|
res, err := b.GetTxByTxIndex(block.Block.Height, uint(idx))
|
||
|
if err == nil {
|
||
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(res.Tx)
|
||
|
if err != nil {
|
||
|
b.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 {
|
||
|
b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
|
||
|
return nil, nil
|
||
|
}
|
||
|
} else {
|
||
|
i := int(idx)
|
||
|
ethMsgs := b.GetEthereumMsgsFromTendermintBlock(block, blockRes)
|
||
|
if i >= len(ethMsgs) {
|
||
|
b.logger.Debug("block txs index out of bound", "index", i)
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
msg = ethMsgs[i]
|
||
|
}
|
||
|
|
||
|
baseFee, err := b.BaseFee(blockRes)
|
||
|
if err != nil {
|
||
|
// handle the error for pruned node.
|
||
|
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", block.Block.Height, "error", err)
|
||
|
}
|
||
|
|
||
|
return rpctypes.NewTransactionFromMsg(
|
||
|
msg,
|
||
|
common.BytesToHash(block.Block.Hash()),
|
||
|
uint64(block.Block.Height),
|
||
|
uint64(idx),
|
||
|
baseFee,
|
||
|
)
|
||
|
}
|