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.
|
* (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`
|
* (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
|
### Improvements
|
||||||
|
|
||||||
* (deps) [\#1147](https://github.com/evmos/ethermint/pull/1147) Bump Go version to `1.18`.
|
* (deps) [\#1147](https://github.com/evmos/ethermint/pull/1147) Bump Go version to `1.18`.
|
||||||
|
@ -17,7 +17,7 @@ in
|
|||||||
buildGoApplication rec {
|
buildGoApplication rec {
|
||||||
inherit pname version tags ldflags;
|
inherit pname version tags ldflags;
|
||||||
src = lib.sourceByRegex ./. [
|
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)?$"
|
"^tests(/.*[.]go)?$"
|
||||||
];
|
];
|
||||||
modules = ./gomod2nix.toml;
|
modules = ./gomod2nix.toml;
|
||||||
|
@ -82,6 +82,9 @@
|
|||||||
- [ethermint/types/v1/dynamic_fee.proto](#ethermint/types/v1/dynamic_fee.proto)
|
- [ethermint/types/v1/dynamic_fee.proto](#ethermint/types/v1/dynamic_fee.proto)
|
||||||
- [ExtensionOptionDynamicFeeTx](#ethermint.types.v1.ExtensionOptionDynamicFeeTx)
|
- [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)
|
- [ethermint/types/v1/web3.proto](#ethermint/types/v1/web3.proto)
|
||||||
- [ExtensionOptionsWeb3Tx](#ethermint.types.v1.ExtensionOptionsWeb3Tx)
|
- [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 messages -->
|
||||||
|
|
||||||
<!-- end enums -->
|
<!-- 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/personal"
|
||||||
"github.com/evmos/ethermint/rpc/namespaces/ethereum/txpool"
|
"github.com/evmos/ethermint/rpc/namespaces/ethereum/txpool"
|
||||||
"github.com/evmos/ethermint/rpc/namespaces/ethereum/web3"
|
"github.com/evmos/ethermint/rpc/namespaces/ethereum/web3"
|
||||||
|
ethermint "github.com/evmos/ethermint/types"
|
||||||
|
|
||||||
rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
|
rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
|
||||||
)
|
)
|
||||||
@ -48,6 +49,7 @@ type APICreator = func(
|
|||||||
clientCtx client.Context,
|
clientCtx client.Context,
|
||||||
tendermintWebsocketClient *rpcclient.WSClient,
|
tendermintWebsocketClient *rpcclient.WSClient,
|
||||||
allowUnprotectedTxs bool,
|
allowUnprotectedTxs bool,
|
||||||
|
indexer ethermint.EVMTxIndexer,
|
||||||
) []rpc.API
|
) []rpc.API
|
||||||
|
|
||||||
// apiCreators defines the JSON-RPC API namespaces.
|
// apiCreators defines the JSON-RPC API namespaces.
|
||||||
@ -55,8 +57,8 @@ var apiCreators map[string]APICreator
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
apiCreators = map[string]APICreator{
|
apiCreators = map[string]APICreator{
|
||||||
EthNamespace: func(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
|
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)
|
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer)
|
||||||
return []rpc.API{
|
return []rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: EthNamespace,
|
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{
|
return []rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: Web3Namespace,
|
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{
|
return []rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: NetNamespace,
|
Namespace: NetNamespace,
|
||||||
@ -92,8 +94,8 @@ func init() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PersonalNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
|
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)
|
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer)
|
||||||
return []rpc.API{
|
return []rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: PersonalNamespace,
|
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{
|
return []rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: TxPoolNamespace,
|
Namespace: TxPoolNamespace,
|
||||||
@ -113,8 +115,8 @@ func init() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DebugNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
|
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)
|
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer)
|
||||||
return []rpc.API{
|
return []rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: DebugNamespace,
|
Namespace: DebugNamespace,
|
||||||
@ -124,8 +126,8 @@ func init() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MinerNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
|
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)
|
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer)
|
||||||
return []rpc.API{
|
return []rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: MinerNamespace,
|
Namespace: MinerNamespace,
|
||||||
@ -139,12 +141,12 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRPCAPIs returns the list of all APIs
|
// 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
|
var apis []rpc.API
|
||||||
|
|
||||||
for _, ns := range selectedAPIs {
|
for _, ns := range selectedAPIs {
|
||||||
if creator, ok := apiCreators[ns]; ok {
|
if creator, ok := apiCreators[ns]; ok {
|
||||||
apis = append(apis, creator(ctx, clientCtx, tmWSClient, allowUnprotectedTxs)...)
|
apis = append(apis, creator(ctx, clientCtx, tmWSClient, allowUnprotectedTxs, indexer)...)
|
||||||
} else {
|
} else {
|
||||||
ctx.Logger.Error("invalid namespace value", "namespace", ns)
|
ctx.Logger.Error("invalid namespace value", "namespace", ns)
|
||||||
}
|
}
|
||||||
|
@ -103,8 +103,8 @@ type EVMBackend interface {
|
|||||||
|
|
||||||
// Tx Info
|
// Tx Info
|
||||||
GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransaction, error)
|
GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransaction, error)
|
||||||
GetTxByEthHash(txHash common.Hash) (*tmrpctypes.ResultTx, error)
|
GetTxByEthHash(txHash common.Hash) (*ethermint.TxResult, error)
|
||||||
GetTxByTxIndex(height int64, txIndex uint) (*tmrpctypes.ResultTx, error)
|
GetTxByTxIndex(height int64, txIndex uint) (*ethermint.TxResult, error)
|
||||||
GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
|
GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
|
||||||
GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error)
|
GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error)
|
||||||
GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
|
GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
|
||||||
@ -140,10 +140,17 @@ type Backend struct {
|
|||||||
chainID *big.Int
|
chainID *big.Int
|
||||||
cfg config.Config
|
cfg config.Config
|
||||||
allowUnprotectedTxs bool
|
allowUnprotectedTxs bool
|
||||||
|
indexer ethermint.EVMTxIndexer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBackend creates a new Backend instance for cosmos and ethereum namespaces
|
// 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)
|
chainID, err := ethermint.ParseChainID(clientCtx.ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -176,5 +183,6 @@ func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context
|
|||||||
chainID: chainID,
|
chainID: chainID,
|
||||||
cfg: appConf,
|
cfg: appConf,
|
||||||
allowUnprotectedTxs: allowUnprotectedTxs,
|
allowUnprotectedTxs: allowUnprotectedTxs,
|
||||||
|
indexer: indexer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
dbm "github.com/tendermint/tm-db"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||||
"github.com/cosmos/cosmos-sdk/server"
|
"github.com/cosmos/cosmos-sdk/server"
|
||||||
@ -20,6 +22,7 @@ import (
|
|||||||
"github.com/evmos/ethermint/app"
|
"github.com/evmos/ethermint/app"
|
||||||
"github.com/evmos/ethermint/crypto/hd"
|
"github.com/evmos/ethermint/crypto/hd"
|
||||||
"github.com/evmos/ethermint/encoding"
|
"github.com/evmos/ethermint/encoding"
|
||||||
|
"github.com/evmos/ethermint/indexer"
|
||||||
"github.com/evmos/ethermint/rpc/backend/mocks"
|
"github.com/evmos/ethermint/rpc/backend/mocks"
|
||||||
rpctypes "github.com/evmos/ethermint/rpc/types"
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||||||
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
@ -56,7 +59,9 @@ func (suite *BackendTestSuite) SetupTest() {
|
|||||||
|
|
||||||
allowUnprotectedTxs := false
|
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.queryClient.QueryClient = mocks.NewQueryClient(suite.T())
|
||||||
suite.backend.clientCtx.Client = mocks.NewClient(suite.T())
|
suite.backend.clientCtx.Client = mocks.NewClient(suite.T())
|
||||||
suite.backend.ctx = rpctypes.ContextWithHeight(1)
|
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:
|
// Check if tx exists on EVM by cross checking with blockResults:
|
||||||
// - Include unsuccessful tx that exceeds block gas limit
|
// - Include unsuccessful tx that exceeds block gas limit
|
||||||
// - Exclude unsuccessful tx with any other error but ExceedBlockGasLimit
|
// - 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()))
|
b.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash()))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -949,7 +949,7 @@ func (suite *BackendTestSuite) TestGetEthereumMsgsFromTendermintBlock() {
|
|||||||
TxsResults: []*types.ResponseDeliverTx{
|
TxsResults: []*types.ResponseDeliverTx{
|
||||||
{
|
{
|
||||||
Code: 1,
|
Code: 1,
|
||||||
Log: ExceedBlockGasLimitError,
|
Log: ethrpc.ExceedBlockGasLimitError,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -964,7 +964,7 @@ func (suite *BackendTestSuite) TestGetEthereumMsgsFromTendermintBlock() {
|
|||||||
TxsResults: []*types.ResponseDeliverTx{
|
TxsResults: []*types.ResponseDeliverTx{
|
||||||
{
|
{
|
||||||
Code: 0,
|
Code: 0,
|
||||||
Log: ExceedBlockGasLimitError,
|
Log: ethrpc.ExceedBlockGasLimitError,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -32,23 +32,14 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi
|
|||||||
return nil, err
|
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
|
// check tx index is not out of bound
|
||||||
if uint32(len(blk.Block.Txs)) < transaction.Index {
|
if uint32(len(blk.Block.Txs)) < transaction.TxIndex {
|
||||||
b.logger.Debug("tx index out of bounds", "index", transaction.Index, "hash", hash.String(), "height", blk.Block.Height)
|
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)
|
return nil, fmt.Errorf("transaction not included in block %v", blk.Block.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
var predecessors []*evmtypes.MsgEthereumTx
|
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)
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error())
|
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 {
|
if err != nil {
|
||||||
b.logger.Debug("tx not found", "hash", hash)
|
b.logger.Debug("tx not found", "hash", hash)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// add predecessor messages in current cosmos tx
|
// 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)
|
ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
@ -79,7 +70,7 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi
|
|||||||
predecessors = append(predecessors, ethMsg)
|
predecessors = append(predecessors, ethMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
ethMessage, ok := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
|
ethMessage, ok := tx.GetMsgs()[transaction.MsgIndex].(*evmtypes.MsgEthereumTx)
|
||||||
if !ok {
|
if !ok {
|
||||||
b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
|
b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
|
||||||
return nil, fmt.Errorf("invalid transaction type %T", tx)
|
return nil, fmt.Errorf("invalid transaction type %T", tx)
|
||||||
|
@ -3,11 +3,14 @@ package backend
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"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"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
rpctypes "github.com/evmos/ethermint/rpc/types"
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||||||
|
ethermint "github.com/evmos/ethermint/types"
|
||||||
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
@ -19,6 +22,64 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac
|
|||||||
hexTx := txHash.Hex()
|
hexTx := txHash.Hex()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return b.getTransactionByHashPending(txHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := b.GetTendermintBlockByNumber(rpctypes.BlockNumber(res.Height))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 {
|
||||||
|
res.EthTxIndex = int32(i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseFee, err := b.BaseFee(blockRes)
|
||||||
|
if err != nil {
|
||||||
|
// handle the error for pruned node.
|
||||||
|
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", blockRes.Height, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpctypes.NewTransactionFromMsg(
|
||||||
|
msg,
|
||||||
|
common.BytesToHash(block.BlockID.Hash.Bytes()),
|
||||||
|
uint64(res.Height),
|
||||||
|
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
|
// try to find tx in mempool
|
||||||
txs, err := b.PendingTransactions()
|
txs, err := b.PendingTransactions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -34,6 +95,7 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac
|
|||||||
}
|
}
|
||||||
|
|
||||||
if msg.Hash == hexTx {
|
if msg.Hash == hexTx {
|
||||||
|
// use zero block values since it's not included in a block yet
|
||||||
rpctx, err := rpctypes.NewTransactionFromMsg(
|
rpctx, err := rpctypes.NewTransactionFromMsg(
|
||||||
msg,
|
msg,
|
||||||
common.Hash{},
|
common.Hash{},
|
||||||
@ -50,72 +112,6 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac
|
|||||||
|
|
||||||
b.logger.Debug("tx not found", "hash", hexTx)
|
b.logger.Debug("tx not found", "hash", hexTx)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
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 {
|
|
||||||
// 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)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if parsedTx.EthTxIndex == -1 {
|
|
||||||
return nil, errors.New("can't find index of ethereum tx")
|
|
||||||
}
|
|
||||||
|
|
||||||
baseFee, err := b.BaseFee(blockRes)
|
|
||||||
if err != nil {
|
|
||||||
// handle the error for pruned node.
|
|
||||||
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", blockRes.Height, "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rpctypes.NewTransactionFromMsg(
|
|
||||||
msg,
|
|
||||||
common.BytesToHash(block.BlockID.Hash.Bytes()),
|
|
||||||
uint64(res.Height),
|
|
||||||
uint64(parsedTx.EthTxIndex),
|
|
||||||
baseFee,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransactionReceipt returns the transaction receipt identified by hash.
|
// GetTransactionReceipt returns the transaction receipt identified by hash.
|
||||||
@ -129,44 +125,17 @@ func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't ignore the txs which exceed block gas limit.
|
resBlock, err := b.GetTendermintBlockByNumber(rpctypes.BlockNumber(res.Height))
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.logger.Debug("block not found", "height", res.Height, "error", err.Error())
|
b.logger.Debug("block not found", "height", res.Height, "error", err.Error())
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(resBlock.Block.Txs[res.TxIndex])
|
||||||
tx, err := b.clientCtx.TxConfig.TxDecoder()(res.Tx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.logger.Debug("decoding failed", "error", err.Error())
|
b.logger.Debug("decoding failed", "error", err.Error())
|
||||||
return nil, fmt.Errorf("failed to decode tx: %w", err)
|
return nil, fmt.Errorf("failed to decode tx: %w", err)
|
||||||
}
|
}
|
||||||
|
ethMsg := tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx)
|
||||||
if res.TxResult.Code != 0 {
|
|
||||||
// tx failed, we should return gas limit as gas used, because that's how the fee get deducted.
|
|
||||||
for i := 0; i <= parsedTx.MsgIndex; i++ {
|
|
||||||
gasLimit := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx).GetGas()
|
|
||||||
parsedTxs.Txs[i].GasUsed = gasLimit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the `msgIndex` is inferred from tx events, should be within the bound,
|
|
||||||
// and the tx is found by eth tx hash, so the msg type must be correct.
|
|
||||||
ethMsg := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
|
|
||||||
|
|
||||||
txData, err := evmtypes.UnpackTxData(ethMsg.Data)
|
txData, err := evmtypes.UnpackTxData(ethMsg.Data)
|
||||||
if err != nil {
|
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())
|
b.logger.Debug("failed to retrieve block results", "height", res.Height, "error", err.Error())
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
for i := 0; i < int(res.Index) && i < len(blockRes.TxsResults); i++ {
|
for _, txResult := range blockRes.TxsResults[0:res.TxIndex] {
|
||||||
cumulativeGasUsed += uint64(blockRes.TxsResults[i].GasUsed)
|
cumulativeGasUsed += uint64(txResult.GasUsed)
|
||||||
}
|
}
|
||||||
cumulativeGasUsed += parsedTxs.AccumulativeGasUsed(parsedTx.MsgIndex)
|
cumulativeGasUsed += res.CumulativeGasUsed
|
||||||
|
|
||||||
// Get the transaction result from the log
|
|
||||||
var status hexutil.Uint
|
var status hexutil.Uint
|
||||||
if res.TxResult.Code != 0 || parsedTx.Failed {
|
if res.Failed {
|
||||||
status = hexutil.Uint(ethtypes.ReceiptStatusFailed)
|
status = hexutil.Uint(ethtypes.ReceiptStatusFailed)
|
||||||
} else {
|
} else {
|
||||||
status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse tx logs from events
|
// parse tx logs from events
|
||||||
logs, err := parsedTx.ParseTxLogs()
|
logs, err := TxLogsFromEvents(blockRes.TxsResults[res.TxIndex].Events, int(res.MsgIndex))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.logger.Debug("failed to parse logs", "hash", hexTx, "error", err.Error())
|
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
|
// Fallback to find tx index by iterating all valid eth transactions
|
||||||
msgs := b.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes)
|
msgs := b.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes)
|
||||||
for i := range msgs {
|
for i := range msgs {
|
||||||
if msgs[i].Hash == hexTx {
|
if msgs[i].Hash == hexTx {
|
||||||
parsedTx.EthTxIndex = int64(i)
|
res.EthTxIndex = int32(i)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// return error if still unable to find the eth tx index
|
||||||
if parsedTx.EthTxIndex == -1 {
|
if res.EthTxIndex == -1 {
|
||||||
return nil, errors.New("can't find index of ethereum tx")
|
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.
|
// They are stored in the chain database.
|
||||||
"transactionHash": hash,
|
"transactionHash": hash,
|
||||||
"contractAddress": nil,
|
"contractAddress": nil,
|
||||||
"gasUsed": hexutil.Uint64(parsedTx.GasUsed),
|
"gasUsed": hexutil.Uint64(res.GasUsed),
|
||||||
|
|
||||||
// Inclusion information: These fields provide information about the inclusion of the
|
// Inclusion information: These fields provide information about the inclusion of the
|
||||||
// transaction corresponding to this receipt.
|
// transaction corresponding to this receipt.
|
||||||
"blockHash": common.BytesToHash(resBlock.Block.Header.Hash()).Hex(),
|
"blockHash": common.BytesToHash(resBlock.Block.Header.Hash()).Hex(),
|
||||||
"blockNumber": hexutil.Uint64(res.Height),
|
"blockNumber": hexutil.Uint64(res.Height),
|
||||||
"transactionIndex": hexutil.Uint64(parsedTx.EthTxIndex),
|
"transactionIndex": hexutil.Uint64(res.EthTxIndex),
|
||||||
|
|
||||||
// sender and receiver (contract or EOA) addreses
|
// sender and receiver (contract or EOA) addreses
|
||||||
"from": from,
|
"from": from,
|
||||||
@ -304,32 +277,66 @@ func (b *Backend) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNum
|
|||||||
// GetTxByEthHash uses `/tx_query` to find transaction by ethereum tx hash
|
// GetTxByEthHash uses `/tx_query` to find transaction by ethereum tx hash
|
||||||
// TODO: Don't need to convert once hashing is fixed on Tendermint
|
// TODO: Don't need to convert once hashing is fixed on Tendermint
|
||||||
// https://github.com/tendermint/tendermint/issues/6539
|
// 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())
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, sdkerrors.Wrapf(err, "GetTxByEthHash %s", hash.Hex())
|
||||||
}
|
}
|
||||||
if len(resTxs.Txs) == 0 {
|
return txResult, nil
|
||||||
return nil, errors.Errorf("ethereum tx not found for hash %s", hash.Hex())
|
|
||||||
}
|
|
||||||
return resTxs.Txs[0], nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTxByTxIndex uses `/tx_query` to find transaction by tx index of valid ethereum txs
|
// 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",
|
query := fmt.Sprintf("tx.height=%d AND %s.%s=%d",
|
||||||
height, evmtypes.TypeMsgEthereumTx,
|
height, evmtypes.TypeMsgEthereumTx,
|
||||||
evmtypes.AttributeKeyTxIndex, index,
|
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, "")
|
resTxs, err := b.clientCtx.Client.TxSearch(b.ctx, query, false, nil, nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(resTxs.Txs) == 0 {
|
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`.
|
// 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
|
var msg *evmtypes.MsgEthereumTx
|
||||||
// try /tx_search first
|
// find in tx indexer
|
||||||
res, err := b.GetTxByTxIndex(block.Block.Height, uint(idx))
|
res, err := b.GetTxByTxIndex(block.Block.Height, uint(idx))
|
||||||
if err == nil {
|
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 {
|
if err != nil {
|
||||||
b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
|
b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
|
||||||
return nil, nil
|
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
|
var ok bool
|
||||||
// msgIndex is inferred from tx events, should be within bound.
|
// 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 {
|
if !ok {
|
||||||
b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
|
b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -23,10 +23,6 @@ import (
|
|||||||
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
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 {
|
type txGasAndReward struct {
|
||||||
gasUsed uint64
|
gasUsed uint64
|
||||||
reward *big.Int
|
reward *big.Int
|
||||||
@ -247,17 +243,6 @@ func ParseTxLogsFromEvent(event abci.Event) ([]*ethtypes.Log, error) {
|
|||||||
return evmtypes.LogsToEthereum(logs), nil
|
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
|
// ShouldIgnoreGasUsed returns true if the gasUsed in result should be ignored
|
||||||
// workaround for issue: https://github.com/cosmos/cosmos-sdk/issues/10832
|
// workaround for issue: https://github.com/cosmos/cosmos-sdk/issues/10832
|
||||||
func ShouldIgnoreGasUsed(res *abci.ResponseDeliverTx) bool {
|
func ShouldIgnoreGasUsed(res *abci.ResponseDeliverTx) bool {
|
||||||
|
@ -2,7 +2,6 @@ package eth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
"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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.TxResult.Code != 0 {
|
if res.Failed {
|
||||||
// failed, return empty logs
|
// failed, return empty logs
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult)
|
resBlockResult, err := e.backend.GetTendermintBlockResultByNumber(&res.Height)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse tx events: %s, %v", hexTx, err)
|
e.logger.Debug("block result not found", "number", res.Height, "error", err.Error())
|
||||||
}
|
return nil, nil
|
||||||
|
|
||||||
parsedTx := parsedTxs.GetTxByHash(txHash)
|
|
||||||
if parsedTx == nil {
|
|
||||||
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse tx logs from events
|
// 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
|
// SignTypedData signs EIP-712 conformant typed data
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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"
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EventFormat is the format version of the events.
|
// EventFormat is the format version of the events.
|
||||||
@ -52,11 +54,9 @@ type ParsedTx struct {
|
|||||||
|
|
||||||
Hash common.Hash
|
Hash common.Hash
|
||||||
// -1 means uninitialized
|
// -1 means uninitialized
|
||||||
EthTxIndex int64
|
EthTxIndex int32
|
||||||
GasUsed uint64
|
GasUsed uint64
|
||||||
Failed bool
|
Failed bool
|
||||||
// unparsed tx log json strings
|
|
||||||
RawLogs [][]byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewParsedTx initialize a ParsedTx
|
// NewParsedTx initialize a ParsedTx
|
||||||
@ -64,20 +64,6 @@ func NewParsedTx(msgIndex int) ParsedTx {
|
|||||||
return ParsedTx{MsgIndex: msgIndex, EthTxIndex: -1}
|
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.
|
// ParsedTxs is the tx infos parsed from eth tx events.
|
||||||
type ParsedTxs struct {
|
type ParsedTxs struct {
|
||||||
// one item per message
|
// one item per message
|
||||||
@ -88,7 +74,7 @@ type ParsedTxs struct {
|
|||||||
|
|
||||||
// ParseTxResult parse eth tx infos from cosmos-sdk events.
|
// 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.
|
// 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
|
format := eventFormatUnknown
|
||||||
// the index of current ethereum_tx event in format 1 or the second part of format 2
|
// the index of current ethereum_tx event in format 1 or the second part of format 2
|
||||||
eventIndex := -1
|
eventIndex := -1
|
||||||
@ -97,8 +83,10 @@ func ParseTxResult(result *abci.ResponseDeliverTx) (*ParsedTxs, error) {
|
|||||||
TxHashes: make(map[common.Hash]int),
|
TxHashes: make(map[common.Hash]int),
|
||||||
}
|
}
|
||||||
for _, event := range result.Events {
|
for _, event := range result.Events {
|
||||||
switch event.Type {
|
if event.Type != evmtypes.EventTypeEthereumTx {
|
||||||
case evmtypes.EventTypeEthereumTx:
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if format == eventFormatUnknown {
|
if format == eventFormatUnknown {
|
||||||
// discover the format version by inspect the first ethereum_tx event.
|
// discover the format version by inspect the first ethereum_tx event.
|
||||||
if len(event.Attributes) > 2 {
|
if len(event.Attributes) > 2 {
|
||||||
@ -128,10 +116,6 @@ func ParseTxResult(result *abci.ResponseDeliverTx) (*ParsedTxs, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case evmtypes.EventTypeTxLog:
|
|
||||||
// reuse the eventIndex set by previous ethereum_tx event
|
|
||||||
p.Txs[eventIndex].RawLogs = parseRawLogs(event.Attributes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// some old versions miss some events, fill it with tx result
|
// some old versions miss some events, fill it with tx result
|
||||||
@ -139,9 +123,42 @@ func ParseTxResult(result *abci.ResponseDeliverTx) (*ParsedTxs, error) {
|
|||||||
p.Txs[0].GasUsed = uint64(result.GasUsed)
|
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
|
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.
|
// newTx parse a new tx from events, called during parsing.
|
||||||
func (p *ParsedTxs) newTx(attrs []abci.EventAttribute) error {
|
func (p *ParsedTxs) newTx(attrs []abci.EventAttribute) error {
|
||||||
msgIndex := len(p.Txs)
|
msgIndex := len(p.Txs)
|
||||||
@ -215,17 +232,17 @@ func fillTxAttribute(tx *ParsedTx, key []byte, value []byte) error {
|
|||||||
case evmtypes.AttributeKeyEthereumTxHash:
|
case evmtypes.AttributeKeyEthereumTxHash:
|
||||||
tx.Hash = common.HexToHash(string(value))
|
tx.Hash = common.HexToHash(string(value))
|
||||||
case evmtypes.AttributeKeyTxIndex:
|
case evmtypes.AttributeKeyTxIndex:
|
||||||
txIndex, err := strconv.ParseInt(string(value), 10, 64)
|
txIndex, err := strconv.ParseUint(string(value), 10, 31)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tx.EthTxIndex = txIndex
|
tx.EthTxIndex = int32(txIndex)
|
||||||
case evmtypes.AttributeKeyTxGasUsed:
|
case evmtypes.AttributeKeyTxGasUsed:
|
||||||
gasUsed, err := strconv.ParseInt(string(value), 10, 64)
|
gasUsed, err := strconv.ParseUint(string(value), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tx.GasUsed = uint64(gasUsed)
|
tx.GasUsed = gasUsed
|
||||||
case evmtypes.AttributeKeyEthereumTxFailed:
|
case evmtypes.AttributeKeyEthereumTxFailed:
|
||||||
tx.Failed = len(value) > 0
|
tx.Failed = len(value) > 0
|
||||||
}
|
}
|
||||||
@ -240,10 +257,3 @@ func fillTxAttributes(tx *ParsedTx, attrs []abci.EventAttribute) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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) {
|
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"
|
address := "0x57f96e6B86CdeFdB3d412547816a82E3E0EbF9D2"
|
||||||
txHash := common.BigToHash(big.NewInt(1))
|
txHash := common.BigToHash(big.NewInt(1))
|
||||||
txHash2 := common.BigToHash(big.NewInt(2))
|
txHash2 := common.BigToHash(big.NewInt(2))
|
||||||
@ -46,11 +41,6 @@ func TestParseTxResult(t *testing.T) {
|
|||||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
{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{
|
{Type: "message", Attributes: []abci.EventAttribute{
|
||||||
{Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")},
|
{Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")},
|
||||||
{Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
|
{Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
|
||||||
@ -76,7 +66,6 @@ func TestParseTxResult(t *testing.T) {
|
|||||||
EthTxIndex: 10,
|
EthTxIndex: 10,
|
||||||
GasUsed: 21000,
|
GasUsed: 21000,
|
||||||
Failed: false,
|
Failed: false,
|
||||||
RawLogs: rawLogs,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MsgIndex: 1,
|
MsgIndex: 1,
|
||||||
@ -84,7 +73,6 @@ func TestParseTxResult(t *testing.T) {
|
|||||||
EthTxIndex: 11,
|
EthTxIndex: 11,
|
||||||
GasUsed: 21000,
|
GasUsed: 21000,
|
||||||
Failed: true,
|
Failed: true,
|
||||||
RawLogs: nil,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -113,11 +101,6 @@ func TestParseTxResult(t *testing.T) {
|
|||||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
{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{
|
{Type: "message", Attributes: []abci.EventAttribute{
|
||||||
{Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")},
|
{Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")},
|
||||||
{Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
|
{Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
|
||||||
@ -133,7 +116,6 @@ func TestParseTxResult(t *testing.T) {
|
|||||||
EthTxIndex: 0,
|
EthTxIndex: 0,
|
||||||
GasUsed: 21000,
|
GasUsed: 21000,
|
||||||
Failed: false,
|
Failed: false,
|
||||||
RawLogs: rawLogs,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -150,11 +132,6 @@ func TestParseTxResult(t *testing.T) {
|
|||||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
{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{
|
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
|
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
|
||||||
{Key: []byte("txIndex"), Value: []byte("0x01")},
|
{Key: []byte("txIndex"), Value: []byte("0x01")},
|
||||||
@ -182,11 +159,6 @@ func TestParseTxResult(t *testing.T) {
|
|||||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
{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{
|
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
|
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
|
||||||
{Key: []byte("txIndex"), Value: []byte("10")},
|
{Key: []byte("txIndex"), Value: []byte("10")},
|
||||||
@ -243,7 +215,7 @@ func TestParseTxResult(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
parsed, err := ParseTxResult(&tc.response)
|
parsed, err := ParseTxResult(&tc.response, nil)
|
||||||
if tc.expTxs == nil {
|
if tc.expTxs == nil {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
@ -252,8 +224,6 @@ func TestParseTxResult(t *testing.T) {
|
|||||||
require.Equal(t, expTx, parsed.GetTxByMsgIndex(msgIndex))
|
require.Equal(t, expTx, parsed.GetTxByMsgIndex(msgIndex))
|
||||||
require.Equal(t, expTx, parsed.GetTxByHash(expTx.Hash))
|
require.Equal(t, expTx, parsed.GetTxByHash(expTx.Hash))
|
||||||
require.Equal(t, expTx, parsed.GetTxByTxIndex(int(expTx.EthTxIndex)))
|
require.Equal(t, expTx, parsed.GetTxByTxIndex(int(expTx.EthTxIndex)))
|
||||||
_, err := expTx.ParseTxLogs()
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
}
|
||||||
// non-exists tx hash
|
// non-exists tx hash
|
||||||
require.Nil(t, parsed.GetTxByHash(common.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"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
@ -22,6 +23,10 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/params"
|
"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.
|
// RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes.
|
||||||
func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEthereumTx, error) {
|
func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEthereumTx, error) {
|
||||||
tx, err := clientCtx.TxConfig.TxDecoder()(txBz)
|
tx, err := clientCtx.TxConfig.TxDecoder()(txBz)
|
||||||
@ -243,3 +248,14 @@ func CheckTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
// MaxOpenConnections sets the maximum number of simultaneous connections
|
||||||
// for the server listener.
|
// for the server listener.
|
||||||
MaxOpenConnections int `mapstructure:"max-open-connections"`
|
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.
|
// TLSConfig defines the certificate and matching private key for the server.
|
||||||
@ -208,6 +210,7 @@ func DefaultJSONRPCConfig() *JSONRPCConfig {
|
|||||||
HTTPIdleTimeout: DefaultHTTPIdleTimeout,
|
HTTPIdleTimeout: DefaultHTTPIdleTimeout,
|
||||||
AllowUnprotectedTxs: DefaultAllowUnprotectedTxs,
|
AllowUnprotectedTxs: DefaultAllowUnprotectedTxs,
|
||||||
MaxOpenConnections: DefaultMaxOpenConnections,
|
MaxOpenConnections: DefaultMaxOpenConnections,
|
||||||
|
EnableIndexer: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,6 +315,7 @@ func GetConfig(v *viper.Viper) Config {
|
|||||||
HTTPTimeout: v.GetDuration("json-rpc.http-timeout"),
|
HTTPTimeout: v.GetDuration("json-rpc.http-timeout"),
|
||||||
HTTPIdleTimeout: v.GetDuration("json-rpc.http-idle-timeout"),
|
HTTPIdleTimeout: v.GetDuration("json-rpc.http-idle-timeout"),
|
||||||
MaxOpenConnections: v.GetInt("json-rpc.max-open-connections"),
|
MaxOpenConnections: v.GetInt("json-rpc.max-open-connections"),
|
||||||
|
EnableIndexer: v.GetBool("json-rpc.enable-indexer"),
|
||||||
},
|
},
|
||||||
TLS: TLSConfig{
|
TLS: TLSConfig{
|
||||||
CertificatePath: v.GetString("tls.certificate-path"),
|
CertificatePath: v.GetString("tls.certificate-path"),
|
||||||
|
@ -70,6 +70,9 @@ allow-unprotected-txs = {{ .JSONRPC.AllowUnprotectedTxs }}
|
|||||||
# for the server listener.
|
# for the server listener.
|
||||||
max-open-connections = {{ .JSONRPC.MaxOpenConnections }}
|
max-open-connections = {{ .JSONRPC.MaxOpenConnections }}
|
||||||
|
|
||||||
|
# EnableIndexer enables the custom transaction indexer for the EVM (ethereum transactions).
|
||||||
|
enable-indexer = {{ .JSONRPC.EnableIndexer }}
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
### TLS Configuration ###
|
### TLS Configuration ###
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -48,6 +48,7 @@ const (
|
|||||||
JSONRPCHTTPIdleTimeout = "json-rpc.http-idle-timeout"
|
JSONRPCHTTPIdleTimeout = "json-rpc.http-idle-timeout"
|
||||||
JSONRPCAllowUnprotectedTxs = "json-rpc.allow-unprotected-txs"
|
JSONRPCAllowUnprotectedTxs = "json-rpc.allow-unprotected-txs"
|
||||||
JSONRPCMaxOpenConnections = "json-rpc.max-open-connections"
|
JSONRPCMaxOpenConnections = "json-rpc.max-open-connections"
|
||||||
|
JSONRPCEnableIndexer = "json-rpc.enable-indexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EVM flags
|
// 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/rpc"
|
||||||
|
|
||||||
"github.com/evmos/ethermint/server/config"
|
"github.com/evmos/ethermint/server/config"
|
||||||
|
ethermint "github.com/evmos/ethermint/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartJSONRPC starts the JSON-RPC server
|
// 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)
|
tmWsClient := ConnectTmWS(tmRPCAddr, tmEndpoint, ctx.Logger)
|
||||||
|
|
||||||
logger := ctx.Logger.With("module", "geth")
|
logger := ctx.Logger.With("module", "geth")
|
||||||
@ -39,7 +40,7 @@ func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEn
|
|||||||
allowUnprotectedTxs := config.JSONRPC.AllowUnprotectedTxs
|
allowUnprotectedTxs := config.JSONRPC.AllowUnprotectedTxs
|
||||||
rpcAPIArr := config.JSONRPC.API
|
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 {
|
for _, api := range apis {
|
||||||
if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil {
|
if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil {
|
||||||
|
@ -40,9 +40,11 @@ import (
|
|||||||
servergrpc "github.com/cosmos/cosmos-sdk/server/grpc"
|
servergrpc "github.com/cosmos/cosmos-sdk/server/grpc"
|
||||||
"github.com/cosmos/cosmos-sdk/server/types"
|
"github.com/cosmos/cosmos-sdk/server/types"
|
||||||
|
|
||||||
|
"github.com/evmos/ethermint/indexer"
|
||||||
ethdebug "github.com/evmos/ethermint/rpc/namespaces/ethereum/debug"
|
ethdebug "github.com/evmos/ethermint/rpc/namespaces/ethereum/debug"
|
||||||
"github.com/evmos/ethermint/server/config"
|
"github.com/evmos/ethermint/server/config"
|
||||||
srvflags "github.com/evmos/ethermint/server/flags"
|
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
|
// 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.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().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().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().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")
|
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
|
// 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
|
// 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.
|
// 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))
|
clientCtx = clientCtx.WithClient(local.New(tmNode))
|
||||||
|
|
||||||
app.RegisterTxService(clientCtx)
|
app.RegisterTxService(clientCtx)
|
||||||
app.RegisterTendermintService(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
|
var apiSrv *api.Server
|
||||||
if config.API.Enable {
|
if config.API.Enable {
|
||||||
genDoc, err := genDocProvider()
|
genDoc, err := genDocProvider()
|
||||||
@ -427,7 +456,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty
|
|||||||
|
|
||||||
tmEndpoint := "/websocket"
|
tmEndpoint := "/websocket"
|
||||||
tmRPCAddr := cfg.RPC.ListenAddress
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -482,6 +511,12 @@ func openDB(rootDir string, backendType dbm.BackendType) (dbm.DB, error) {
|
|||||||
return dbm.NewDB("application", backendType, dataDir)
|
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) {
|
func openTraceWriter(traceWriterFile string) (w io.Writer, err error) {
|
||||||
if traceWriterFile == "" {
|
if traceWriterFile == "" {
|
||||||
return
|
return
|
||||||
|
@ -45,6 +45,9 @@ func AddCommands(rootCmd *cobra.Command, defaultNodeHome string, appCreator type
|
|||||||
sdkserver.ExportCmd(appExport, defaultNodeHome),
|
sdkserver.ExportCmd(appExport, defaultNodeHome),
|
||||||
version.NewVersionCommand(),
|
version.NewVersionCommand(),
|
||||||
sdkserver.NewRollbackCmd(defaultNodeHome),
|
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
|
import pytest
|
||||||
|
|
||||||
from .network import setup_ethermint, setup_geth
|
from .network import setup_custom_ethermint, setup_ethermint, setup_geth
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
@ -9,14 +11,24 @@ def ethermint(tmp_path_factory):
|
|||||||
yield from setup_ethermint(path, 26650)
|
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")
|
@pytest.fixture(scope="session")
|
||||||
def geth(tmp_path_factory):
|
def geth(tmp_path_factory):
|
||||||
path = tmp_path_factory.mktemp("geth")
|
path = tmp_path_factory.mktemp("geth")
|
||||||
yield from setup_geth(path, 8545)
|
yield from setup_geth(path, 8545)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", params=["ethermint", "geth", "ethermint-ws"])
|
@pytest.fixture(
|
||||||
def cluster(request, ethermint, geth):
|
scope="session", params=["ethermint", "geth", "ethermint-ws", "enable-indexer"]
|
||||||
|
)
|
||||||
|
def cluster(request, ethermint, ethermint_indexer, geth):
|
||||||
"""
|
"""
|
||||||
run on both ethermint and geth
|
run on both ethermint and geth
|
||||||
"""
|
"""
|
||||||
@ -29,5 +41,7 @@ def cluster(request, ethermint, geth):
|
|||||||
ethermint_ws = ethermint.copy()
|
ethermint_ws = ethermint.copy()
|
||||||
ethermint_ws.use_websocket()
|
ethermint_ws.use_websocket()
|
||||||
yield ethermint_ws
|
yield ethermint_ws
|
||||||
|
elif provider == "enable-indexer":
|
||||||
|
yield ethermint_indexer
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -131,7 +131,7 @@ func startInProcess(cfg Config, val *Validator) error {
|
|||||||
tmEndpoint := "/websocket"
|
tmEndpoint := "/websocket"
|
||||||
tmRPCAddr := val.RPCAddress
|
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 {
|
if err != nil {
|
||||||
return err
|
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