Skip to content

Commit

Permalink
fix(rpc, ante): Emit Ethereum tx hash in AnteHandler to support que…
Browse files Browse the repository at this point in the history
…ry failed transactions (evmos#1062)

* Emit eth tx hash in ante handler to support query failed transactions

WIP: evmos#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 <[email protected]>

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 <[email protected]>

* update comments

* refactoring

* Update rpc/namespaces/ethereum/eth/api.go

* fix lint

* Apply suggestions from code review

Co-authored-by: Federico Kunze Küllmer <[email protected]>
  • Loading branch information
yihuang and fedekunze committed Jun 6, 2022
1 parent 17f543a commit 39ab2dd
Show file tree
Hide file tree
Showing 13 changed files with 708 additions and 174 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

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

### API Breaking

Expand Down
34 changes: 34 additions & 0 deletions app/ante/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ante
import (
"errors"
"math/big"
"strconv"

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

return next(ctx, tx, simulate)
}

// EthEmitEventDecorator emit events in ante handler in case of tx execution failed (out of block gas limit).
type EthEmitEventDecorator struct {
evmKeeper EVMKeeper
}

// NewEthEmitEventDecorator creates a new EthEmitEventDecorator
func NewEthEmitEventDecorator(evmKeeper EVMKeeper) EthEmitEventDecorator {
return EthEmitEventDecorator{evmKeeper}
}

// AnteHandle emits some basic events for the eth messages
func (eeed EthEmitEventDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
// After eth tx passed ante handler, the fee is deducted and nonce increased, it shouldn't be ignored by json-rpc,
// we need to emit some basic events at the very end of ante handler to be indexed by tendermint.
txIndex := eeed.evmKeeper.GetTxIndexTransient(ctx)
for i, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
}

// emit ethereum tx hash as event, should be indexed by tm tx indexer for query purpose.
// it's emitted in ante handler so we can query failed transaction (out of block gas limit).
ctx.EventManager().EmitEvent(sdk.NewEvent(
evmtypes.EventTypeEthereumTx,
sdk.NewAttribute(evmtypes.AttributeKeyEthereumTxHash, msgEthTx.Hash),
sdk.NewAttribute(evmtypes.AttributeKeyTxIndex, strconv.FormatUint(txIndex+uint64(i), 10)),
))
}

return next(ctx, tx, simulate)
}
1 change: 1 addition & 0 deletions app/ante/handler_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler {
NewEthGasConsumeDecorator(options.EvmKeeper, options.MaxTxGasWanted),
NewCanTransferDecorator(options.EvmKeeper),
NewEthIncrementSenderSequenceDecorator(options.AccountKeeper), // innermost AnteDecorator.
NewEthEmitEventDecorator(options.EvmKeeper), // emit eth tx hash and index at the very last ante handler.
)
}

Expand Down
1 change: 1 addition & 0 deletions app/ante/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type EVMKeeper interface {
GetBaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int
GetBalance(ctx sdk.Context, addr common.Address) *big.Int
ResetTransientGasUsed(ctx sdk.Context)
GetTxIndexTransient(ctx sdk.Context) uint64
}

type protoTxProvider interface {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.17
require (
github.com/btcsuite/btcd v0.22.0-beta
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce
github.com/cosmos/cosmos-sdk v0.45.4
github.com/cosmos/cosmos-sdk v0.45.5-0.20220523154235-2921a1c3c918
github.com/cosmos/go-bip39 v1.0.0
github.com/cosmos/ibc-go/v2 v2.2.0
github.com/davecgh/go-spew v1.1.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44=
github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU=
github.com/cosmos/cosmos-sdk v0.45.1/go.mod h1:XXS/asyCqWNWkx2rW6pSuen+EVcpAFxq6khrhnZgHaQ=
github.com/cosmos/cosmos-sdk v0.45.4 h1:eStDAhJdMY8n5arbBRe+OwpNeBSunxSBHp1g55ulfdA=
github.com/cosmos/cosmos-sdk v0.45.4/go.mod h1:WOqtDxN3eCCmnYLVla10xG7lEXkFjpTaqm2a2WasgCc=
github.com/cosmos/cosmos-sdk v0.45.5-0.20220523154235-2921a1c3c918 h1:adHQCXXYYLO+VxH9aSifiKofXwOwRUBx0lxny5fKQCg=
github.com/cosmos/cosmos-sdk v0.45.5-0.20220523154235-2921a1c3c918/go.mod h1:WOqtDxN3eCCmnYLVla10xG7lEXkFjpTaqm2a2WasgCc=
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
Expand Down
43 changes: 25 additions & 18 deletions rpc/ethereum/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,8 +373,8 @@ func (e *EVMBackend) EthBlockFromTendermint(

for _, txsResult := range resBlockResult.TxsResults {
// workaround for cosmos-sdk bug. https://github.com/cosmos/cosmos-sdk/issues/10832
if txsResult.GetCode() == 11 && txsResult.GetLog() == "no block gas left to run tx: out of gas" {
// block gas limit has exceeded, other txs must have failed for the same reason.
if ShouldIgnoreGasUsed(txsResult) {
// block gas limit has exceeded, other txs must have failed with same reason.
break
}
gasUsed += uint64(txsResult.GetGasUsed())
Expand Down Expand Up @@ -534,6 +534,7 @@ func (e *EVMBackend) GetCoinbase() (sdk.AccAddress, error) {
func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransaction, error) {
res, err := e.GetTxByEthHash(txHash)
hexTx := txHash.Hex()

if err != nil {
// try to find tx in mempool
txs, err := e.PendingTransactions()
Expand Down Expand Up @@ -568,12 +569,17 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
return nil, nil
}

if res.TxResult.Code != 0 {
if !TxSuccessOrExceedsBlockGasLimit(&res.TxResult) {
return nil, errors.New("invalid ethereum tx")
}

msgIndex, attrs := types.FindTxAttributes(res.TxResult.Events, hexTx)
if msgIndex < 0 {
parsedTxs, err := types.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)
}

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

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

// Try to find txIndex from events
found := false
txIndex, err := types.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxIndex)
if err == nil {
found = true
} else {
if parsedTx.EthTxIndex == -1 {
// Fallback to find tx index by iterating all valid eth transactions
blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
if err != nil {
Expand All @@ -608,22 +609,27 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
msgs := e.GetEthereumMsgsFromTendermintBlock(block, blockRes)
for i := range msgs {
if msgs[i].Hash == hexTx {
txIndex = uint64(i)
found = true
parsedTx.EthTxIndex = int64(i)
break
}
}
}
if !found {
if parsedTx.EthTxIndex == -1 {
return nil, errors.New("can't find index of ethereum tx")
}

baseFee, err := e.BaseFee(block.Block.Height)
if err != nil {
e.logger.Debug("HeaderByHash BaseFee failed", "height", block.Block.Height, "error", err.Error())
return nil, err
}

return types.NewTransactionFromMsg(
msg,
common.BytesToHash(block.BlockID.Hash.Bytes()),
uint64(res.Height),
txIndex,
nil,
uint64(parsedTx.EthTxIndex),
baseFee,
)
}

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

for i, tx := range block.Block.Txs {
// check tx exists on EVM by cross checking with blockResults
if txResults[i].Code != 0 {
// include the tx that exceeds block gas limit
if !TxSuccessOrExceedsBlockGasLimit(txResults[i]) {
e.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash()))
continue
}
Expand Down
21 changes: 21 additions & 0 deletions rpc/ethereum/backend/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"math/big"
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
Expand All @@ -19,6 +20,10 @@ import (
evmtypes "github.com/tharsis/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:"

// SetTxDefaults populates tx message with default values in case they are not
// provided on the args
func (e *EVMBackend) SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error) {
Expand Down Expand Up @@ -251,3 +256,19 @@ 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 returns if the tx should be included in json-rpc responses
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 {
return res.GetCode() == 11 && strings.Contains(res.GetLog(), "no block gas left to run tx: out of gas")
}
12 changes: 8 additions & 4 deletions rpc/ethereum/namespaces/debug/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,12 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (
return nil, err
}

msgIndex, _ := rpctypes.FindTxAttributes(transaction.TxResult.Events, hash.Hex())
if msgIndex < 0 {
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())
}

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

// add predecessor messages in current cosmos tx
for i := 0; i < msgIndex; i++ {
for i := 0; i < parsedTx.MsgIndex; i++ {
ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx)
if !ok {
continue
}
predecessors = append(predecessors, ethMsg)
}

ethMessage, ok := tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx)
ethMessage, ok := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
if !ok {
a.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
return nil, fmt.Errorf("invalid transaction type %T", tx)
Expand Down
Loading

0 comments on commit 39ab2dd

Please sign in to comment.