laconicd-deprecated/rpc/types/events.go
yihuang 5533beed71
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>
2022-05-31 10:26:40 +00:00

237 lines
6.5 KiB
Go

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
}