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>
This commit is contained in:
yihuang
2022-05-31 10:26:40 +00:00
committed by GitHub
co-authored by Federico Kunze Küllmer
parent afc09f9d59
commit 5533beed71
14 changed files with 718 additions and 179 deletions
+22 -20
View File
@@ -293,8 +293,8 @@ func (b *Backend) 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())
@@ -454,6 +454,7 @@ func (b *Backend) GetCoinbase() (sdk.AccAddress, error) {
func (b *Backend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransaction, error) {
res, err := b.GetTxByEthHash(txHash)
hexTx := txHash.Hex()
if err != nil {
// try to find tx in mempool
txs, err := b.PendingTransactions()
@@ -488,12 +489,17 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransactio
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)
}
@@ -503,7 +509,7 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransactio
}
// 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")
}
@@ -514,12 +520,7 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransactio
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 := b.clientCtx.Client.BlockResults(b.ctx, &block.Block.Height)
if err != nil {
@@ -528,13 +529,12 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransactio
msgs := b.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")
}
@@ -547,7 +547,7 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransactio
msg,
common.BytesToHash(block.BlockID.Hash.Bytes()),
uint64(res.Height),
txIndex,
uint64(parsedTx.EthTxIndex),
baseFee,
)
}
@@ -915,21 +915,23 @@ func (b *Backend) FeeHistory(
// GetEthereumMsgsFromTendermintBlock returns all real MsgEthereumTxs from a Tendermint block.
// It also ensures consistency over the correct txs indexes across RPC endpoints
func (b *Backend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx {
func (b *Backend) GetEthereumMsgsFromTendermintBlock(resBlock *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx {
var result []*evmtypes.MsgEthereumTx
block := resBlock.Block
txResults := blockRes.TxsResults
for i, tx := range block.Block.Txs {
for i, tx := range 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]) {
b.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash()))
continue
}
tx, err := b.clientCtx.TxConfig.TxDecoder()(tx)
if err != nil {
b.logger.Debug("failed to decode transaction in block", "height", block.Block.Height, "error", err.Error())
b.logger.Debug("failed to decode transaction in block", "height", block.Height, "error", err.Error())
continue
}
+21
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"math/big"
"sort"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
@@ -23,6 +24,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:"
type txGasAndReward struct {
gasUsed uint64
reward *big.Int
@@ -369,3 +374,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")
}
+8 -4
View File
@@ -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())
}
@@ -124,7 +128,7 @@ 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
@@ -132,7 +136,7 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (
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)
+57 -45
View File
@@ -405,12 +405,23 @@ func (e *PublicAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, err
return nil, nil
}
msgIndex, _ := rpctypes.FindTxAttributes(res.TxResult.Events, hexTx)
if msgIndex < 0 {
if res.TxResult.Code != 0 {
// failed, return empty logs
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(txHash)
if parsedTx == nil {
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
}
// parse tx logs from events
return backend.TxLogsFromEvents(res.TxResult.Events, msgIndex)
return parsedTx.ParseTxLogs()
}
// Sign signs the provided data using the private key of address via Geth's signature standard.
@@ -735,15 +746,20 @@ func (e *PublicAPI) getTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock,
e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
return nil, nil
}
// find msg index in events
msgIndex := rpctypes.FindTxAttributesByIndex(res.TxResult.Events, uint64(idx))
if msgIndex < 0 {
e.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()[msgIndex].(*evmtypes.MsgEthereumTx)
msg, ok = tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
if !ok {
e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
return nil, nil
@@ -825,8 +841,18 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
return nil, nil
}
msgIndex, attrs := rpctypes.FindTxAttributes(res.TxResult.Events, hexTx)
if msgIndex < 0 {
// don't ignore the txs which exceed block gas limit.
if !backend.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)
}
@@ -842,14 +868,18 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
return nil, fmt.Errorf("failed to decode tx: %w", err)
}
// the `msgIndex` is inferred from tx events, should be within the bound.
msg := tx.GetMsgs()[msgIndex]
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
e.logger.Debug(fmt.Sprintf("invalid tx type: %T", msg))
return nil, fmt.Errorf("invalid tx type: %T", msg)
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 {
e.logger.Error("failed to unpack tx data", "error", err.Error())
@@ -862,27 +892,14 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
e.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 += rpctypes.AccumulativeGasUsedOfMsg(res.TxResult.Events, msgIndex)
var gasUsed uint64
if len(tx.GetMsgs()) == 1 {
// backward compatibility
gasUsed = uint64(res.TxResult.GasUsed)
} else {
gasUsed, err = rpctypes.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxGasUsed)
if err != nil {
return nil, err
}
}
cumulativeGasUsed += parsedTxs.AccumulativeGasUsed(parsedTx.MsgIndex)
// Get the transaction result from the log
_, found := attrs[evmtypes.AttributeKeyEthereumTxFailed]
var status hexutil.Uint
if found {
if res.TxResult.Code != 0 || parsedTx.Failed {
status = hexutil.Uint(ethtypes.ReceiptStatusFailed)
} else {
status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful)
@@ -894,28 +911,23 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
}
// parse tx logs from events
logs, err := backend.TxLogsFromEvents(res.TxResult.Events, msgIndex)
logs, err := parsedTx.ParseTxLogs()
if err != nil {
e.logger.Debug("logs not found", "hash", hexTx, "error", err.Error())
e.logger.Debug("failed to parse logs", "hash", hexTx, "error", err.Error())
}
// Try to find txIndex from events
found = false
txIndex, err := rpctypes.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
msgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock, 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")
}
@@ -930,14 +942,14 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
// They are stored in the chain database.
"transactionHash": hash,
"contractAddress": nil,
"gasUsed": hexutil.Uint64(gasUsed),
"gasUsed": hexutil.Uint64(parsedTx.GasUsed),
"type": hexutil.Uint(txData.TxType()),
// 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(txIndex),
"transactionIndex": hexutil.Uint64(parsedTx.EthTxIndex),
// sender and receiver (contract or EOA) addreses
"from": from,
+236
View File
@@ -0,0 +1,236 @@
package types
import (
"encoding/json"
"strconv"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
abci "github.com/tendermint/tendermint/abci/types"
evmtypes "github.com/tharsis/ethermint/x/evm/types"
)
// EventFormat is the format version of the events.
//
// To fix the issue of tx exceeds block gas limit, we changed the event format in a breaking way.
// But to avoid forcing clients to re-sync from scatch, we make json-rpc logic to be compatible with both formats.
type EventFormat int
const (
eventFormatUnknown EventFormat = iota
// Event Format 1 (the format used before PR #1062):
// ```
// ethereum_tx(amount, ethereumTxHash, [txIndex, txGasUsed], txHash, [receipient], ethereumTxFailed)
// tx_log(txLog, txLog, ...)
// ethereum_tx(amount, ethereumTxHash, [txIndex, txGasUsed], txHash, [receipient], ethereumTxFailed)
// tx_log(txLog, txLog, ...)
// ...
// ```
eventFormat1
// Event Format 2 (the format used after PR #1062):
// ```
// ethereum_tx(ethereumTxHash, txIndex)
// ethereum_tx(ethereumTxHash, txIndex)
// ...
// ethereum_tx(amount, ethereumTxHash, txIndex, txGasUsed, txHash, [receipient], ethereumTxFailed)
// tx_log(txLog, txLog, ...)
// ethereum_tx(amount, ethereumTxHash, txIndex, txGasUsed, txHash, [receipient], ethereumTxFailed)
// tx_log(txLog, txLog, ...)
// ...
// ```
// If the transaction exceeds block gas limit, it only emits the first part.
eventFormat2
)
// ParsedTx is the tx infos parsed from events.
type ParsedTx struct {
MsgIndex int
// the following fields are parsed from events
Hash common.Hash
// -1 means uninitialized
EthTxIndex int64
GasUsed uint64
Failed bool
// unparsed tx log json strings
RawLogs [][]byte
}
// NewParsedTx initialize a ParsedTx
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
Txs []ParsedTx
// map tx hash to msg index
TxHashes map[common.Hash]int
}
// 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) {
format := eventFormatUnknown
// the index of current ethereum_tx event in format 1 or the second part of format 2
eventIndex := -1
p := &ParsedTxs{
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 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 {
// 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)
}
}
// some old versions miss some events, fill it with tx result
if len(p.Txs) == 1 {
p.Txs[0].GasUsed = uint64(result.GasUsed)
}
return p, nil
}
// newTx parse a new tx from events, called during parsing.
func (p *ParsedTxs) newTx(attrs []abci.EventAttribute) error {
msgIndex := len(p.Txs)
tx := NewParsedTx(msgIndex)
if err := fillTxAttributes(&tx, attrs); err != nil {
return err
}
p.Txs = append(p.Txs, tx)
p.TxHashes[tx.Hash] = msgIndex
return nil
}
// updateTx updates an exiting tx from events, called during parsing.
func (p *ParsedTxs) updateTx(eventIndex int, attrs []abci.EventAttribute) error {
return fillTxAttributes(&p.Txs[eventIndex], attrs)
}
// GetTxByHash find ParsedTx by tx hash, returns nil if not exists.
func (p *ParsedTxs) GetTxByHash(hash common.Hash) *ParsedTx {
if idx, ok := p.TxHashes[hash]; ok {
return &p.Txs[idx]
}
return nil
}
// GetTxByMsgIndex returns ParsedTx by msg index
func (p *ParsedTxs) GetTxByMsgIndex(i int) *ParsedTx {
if i < 0 || i >= len(p.Txs) {
return nil
}
return &p.Txs[i]
}
// GetTxByTxIndex returns ParsedTx by tx index
func (p *ParsedTxs) GetTxByTxIndex(txIndex int) *ParsedTx {
if len(p.Txs) == 0 {
return nil
}
// assuming the `EthTxIndex` increase continuously,
// convert TxIndex to MsgIndex by subtract the begin TxIndex.
msgIndex := txIndex - int(p.Txs[0].EthTxIndex)
// GetTxByMsgIndex will check the bound
return p.GetTxByMsgIndex(msgIndex)
}
// AccumulativeGasUsed calculates the accumulated gas used within the batch of txs
func (p *ParsedTxs) AccumulativeGasUsed(msgIndex int) (result uint64) {
for i := 0; i <= msgIndex; i++ {
result += p.Txs[i].GasUsed
}
return result
}
// fillTxAttribute parse attributes by name, less efficient than hardcode the index, but more stable against event
// format changes.
func fillTxAttribute(tx *ParsedTx, key []byte, value []byte) error {
switch string(key) {
case evmtypes.AttributeKeyEthereumTxHash:
tx.Hash = common.HexToHash(string(value))
case evmtypes.AttributeKeyTxIndex:
txIndex, err := strconv.ParseInt(string(value), 10, 64)
if err != nil {
return err
}
tx.EthTxIndex = txIndex
case evmtypes.AttributeKeyTxGasUsed:
gasUsed, err := strconv.ParseInt(string(value), 10, 64)
if err != nil {
return err
}
tx.GasUsed = uint64(gasUsed)
case evmtypes.AttributeKeyEthereumTxFailed:
tx.Failed = len(value) > 0
}
return nil
}
func fillTxAttributes(tx *ParsedTx, attrs []abci.EventAttribute) error {
for _, attr := range attrs {
if err := fillTxAttribute(tx, attr.Key, attr.Value); err != nil {
return err
}
}
return nil
}
func parseRawLogs(attrs []abci.EventAttribute) (logs [][]byte) {
for _, attr := range attrs {
logs = append(logs, attr.Value)
}
return logs
}
+321
View File
@@ -0,0 +1,321 @@
package types
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
evmtypes "github.com/tharsis/ethermint/x/evm/types"
)
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))
testCases := []struct {
name string
response abci.ResponseDeliverTx
expTxs []*ParsedTx // expected parse result, nil means expect error.
}{
{"format 1 events",
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{}},
},
},
[]*ParsedTx{
{
MsgIndex: 0,
Hash: txHash,
EthTxIndex: 10,
GasUsed: 21000,
Failed: false,
RawLogs: rawLogs,
},
{
MsgIndex: 1,
Hash: txHash2,
EthTxIndex: 11,
GasUsed: 21000,
Failed: true,
RawLogs: nil,
},
},
},
{"format 2 events",
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("0")},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{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)},
}},
},
},
[]*ParsedTx{
{
MsgIndex: 0,
Hash: txHash,
EthTxIndex: 0,
GasUsed: 21000,
Failed: false,
RawLogs: rawLogs,
},
},
},
{"format 1 events, failed",
abci.ResponseDeliverTx{
GasUsed: 21000,
Events: []abci.Event{
{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: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
{Key: []byte("txIndex"), Value: []byte("0x01")},
{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{}},
},
},
nil,
},
{"format 1 events, failed",
abci.ResponseDeliverTx{
GasUsed: 21000,
Events: []abci.Event{
{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: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
{Key: []byte("txIndex"), Value: []byte("10")},
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("0x01")},
{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{}},
},
},
nil,
},
{"format 2 events failed",
abci.ResponseDeliverTx{
GasUsed: 21000,
Events: []abci.Event{
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
{Key: []byte("txIndex"), Value: []byte("0x01")},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("21000")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
},
},
nil,
},
{"format 2 events failed",
abci.ResponseDeliverTx{
GasUsed: 21000,
Events: []abci.Event{
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
{Key: []byte("txIndex"), Value: []byte("10")},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("0x01")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
},
},
nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
parsed, err := ParseTxResult(&tc.response)
if tc.expTxs == nil {
require.Error(t, err)
} else {
require.NoError(t, err)
for msgIndex, expTx := range tc.expTxs {
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{}))
// out of range
require.Nil(t, parsed.GetTxByMsgIndex(len(tc.expTxs)))
require.Nil(t, parsed.GetTxByTxIndex(99999999))
}
})
}
}
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)
}
-104
View File
@@ -5,7 +5,6 @@ import (
"context"
"fmt"
"math/big"
"strconv"
abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"
@@ -222,106 +221,3 @@ func BaseFeeFromEvents(events []abci.Event) *big.Int {
}
return nil
}
// FindTxAttributes returns the msg index of the eth tx in cosmos tx, and the attributes,
// returns -1 and nil if not found.
func FindTxAttributes(events []abci.Event, txHash string) (int, map[string]string) {
msgIndex := -1
for _, event := range events {
if event.Type != evmtypes.EventTypeEthereumTx {
continue
}
msgIndex++
value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyEthereumTxHash))
if !bytes.Equal(value, []byte(txHash)) {
continue
}
// found, convert attributes to map for later lookup
attrs := make(map[string]string, len(event.Attributes))
for _, attr := range event.Attributes {
attrs[string(attr.Key)] = string(attr.Value)
}
return msgIndex, attrs
}
// not found
return -1, nil
}
// FindTxAttributesByIndex search the msg in tx events by txIndex
// returns the msgIndex, returns -1 if not found.
func FindTxAttributesByIndex(events []abci.Event, txIndex uint64) int {
strIndex := []byte(strconv.FormatUint(txIndex, 10))
txIndexKey := []byte(evmtypes.AttributeKeyTxIndex)
msgIndex := -1
for _, event := range events {
if event.Type != evmtypes.EventTypeEthereumTx {
continue
}
msgIndex++
value := FindAttribute(event.Attributes, txIndexKey)
if !bytes.Equal(value, strIndex) {
continue
}
// found, convert attributes to map for later lookup
return msgIndex
}
// not found
return -1
}
// FindAttribute find event attribute with specified key, if not found returns nil.
func FindAttribute(attrs []abci.EventAttribute, key []byte) []byte {
for _, attr := range attrs {
if !bytes.Equal(attr.Key, key) {
continue
}
return attr.Value
}
return nil
}
// GetUint64Attribute parses the uint64 value from event attributes
func GetUint64Attribute(attrs map[string]string, key string) (uint64, error) {
value, found := attrs[key]
if !found {
return 0, fmt.Errorf("tx index attribute not found: %s", key)
}
var result int64
result, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return 0, err
}
if result < 0 {
return 0, fmt.Errorf("negative tx index: %d", result)
}
return uint64(result), nil
}
// AccumulativeGasUsedOfMsg accumulate the gas used by msgs before `msgIndex`.
func AccumulativeGasUsedOfMsg(events []abci.Event, msgIndex int) (gasUsed uint64) {
for _, event := range events {
if event.Type != evmtypes.EventTypeEthereumTx {
continue
}
if msgIndex < 0 {
break
}
msgIndex--
value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyTxGasUsed))
var result int64
result, err := strconv.ParseInt(string(value), 10, 64)
if err != nil {
continue
}
gasUsed += uint64(result)
}
return
}