Skip to content

Commit 39ab2dd

Browse files
yihuangfedekunze
andcommitted
fix(rpc, ante): Emit Ethereum tx hash in AnteHandler to support query failed transactions (#1062)
* Emit eth tx hash in ante handler to support query failed transactions WIP: #1045 Solution: - emit eth tx hash in ante handler - modify rpc to use it fix ante handler support failed tx in receipt add unit tests need to patch cosmos-sdk to work update cosmos-sdk to v0.45.x release branch fix failed status fix unit tests add unit test cases cleanup dead code Apply suggestions from code review Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> fix lint fix review suggestions fix build fix gas used of failed tx add back the redundant events * fix get tx by index * add unit tests for events * Update rpc/types/events.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * update comments * refactoring * Update rpc/namespaces/ethereum/eth/api.go * fix lint * Apply suggestions from code review Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
1 parent 17f543a commit 39ab2dd

13 files changed

Lines changed: 708 additions & 174 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
4141

4242
* (rpc) [tharsis#1059](https://github.com/tharsis/ethermint/pull/1059) Remove unnecessary event filtering logic on the `eth_baseFee` JSON-RPC endpoint.
4343
* (rpc) [tharsis#1082](https://github.com/tharsis/ethermint/pull/1082) fix gas price returned in getTransaction api.
44+
* (ante) [tharsis#1062](https://github.com/tharsis/ethermint/pull/1062) Emit event of eth tx hash in ante handler to support query failed transactions.
4445

4546
### API Breaking
4647

app/ante/eth.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ante
33
import (
44
"errors"
55
"math/big"
6+
"strconv"
67

78
sdk "github.com/cosmos/cosmos-sdk/types"
89
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
@@ -535,3 +536,36 @@ func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulat
535536

536537
return next(ctx, tx, simulate)
537538
}
539+
540+
// EthEmitEventDecorator emit events in ante handler in case of tx execution failed (out of block gas limit).
541+
type EthEmitEventDecorator struct {
542+
evmKeeper EVMKeeper
543+
}
544+
545+
// NewEthEmitEventDecorator creates a new EthEmitEventDecorator
546+
func NewEthEmitEventDecorator(evmKeeper EVMKeeper) EthEmitEventDecorator {
547+
return EthEmitEventDecorator{evmKeeper}
548+
}
549+
550+
// AnteHandle emits some basic events for the eth messages
551+
func (eeed EthEmitEventDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
552+
// After eth tx passed ante handler, the fee is deducted and nonce increased, it shouldn't be ignored by json-rpc,
553+
// we need to emit some basic events at the very end of ante handler to be indexed by tendermint.
554+
txIndex := eeed.evmKeeper.GetTxIndexTransient(ctx)
555+
for i, msg := range tx.GetMsgs() {
556+
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
557+
if !ok {
558+
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
559+
}
560+
561+
// emit ethereum tx hash as event, should be indexed by tm tx indexer for query purpose.
562+
// it's emitted in ante handler so we can query failed transaction (out of block gas limit).
563+
ctx.EventManager().EmitEvent(sdk.NewEvent(
564+
evmtypes.EventTypeEthereumTx,
565+
sdk.NewAttribute(evmtypes.AttributeKeyEthereumTxHash, msgEthTx.Hash),
566+
sdk.NewAttribute(evmtypes.AttributeKeyTxIndex, strconv.FormatUint(txIndex+uint64(i), 10)),
567+
))
568+
}
569+
570+
return next(ctx, tx, simulate)
571+
}

app/ante/handler_options.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler {
5757
NewEthGasConsumeDecorator(options.EvmKeeper, options.MaxTxGasWanted),
5858
NewCanTransferDecorator(options.EvmKeeper),
5959
NewEthIncrementSenderSequenceDecorator(options.AccountKeeper), // innermost AnteDecorator.
60+
NewEthEmitEventDecorator(options.EvmKeeper), // emit eth tx hash and index at the very last ante handler.
6061
)
6162
}
6263

app/ante/interfaces.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type EVMKeeper interface {
2626
GetBaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int
2727
GetBalance(ctx sdk.Context, addr common.Address) *big.Int
2828
ResetTransientGasUsed(ctx sdk.Context)
29+
GetTxIndexTransient(ctx sdk.Context) uint64
2930
}
3031

3132
type protoTxProvider interface {

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.17
55
require (
66
github.com/btcsuite/btcd v0.22.0-beta
77
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce
8-
github.com/cosmos/cosmos-sdk v0.45.4
8+
github.com/cosmos/cosmos-sdk v0.45.5-0.20220523154235-2921a1c3c918
99
github.com/cosmos/go-bip39 v1.0.0
1010
github.com/cosmos/ibc-go/v2 v2.2.0
1111
github.com/davecgh/go-spew v1.1.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
253253
github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44=
254254
github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU=
255255
github.com/cosmos/cosmos-sdk v0.45.1/go.mod h1:XXS/asyCqWNWkx2rW6pSuen+EVcpAFxq6khrhnZgHaQ=
256-
github.com/cosmos/cosmos-sdk v0.45.4 h1:eStDAhJdMY8n5arbBRe+OwpNeBSunxSBHp1g55ulfdA=
257-
github.com/cosmos/cosmos-sdk v0.45.4/go.mod h1:WOqtDxN3eCCmnYLVla10xG7lEXkFjpTaqm2a2WasgCc=
256+
github.com/cosmos/cosmos-sdk v0.45.5-0.20220523154235-2921a1c3c918 h1:adHQCXXYYLO+VxH9aSifiKofXwOwRUBx0lxny5fKQCg=
257+
github.com/cosmos/cosmos-sdk v0.45.5-0.20220523154235-2921a1c3c918/go.mod h1:WOqtDxN3eCCmnYLVla10xG7lEXkFjpTaqm2a2WasgCc=
258258
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
259259
github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
260260
github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=

rpc/ethereum/backend/backend.go

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,8 @@ func (e *EVMBackend) EthBlockFromTendermint(
373373

374374
for _, txsResult := range resBlockResult.TxsResults {
375375
// workaround for cosmos-sdk bug. https://github.com/cosmos/cosmos-sdk/issues/10832
376-
if txsResult.GetCode() == 11 && txsResult.GetLog() == "no block gas left to run tx: out of gas" {
377-
// block gas limit has exceeded, other txs must have failed for the same reason.
376+
if ShouldIgnoreGasUsed(txsResult) {
377+
// block gas limit has exceeded, other txs must have failed with same reason.
378378
break
379379
}
380380
gasUsed += uint64(txsResult.GetGasUsed())
@@ -534,6 +534,7 @@ func (e *EVMBackend) GetCoinbase() (sdk.AccAddress, error) {
534534
func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransaction, error) {
535535
res, err := e.GetTxByEthHash(txHash)
536536
hexTx := txHash.Hex()
537+
537538
if err != nil {
538539
// try to find tx in mempool
539540
txs, err := e.PendingTransactions()
@@ -568,12 +569,17 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
568569
return nil, nil
569570
}
570571

571-
if res.TxResult.Code != 0 {
572+
if !TxSuccessOrExceedsBlockGasLimit(&res.TxResult) {
572573
return nil, errors.New("invalid ethereum tx")
573574
}
574575

575-
msgIndex, attrs := types.FindTxAttributes(res.TxResult.Events, hexTx)
576-
if msgIndex < 0 {
576+
parsedTxs, err := types.ParseTxResult(&res.TxResult)
577+
if err != nil {
578+
return nil, fmt.Errorf("failed to parse tx events: %s", hexTx)
579+
}
580+
581+
parsedTx := parsedTxs.GetTxByHash(txHash)
582+
if parsedTx == nil {
577583
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
578584
}
579585

@@ -583,7 +589,7 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
583589
}
584590

585591
// the `msgIndex` is inferred from tx events, should be within the bound.
586-
msg, ok := tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx)
592+
msg, ok := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
587593
if !ok {
588594
return nil, errors.New("invalid ethereum tx")
589595
}
@@ -594,12 +600,7 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
594600
return nil, err
595601
}
596602

597-
// Try to find txIndex from events
598-
found := false
599-
txIndex, err := types.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxIndex)
600-
if err == nil {
601-
found = true
602-
} else {
603+
if parsedTx.EthTxIndex == -1 {
603604
// Fallback to find tx index by iterating all valid eth transactions
604605
blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
605606
if err != nil {
@@ -608,22 +609,27 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
608609
msgs := e.GetEthereumMsgsFromTendermintBlock(block, blockRes)
609610
for i := range msgs {
610611
if msgs[i].Hash == hexTx {
611-
txIndex = uint64(i)
612-
found = true
612+
parsedTx.EthTxIndex = int64(i)
613613
break
614614
}
615615
}
616616
}
617-
if !found {
617+
if parsedTx.EthTxIndex == -1 {
618618
return nil, errors.New("can't find index of ethereum tx")
619619
}
620620

621+
baseFee, err := e.BaseFee(block.Block.Height)
622+
if err != nil {
623+
e.logger.Debug("HeaderByHash BaseFee failed", "height", block.Block.Height, "error", err.Error())
624+
return nil, err
625+
}
626+
621627
return types.NewTransactionFromMsg(
622628
msg,
623629
common.BytesToHash(block.BlockID.Hash.Bytes()),
624630
uint64(res.Height),
625-
txIndex,
626-
nil,
631+
uint64(parsedTx.EthTxIndex),
632+
baseFee,
627633
)
628634
}
629635

@@ -902,7 +908,8 @@ func (e *EVMBackend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.Result
902908

903909
for i, tx := range block.Block.Txs {
904910
// check tx exists on EVM by cross checking with blockResults
905-
if txResults[i].Code != 0 {
911+
// include the tx that exceeds block gas limit
912+
if !TxSuccessOrExceedsBlockGasLimit(txResults[i]) {
906913
e.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash()))
907914
continue
908915
}

rpc/ethereum/backend/utils.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"math/big"
9+
"strings"
910

1011
sdk "github.com/cosmos/cosmos-sdk/types"
1112
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
@@ -19,6 +20,10 @@ import (
1920
evmtypes "github.com/tharsis/ethermint/x/evm/types"
2021
)
2122

23+
// ExceedBlockGasLimitError defines the error message when tx execution exceeds the block gas limit.
24+
// The tx fee is deducted in ante handler, so it shouldn't be ignored in JSON-RPC API.
25+
const ExceedBlockGasLimitError = "out of gas in location: block gas meter; gasWanted:"
26+
2227
// SetTxDefaults populates tx message with default values in case they are not
2328
// provided on the args
2429
func (e *EVMBackend) SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error) {
@@ -251,3 +256,19 @@ func ParseTxLogsFromEvent(event abci.Event) ([]*ethtypes.Log, error) {
251256
}
252257
return evmtypes.LogsToEthereum(logs), nil
253258
}
259+
260+
// TxExceedBlockGasLimit returns true if the tx exceeds block gas limit.
261+
func TxExceedBlockGasLimit(res *abci.ResponseDeliverTx) bool {
262+
return strings.Contains(res.Log, ExceedBlockGasLimitError)
263+
}
264+
265+
// TxSuccessOrExceedsBlockGasLimit returns if the tx should be included in json-rpc responses
266+
func TxSuccessOrExceedsBlockGasLimit(res *abci.ResponseDeliverTx) bool {
267+
return res.Code == 0 || TxExceedBlockGasLimit(res)
268+
}
269+
270+
// ShouldIgnoreGasUsed returns true if the gasUsed in result should be ignored
271+
// workaround for issue: https://github.com/cosmos/cosmos-sdk/issues/10832
272+
func ShouldIgnoreGasUsed(res *abci.ResponseDeliverTx) bool {
273+
return res.GetCode() == 11 && strings.Contains(res.GetLog(), "no block gas left to run tx: out of gas")
274+
}

rpc/ethereum/namespaces/debug/api.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,12 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (
8989
return nil, err
9090
}
9191

92-
msgIndex, _ := rpctypes.FindTxAttributes(transaction.TxResult.Events, hash.Hex())
93-
if msgIndex < 0 {
92+
parsedTxs, err := rpctypes.ParseTxResult(&transaction.TxResult)
93+
if err != nil {
94+
return nil, fmt.Errorf("failed to parse tx events: %s", hash.Hex())
95+
}
96+
parsedTx := parsedTxs.GetTxByHash(hash)
97+
if parsedTx == nil {
9498
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hash.Hex())
9599
}
96100

@@ -124,15 +128,15 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (
124128
}
125129

126130
// add predecessor messages in current cosmos tx
127-
for i := 0; i < msgIndex; i++ {
131+
for i := 0; i < parsedTx.MsgIndex; i++ {
128132
ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx)
129133
if !ok {
130134
continue
131135
}
132136
predecessors = append(predecessors, ethMsg)
133137
}
134138

135-
ethMessage, ok := tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx)
139+
ethMessage, ok := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
136140
if !ok {
137141
a.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
138142
return nil, fmt.Errorf("invalid transaction type %T", tx)

0 commit comments

Comments
 (0)