Sync from fork #74
@ -92,6 +92,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
* (rpc) [\#1143](https://github.com/evmos/ethermint/pull/1143) Restrict unprotected txs on the node JSON-RPC configuration.
|
||||
* (all) [\#1137](https://github.com/evmos/ethermint/pull/1137) Rename go module to `evmos/ethermint`
|
||||
|
||||
### API Breaking
|
||||
|
||||
- (json-rpc) [tharsis#1121](https://github.com/tharsis/ethermint/pull/1121) Store eth tx index separately
|
||||
|
||||
### Improvements
|
||||
|
||||
* (deps) [\#1147](https://github.com/evmos/ethermint/pull/1147) Bump Go version to `1.18`.
|
||||
|
@ -17,7 +17,7 @@ in
|
||||
buildGoApplication rec {
|
||||
inherit pname version tags ldflags;
|
||||
src = lib.sourceByRegex ./. [
|
||||
"^(x|app|cmd|client|server|crypto|rpc|types|encoding|ethereum|testutil|version|go.mod|go.sum|gomod2nix.toml)($|/.*)"
|
||||
"^(x|app|cmd|client|server|crypto|rpc|types|encoding|ethereum|indexer|testutil|version|go.mod|go.sum|gomod2nix.toml)($|/.*)"
|
||||
"^tests(/.*[.]go)?$"
|
||||
];
|
||||
modules = ./gomod2nix.toml;
|
||||
|
@ -82,6 +82,9 @@
|
||||
- [ethermint/types/v1/dynamic_fee.proto](#ethermint/types/v1/dynamic_fee.proto)
|
||||
- [ExtensionOptionDynamicFeeTx](#ethermint.types.v1.ExtensionOptionDynamicFeeTx)
|
||||
|
||||
- [ethermint/types/v1/indexer.proto](#ethermint/types/v1/indexer.proto)
|
||||
- [TxResult](#ethermint.types.v1.TxResult)
|
||||
|
||||
- [ethermint/types/v1/web3.proto](#ethermint/types/v1/web3.proto)
|
||||
- [ExtensionOptionsWeb3Tx](#ethermint.types.v1.ExtensionOptionsWeb3Tx)
|
||||
|
||||
@ -1168,6 +1171,43 @@ ExtensionOptionDynamicFeeTx is an extension option that specify the maxPrioPrice
|
||||
|
||||
|
||||
|
||||
<!-- end messages -->
|
||||
|
||||
<!-- end enums -->
|
||||
|
||||
<!-- end HasExtensions -->
|
||||
|
||||
<!-- end services -->
|
||||
|
||||
|
||||
|
||||
<a name="ethermint/types/v1/indexer.proto"></a>
|
||||
<p align="right"><a href="#top">Top</a></p>
|
||||
|
||||
## ethermint/types/v1/indexer.proto
|
||||
|
||||
|
||||
|
||||
<a name="ethermint.types.v1.TxResult"></a>
|
||||
|
||||
### TxResult
|
||||
TxResult is the value stored in eth tx indexer
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| `height` | [int64](#int64) | | the block height |
|
||||
| `tx_index` | [uint32](#uint32) | | cosmos tx index |
|
||||
| `msg_index` | [uint32](#uint32) | | the msg index in a batch tx |
|
||||
| `eth_tx_index` | [int32](#int32) | | eth tx index, the index in the list of valid eth tx in the block, aka. the transaction list returned by eth_getBlock api. |
|
||||
| `failed` | [bool](#bool) | | if the eth tx is failed |
|
||||
| `gas_used` | [uint64](#uint64) | | gas used by tx, if exceeds block gas limit, it's set to gas limit which is what's actually deducted by ante handler. |
|
||||
| `cumulative_gas_used` | [uint64](#uint64) | | the cumulative gas used within current batch tx |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- end messages -->
|
||||
|
||||
<!-- end enums -->
|
||||
|
230
indexer/kv_indexer.go
Normal file
230
indexer/kv_indexer.go
Normal file
@ -0,0 +1,230 @@
|
||||
package indexer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
ethermint "github.com/evmos/ethermint/types"
|
||||
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||
)
|
||||
|
||||
const (
|
||||
KeyPrefixTxHash = 1
|
||||
KeyPrefixTxIndex = 2
|
||||
|
||||
// TxIndexKeyLength is the length of tx-index key
|
||||
TxIndexKeyLength = 1 + 8 + 8
|
||||
)
|
||||
|
||||
var _ ethermint.EVMTxIndexer = &KVIndexer{}
|
||||
|
||||
// KVIndexer implements a eth tx indexer on a KV db.
|
||||
type KVIndexer struct {
|
||||
db dbm.DB
|
||||
logger log.Logger
|
||||
clientCtx client.Context
|
||||
}
|
||||
|
||||
// NewKVIndexer creates the KVIndexer
|
||||
func NewKVIndexer(db dbm.DB, logger log.Logger, clientCtx client.Context) *KVIndexer {
|
||||
return &KVIndexer{db, logger, clientCtx}
|
||||
}
|
||||
|
||||
// IndexBlock index all the eth txs in a block through the following steps:
|
||||
// - Iterates over all of the Txs in Block
|
||||
// - Parses eth Tx infos from cosmos-sdk events for every TxResult
|
||||
// - Iterates over all the messages of the Tx
|
||||
// - Builds and stores a indexer.TxResult based on parsed events for every message
|
||||
func (kv *KVIndexer) IndexBlock(block *tmtypes.Block, txResults []*abci.ResponseDeliverTx) error {
|
||||
height := block.Header.Height
|
||||
|
||||
batch := kv.db.NewBatch()
|
||||
defer batch.Close()
|
||||
|
||||
// record index of valid eth tx during the iteration
|
||||
var ethTxIndex int32
|
||||
for txIndex, tx := range block.Txs {
|
||||
result := txResults[txIndex]
|
||||
if !rpctypes.TxSuccessOrExceedsBlockGasLimit(result) {
|
||||
continue
|
||||
}
|
||||
|
||||
tx, err := kv.clientCtx.TxConfig.TxDecoder()(tx)
|
||||
if err != nil {
|
||||
kv.logger.Error("Fail to decode tx", "err", err, "block", height, "txIndex", txIndex)
|
||||
continue
|
||||
}
|
||||
|
||||
if !isEthTx(tx) {
|
||||
continue
|
||||
}
|
||||
|
||||
txs, err := rpctypes.ParseTxResult(result, tx)
|
||||
if err != nil {
|
||||
kv.logger.Error("Fail to parse event", "err", err, "block", height, "txIndex", txIndex)
|
||||
continue
|
||||
}
|
||||
|
||||
var cumulativeGasUsed uint64
|
||||
for msgIndex, msg := range tx.GetMsgs() {
|
||||
ethMsg := msg.(*evmtypes.MsgEthereumTx)
|
||||
txHash := common.HexToHash(ethMsg.Hash)
|
||||
|
||||
txResult := ethermint.TxResult{
|
||||
Height: height,
|
||||
TxIndex: uint32(txIndex),
|
||||
MsgIndex: uint32(msgIndex),
|
||||
EthTxIndex: ethTxIndex,
|
||||
}
|
||||
if result.Code != abci.CodeTypeOK {
|
||||
// exceeds block gas limit scenario, set gas used to gas limit because that's what's charged by ante handler.
|
||||
// some old versions don't emit any events, so workaround here directly.
|
||||
txResult.GasUsed = ethMsg.GetGas()
|
||||
txResult.Failed = true
|
||||
} else {
|
||||
parsedTx := txs.GetTxByMsgIndex(msgIndex)
|
||||
if parsedTx == nil {
|
||||
kv.logger.Error("msg index not found in events", "msgIndex", msgIndex)
|
||||
continue
|
||||
}
|
||||
if parsedTx.EthTxIndex >= 0 && parsedTx.EthTxIndex != ethTxIndex {
|
||||
kv.logger.Error("eth tx index don't match", "expect", ethTxIndex, "found", parsedTx.EthTxIndex)
|
||||
}
|
||||
txResult.GasUsed = parsedTx.GasUsed
|
||||
txResult.Failed = parsedTx.Failed
|
||||
}
|
||||
|
||||
cumulativeGasUsed += txResult.GasUsed
|
||||
txResult.CumulativeGasUsed = cumulativeGasUsed
|
||||
ethTxIndex++
|
||||
|
||||
if err := saveTxResult(kv.clientCtx.Codec, batch, txHash, &txResult); err != nil {
|
||||
return sdkerrors.Wrapf(err, "IndexBlock %d", height)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := batch.Write(); err != nil {
|
||||
return sdkerrors.Wrapf(err, "IndexBlock %d, write batch", block.Height)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LastIndexedBlock returns the latest indexed block number, returns -1 if db is empty
|
||||
func (kv *KVIndexer) LastIndexedBlock() (int64, error) {
|
||||
return LoadLastBlock(kv.db)
|
||||
}
|
||||
|
||||
// FirstIndexedBlock returns the first indexed block number, returns -1 if db is empty
|
||||
func (kv *KVIndexer) FirstIndexedBlock() (int64, error) {
|
||||
return LoadFirstBlock(kv.db)
|
||||
}
|
||||
|
||||
// GetByTxHash finds eth tx by eth tx hash
|
||||
func (kv *KVIndexer) GetByTxHash(hash common.Hash) (*ethermint.TxResult, error) {
|
||||
bz, err := kv.db.Get(TxHashKey(hash))
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrapf(err, "GetByTxHash %s", hash.Hex())
|
||||
}
|
||||
if len(bz) == 0 {
|
||||
return nil, fmt.Errorf("tx not found, hash: %s", hash.Hex())
|
||||
}
|
||||
var txKey ethermint.TxResult
|
||||
if err := kv.clientCtx.Codec.Unmarshal(bz, &txKey); err != nil {
|
||||
return nil, sdkerrors.Wrapf(err, "GetByTxHash %s", hash.Hex())
|
||||
}
|
||||
return &txKey, nil
|
||||
}
|
||||
|
||||
// GetByBlockAndIndex finds eth tx by block number and eth tx index
|
||||
func (kv *KVIndexer) GetByBlockAndIndex(blockNumber int64, txIndex int32) (*ethermint.TxResult, error) {
|
||||
bz, err := kv.db.Get(TxIndexKey(blockNumber, txIndex))
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrapf(err, "GetByBlockAndIndex %d %d", blockNumber, txIndex)
|
||||
}
|
||||
if len(bz) == 0 {
|
||||
return nil, fmt.Errorf("tx not found, block: %d, eth-index: %d", blockNumber, txIndex)
|
||||
}
|
||||
return kv.GetByTxHash(common.BytesToHash(bz))
|
||||
}
|
||||
|
||||
// TxHashKey returns the key for db entry: `tx hash -> tx result struct`
|
||||
func TxHashKey(hash common.Hash) []byte {
|
||||
return append([]byte{KeyPrefixTxHash}, hash.Bytes()...)
|
||||
}
|
||||
|
||||
// TxIndexKey returns the key for db entry: `(block number, tx index) -> tx hash`
|
||||
func TxIndexKey(blockNumber int64, txIndex int32) []byte {
|
||||
bz1 := sdk.Uint64ToBigEndian(uint64(blockNumber))
|
||||
bz2 := sdk.Uint64ToBigEndian(uint64(txIndex))
|
||||
return append(append([]byte{KeyPrefixTxIndex}, bz1...), bz2...)
|
||||
}
|
||||
|
||||
// LoadLastBlock returns the latest indexed block number, returns -1 if db is empty
|
||||
func LoadLastBlock(db dbm.DB) (int64, error) {
|
||||
it, err := db.ReverseIterator([]byte{KeyPrefixTxIndex}, []byte{KeyPrefixTxIndex + 1})
|
||||
if err != nil {
|
||||
return 0, sdkerrors.Wrap(err, "LoadLastBlock")
|
||||
}
|
||||
defer it.Close()
|
||||
if !it.Valid() {
|
||||
return -1, nil
|
||||
}
|
||||
return parseBlockNumberFromKey(it.Key())
|
||||
}
|
||||
|
||||
// LoadFirstBlock loads the first indexed block, returns -1 if db is empty
|
||||
func LoadFirstBlock(db dbm.DB) (int64, error) {
|
||||
it, err := db.Iterator([]byte{KeyPrefixTxIndex}, []byte{KeyPrefixTxIndex + 1})
|
||||
if err != nil {
|
||||
return 0, sdkerrors.Wrap(err, "LoadFirstBlock")
|
||||
}
|
||||
defer it.Close()
|
||||
if !it.Valid() {
|
||||
return -1, nil
|
||||
}
|
||||
return parseBlockNumberFromKey(it.Key())
|
||||
}
|
||||
|
||||
// isEthTx check if the tx is an eth tx
|
||||
func isEthTx(tx sdk.Tx) bool {
|
||||
extTx, ok := tx.(authante.HasExtensionOptionsTx)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
opts := extTx.GetExtensionOptions()
|
||||
if len(opts) != 1 || opts[0].GetTypeUrl() != "/ethermint.evm.v1.ExtensionOptionsEthereumTx" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// saveTxResult index the txResult into the kv db batch
|
||||
func saveTxResult(codec codec.Codec, batch dbm.Batch, txHash common.Hash, txResult *ethermint.TxResult) error {
|
||||
bz := codec.MustMarshal(txResult)
|
||||
if err := batch.Set(TxHashKey(txHash), bz); err != nil {
|
||||
return sdkerrors.Wrap(err, "set tx-hash key")
|
||||
}
|
||||
if err := batch.Set(TxIndexKey(txResult.Height, txResult.EthTxIndex), txHash.Bytes()); err != nil {
|
||||
return sdkerrors.Wrap(err, "set tx-index key")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseBlockNumberFromKey(key []byte) (int64, error) {
|
||||
if len(key) != TxIndexKeyLength {
|
||||
return 0, fmt.Errorf("wrong tx index key length, expect: %d, got: %d", TxIndexKeyLength, len(key))
|
||||
}
|
||||
|
||||
return int64(sdk.BigEndianToUint64(key[1:9])), nil
|
||||
}
|
189
indexer/kv_indexer_test.go
Normal file
189
indexer/kv_indexer_test.go
Normal file
@ -0,0 +1,189 @@
|
||||
package indexer_test
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/simapp/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/evmos/ethermint/app"
|
||||
"github.com/evmos/ethermint/crypto/ethsecp256k1"
|
||||
evmenc "github.com/evmos/ethermint/encoding"
|
||||
"github.com/evmos/ethermint/indexer"
|
||||
"github.com/evmos/ethermint/tests"
|
||||
"github.com/evmos/ethermint/x/evm/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmlog "github.com/tendermint/tendermint/libs/log"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
func TestKVIndexer(t *testing.T) {
|
||||
priv, err := ethsecp256k1.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
from := common.BytesToAddress(priv.PubKey().Address().Bytes())
|
||||
signer := tests.NewSigner(priv)
|
||||
ethSigner := ethtypes.LatestSignerForChainID(nil)
|
||||
|
||||
to := common.BigToAddress(big.NewInt(1))
|
||||
tx := types.NewTx(
|
||||
nil, 0, &to, big.NewInt(1000), 21000, nil, nil, nil, nil, nil,
|
||||
)
|
||||
tx.From = from.Hex()
|
||||
require.NoError(t, tx.Sign(ethSigner, signer))
|
||||
txHash := tx.AsTransaction().Hash()
|
||||
|
||||
encodingConfig := MakeEncodingConfig()
|
||||
clientCtx := client.Context{}.WithTxConfig(encodingConfig.TxConfig).WithCodec(encodingConfig.Codec)
|
||||
|
||||
// build cosmos-sdk wrapper tx
|
||||
tmTx, err := tx.BuildTx(clientCtx.TxConfig.NewTxBuilder(), "aphoton")
|
||||
require.NoError(t, err)
|
||||
txBz, err := clientCtx.TxConfig.TxEncoder()(tmTx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// build an invalid wrapper tx
|
||||
builder := clientCtx.TxConfig.NewTxBuilder()
|
||||
require.NoError(t, builder.SetMsgs(tx))
|
||||
tmTx2 := builder.GetTx()
|
||||
txBz2, err := clientCtx.TxConfig.TxEncoder()(tmTx2)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
block *tmtypes.Block
|
||||
blockResult []*abci.ResponseDeliverTx
|
||||
expSuccess bool
|
||||
}{
|
||||
{
|
||||
"success, format 1",
|
||||
&tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}},
|
||||
[]*abci.ResponseDeliverTx{
|
||||
&abci.ResponseDeliverTx{
|
||||
Code: 0,
|
||||
Events: []abci.Event{
|
||||
{Type: types.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
|
||||
{Key: []byte("txIndex"), Value: []byte("0")},
|
||||
{Key: []byte("amount"), Value: []byte("1000")},
|
||||
{Key: []byte("txGasUsed"), Value: []byte("21000")},
|
||||
{Key: []byte("txHash"), Value: []byte("")},
|
||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"success, format 2",
|
||||
&tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}},
|
||||
[]*abci.ResponseDeliverTx{
|
||||
&abci.ResponseDeliverTx{
|
||||
Code: 0,
|
||||
Events: []abci.Event{
|
||||
{Type: types.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
|
||||
{Key: []byte("txIndex"), Value: []byte("0")},
|
||||
}},
|
||||
{Type: types.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")},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"success, exceed block gas limit",
|
||||
&tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}},
|
||||
[]*abci.ResponseDeliverTx{
|
||||
&abci.ResponseDeliverTx{
|
||||
Code: 11,
|
||||
Log: "out of gas in location: block gas meter; gasWanted: 21000",
|
||||
Events: []abci.Event{},
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"fail, failed eth tx",
|
||||
&tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}},
|
||||
[]*abci.ResponseDeliverTx{
|
||||
&abci.ResponseDeliverTx{
|
||||
Code: 15,
|
||||
Log: "nonce mismatch",
|
||||
Events: []abci.Event{},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"fail, invalid events",
|
||||
&tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}},
|
||||
[]*abci.ResponseDeliverTx{
|
||||
&abci.ResponseDeliverTx{
|
||||
Code: 0,
|
||||
Events: []abci.Event{},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"fail, not eth tx",
|
||||
&tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz2}}},
|
||||
[]*abci.ResponseDeliverTx{
|
||||
&abci.ResponseDeliverTx{
|
||||
Code: 0,
|
||||
Events: []abci.Event{},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
db := dbm.NewMemDB()
|
||||
idxer := indexer.NewKVIndexer(db, tmlog.NewNopLogger(), clientCtx)
|
||||
|
||||
err = idxer.IndexBlock(tc.block, tc.blockResult)
|
||||
require.NoError(t, err)
|
||||
if !tc.expSuccess {
|
||||
first, err := idxer.FirstIndexedBlock()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(-1), first)
|
||||
|
||||
last, err := idxer.LastIndexedBlock()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(-1), last)
|
||||
} else {
|
||||
first, err := idxer.FirstIndexedBlock()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.block.Header.Height, first)
|
||||
|
||||
last, err := idxer.LastIndexedBlock()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.block.Header.Height, last)
|
||||
|
||||
res1, err := idxer.GetByTxHash(txHash)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res1)
|
||||
res2, err := idxer.GetByBlockAndIndex(1, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, res1, res2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MakeEncodingConfig creates the EncodingConfig
|
||||
func MakeEncodingConfig() params.EncodingConfig {
|
||||
return evmenc.MakeConfig(app.ModuleBasics)
|
||||
}
|
29
proto/ethermint/types/v1/indexer.proto
Normal file
29
proto/ethermint/types/v1/indexer.proto
Normal file
@ -0,0 +1,29 @@
|
||||
syntax = "proto3";
|
||||
package ethermint.types.v1;
|
||||
|
||||
import "gogoproto/gogo.proto";
|
||||
|
||||
option go_package = "github.com/evmos/ethermint/types";
|
||||
|
||||
// TxResult is the value stored in eth tx indexer
|
||||
message TxResult {
|
||||
option (gogoproto.goproto_getters) = false;
|
||||
|
||||
// the block height
|
||||
int64 height = 1;
|
||||
// cosmos tx index
|
||||
uint32 tx_index = 2;
|
||||
// the msg index in a batch tx
|
||||
uint32 msg_index = 3;
|
||||
|
||||
// eth tx index, the index in the list of valid eth tx in the block,
|
||||
// aka. the transaction list returned by eth_getBlock api.
|
||||
int32 eth_tx_index = 4;
|
||||
// if the eth tx is failed
|
||||
bool failed = 5;
|
||||
// gas used by tx, if exceeds block gas limit,
|
||||
// it's set to gas limit which is what's actually deducted by ante handler.
|
||||
uint64 gas_used = 6;
|
||||
// the cumulative gas used within current batch tx
|
||||
uint64 cumulative_gas_used = 7;
|
||||
}
|
28
rpc/apis.go
28
rpc/apis.go
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 ðermint.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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -108,6 +108,8 @@ type JSONRPCConfig struct {
|
||||
// MaxOpenConnections sets the maximum number of simultaneous connections
|
||||
// for the server listener.
|
||||
MaxOpenConnections int `mapstructure:"max-open-connections"`
|
||||
// EnableIndexer defines if enable the custom indexer service.
|
||||
EnableIndexer bool `mapstructure:"enable-indexer"`
|
||||
}
|
||||
|
||||
// TLSConfig defines the certificate and matching private key for the server.
|
||||
@ -208,6 +210,7 @@ func DefaultJSONRPCConfig() *JSONRPCConfig {
|
||||
HTTPIdleTimeout: DefaultHTTPIdleTimeout,
|
||||
AllowUnprotectedTxs: DefaultAllowUnprotectedTxs,
|
||||
MaxOpenConnections: DefaultMaxOpenConnections,
|
||||
EnableIndexer: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,6 +315,7 @@ func GetConfig(v *viper.Viper) Config {
|
||||
HTTPTimeout: v.GetDuration("json-rpc.http-timeout"),
|
||||
HTTPIdleTimeout: v.GetDuration("json-rpc.http-idle-timeout"),
|
||||
MaxOpenConnections: v.GetInt("json-rpc.max-open-connections"),
|
||||
EnableIndexer: v.GetBool("json-rpc.enable-indexer"),
|
||||
},
|
||||
TLS: TLSConfig{
|
||||
CertificatePath: v.GetString("tls.certificate-path"),
|
||||
|
@ -70,6 +70,9 @@ allow-unprotected-txs = {{ .JSONRPC.AllowUnprotectedTxs }}
|
||||
# for the server listener.
|
||||
max-open-connections = {{ .JSONRPC.MaxOpenConnections }}
|
||||
|
||||
# EnableIndexer enables the custom transaction indexer for the EVM (ethereum transactions).
|
||||
enable-indexer = {{ .JSONRPC.EnableIndexer }}
|
||||
|
||||
###############################################################################
|
||||
### TLS Configuration ###
|
||||
###############################################################################
|
||||
|
@ -48,6 +48,7 @@ const (
|
||||
JSONRPCHTTPIdleTimeout = "json-rpc.http-idle-timeout"
|
||||
JSONRPCAllowUnprotectedTxs = "json-rpc.allow-unprotected-txs"
|
||||
JSONRPCMaxOpenConnections = "json-rpc.max-open-connections"
|
||||
JSONRPCEnableIndexer = "json-rpc.enable-indexer"
|
||||
)
|
||||
|
||||
// EVM flags
|
||||
|
112
server/indexer_cmd.go
Normal file
112
server/indexer_cmd.go
Normal file
@ -0,0 +1,112 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/evmos/ethermint/indexer"
|
||||
tmnode "github.com/tendermint/tendermint/node"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
tmstore "github.com/tendermint/tendermint/store"
|
||||
)
|
||||
|
||||
func NewIndexTxCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "index-eth-tx [forward|backward]",
|
||||
Short: "Index historical eth txs",
|
||||
Long: `Index historical eth txs, it only support two traverse direction to avoid creating gaps in the indexer db if using arbitrary block ranges:
|
||||
- backward: index the blocks from the first indexed block to the earliest block in the chain.
|
||||
- forward: index the blocks from the latest indexed block to latest block in the chain.
|
||||
`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
serverCtx := server.GetServerContextFromCmd(cmd)
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
direction := args[0]
|
||||
if direction != "backward" && direction != "forward" {
|
||||
return fmt.Errorf("unknown index direction, expect: backward|forward, got: %s", direction)
|
||||
}
|
||||
|
||||
cfg := serverCtx.Config
|
||||
home := cfg.RootDir
|
||||
logger := serverCtx.Logger
|
||||
idxDB, err := OpenIndexerDB(home, server.GetAppDBBackend(serverCtx.Viper))
|
||||
if err != nil {
|
||||
logger.Error("failed to open evm indexer DB", "error", err.Error())
|
||||
return err
|
||||
}
|
||||
idxer := indexer.NewKVIndexer(idxDB, logger.With("module", "evmindex"), clientCtx)
|
||||
|
||||
// open local tendermint db, because the local rpc won't be available.
|
||||
tmdb, err := tmnode.DefaultDBProvider(&tmnode.DBContext{ID: "blockstore", Config: cfg})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockStore := tmstore.NewBlockStore(tmdb)
|
||||
|
||||
stateDB, err := tmnode.DefaultDBProvider(&tmnode.DBContext{ID: "state", Config: cfg})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
|
||||
indexBlock := func(height int64) error {
|
||||
blk := blockStore.LoadBlock(height)
|
||||
if blk == nil {
|
||||
return fmt.Errorf("block not found %d", height)
|
||||
}
|
||||
resBlk, err := stateStore.LoadABCIResponses(height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := idxer.IndexBlock(blk, resBlk.DeliverTxs); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(height)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "backward":
|
||||
first, err := idxer.FirstIndexedBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if first == -1 {
|
||||
return fmt.Errorf("indexer db is empty")
|
||||
}
|
||||
for i := first - 1; i > 0; i-- {
|
||||
if err := indexBlock(i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case "forward":
|
||||
latest, err := idxer.LastIndexedBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if latest == -1 {
|
||||
// start from genesis if empty
|
||||
latest = 0
|
||||
}
|
||||
for i := latest + 1; i <= blockStore.Height(); i++ {
|
||||
if err := indexBlock(i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown direction %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
109
server/indexer_service.go
Normal file
109
server/indexer_service.go
Normal file
@ -0,0 +1,109 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/service"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
ethermint "github.com/evmos/ethermint/types"
|
||||
)
|
||||
|
||||
const (
|
||||
ServiceName = "EVMIndexerService"
|
||||
|
||||
NewBlockWaitTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
// EVMIndexerService indexes transactions for json-rpc service.
|
||||
type EVMIndexerService struct {
|
||||
service.BaseService
|
||||
|
||||
txIdxr ethermint.EVMTxIndexer
|
||||
client rpcclient.Client
|
||||
}
|
||||
|
||||
// NewEVMIndexerService returns a new service instance.
|
||||
func NewEVMIndexerService(
|
||||
txIdxr ethermint.EVMTxIndexer,
|
||||
client rpcclient.Client,
|
||||
) *EVMIndexerService {
|
||||
is := &EVMIndexerService{txIdxr: txIdxr, client: client}
|
||||
is.BaseService = *service.NewBaseService(nil, ServiceName, is)
|
||||
return is
|
||||
}
|
||||
|
||||
// OnStart implements service.Service by subscribing for new blocks
|
||||
// and indexing them by events.
|
||||
func (eis *EVMIndexerService) OnStart() error {
|
||||
ctx := context.Background()
|
||||
status, err := eis.client.Status(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
latestBlock := status.SyncInfo.LatestBlockHeight
|
||||
newBlockSignal := make(chan struct{}, 1)
|
||||
|
||||
// Use SubscribeUnbuffered here to ensure both subscriptions does not get
|
||||
// canceled due to not pulling messages fast enough. Cause this might
|
||||
// sometimes happen when there are no other subscribers.
|
||||
blockHeadersChan, err := eis.client.Subscribe(
|
||||
ctx,
|
||||
ServiceName,
|
||||
types.QueryForEvent(types.EventNewBlockHeader).String(),
|
||||
0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
msg := <-blockHeadersChan
|
||||
eventDataHeader := msg.Data.(types.EventDataNewBlockHeader)
|
||||
if eventDataHeader.Header.Height > latestBlock {
|
||||
latestBlock = eventDataHeader.Header.Height
|
||||
// notify
|
||||
select {
|
||||
case newBlockSignal <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
lastBlock, err := eis.txIdxr.LastIndexedBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lastBlock == -1 {
|
||||
lastBlock = latestBlock
|
||||
}
|
||||
for {
|
||||
if latestBlock <= lastBlock {
|
||||
// nothing to index. wait for signal of new block
|
||||
select {
|
||||
case <-newBlockSignal:
|
||||
case <-time.After(NewBlockWaitTimeout):
|
||||
}
|
||||
continue
|
||||
}
|
||||
for i := lastBlock + 1; i <= latestBlock; i++ {
|
||||
block, err := eis.client.Block(ctx, &i)
|
||||
if err != nil {
|
||||
eis.Logger.Error("failed to fetch block", "height", i, "err", err)
|
||||
break
|
||||
}
|
||||
blockResult, err := eis.client.BlockResults(ctx, &i)
|
||||
if err != nil {
|
||||
eis.Logger.Error("failed to fetch block result", "height", i, "err", err)
|
||||
break
|
||||
}
|
||||
if err := eis.txIdxr.IndexBlock(block.Block, blockResult.TxsResults); err != nil {
|
||||
eis.Logger.Error("failed to index block", "height", i, "err", err)
|
||||
}
|
||||
lastBlock = blockResult.Height
|
||||
}
|
||||
}
|
||||
}
|
@ -15,10 +15,11 @@ import (
|
||||
"github.com/evmos/ethermint/rpc"
|
||||
|
||||
"github.com/evmos/ethermint/server/config"
|
||||
ethermint "github.com/evmos/ethermint/types"
|
||||
)
|
||||
|
||||
// StartJSONRPC starts the JSON-RPC server
|
||||
func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEndpoint string, config *config.Config) (*http.Server, chan struct{}, error) {
|
||||
func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEndpoint string, config *config.Config, indexer ethermint.EVMTxIndexer) (*http.Server, chan struct{}, error) {
|
||||
tmWsClient := ConnectTmWS(tmRPCAddr, tmEndpoint, ctx.Logger)
|
||||
|
||||
logger := ctx.Logger.With("module", "geth")
|
||||
@ -39,7 +40,7 @@ func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEn
|
||||
allowUnprotectedTxs := config.JSONRPC.AllowUnprotectedTxs
|
||||
rpcAPIArr := config.JSONRPC.API
|
||||
|
||||
apis := rpc.GetRPCAPIs(ctx, clientCtx, tmWsClient, allowUnprotectedTxs, rpcAPIArr)
|
||||
apis := rpc.GetRPCAPIs(ctx, clientCtx, tmWsClient, allowUnprotectedTxs, indexer, rpcAPIArr)
|
||||
|
||||
for _, api := range apis {
|
||||
if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
|
@ -40,9 +40,11 @@ import (
|
||||
servergrpc "github.com/cosmos/cosmos-sdk/server/grpc"
|
||||
"github.com/cosmos/cosmos-sdk/server/types"
|
||||
|
||||
"github.com/evmos/ethermint/indexer"
|
||||
ethdebug "github.com/evmos/ethermint/rpc/namespaces/ethereum/debug"
|
||||
"github.com/evmos/ethermint/server/config"
|
||||
srvflags "github.com/evmos/ethermint/server/flags"
|
||||
ethermint "github.com/evmos/ethermint/types"
|
||||
)
|
||||
|
||||
// StartCmd runs the service passed in, either stand-alone or in-process with
|
||||
@ -165,6 +167,7 @@ which accepts a path for the resulting pprof file.
|
||||
cmd.Flags().Int32(srvflags.JSONRPCLogsCap, config.DefaultLogsCap, "Sets the max number of results can be returned from single `eth_getLogs` query")
|
||||
cmd.Flags().Int32(srvflags.JSONRPCBlockRangeCap, config.DefaultBlockRangeCap, "Sets the max block range allowed for `eth_getLogs` query")
|
||||
cmd.Flags().Int(srvflags.JSONRPCMaxOpenConnections, config.DefaultMaxOpenConnections, "Sets the maximum number of simultaneous connections for the server listener")
|
||||
cmd.Flags().Bool(srvflags.JSONRPCEnableIndexer, false, "Enable the custom tx indexer for json-rpc")
|
||||
|
||||
cmd.Flags().String(srvflags.EVMTracer, config.DefaultEVMTracer, "the EVM tracer type to collect execution traces from the EVM transaction execution (json|struct|access_list|markdown)")
|
||||
cmd.Flags().Uint64(srvflags.EVMMaxTxGasWanted, config.DefaultMaxTxGasWanted, "the gas wanted for each eth tx returned in ante handler in check tx mode")
|
||||
@ -323,13 +326,39 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty
|
||||
// Add the tx service to the gRPC router. We only need to register this
|
||||
// service if API or gRPC or JSONRPC is enabled, and avoid doing so in the general
|
||||
// case, because it spawns a new local tendermint RPC client.
|
||||
if config.API.Enable || config.GRPC.Enable || config.JSONRPC.Enable {
|
||||
if config.API.Enable || config.GRPC.Enable || config.JSONRPC.Enable || config.JSONRPC.EnableIndexer {
|
||||
clientCtx = clientCtx.WithClient(local.New(tmNode))
|
||||
|
||||
app.RegisterTxService(clientCtx)
|
||||
app.RegisterTendermintService(clientCtx)
|
||||
}
|
||||
|
||||
var idxer ethermint.EVMTxIndexer
|
||||
if config.JSONRPC.EnableIndexer {
|
||||
idxDB, err := OpenIndexerDB(home, server.GetAppDBBackend(ctx.Viper))
|
||||
if err != nil {
|
||||
logger.Error("failed to open evm indexer DB", "error", err.Error())
|
||||
return err
|
||||
}
|
||||
idxLogger := ctx.Logger.With("module", "evmindex")
|
||||
idxer = indexer.NewKVIndexer(idxDB, idxLogger, clientCtx)
|
||||
indexerService := NewEVMIndexerService(idxer, clientCtx.Client)
|
||||
indexerService.SetLogger(idxLogger)
|
||||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
if err := indexerService.Start(); err != nil {
|
||||
errCh <- err
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case <-time.After(types.ServerStartTime): // assume server started successfully
|
||||
}
|
||||
}
|
||||
|
||||
var apiSrv *api.Server
|
||||
if config.API.Enable {
|
||||
genDoc, err := genDocProvider()
|
||||
@ -427,7 +456,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty
|
||||
|
||||
tmEndpoint := "/websocket"
|
||||
tmRPCAddr := cfg.RPC.ListenAddress
|
||||
httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &config)
|
||||
httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &config, idxer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -482,6 +511,12 @@ func openDB(rootDir string, backendType dbm.BackendType) (dbm.DB, error) {
|
||||
return dbm.NewDB("application", backendType, dataDir)
|
||||
}
|
||||
|
||||
// OpenIndexerDB opens the custom eth indexer db, using the same db backend as the main app
|
||||
func OpenIndexerDB(rootDir string, backendType dbm.BackendType) (dbm.DB, error) {
|
||||
dataDir := filepath.Join(rootDir, "data")
|
||||
return dbm.NewDB("evmindexer", backendType, dataDir)
|
||||
}
|
||||
|
||||
func openTraceWriter(traceWriterFile string) (w io.Writer, err error) {
|
||||
if traceWriterFile == "" {
|
||||
return
|
||||
|
@ -45,6 +45,9 @@ func AddCommands(rootCmd *cobra.Command, defaultNodeHome string, appCreator type
|
||||
sdkserver.ExportCmd(appExport, defaultNodeHome),
|
||||
version.NewVersionCommand(),
|
||||
sdkserver.NewRollbackCmd(defaultNodeHome),
|
||||
|
||||
// custom tx indexer command
|
||||
NewIndexTxCmd(),
|
||||
)
|
||||
}
|
||||
|
||||
|
20
tests/integration_tests/configs/enable-indexer.jsonnet
Normal file
20
tests/integration_tests/configs/enable-indexer.jsonnet
Normal file
@ -0,0 +1,20 @@
|
||||
local config = import 'default.jsonnet';
|
||||
|
||||
config {
|
||||
'ethermint_9000-1'+: {
|
||||
config+: {
|
||||
tx_index+: {
|
||||
indexer: 'null',
|
||||
},
|
||||
},
|
||||
'app-config'+: {
|
||||
pruning: 'everything',
|
||||
'state-sync'+: {
|
||||
'snapshot-interval': 0,
|
||||
},
|
||||
'json-rpc'+: {
|
||||
'enable-indexer': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from .network import setup_ethermint, setup_geth
|
||||
from .network import setup_custom_ethermint, setup_ethermint, setup_geth
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@ -9,14 +11,24 @@ def ethermint(tmp_path_factory):
|
||||
yield from setup_ethermint(path, 26650)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ethermint_indexer(tmp_path_factory):
|
||||
path = tmp_path_factory.mktemp("indexer")
|
||||
yield from setup_custom_ethermint(
|
||||
path, 26660, Path(__file__).parent / "configs/enable-indexer.jsonnet"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def geth(tmp_path_factory):
|
||||
path = tmp_path_factory.mktemp("geth")
|
||||
yield from setup_geth(path, 8545)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", params=["ethermint", "geth", "ethermint-ws"])
|
||||
def cluster(request, ethermint, geth):
|
||||
@pytest.fixture(
|
||||
scope="session", params=["ethermint", "geth", "ethermint-ws", "enable-indexer"]
|
||||
)
|
||||
def cluster(request, ethermint, ethermint_indexer, geth):
|
||||
"""
|
||||
run on both ethermint and geth
|
||||
"""
|
||||
@ -29,5 +41,7 @@ def cluster(request, ethermint, geth):
|
||||
ethermint_ws = ethermint.copy()
|
||||
ethermint_ws.use_websocket()
|
||||
yield ethermint_ws
|
||||
elif provider == "enable-indexer":
|
||||
yield ethermint_indexer
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
@ -131,7 +131,7 @@ func startInProcess(cfg Config, val *Validator) error {
|
||||
tmEndpoint := "/websocket"
|
||||
tmRPCAddr := val.RPCAddress
|
||||
|
||||
val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, val.AppConfig)
|
||||
val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, val.AppConfig, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
19
types/indexer.go
Normal file
19
types/indexer.go
Normal file
@ -0,0 +1,19 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// EVMTxIndexer defines the interface of custom eth tx indexer.
|
||||
type EVMTxIndexer interface {
|
||||
// LastIndexedBlock returns -1 if indexer db is empty
|
||||
LastIndexedBlock() (int64, error)
|
||||
IndexBlock(*tmtypes.Block, []*abci.ResponseDeliverTx) error
|
||||
|
||||
// GetByTxHash returns nil if tx not found.
|
||||
GetByTxHash(common.Hash) (*TxResult, error)
|
||||
// GetByBlockAndIndex returns nil if tx not found.
|
||||
GetByBlockAndIndex(int64, int32) (*TxResult, error)
|
||||
}
|
485
types/indexer.pb.go
generated
Normal file
485
types/indexer.pb.go
generated
Normal file
@ -0,0 +1,485 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: ethermint/types/v1/indexer.proto
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
_ "github.com/gogo/protobuf/gogoproto"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
io "io"
|
||||
math "math"
|
||||
math_bits "math/bits"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// TxResult is the value stored in eth tx indexer
|
||||
type TxResult struct {
|
||||
// the block height
|
||||
Height int64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"`
|
||||
// cosmos tx index
|
||||
TxIndex uint32 `protobuf:"varint,2,opt,name=tx_index,json=txIndex,proto3" json:"tx_index,omitempty"`
|
||||
// the msg index in a batch tx
|
||||
MsgIndex uint32 `protobuf:"varint,3,opt,name=msg_index,json=msgIndex,proto3" json:"msg_index,omitempty"`
|
||||
// eth tx index, the index in the list of valid eth tx in the block,
|
||||
// aka. the transaction list returned by eth_getBlock api.
|
||||
EthTxIndex int32 `protobuf:"varint,4,opt,name=eth_tx_index,json=ethTxIndex,proto3" json:"eth_tx_index,omitempty"`
|
||||
// if the eth tx is failed
|
||||
Failed bool `protobuf:"varint,5,opt,name=failed,proto3" json:"failed,omitempty"`
|
||||
// gas used by tx, if exceeds block gas limit,
|
||||
// it's set to gas limit which is what's actually deducted by ante handler.
|
||||
GasUsed uint64 `protobuf:"varint,6,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"`
|
||||
// the cumulative gas used within current batch tx
|
||||
CumulativeGasUsed uint64 `protobuf:"varint,7,opt,name=cumulative_gas_used,json=cumulativeGasUsed,proto3" json:"cumulative_gas_used,omitempty"`
|
||||
}
|
||||
|
||||
func (m *TxResult) Reset() { *m = TxResult{} }
|
||||
func (m *TxResult) String() string { return proto.CompactTextString(m) }
|
||||
func (*TxResult) ProtoMessage() {}
|
||||
func (*TxResult) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_1197e10a8be8ed28, []int{0}
|
||||
}
|
||||
func (m *TxResult) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *TxResult) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_TxResult.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *TxResult) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_TxResult.Merge(m, src)
|
||||
}
|
||||
func (m *TxResult) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *TxResult) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_TxResult.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_TxResult proto.InternalMessageInfo
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*TxResult)(nil), "ethermint.types.v1.TxResult")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("ethermint/types/v1/indexer.proto", fileDescriptor_1197e10a8be8ed28) }
|
||||
|
||||
var fileDescriptor_1197e10a8be8ed28 = []byte{
|
||||
// 295 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x90, 0x31, 0x4b, 0xc3, 0x40,
|
||||
0x18, 0x86, 0x73, 0xb6, 0x4d, 0xe3, 0xa1, 0x83, 0x51, 0x4a, 0x54, 0x88, 0x87, 0x53, 0xa6, 0x84,
|
||||
0xe2, 0xd6, 0xd1, 0x45, 0x5c, 0x8f, 0xba, 0xb8, 0x84, 0xb4, 0xf9, 0xbc, 0x3b, 0xe8, 0xf5, 0x4a,
|
||||
0xef, 0x4b, 0x88, 0xff, 0xc0, 0xd1, 0x9f, 0xe0, 0xcf, 0x71, 0xec, 0xe8, 0x28, 0x2d, 0xfe, 0x0f,
|
||||
0xe9, 0x35, 0x44, 0x70, 0xfb, 0x5e, 0x9e, 0xe7, 0xe3, 0x85, 0x97, 0x32, 0x40, 0x09, 0x6b, 0xad,
|
||||
0x96, 0x98, 0xe1, 0xeb, 0x0a, 0x6c, 0x56, 0x8f, 0x33, 0xb5, 0x2c, 0xa1, 0x81, 0x75, 0xba, 0x5a,
|
||||
0x1b, 0x34, 0x61, 0xd8, 0x19, 0xa9, 0x33, 0xd2, 0x7a, 0x7c, 0x75, 0x21, 0x8c, 0x30, 0x0e, 0x67,
|
||||
0xfb, 0xeb, 0x60, 0xde, 0xfe, 0x10, 0x1a, 0x4c, 0x1b, 0x0e, 0xb6, 0x5a, 0x60, 0x38, 0xa2, 0xbe,
|
||||
0x04, 0x25, 0x24, 0x46, 0x84, 0x91, 0xa4, 0xc7, 0xdb, 0x14, 0x5e, 0xd2, 0x00, 0x9b, 0xdc, 0x55,
|
||||
0x44, 0x47, 0x8c, 0x24, 0xa7, 0x7c, 0x88, 0xcd, 0xe3, 0x3e, 0x86, 0xd7, 0xf4, 0x58, 0x5b, 0xd1,
|
||||
0xb2, 0x9e, 0x63, 0x81, 0xb6, 0xe2, 0x00, 0x19, 0x3d, 0x01, 0x94, 0x79, 0xf7, 0xdb, 0x67, 0x24,
|
||||
0x19, 0x70, 0x0a, 0x28, 0xa7, 0xed, 0xfb, 0x88, 0xfa, 0x2f, 0x85, 0x5a, 0x40, 0x19, 0x0d, 0x18,
|
||||
0x49, 0x02, 0xde, 0xa6, 0x7d, 0xa3, 0x28, 0x6c, 0x5e, 0x59, 0x28, 0x23, 0x9f, 0x91, 0xa4, 0xcf,
|
||||
0x87, 0xa2, 0xb0, 0x4f, 0x16, 0xca, 0x30, 0xa5, 0xe7, 0xf3, 0x4a, 0x57, 0x8b, 0x02, 0x55, 0x0d,
|
||||
0x79, 0x67, 0x0d, 0x9d, 0x75, 0xf6, 0x87, 0x1e, 0x0e, 0xfe, 0xa4, 0xff, 0xf6, 0x71, 0xe3, 0xdd,
|
||||
0x4f, 0x3e, 0xb7, 0x31, 0xd9, 0x6c, 0x63, 0xf2, 0xbd, 0x8d, 0xc9, 0xfb, 0x2e, 0xf6, 0x36, 0xbb,
|
||||
0xd8, 0xfb, 0xda, 0xc5, 0xde, 0x33, 0x13, 0x0a, 0x65, 0x35, 0x4b, 0xe7, 0x46, 0x67, 0x50, 0x6b,
|
||||
0x63, 0xb3, 0x7f, 0xf3, 0xce, 0x7c, 0x37, 0xd5, 0xdd, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc1,
|
||||
0x34, 0xa8, 0x0b, 0x78, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *TxResult) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *TxResult) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *TxResult) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.CumulativeGasUsed != 0 {
|
||||
i = encodeVarintIndexer(dAtA, i, uint64(m.CumulativeGasUsed))
|
||||
i--
|
||||
dAtA[i] = 0x38
|
||||
}
|
||||
if m.GasUsed != 0 {
|
||||
i = encodeVarintIndexer(dAtA, i, uint64(m.GasUsed))
|
||||
i--
|
||||
dAtA[i] = 0x30
|
||||
}
|
||||
if m.Failed {
|
||||
i--
|
||||
if m.Failed {
|
||||
dAtA[i] = 1
|
||||
} else {
|
||||
dAtA[i] = 0
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x28
|
||||
}
|
||||
if m.EthTxIndex != 0 {
|
||||
i = encodeVarintIndexer(dAtA, i, uint64(m.EthTxIndex))
|
||||
i--
|
||||
dAtA[i] = 0x20
|
||||
}
|
||||
if m.MsgIndex != 0 {
|
||||
i = encodeVarintIndexer(dAtA, i, uint64(m.MsgIndex))
|
||||
i--
|
||||
dAtA[i] = 0x18
|
||||
}
|
||||
if m.TxIndex != 0 {
|
||||
i = encodeVarintIndexer(dAtA, i, uint64(m.TxIndex))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
}
|
||||
if m.Height != 0 {
|
||||
i = encodeVarintIndexer(dAtA, i, uint64(m.Height))
|
||||
i--
|
||||
dAtA[i] = 0x8
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintIndexer(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovIndexer(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return base
|
||||
}
|
||||
func (m *TxResult) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if m.Height != 0 {
|
||||
n += 1 + sovIndexer(uint64(m.Height))
|
||||
}
|
||||
if m.TxIndex != 0 {
|
||||
n += 1 + sovIndexer(uint64(m.TxIndex))
|
||||
}
|
||||
if m.MsgIndex != 0 {
|
||||
n += 1 + sovIndexer(uint64(m.MsgIndex))
|
||||
}
|
||||
if m.EthTxIndex != 0 {
|
||||
n += 1 + sovIndexer(uint64(m.EthTxIndex))
|
||||
}
|
||||
if m.Failed {
|
||||
n += 2
|
||||
}
|
||||
if m.GasUsed != 0 {
|
||||
n += 1 + sovIndexer(uint64(m.GasUsed))
|
||||
}
|
||||
if m.CumulativeGasUsed != 0 {
|
||||
n += 1 + sovIndexer(uint64(m.CumulativeGasUsed))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovIndexer(x uint64) (n int) {
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozIndexer(x uint64) (n int) {
|
||||
return sovIndexer(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *TxResult) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowIndexer
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: TxResult: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: TxResult: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType)
|
||||
}
|
||||
m.Height = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowIndexer
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Height |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field TxIndex", wireType)
|
||||
}
|
||||
m.TxIndex = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowIndexer
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.TxIndex |= uint32(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field MsgIndex", wireType)
|
||||
}
|
||||
m.MsgIndex = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowIndexer
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.MsgIndex |= uint32(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field EthTxIndex", wireType)
|
||||
}
|
||||
m.EthTxIndex = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowIndexer
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.EthTxIndex |= int32(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 5:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Failed", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowIndexer
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
v |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Failed = bool(v != 0)
|
||||
case 6:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field GasUsed", wireType)
|
||||
}
|
||||
m.GasUsed = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowIndexer
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.GasUsed |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 7:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field CumulativeGasUsed", wireType)
|
||||
}
|
||||
m.CumulativeGasUsed = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowIndexer
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.CumulativeGasUsed |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipIndexer(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthIndexer
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipIndexer(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
depth := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowIndexer
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowIndexer
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowIndexer
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthIndexer
|
||||
}
|
||||
iNdEx += length
|
||||
case 3:
|
||||
depth++
|
||||
case 4:
|
||||
if depth == 0 {
|
||||
return 0, ErrUnexpectedEndOfGroupIndexer
|
||||
}
|
||||
depth--
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthIndexer
|
||||
}
|
||||
if depth == 0 {
|
||||
return iNdEx, nil
|
||||
}
|
||||
}
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthIndexer = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowIndexer = fmt.Errorf("proto: integer overflow")
|
||||
ErrUnexpectedEndOfGroupIndexer = fmt.Errorf("proto: unexpected end of group")
|
||||
)
|
Loading…
Reference in New Issue
Block a user