feat!: Store eth tx index separately (#1121)

* Store eth tx index separately

Closes: #1075
Solution:
- run a optional indexer service
- adapt the json-rpc to the more efficient query

changelog

changelog

fix lint

fix backward compatibility

fix lint

timeout

better strconv

fix linter

fix package name

add cli command to index old tx

fix for loop

indexer cmd don't have access to local rpc

workaround exceed block gas limit situation

add unit tests for indexer

refactor

polish the indexer module

Update server/config/toml.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

improve comments

share code between GetTxByEthHash and GetTxByIndex

fix unit test

Update server/indexer.go

Co-authored-by: Freddy Caceres <facs95@gmail.com>

* Apply suggestions from code review

* test enable-indexer in integration test

* fix go lint

* address review suggestions

* fix linter

* address review suggestions

- test indexer in backend unit test
- add comments

* fix build

* fix test

* service name

Co-authored-by: Freddy Caceres <facs95@gmail.com>
Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
yihuang
2022-08-11 22:49:05 +02:00
committed by GitHub
co-authored by Freddy Caceres Federico Kunze Küllmer
parent 737c1de694
commit 77ed4aa754
31 changed files with 1559 additions and 345 deletions
+15 -13
View File
@@ -19,6 +19,7 @@ import (
"github.com/evmos/ethermint/rpc/namespaces/ethereum/personal"
"github.com/evmos/ethermint/rpc/namespaces/ethereum/txpool"
"github.com/evmos/ethermint/rpc/namespaces/ethereum/web3"
ethermint "github.com/evmos/ethermint/types"
rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
)
@@ -48,6 +49,7 @@ type APICreator = func(
clientCtx client.Context,
tendermintWebsocketClient *rpcclient.WSClient,
allowUnprotectedTxs bool,
indexer ethermint.EVMTxIndexer,
) []rpc.API
// apiCreators defines the JSON-RPC API namespaces.
@@ -55,8 +57,8 @@ var apiCreators map[string]APICreator
func init() {
apiCreators = map[string]APICreator{
EthNamespace: func(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
EthNamespace: func(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool, indexer ethermint.EVMTxIndexer) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer)
return []rpc.API{
{
Namespace: EthNamespace,
@@ -72,7 +74,7 @@ func init() {
},
}
},
Web3Namespace: func(*server.Context, client.Context, *rpcclient.WSClient, bool) []rpc.API {
Web3Namespace: func(*server.Context, client.Context, *rpcclient.WSClient, bool, ethermint.EVMTxIndexer) []rpc.API {
return []rpc.API{
{
Namespace: Web3Namespace,
@@ -82,7 +84,7 @@ func init() {
},
}
},
NetNamespace: func(_ *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, _ bool) []rpc.API {
NetNamespace: func(_ *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, _ bool, _ ethermint.EVMTxIndexer) []rpc.API {
return []rpc.API{
{
Namespace: NetNamespace,
@@ -92,8 +94,8 @@ func init() {
},
}
},
PersonalNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
PersonalNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool, indexer ethermint.EVMTxIndexer) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer)
return []rpc.API{
{
Namespace: PersonalNamespace,
@@ -103,7 +105,7 @@ func init() {
},
}
},
TxPoolNamespace: func(ctx *server.Context, _ client.Context, _ *rpcclient.WSClient, _ bool) []rpc.API {
TxPoolNamespace: func(ctx *server.Context, _ client.Context, _ *rpcclient.WSClient, _ bool, _ ethermint.EVMTxIndexer) []rpc.API {
return []rpc.API{
{
Namespace: TxPoolNamespace,
@@ -113,8 +115,8 @@ func init() {
},
}
},
DebugNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
DebugNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool, indexer ethermint.EVMTxIndexer) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer)
return []rpc.API{
{
Namespace: DebugNamespace,
@@ -124,8 +126,8 @@ func init() {
},
}
},
MinerNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
MinerNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool, indexer ethermint.EVMTxIndexer) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer)
return []rpc.API{
{
Namespace: MinerNamespace,
@@ -139,12 +141,12 @@ func init() {
}
// GetRPCAPIs returns the list of all APIs
func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool, selectedAPIs []string) []rpc.API {
func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool, indexer ethermint.EVMTxIndexer, selectedAPIs []string) []rpc.API {
var apis []rpc.API
for _, ns := range selectedAPIs {
if creator, ok := apiCreators[ns]; ok {
apis = append(apis, creator(ctx, clientCtx, tmWSClient, allowUnprotectedTxs)...)
apis = append(apis, creator(ctx, clientCtx, tmWSClient, allowUnprotectedTxs, indexer)...)
} else {
ctx.Logger.Error("invalid namespace value", "namespace", ns)
}
+11 -3
View File
@@ -103,8 +103,8 @@ type EVMBackend interface {
// Tx Info
GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransaction, error)
GetTxByEthHash(txHash common.Hash) (*tmrpctypes.ResultTx, error)
GetTxByTxIndex(height int64, txIndex uint) (*tmrpctypes.ResultTx, error)
GetTxByEthHash(txHash common.Hash) (*ethermint.TxResult, error)
GetTxByTxIndex(height int64, txIndex uint) (*ethermint.TxResult, error)
GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error)
GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
@@ -140,10 +140,17 @@ type Backend struct {
chainID *big.Int
cfg config.Config
allowUnprotectedTxs bool
indexer ethermint.EVMTxIndexer
}
// NewBackend creates a new Backend instance for cosmos and ethereum namespaces
func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context, allowUnprotectedTxs bool) *Backend {
func NewBackend(
ctx *server.Context,
logger log.Logger,
clientCtx client.Context,
allowUnprotectedTxs bool,
indexer ethermint.EVMTxIndexer,
) *Backend {
chainID, err := ethermint.ParseChainID(clientCtx.ChainID)
if err != nil {
panic(err)
@@ -176,5 +183,6 @@ func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context
chainID: chainID,
cfg: appConf,
allowUnprotectedTxs: allowUnprotectedTxs,
indexer: indexer,
}
}
+6 -1
View File
@@ -8,6 +8,8 @@ import (
"path/filepath"
"testing"
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/server"
@@ -20,6 +22,7 @@ import (
"github.com/evmos/ethermint/app"
"github.com/evmos/ethermint/crypto/hd"
"github.com/evmos/ethermint/encoding"
"github.com/evmos/ethermint/indexer"
"github.com/evmos/ethermint/rpc/backend/mocks"
rpctypes "github.com/evmos/ethermint/rpc/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
@@ -56,7 +59,9 @@ func (suite *BackendTestSuite) SetupTest() {
allowUnprotectedTxs := false
suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
idxer := indexer.NewKVIndexer(dbm.NewMemDB(), ctx.Logger, clientCtx)
suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, idxer)
suite.backend.queryClient.QueryClient = mocks.NewQueryClient(suite.T())
suite.backend.clientCtx.Client = mocks.NewClient(suite.T())
suite.backend.ctx = rpctypes.ContextWithHeight(1)
+1 -1
View File
@@ -297,7 +297,7 @@ func (b *Backend) GetEthereumMsgsFromTendermintBlock(
// Check if tx exists on EVM by cross checking with blockResults:
// - Include unsuccessful tx that exceeds block gas limit
// - Exclude unsuccessful tx with any other error but ExceedBlockGasLimit
if !TxSuccessOrExceedsBlockGasLimit(txResults[i]) {
if !rpctypes.TxSuccessOrExceedsBlockGasLimit(txResults[i]) {
b.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash()))
continue
}
+2 -2
View File
@@ -949,7 +949,7 @@ func (suite *BackendTestSuite) TestGetEthereumMsgsFromTendermintBlock() {
TxsResults: []*types.ResponseDeliverTx{
{
Code: 1,
Log: ExceedBlockGasLimitError,
Log: ethrpc.ExceedBlockGasLimitError,
},
},
},
@@ -964,7 +964,7 @@ func (suite *BackendTestSuite) TestGetEthereumMsgsFromTendermintBlock() {
TxsResults: []*types.ResponseDeliverTx{
{
Code: 0,
Log: ExceedBlockGasLimitError,
Log: ethrpc.ExceedBlockGasLimitError,
},
},
},
+6 -15
View File
@@ -32,23 +32,14 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi
return nil, err
}
parsedTxs, err := rpctypes.ParseTxResult(&transaction.TxResult)
if err != nil {
return nil, fmt.Errorf("failed to parse tx events: %s", hash.Hex())
}
parsedTx := parsedTxs.GetTxByHash(hash)
if parsedTx == nil {
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hash.Hex())
}
// check tx index is not out of bound
if uint32(len(blk.Block.Txs)) < transaction.Index {
b.logger.Debug("tx index out of bounds", "index", transaction.Index, "hash", hash.String(), "height", blk.Block.Height)
if uint32(len(blk.Block.Txs)) < transaction.TxIndex {
b.logger.Debug("tx index out of bounds", "index", transaction.TxIndex, "hash", hash.String(), "height", blk.Block.Height)
return nil, fmt.Errorf("transaction not included in block %v", blk.Block.Height)
}
var predecessors []*evmtypes.MsgEthereumTx
for _, txBz := range blk.Block.Txs[:transaction.Index] {
for _, txBz := range blk.Block.Txs[:transaction.TxIndex] {
tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
if err != nil {
b.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error())
@@ -64,14 +55,14 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi
}
}
tx, err := b.clientCtx.TxConfig.TxDecoder()(transaction.Tx)
tx, err := b.clientCtx.TxConfig.TxDecoder()(blk.Block.Txs[transaction.TxIndex])
if err != nil {
b.logger.Debug("tx not found", "hash", hash)
return nil, err
}
// add predecessor messages in current cosmos tx
for i := 0; i < parsedTx.MsgIndex; i++ {
for i := 0; i < int(transaction.MsgIndex); i++ {
ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx)
if !ok {
continue
@@ -79,7 +70,7 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi
predecessors = append(predecessors, ethMsg)
}
ethMessage, ok := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
ethMessage, ok := tx.GetMsgs()[transaction.MsgIndex].(*evmtypes.MsgEthereumTx)
if !ok {
b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
return nil, fmt.Errorf("invalid transaction type %T", tx)
+121 -124
View File
@@ -3,11 +3,14 @@ package backend
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"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"
ethermint "github.com/evmos/ethermint/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
"github.com/pkg/errors"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
@@ -19,87 +22,43 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac
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
return b.getTransactionByHashPending(txHash)
}
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)
block, err := b.GetTendermintBlockByNumber(rpctypes.BlockNumber(res.Height))
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)
tx, err := b.clientCtx.TxConfig.TxDecoder()(block.Block.Txs[res.TxIndex])
if err != nil {
return nil, err
}
// the `res.MsgIndex` is inferred from tx index, should be within the bound.
msg, ok := tx.GetMsgs()[res.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 {
if res.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)
res.EthTxIndex = int32(i)
break
}
}
}
if parsedTx.EthTxIndex == -1 {
// if we still unable to find the eth tx index, return error, shouldn't happen.
if res.EthTxIndex == -1 {
return nil, errors.New("can't find index of ethereum tx")
}
@@ -113,11 +72,48 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac
msg,
common.BytesToHash(block.BlockID.Hash.Bytes()),
uint64(res.Height),
uint64(parsedTx.EthTxIndex),
uint64(res.EthTxIndex),
baseFee,
)
}
// getTransactionByHashPending find pending tx from mempool
func (b *Backend) getTransactionByHashPending(txHash common.Hash) (*rpctypes.RPCTransaction, error) {
hexTx := txHash.Hex()
// 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 {
// use zero block values since it's not included in a block yet
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
}
// GetTransactionReceipt returns the transaction receipt identified by hash.
func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
hexTx := hash.Hex()
@@ -129,44 +125,17 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{
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)
resBlock, err := b.GetTendermintBlockByNumber(rpctypes.BlockNumber(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)
tx, err := b.clientCtx.TxConfig.TxDecoder()(resBlock.Block.Txs[res.TxIndex])
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)
ethMsg := tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx)
txData, err := evmtypes.UnpackTxData(ethMsg.Data)
if err != nil {
@@ -180,42 +149,46 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{
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)
for _, txResult := range blockRes.TxsResults[0:res.TxIndex] {
cumulativeGasUsed += uint64(txResult.GasUsed)
}
cumulativeGasUsed += parsedTxs.AccumulativeGasUsed(parsedTx.MsgIndex)
cumulativeGasUsed += res.CumulativeGasUsed
// Get the transaction result from the log
var status hexutil.Uint
if res.TxResult.Code != 0 || parsedTx.Failed {
if res.Failed {
status = hexutil.Uint(ethtypes.ReceiptStatusFailed)
} else {
status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful)
}
from, err := ethMsg.GetSender(b.chainID)
chainID, err := b.ChainID()
if err != nil {
return nil, err
}
from, err := ethMsg.GetSender(chainID.ToInt())
if err != nil {
return nil, err
}
// parse tx logs from events
logs, err := parsedTx.ParseTxLogs()
logs, err := TxLogsFromEvents(blockRes.TxsResults[res.TxIndex].Events, int(res.MsgIndex))
if err != nil {
b.logger.Debug("failed to parse logs", "hash", hexTx, "error", err.Error())
}
if parsedTx.EthTxIndex == -1 {
if res.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)
res.EthTxIndex = int32(i)
break
}
}
}
if parsedTx.EthTxIndex == -1 {
// return error if still unable to find the eth tx index
if res.EthTxIndex == -1 {
return nil, errors.New("can't find index of ethereum tx")
}
@@ -230,13 +203,13 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{
// They are stored in the chain database.
"transactionHash": hash,
"contractAddress": nil,
"gasUsed": hexutil.Uint64(parsedTx.GasUsed),
"gasUsed": hexutil.Uint64(res.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),
"transactionIndex": hexutil.Uint64(res.EthTxIndex),
// sender and receiver (contract or EOA) addreses
"from": from,
@@ -304,32 +277,66 @@ func (b *Backend) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNum
// 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) {
func (b *Backend) GetTxByEthHash(hash common.Hash) (*ethermint.TxResult, error) {
if b.indexer != nil {
return b.indexer.GetByTxHash(hash)
}
// fallback to tendermint tx indexer
query := fmt.Sprintf("%s.%s='%s'", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash, hash.Hex())
resTxs, err := b.clientCtx.Client.TxSearch(b.ctx, query, false, nil, nil, "")
txResult, err := b.queryTendermintTxIndexer(query, func(txs *rpctypes.ParsedTxs) *rpctypes.ParsedTx {
return txs.GetTxByHash(hash)
})
if err != nil {
return nil, err
return nil, sdkerrors.Wrapf(err, "GetTxByEthHash %s", hash.Hex())
}
if len(resTxs.Txs) == 0 {
return nil, errors.Errorf("ethereum tx not found for hash %s", hash.Hex())
}
return resTxs.Txs[0], nil
return txResult, 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) {
func (b *Backend) GetTxByTxIndex(height int64, index uint) (*ethermint.TxResult, error) {
if b.indexer != nil {
return b.indexer.GetByBlockAndIndex(height, int32(index))
}
// fallback to tendermint tx indexer
query := fmt.Sprintf("tx.height=%d AND %s.%s=%d",
height, evmtypes.TypeMsgEthereumTx,
evmtypes.AttributeKeyTxIndex, index,
)
txResult, err := b.queryTendermintTxIndexer(query, func(txs *rpctypes.ParsedTxs) *rpctypes.ParsedTx {
return txs.GetTxByTxIndex(int(index))
})
if err != nil {
return nil, sdkerrors.Wrapf(err, "GetTxByTxIndex %d %d", height, index)
}
return txResult, nil
}
// queryTendermintTxIndexer query tx in tendermint tx indexer
func (b *Backend) queryTendermintTxIndexer(query string, txGetter func(*rpctypes.ParsedTxs) *rpctypes.ParsedTx) (*ethermint.TxResult, error) {
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 nil, errors.New("ethereum tx not found")
}
return resTxs.Txs[0], nil
txResult := resTxs.Txs[0]
if !rpctypes.TxSuccessOrExceedsBlockGasLimit(&txResult.TxResult) {
return nil, errors.New("invalid ethereum tx")
}
var tx sdk.Tx
if txResult.TxResult.Code != 0 {
// it's only needed when the tx exceeds block gas limit
tx, err = b.clientCtx.TxConfig.TxDecoder()(txResult.Tx)
if err != nil {
return nil, fmt.Errorf("invalid ethereum tx")
}
}
return rpctypes.ParseTxIndexerResult(txResult, tx, txGetter)
}
// getTransactionByBlockAndIndex is the common code shared by `GetTransactionByBlockNumberAndIndex` and `GetTransactionByBlockHashAndIndex`.
@@ -340,28 +347,18 @@ func (b *Backend) GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, i
}
var msg *evmtypes.MsgEthereumTx
// try /tx_search first
// find in tx indexer
res, err := b.GetTxByTxIndex(block.Block.Height, uint(idx))
if err == nil {
tx, err := b.clientCtx.TxConfig.TxDecoder()(res.Tx)
tx, err := b.clientCtx.TxConfig.TxDecoder()(block.Block.Txs[res.TxIndex])
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)
msg, ok = tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx)
if !ok {
b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
return nil, nil
-15
View File
@@ -23,10 +23,6 @@ import (
evmtypes "github.com/evmos/ethermint/x/evm/types"
)
// ExceedBlockGasLimitError defines the error message when tx execution exceeds the block gas limit.
// The tx fee is deducted in ante handler, so it shouldn't be ignored in JSON-RPC API.
const ExceedBlockGasLimitError = "out of gas in location: block gas meter; gasWanted:"
type txGasAndReward struct {
gasUsed uint64
reward *big.Int
@@ -247,17 +243,6 @@ func ParseTxLogsFromEvent(event abci.Event) ([]*ethtypes.Log, error) {
return evmtypes.LogsToEthereum(logs), nil
}
// TxExceedBlockGasLimit returns true if the tx exceeds block gas limit.
func TxExceedBlockGasLimit(res *abci.ResponseDeliverTx) bool {
return strings.Contains(res.Log, ExceedBlockGasLimitError)
}
// TxSuccessOrExceedsBlockGasLimit returnsrue if the transaction was successful
// or if it failed with an ExceedBlockGasLimit error
func TxSuccessOrExceedsBlockGasLimit(res *abci.ResponseDeliverTx) bool {
return res.Code == 0 || TxExceedBlockGasLimit(res)
}
// ShouldIgnoreGasUsed returns true if the gasUsed in result should be ignored
// workaround for issue: https://github.com/cosmos/cosmos-sdk/issues/10832
func ShouldIgnoreGasUsed(res *abci.ResponseDeliverTx) bool {
+5 -10
View File
@@ -2,7 +2,6 @@ package eth
import (
"context"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
@@ -453,23 +452,19 @@ func (e *PublicAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, err
return nil, nil
}
if res.TxResult.Code != 0 {
if res.Failed {
// failed, return empty logs
return nil, nil
}
parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult)
resBlockResult, err := e.backend.GetTendermintBlockResultByNumber(&res.Height)
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)
e.logger.Debug("block result not found", "number", res.Height, "error", err.Error())
return nil, nil
}
// parse tx logs from events
return parsedTx.ParseTxLogs()
return backend.TxLogsFromEvents(resBlockResult.TxsResults[res.TxIndex].Events, int(res.MsgIndex))
}
// SignTypedData signs EIP-712 conformant typed data
+68 -58
View File
@@ -1,13 +1,15 @@
package types
import (
"encoding/json"
"fmt"
"strconv"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethermint "github.com/evmos/ethermint/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
abci "github.com/tendermint/tendermint/abci/types"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
)
// EventFormat is the format version of the events.
@@ -52,11 +54,9 @@ type ParsedTx struct {
Hash common.Hash
// -1 means uninitialized
EthTxIndex int64
EthTxIndex int32
GasUsed uint64
Failed bool
// unparsed tx log json strings
RawLogs [][]byte
}
// NewParsedTx initialize a ParsedTx
@@ -64,20 +64,6 @@ func NewParsedTx(msgIndex int) ParsedTx {
return ParsedTx{MsgIndex: msgIndex, EthTxIndex: -1}
}
// ParseTxLogs decode the raw logs into ethereum format.
func (p ParsedTx) ParseTxLogs() ([]*ethtypes.Log, error) {
logs := make([]*evmtypes.Log, 0, len(p.RawLogs))
for _, raw := range p.RawLogs {
var log evmtypes.Log
if err := json.Unmarshal(raw, &log); err != nil {
return nil, err
}
logs = append(logs, &log)
}
return evmtypes.LogsToEthereum(logs), nil
}
// ParsedTxs is the tx infos parsed from eth tx events.
type ParsedTxs struct {
// one item per message
@@ -88,7 +74,7 @@ type ParsedTxs struct {
// ParseTxResult parse eth tx infos from cosmos-sdk events.
// It supports two event formats, the formats are described in the comments of the format constants.
func ParseTxResult(result *abci.ResponseDeliverTx) (*ParsedTxs, error) {
func ParseTxResult(result *abci.ResponseDeliverTx, tx sdk.Tx) (*ParsedTxs, error) {
format := eventFormatUnknown
// the index of current ethereum_tx event in format 1 or the second part of format 2
eventIndex := -1
@@ -97,40 +83,38 @@ func ParseTxResult(result *abci.ResponseDeliverTx) (*ParsedTxs, error) {
TxHashes: make(map[common.Hash]int),
}
for _, event := range result.Events {
switch event.Type {
case evmtypes.EventTypeEthereumTx:
if format == eventFormatUnknown {
// discover the format version by inspect the first ethereum_tx event.
if len(event.Attributes) > 2 {
format = eventFormat1
} else {
format = eventFormat2
}
}
if event.Type != evmtypes.EventTypeEthereumTx {
continue
}
if len(event.Attributes) == 2 {
// the first part of format 2
if format == eventFormatUnknown {
// discover the format version by inspect the first ethereum_tx event.
if len(event.Attributes) > 2 {
format = eventFormat1
} else {
format = eventFormat2
}
}
if len(event.Attributes) == 2 {
// the first part of format 2
if err := p.newTx(event.Attributes); err != nil {
return nil, err
}
} else {
// format 1 or second part of format 2
eventIndex++
if format == eventFormat1 {
// append tx
if err := p.newTx(event.Attributes); err != nil {
return nil, err
}
} else {
// format 1 or second part of format 2
eventIndex++
if format == eventFormat1 {
// append tx
if err := p.newTx(event.Attributes); err != nil {
return nil, err
}
} else {
// the second part of format 2, update tx fields
if err := p.updateTx(eventIndex, event.Attributes); err != nil {
return nil, err
}
// the second part of format 2, update tx fields
if err := p.updateTx(eventIndex, event.Attributes); err != nil {
return nil, err
}
}
case evmtypes.EventTypeTxLog:
// reuse the eventIndex set by previous ethereum_tx event
p.Txs[eventIndex].RawLogs = parseRawLogs(event.Attributes)
}
}
@@ -139,9 +123,42 @@ func ParseTxResult(result *abci.ResponseDeliverTx) (*ParsedTxs, error) {
p.Txs[0].GasUsed = uint64(result.GasUsed)
}
// this could only happen if tx exceeds block gas limit
if result.Code != 0 && tx != nil {
for i := 0; i < len(p.Txs); i++ {
p.Txs[i].Failed = true
// replace gasUsed with gasLimit because that's what's actually deducted.
gasLimit := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx).GetGas()
p.Txs[i].GasUsed = gasLimit
}
}
return p, nil
}
// ParseTxIndexerResult parse tm tx result to a format compatible with the custom tx indexer.
func ParseTxIndexerResult(txResult *tmrpctypes.ResultTx, tx sdk.Tx, getter func(*ParsedTxs) *ParsedTx) (*ethermint.TxResult, error) {
txs, err := ParseTxResult(&txResult.TxResult, tx)
if err != nil {
return nil, fmt.Errorf("failed to parse tx events: block %d, index %d, %v", txResult.Height, txResult.Index, err)
}
parsedTx := getter(txs)
if parsedTx == nil {
return nil, fmt.Errorf("ethereum tx not found in msgs: block %d, index %d", txResult.Height, txResult.Index)
}
return &ethermint.TxResult{
Height: txResult.Height,
TxIndex: txResult.Index,
MsgIndex: uint32(parsedTx.MsgIndex),
EthTxIndex: parsedTx.EthTxIndex,
Failed: parsedTx.Failed,
GasUsed: parsedTx.GasUsed,
CumulativeGasUsed: txs.AccumulativeGasUsed(parsedTx.MsgIndex),
}, nil
}
// newTx parse a new tx from events, called during parsing.
func (p *ParsedTxs) newTx(attrs []abci.EventAttribute) error {
msgIndex := len(p.Txs)
@@ -215,17 +232,17 @@ func fillTxAttribute(tx *ParsedTx, key []byte, value []byte) error {
case evmtypes.AttributeKeyEthereumTxHash:
tx.Hash = common.HexToHash(string(value))
case evmtypes.AttributeKeyTxIndex:
txIndex, err := strconv.ParseInt(string(value), 10, 64)
txIndex, err := strconv.ParseUint(string(value), 10, 31)
if err != nil {
return err
}
tx.EthTxIndex = txIndex
tx.EthTxIndex = int32(txIndex)
case evmtypes.AttributeKeyTxGasUsed:
gasUsed, err := strconv.ParseInt(string(value), 10, 64)
gasUsed, err := strconv.ParseUint(string(value), 10, 64)
if err != nil {
return err
}
tx.GasUsed = uint64(gasUsed)
tx.GasUsed = gasUsed
case evmtypes.AttributeKeyEthereumTxFailed:
tx.Failed = len(value) > 0
}
@@ -240,10 +257,3 @@ func fillTxAttributes(tx *ParsedTx, attrs []abci.EventAttribute) error {
}
return nil
}
func parseRawLogs(attrs []abci.EventAttribute) (logs [][]byte) {
for _, attr := range attrs {
logs = append(logs, attr.Value)
}
return logs
}
+1 -94
View File
@@ -11,11 +11,6 @@ import (
)
func TestParseTxResult(t *testing.T) {
rawLogs := [][]byte{
[]byte("{\"address\":\"0xdcC261c03cD2f33eBea404318Cdc1D9f8b78e1AD\",\"topics\":[\"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\",\"0x000000000000000000000000569608516a81c0b1247310a3e0cd001046da0663\",\"0x0000000000000000000000002eea2c1ae0cdd2622381c2f9201b2a07c037b1f6\"],\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANB/GezJGOI=\",\"blockNumber\":1803258,\"transactionHash\":\"0xcf4354b55b9ac77436cf8b2f5c229ad3b3119b5196cd79ac5c6c382d9f7b0a71\",\"transactionIndex\":1,\"blockHash\":\"0xa69a510b0848180a094904ea9ae3f0ca2216029470c8e03e6941b402aba610d8\",\"logIndex\":5}"),
[]byte("{\"address\":\"0x569608516A81C0B1247310A3E0CD001046dA0663\",\"topics\":[\"0xe2403640ba68fed3a2f88b7557551d1993f84b99bb10ff833f0cf8db0c5e0486\",\"0x0000000000000000000000002eea2c1ae0cdd2622381c2f9201b2a07c037b1f6\"],\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANB/GezJGOI=\",\"blockNumber\":1803258,\"transactionHash\":\"0xcf4354b55b9ac77436cf8b2f5c229ad3b3119b5196cd79ac5c6c382d9f7b0a71\",\"transactionIndex\":1,\"blockHash\":\"0xa69a510b0848180a094904ea9ae3f0ca2216029470c8e03e6941b402aba610d8\",\"logIndex\":6}"),
[]byte("{\"address\":\"0x569608516A81C0B1247310A3E0CD001046dA0663\",\"topics\":[\"0xf279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568\",\"0x0000000000000000000000002eea2c1ae0cdd2622381c2f9201b2a07c037b1f6\",\"0x0000000000000000000000000000000000000000000000000000000000000001\"],\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\",\"blockNumber\":1803258,\"transactionHash\":\"0xcf4354b55b9ac77436cf8b2f5c229ad3b3119b5196cd79ac5c6c382d9f7b0a71\",\"transactionIndex\":1,\"blockHash\":\"0xa69a510b0848180a094904ea9ae3f0ca2216029470c8e03e6941b402aba610d8\",\"logIndex\":7}"),
}
address := "0x57f96e6B86CdeFdB3d412547816a82E3E0EbF9D2"
txHash := common.BigToHash(big.NewInt(1))
txHash2 := common.BigToHash(big.NewInt(2))
@@ -46,11 +41,6 @@ func TestParseTxResult(t *testing.T) {
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
{Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[0]},
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[1]},
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[2]},
}},
{Type: "message", Attributes: []abci.EventAttribute{
{Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")},
{Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
@@ -76,7 +66,6 @@ func TestParseTxResult(t *testing.T) {
EthTxIndex: 10,
GasUsed: 21000,
Failed: false,
RawLogs: rawLogs,
},
{
MsgIndex: 1,
@@ -84,7 +73,6 @@ func TestParseTxResult(t *testing.T) {
EthTxIndex: 11,
GasUsed: 21000,
Failed: true,
RawLogs: nil,
},
},
},
@@ -113,11 +101,6 @@ func TestParseTxResult(t *testing.T) {
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
{Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[0]},
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[1]},
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[2]},
}},
{Type: "message", Attributes: []abci.EventAttribute{
{Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")},
{Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
@@ -133,7 +116,6 @@ func TestParseTxResult(t *testing.T) {
EthTxIndex: 0,
GasUsed: 21000,
Failed: false,
RawLogs: rawLogs,
},
},
},
@@ -150,11 +132,6 @@ func TestParseTxResult(t *testing.T) {
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
{Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[0]},
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[1]},
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[2]},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
{Key: []byte("txIndex"), Value: []byte("0x01")},
@@ -182,11 +159,6 @@ func TestParseTxResult(t *testing.T) {
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
{Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[0]},
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[1]},
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[2]},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
{Key: []byte("txIndex"), Value: []byte("10")},
@@ -243,7 +215,7 @@ func TestParseTxResult(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
parsed, err := ParseTxResult(&tc.response)
parsed, err := ParseTxResult(&tc.response, nil)
if tc.expTxs == nil {
require.Error(t, err)
} else {
@@ -252,8 +224,6 @@ func TestParseTxResult(t *testing.T) {
require.Equal(t, expTx, parsed.GetTxByMsgIndex(msgIndex))
require.Equal(t, expTx, parsed.GetTxByHash(expTx.Hash))
require.Equal(t, expTx, parsed.GetTxByTxIndex(int(expTx.EthTxIndex)))
_, err := expTx.ParseTxLogs()
require.NoError(t, err)
}
// non-exists tx hash
require.Nil(t, parsed.GetTxByHash(common.Hash{}))
@@ -264,66 +234,3 @@ func TestParseTxResult(t *testing.T) {
})
}
}
func TestParseTxLogs(t *testing.T) {
rawLogs := [][]byte{
[]byte("{\"address\":\"0xdcC261c03cD2f33eBea404318Cdc1D9f8b78e1AD\",\"topics\":[\"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\",\"0x000000000000000000000000569608516a81c0b1247310a3e0cd001046da0663\",\"0x0000000000000000000000002eea2c1ae0cdd2622381c2f9201b2a07c037b1f6\"],\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANB/GezJGOI=\",\"blockNumber\":1803258,\"transactionHash\":\"0xcf4354b55b9ac77436cf8b2f5c229ad3b3119b5196cd79ac5c6c382d9f7b0a71\",\"transactionIndex\":1,\"blockHash\":\"0xa69a510b0848180a094904ea9ae3f0ca2216029470c8e03e6941b402aba610d8\",\"logIndex\":5}"),
[]byte("{\"address\":\"0x569608516A81C0B1247310A3E0CD001046dA0663\",\"topics\":[\"0xe2403640ba68fed3a2f88b7557551d1993f84b99bb10ff833f0cf8db0c5e0486\",\"0x0000000000000000000000002eea2c1ae0cdd2622381c2f9201b2a07c037b1f6\"],\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANB/GezJGOI=\",\"blockNumber\":1803258,\"transactionHash\":\"0xcf4354b55b9ac77436cf8b2f5c229ad3b3119b5196cd79ac5c6c382d9f7b0a71\",\"transactionIndex\":1,\"blockHash\":\"0xa69a510b0848180a094904ea9ae3f0ca2216029470c8e03e6941b402aba610d8\",\"logIndex\":6}"),
[]byte("{\"address\":\"0x569608516A81C0B1247310A3E0CD001046dA0663\",\"topics\":[\"0xf279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568\",\"0x0000000000000000000000002eea2c1ae0cdd2622381c2f9201b2a07c037b1f6\",\"0x0000000000000000000000000000000000000000000000000000000000000001\"],\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\",\"blockNumber\":1803258,\"transactionHash\":\"0xcf4354b55b9ac77436cf8b2f5c229ad3b3119b5196cd79ac5c6c382d9f7b0a71\",\"transactionIndex\":1,\"blockHash\":\"0xa69a510b0848180a094904ea9ae3f0ca2216029470c8e03e6941b402aba610d8\",\"logIndex\":7}"),
}
address := "0x57f96e6B86CdeFdB3d412547816a82E3E0EbF9D2"
txHash := common.BigToHash(big.NewInt(1))
txHash2 := common.BigToHash(big.NewInt(2))
response := abci.ResponseDeliverTx{
GasUsed: 21000,
Events: []abci.Event{
{Type: "coin_received", Attributes: []abci.EventAttribute{
{Key: []byte("receiver"), Value: []byte("ethm12luku6uxehhak02py4rcz65zu0swh7wjun6msa")},
{Key: []byte("amount"), Value: []byte("1252860basetcro")},
}},
{Type: "coin_spent", Attributes: []abci.EventAttribute{
{Key: []byte("spender"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
{Key: []byte("amount"), Value: []byte("1252860basetcro")},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
{Key: []byte("txIndex"), Value: []byte("10")},
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("21000")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
{Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[0]},
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[1]},
{Key: []byte(evmtypes.AttributeKeyTxLog), Value: rawLogs[2]},
}},
{Type: "message", Attributes: []abci.EventAttribute{
{Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")},
{Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
{Key: []byte("module"), Value: []byte("evm")},
{Key: []byte("sender"), Value: []byte(address)},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
{Key: []byte("txIndex"), Value: []byte("11")},
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("21000")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
{Key: []byte("ethereumTxFailed"), Value: []byte("contract reverted")},
}},
{Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{}},
},
}
parsed, err := ParseTxResult(&response)
require.NoError(t, err)
tx1 := parsed.GetTxByMsgIndex(0)
txLogs1, err := tx1.ParseTxLogs()
require.NoError(t, err)
require.NotEmpty(t, txLogs1)
tx2 := parsed.GetTxByMsgIndex(1)
txLogs2, err := tx2.ParseTxLogs()
require.Empty(t, txLogs2)
}
+16
View File
@@ -5,6 +5,7 @@ import (
"context"
"fmt"
"math/big"
"strings"
abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"
@@ -22,6 +23,10 @@ import (
"github.com/ethereum/go-ethereum/params"
)
// ExceedBlockGasLimitError defines the error message when tx execution exceeds the block gas limit.
// The tx fee is deducted in ante handler, so it shouldn't be ignored in JSON-RPC API.
const ExceedBlockGasLimitError = "out of gas in location: block gas meter; gasWanted:"
// RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes.
func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEthereumTx, error) {
tx, err := clientCtx.TxConfig.TxDecoder()(txBz)
@@ -243,3 +248,14 @@ func CheckTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
}
return nil
}
// TxExceedBlockGasLimit returns true if the tx exceeds block gas limit.
func TxExceedBlockGasLimit(res *abci.ResponseDeliverTx) bool {
return strings.Contains(res.Log, ExceedBlockGasLimitError)
}
// TxSuccessOrExceedsBlockGasLimit returnsrue if the transaction was successful
// or if it failed with an ExceedBlockGasLimit error
func TxSuccessOrExceedsBlockGasLimit(res *abci.ResponseDeliverTx) bool {
return res.Code == 0 || TxExceedBlockGasLimit(res)
}