From de36cfdb9511fa3b575a722f1585e04e0c897570 Mon Sep 17 00:00:00 2001 From: beer-1 Date: Fri, 22 Nov 2024 16:55:54 +0900 Subject: [PATCH] add more test cases to cover indexer --- app/app.go | 4 +- indexer/abci_test.go | 60 +++++++++++++++ indexer/indexer.go | 2 +- indexer/mempool_test.go | 44 +++++++++++ indexer/reader.go | 2 +- indexer/reader_test.go | 154 ++++++++++++++++++++++++++++++++++++++ indexer/tx_helper_test.go | 34 ++++++--- jsonrpc/backend/tx.go | 2 +- 8 files changed, 289 insertions(+), 13 deletions(-) create mode 100644 indexer/mempool_test.go create mode 100644 indexer/reader_test.go diff --git a/app/app.go b/app/app.go index 48b936f..02958f4 100644 --- a/app/app.go +++ b/app/app.go @@ -602,7 +602,9 @@ func (app *MinitiaApp) Close() error { return err } - app.evmIndexer.Stop() + if app.evmIndexer != nil { + app.evmIndexer.Stop() + } return nil } diff --git a/indexer/abci_test.go b/indexer/abci_test.go index 80e882d..de2968c 100644 --- a/indexer/abci_test.go +++ b/indexer/abci_test.go @@ -2,6 +2,7 @@ package indexer_test import ( "math/big" + "sync" "testing" "github.com/stretchr/testify/require" @@ -16,6 +17,7 @@ import ( func Test_ListenFinalizeBlock(t *testing.T) { app, indexer, addrs, privKeys := setupIndexer(t) + defer app.Close() tx, evmTxHash := generateCreateERC20Tx(t, app, privKeys[0]) finalizeReq, finalizeRes := executeTxs(t, app, tx) @@ -56,4 +58,62 @@ func Test_ListenFinalizeBlock(t *testing.T) { evmTx, err = indexer.TxByHash(ctx, evmTxHash) require.NoError(t, err) require.NotNil(t, evmTx) + + // check the block header is indexed + header, err := indexer.BlockHeaderByNumber(ctx, uint64(finalizeReq.Height)) + require.NoError(t, err) + require.NotNil(t, header) + require.Equal(t, finalizeReq.Height, header.Number.Int64()) + +} + +func Test_ListenFinalizeBlock_Subscribe(t *testing.T) { + app, indexer, _, privKeys := setupIndexer(t) + defer app.Close() + + blockChan, logsChan, pendChan := indexer.Subscribe() + close(pendChan) + + tx, evmTxHash := generateCreateERC20Tx(t, app, privKeys[0]) + finalizeReq, finalizeRes := executeTxs(t, app, tx) + checkTxResult(t, finalizeRes.TxResults[0], true) + + events := finalizeRes.TxResults[0].Events + createEvent := events[len(events)-3] + require.Equal(t, evmtypes.EventTypeContractCreated, createEvent.GetType()) + + contractAddr, err := hexutil.Decode(createEvent.Attributes[0].Value) + require.NoError(t, err) + + // listen finalize block + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + for { + select { + case block := <-blockChan: + require.NotNil(t, block) + require.Equal(t, finalizeReq.Height, block.Number.Int64()) + wg.Done() + case logs := <-logsChan: + require.NotNil(t, logs) + + for _, log := range logs { + if log.Address == common.BytesToAddress(contractAddr) { + require.Equal(t, evmTxHash, log.TxHash) + require.Equal(t, uint64(finalizeReq.Height), log.BlockNumber) + wg.Done() + } + } + } + } + }() + + err = indexer.ListenFinalizeBlock(ctx.WithBlockGasMeter(storetypes.NewInfiniteGasMeter()), *finalizeReq, *finalizeRes) + require.NoError(t, err) + + wg.Wait() } diff --git a/indexer/indexer.go b/indexer/indexer.go index bc40a22..51ea98f 100644 --- a/indexer/indexer.go +++ b/indexer/indexer.go @@ -35,7 +35,7 @@ type EVMIndexer interface { // tx receipt TxReceiptByHash(ctx context.Context, hash common.Hash) (*coretypes.Receipt, error) - IterateBlockTxRecepts(ctx context.Context, blockHeight uint64, cb func(tx *coretypes.Receipt) (bool, error)) error + IterateBlockTxReceipts(ctx context.Context, blockHeight uint64, cb func(tx *coretypes.Receipt) (bool, error)) error // block BlockHashToNumber(ctx context.Context, hash common.Hash) (uint64, error) diff --git a/indexer/mempool_test.go b/indexer/mempool_test.go new file mode 100644 index 0000000..4ea97ed --- /dev/null +++ b/indexer/mempool_test.go @@ -0,0 +1,44 @@ +package indexer_test + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/types/mempool" +) + +func Test_Mempool_Subscribe(t *testing.T) { + app, indexer, _, privKeys := setupIndexer(t) + defer app.Close() + + blockChan, logsChan, pendChan := indexer.Subscribe() + close(blockChan) + close(logsChan) + + tx, evmTxHash := generateCreateERC20Tx(t, app, privKeys[0]) + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + pendingTx := <-pendChan + require.NotNil(t, pendingTx) + require.Equal(t, evmTxHash, pendingTx.Hash) + wg.Done() + }() + + noopMempool := &mempool.NoOpMempool{} + mempool := indexer.MempoolWrapper(noopMempool) + + // insert tx into mempool + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + err = mempool.Insert(ctx, tx) + require.NoError(t, err) + + rpcTx := indexer.TxInMempool(evmTxHash) + require.Equal(t, evmTxHash, rpcTx.Hash) + + wg.Wait() +} diff --git a/indexer/reader.go b/indexer/reader.go index a95e04c..b2bbfe9 100644 --- a/indexer/reader.go +++ b/indexer/reader.go @@ -53,7 +53,7 @@ func (e *EVMIndexerImpl) IterateBlockTxs(ctx context.Context, blockHeight uint64 } // IterateBlockTxs implements EVMIndexer. -func (e *EVMIndexerImpl) IterateBlockTxRecepts(ctx context.Context, blockHeight uint64, cb func(tx *coretypes.Receipt) (bool, error)) error { +func (e *EVMIndexerImpl) IterateBlockTxReceipts(ctx context.Context, blockHeight uint64, cb func(tx *coretypes.Receipt) (bool, error)) error { return e.BlockAndIndexToTxHashMap.Walk(ctx, collections.NewPrefixedPairRange[uint64, uint64](blockHeight), func(key collections.Pair[uint64, uint64], txHashBz []byte) (bool, error) { txHash := common.BytesToHash(txHashBz) txRecept, err := e.TxReceiptByHash(ctx, txHash) diff --git a/indexer/reader_test.go b/indexer/reader_test.go new file mode 100644 index 0000000..677d4df --- /dev/null +++ b/indexer/reader_test.go @@ -0,0 +1,154 @@ +package indexer_test + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + cmttypes "github.com/cometbft/cometbft/types" + + storetypes "cosmossdk.io/store/types" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + coretypes "github.com/ethereum/go-ethereum/core/types" + + rpctypes "github.com/initia-labs/minievm/jsonrpc/types" + evmtypes "github.com/initia-labs/minievm/x/evm/types" +) + +func Test_Reader(t *testing.T) { + app, indexer, addrs, privKeys := setupIndexer(t) + defer app.Close() + + tx, evmTxHash := generateCreateERC20Tx(t, app, privKeys[0]) + finalizeReq, finalizeRes := executeTxs(t, app, tx) + checkTxResult(t, finalizeRes.TxResults[0], true) + + events := finalizeRes.TxResults[0].Events + createEvent := events[len(events)-3] + require.Equal(t, evmtypes.EventTypeContractCreated, createEvent.GetType()) + + contractAddr, err := hexutil.Decode(createEvent.Attributes[0].Value) + require.NoError(t, err) + + // listen finalize block + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + err = indexer.ListenFinalizeBlock(ctx.WithBlockGasMeter(storetypes.NewInfiniteGasMeter()), *finalizeReq, *finalizeRes) + require.NoError(t, err) + + // check the tx is indexed + evmTx, err := indexer.TxByHash(ctx, evmTxHash) + require.NoError(t, err) + require.NotNil(t, evmTx) + + // mint 1_000_000 tokens to the first address + tx, evmTxHash = generateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, evmTxHash2 := generateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), 2) + finalizeReq, finalizeRes = executeTxs(t, app, tx, tx2) + checkTxResult(t, finalizeRes.TxResults[0], true) + checkTxResult(t, finalizeRes.TxResults[1], true) + + txBytes, err := app.TxEncode(tx) + require.NoError(t, err) + txBytes2, err := app.TxEncode(tx2) + require.NoError(t, err) + + cmtTx := cmttypes.Tx(txBytes) + cosmosTxHash := cmtTx.Hash() + cmtTx2 := cmttypes.Tx(txBytes2) + cosmosTxHash2 := cmtTx2.Hash() + + // listen finalize block + ctx, err = app.CreateQueryContext(0, false) + require.NoError(t, err) + + err = indexer.ListenFinalizeBlock(ctx.WithBlockGasMeter(storetypes.NewInfiniteGasMeter()), *finalizeReq, *finalizeRes) + require.NoError(t, err) + + // check the tx is indexed + evmTx, err = indexer.TxByHash(ctx, evmTxHash) + require.NoError(t, err) + require.NotNil(t, evmTx) + evmTx, err = indexer.TxByHash(ctx, evmTxHash2) + require.NoError(t, err) + require.NotNil(t, evmTx) + + // check the block header is indexed + header, err := indexer.BlockHeaderByNumber(ctx, uint64(finalizeReq.Height)) + require.NoError(t, err) + require.NotNil(t, header) + require.Equal(t, finalizeReq.Height, header.Number.Int64()) + + // check tx hash by block and index + txHash, err := indexer.TxHashByBlockAndIndex(ctx, uint64(finalizeReq.Height), 1) + require.NoError(t, err) + require.Equal(t, evmTxHash, txHash) + + txHash, err = indexer.TxHashByBlockAndIndex(ctx, uint64(finalizeReq.Height), 2) + require.NoError(t, err) + require.Equal(t, evmTxHash2, txHash) + + // iterate block txs + count := 0 + err = indexer.IterateBlockTxs(ctx, uint64(finalizeReq.Height), func(tx *rpctypes.RPCTransaction) (bool, error) { + count++ + if count == 1 { + require.Equal(t, evmTxHash, tx.Hash) + } else if count == 2 { + require.Equal(t, evmTxHash2, tx.Hash) + } + return false, nil + }) + require.NoError(t, err) + require.Equal(t, 2, count) + + // receipt by hash + receipt1, err := indexer.TxReceiptByHash(ctx, evmTxHash) + require.NoError(t, err) + require.NotNil(t, receipt1) + + // receipt by hash + receipt2, err := indexer.TxReceiptByHash(ctx, evmTxHash2) + require.NoError(t, err) + require.NotNil(t, receipt2) + + // iterate block tx receipts + count = 0 + err = indexer.IterateBlockTxReceipts(ctx, uint64(finalizeReq.Height), func(receipt *coretypes.Receipt) (bool, error) { + count++ + if count == 1 { + require.Equal(t, receipt1, receipt) + } else if count == 2 { + require.Equal(t, receipt2, receipt) + } + return false, nil + }) + require.NoError(t, err) + + // block hash to number + blockNumber, err := indexer.BlockHashToNumber(ctx, header.Hash()) + require.NoError(t, err) + require.Equal(t, uint64(finalizeReq.Height), blockNumber) + + // cosmos tx hash + hash, err := indexer.CosmosTxHashByTxHash(ctx, evmTxHash) + require.NoError(t, err) + require.Equal(t, cosmosTxHash, hash) + + hash, err = indexer.CosmosTxHashByTxHash(ctx, evmTxHash2) + require.NoError(t, err) + require.Equal(t, cosmosTxHash2, hash) + + // tx hash by cosmos tx hash + txHash, err = indexer.TxHashByCosmosTxHash(ctx, cosmosTxHash) + require.NoError(t, err) + require.Equal(t, evmTxHash, txHash) + + txHash, err = indexer.TxHashByCosmosTxHash(ctx, cosmosTxHash2) + require.NoError(t, err) + require.Equal(t, evmTxHash2, txHash) +} diff --git a/indexer/tx_helper_test.go b/indexer/tx_helper_test.go index dd66458..0479a8b 100644 --- a/indexer/tx_helper_test.go +++ b/indexer/tx_helper_test.go @@ -56,6 +56,7 @@ func generateTx( privKey *ecdsa.PrivateKey, to *common.Address, inputBz []byte, + seqNum ...uint64, ) (sdk.Tx, common.Hash) { ctx, err := app.CreateQueryContext(0, false) require.NoError(t, err) @@ -63,14 +64,19 @@ func generateTx( gasLimit := new(big.Int).SetUint64(1_000_000) gasPrice := new(big.Int).SetUint64(1_000_000_000) - cosmosKey := ethsecp256k1.PrivKey{Key: crypto.FromECDSA(privKey)} - addrBz := cosmosKey.PubKey().Address() - nonce, err := app.AccountKeeper.GetSequence(ctx, sdk.AccAddress(addrBz)) - require.NoError(t, err) + nonce := uint64(0) + if len(seqNum) == 0 { + cosmosKey := ethsecp256k1.PrivKey{Key: crypto.FromECDSA(privKey)} + addrBz := cosmosKey.PubKey().Address() + nonce, err = app.AccountKeeper.GetSequence(ctx, sdk.AccAddress(addrBz)) + require.NoError(t, err) + } else { + nonce = seqNum[0] + } ethChainID := evmtypes.ConvertCosmosChainIDToEthereumChainID(ctx.ChainID()) ethTx := coretypes.NewTx(&coretypes.DynamicFeeTx{ - ChainID: evmtypes.ConvertCosmosChainIDToEthereumChainID(ctx.ChainID()), + ChainID: ethChainID, Nonce: nonce, GasTipCap: big.NewInt(0), GasFeeCap: gasPrice, @@ -92,7 +98,7 @@ func generateTx( return sdkTx, signedTx.Hash() } -func generateCreateERC20Tx(t *testing.T, app *minitiaapp.MinitiaApp, privKey *ecdsa.PrivateKey) (sdk.Tx, common.Hash) { +func generateCreateERC20Tx(t *testing.T, app *minitiaapp.MinitiaApp, privKey *ecdsa.PrivateKey, seqNum ...uint64) (sdk.Tx, common.Hash) { ctx, err := app.CreateQueryContext(0, false) require.NoError(t, err) @@ -105,17 +111,27 @@ func generateCreateERC20Tx(t *testing.T, app *minitiaapp.MinitiaApp, privKey *ec inputBz, err := abi.Pack("createERC20", "foo", "foo", uint8(6)) require.NoError(t, err) - return generateTx(t, app, privKey, ðFactoryAddr, inputBz) + return generateTx(t, app, privKey, ðFactoryAddr, inputBz, seqNum...) } -func generateMintERC20Tx(t *testing.T, app *minitiaapp.MinitiaApp, privKey *ecdsa.PrivateKey, erc20Addr, recipient common.Address, amount *big.Int) (sdk.Tx, common.Hash) { +func generateMintERC20Tx(t *testing.T, app *minitiaapp.MinitiaApp, privKey *ecdsa.PrivateKey, erc20Addr, recipient common.Address, amount *big.Int, seqNum ...uint64) (sdk.Tx, common.Hash) { abi, err := erc20.Erc20MetaData.GetAbi() require.NoError(t, err) inputBz, err := abi.Pack("mint", recipient, amount) require.NoError(t, err) - return generateTx(t, app, privKey, &erc20Addr, inputBz) + return generateTx(t, app, privKey, &erc20Addr, inputBz, seqNum...) +} + +func generateTransferERC20Tx(t *testing.T, app *minitiaapp.MinitiaApp, privKey *ecdsa.PrivateKey, erc20Addr, recipient common.Address, amount *big.Int, seqNum ...uint64) (sdk.Tx, common.Hash) { + abi, err := erc20.Erc20MetaData.GetAbi() + require.NoError(t, err) + + inputBz, err := abi.Pack("transfer", recipient, amount) + require.NoError(t, err) + + return generateTx(t, app, privKey, &erc20Addr, inputBz, seqNum...) } // execute txs and finalize block and commit block diff --git a/jsonrpc/backend/tx.go b/jsonrpc/backend/tx.go index b1d8f9b..3451a7b 100644 --- a/jsonrpc/backend/tx.go +++ b/jsonrpc/backend/tx.go @@ -454,7 +454,7 @@ func (b *JSONRPCBackend) getBlockReceipts(blockNumber uint64) ([]*coretypes.Rece } recepts := []*coretypes.Receipt{} - err = b.app.EVMIndexer().IterateBlockTxRecepts(queryCtx, blockNumber, func(recept *coretypes.Receipt) (bool, error) { + err = b.app.EVMIndexer().IterateBlockTxReceipts(queryCtx, blockNumber, func(recept *coretypes.Receipt) (bool, error) { recepts = append(recepts, recept) return false, nil })