From a8f5230b1101521a150d5e44f2cb265e5491fe63 Mon Sep 17 00:00:00 2001 From: beer-1 <147697694+beer-1@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:49:39 +0900 Subject: [PATCH] feat: add indexer tests (#115) * add indexer tests * add more test cases to cover indexer * add testcase to ensure contract address in the receipt * add jsonrpc tests * remove print * fix lint * disable race detector until cosmos fixed it * add filter test * use defer to close chan * fix test * add more tests for filters * wait indexer to be ready * fix tests * add config to disable evm indexer to safe the space * fix default value --- .codecov.yml | 1 + .github/workflows/test.yml | 4 +- app/ante/ante_test.go | 7 + app/ante/fee.go | 27 +- app/ante/fee_test.go | 19 +- app/app.go | 19 +- app/const.go | 3 - app/indexer.go | 2 +- app/test_helpers.go | 12 +- app/upgrade.go | 12 +- indexer/abci.go | 4 + indexer/abci_test.go | 144 ++++++ indexer/indexer.go | 36 +- indexer/mempool.go | 5 + indexer/mempool_test.go | 54 ++ indexer/reader.go | 2 +- indexer/reader_test.go | 146 ++++++ jsonrpc/backend/backend_test.go | 125 +++++ jsonrpc/backend/block_test.go | 175 +++++++ jsonrpc/backend/cosmos_test.go | 98 ++++ jsonrpc/backend/eth_test.go | 192 +++++++ jsonrpc/backend/feehistory.go | 14 +- jsonrpc/backend/feehistory_test.go | 76 +++ jsonrpc/backend/filters_test.go | 53 ++ jsonrpc/backend/gas_test.go | 89 ++++ jsonrpc/backend/net_test.go | 36 ++ jsonrpc/backend/tx.go | 6 +- jsonrpc/backend/tx_test.go | 455 +++++++++++++++++ jsonrpc/backend/txpool_test.go | 354 +++++++++++++ jsonrpc/backend/web3_test.go | 17 + jsonrpc/namespaces/eth/api.go | 4 +- jsonrpc/namespaces/eth/filters/api.go | 36 +- jsonrpc/namespaces/eth/filters/api_test.go | 472 ++++++++++++++++++ .../eth/filters/subscriptions_test.go | 178 +++++++ jsonrpc/types/tx.go | 8 + jsonrpc/types/tx_test.go | 2 + tests/app_creator.go | 58 +++ tests/jsonrpc_creator.go | 90 ++++ tests/mock_comet.go | 188 +++++++ tests/tx_helper.go | 218 ++++++++ x/evm/config/config.go | 12 + x/evm/keeper/erc20_stores_test.go | 6 +- x/evm/keeper/txutils_test.go | 1 + 43 files changed, 3373 insertions(+), 87 deletions(-) create mode 100644 indexer/abci_test.go create mode 100644 indexer/mempool_test.go create mode 100644 indexer/reader_test.go create mode 100644 jsonrpc/backend/backend_test.go create mode 100644 jsonrpc/backend/block_test.go create mode 100644 jsonrpc/backend/cosmos_test.go create mode 100644 jsonrpc/backend/eth_test.go create mode 100644 jsonrpc/backend/feehistory_test.go create mode 100644 jsonrpc/backend/filters_test.go create mode 100644 jsonrpc/backend/gas_test.go create mode 100644 jsonrpc/backend/net_test.go create mode 100644 jsonrpc/backend/tx_test.go create mode 100644 jsonrpc/backend/txpool_test.go create mode 100644 jsonrpc/backend/web3_test.go create mode 100644 jsonrpc/namespaces/eth/filters/api_test.go create mode 100644 jsonrpc/namespaces/eth/filters/subscriptions_test.go create mode 100644 tests/app_creator.go create mode 100644 tests/jsonrpc_creator.go create mode 100644 tests/mock_comet.go create mode 100644 tests/tx_helper.go diff --git a/.codecov.yml b/.codecov.yml index 66c3d3dd..010b1a63 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -37,3 +37,4 @@ ignore: - "x/ibc/testing" - "x/evm/contracts" - "**/*.sol" + - "tests/" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fe8dfb57..f8a71546 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,9 @@ jobs: make build - name: test & coverage report creation run: | - go test ./... -mod=readonly -timeout 12m -race -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' + # temporary disable -race flag until this issue fixed: + # - https://github.com/cosmos/cosmos-sdk/issues/22650 + go test ./... -mod=readonly -timeout 12m -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' if: env.GIT_DIFF env: GIT_DIFF: ${{ env.GIT_DIFF }} diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go index ea18b7cf..7364219f 100644 --- a/app/ante/ante_test.go +++ b/app/ante/ante_test.go @@ -58,6 +58,9 @@ func (suite *AnteTestSuite) createTestApp(tempDir string) (*minievmapp.MinitiaAp err = app.EVMKeeper.Params.Set(ctx, params) suite.NoError(err) + err = app.EVMKeeper.Initialize(ctx) + suite.NoError(err) + return app, ctx } @@ -131,3 +134,7 @@ func (suite *AnteTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums [] func TestAnteTestSuite(t *testing.T) { suite.Run(t, new(AnteTestSuite)) } + +func noopAnteHandler(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { + return ctx, nil +} diff --git a/app/ante/fee.go b/app/ante/fee.go index 07d9d174..dc352980 100644 --- a/app/ante/fee.go +++ b/app/ante/fee.go @@ -10,9 +10,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/types" ) -// feeDeductionGasAmount is a estimated gas amount of fee payment -const feeDeductionGasAmount = 250_000 - // GasFreeFeeDecorator is a decorator that sets the gas meter to infinite before calling the inner DeductFeeDecorator // and then resets the gas meter to the original value after the inner DeductFeeDecorator is called. // @@ -34,6 +31,9 @@ func NewGasFreeFeeDecorator( } } +// gasLimitForFeeDeduction is the gas limit used for fee deduction. +const gasLimitForFeeDeduction = 1_000_000 + func (fd GasFreeFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { feeTx, ok := tx.(sdk.FeeTx) if !ok { @@ -43,19 +43,22 @@ func (fd GasFreeFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bo fees := feeTx.GetFee() feeDenom, err := fd.ek.GetFeeDenom(ctx.WithGasMeter(storetypes.NewInfiniteGasMeter())) if !(err == nil && len(fees) == 1 && fees[0].Denom == feeDenom) { - if simulate && fees.IsZero() { - // Charge gas for fee deduction simulation - // - // At gas simulation normally gas amount is zero, so the gas is not charged in the simulation. - ctx.GasMeter().ConsumeGas(feeDeductionGasAmount, "fee deduction") - } - return fd.inner.AnteHandle(ctx, tx, simulate, next) } // If the fee contains only one denom and it is the fee denom, set the gas meter to infinite // to avoid gas consumption for fee deduction. gasMeter := ctx.GasMeter() - ctx, err = fd.inner.AnteHandle(ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()), tx, simulate, next) - return ctx.WithGasMeter(gasMeter), err + ctx, err = fd.inner.AnteHandle(ctx.WithGasMeter(storetypes.NewGasMeter(gasLimitForFeeDeduction)), tx, simulate, noopAnteHandler) + // restore the original gas meter + ctx = ctx.WithGasMeter(gasMeter) + if err != nil { + return ctx, err + } + + return next(ctx, tx, simulate) +} + +func noopAnteHandler(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { + return ctx, nil } diff --git a/app/ante/fee_test.go b/app/ante/fee_test.go index 4c50f512..40fc9802 100644 --- a/app/ante/fee_test.go +++ b/app/ante/fee_test.go @@ -25,8 +25,10 @@ func (suite *AnteTestSuite) Test_NotSpendingGasForTxWithFeeDenom() { gasLimit := uint64(200_000) atomFeeAmount := sdk.NewCoins(sdk.NewCoin("atom", math.NewInt(200))) - suite.app.EVMKeeper.ERC20Keeper().MintCoins(suite.ctx, addr1, feeAmount.MulInt(math.NewInt(10))) - suite.app.EVMKeeper.ERC20Keeper().MintCoins(suite.ctx, addr1, atomFeeAmount.MulInt(math.NewInt(10))) + err := suite.app.EVMKeeper.ERC20Keeper().MintCoins(suite.ctx, addr1, feeAmount.MulInt(math.NewInt(10))) + suite.Require().NoError(err) + err = suite.app.EVMKeeper.ERC20Keeper().MintCoins(suite.ctx, addr1, atomFeeAmount.MulInt(math.NewInt(10))) + suite.Require().NoError(err) // Case 1. only fee denom suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) @@ -40,34 +42,35 @@ func (suite *AnteTestSuite) Test_NotSpendingGasForTxWithFeeDenom() { suite.Require().NoError(err) gasMeter := storetypes.NewGasMeter(500000) - feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, nil) + _, err = feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, noopAnteHandler) + suite.Require().NoError(err) suite.Require().Zero(gasMeter.GasConsumed(), "should not consume gas for fee deduction") // Case 2. fee denom and other denom suite.txBuilder.SetFeeAmount(feeAmount.Add(atomFeeAmount...)) gasMeter = storetypes.NewGasMeter(500000) - feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, nil) + feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, noopAnteHandler) suite.Require().NotZero(gasMeter.GasConsumed(), "should consume gas for fee deduction") // Case 3. other denom suite.txBuilder.SetFeeAmount(feeAmount.Add(atomFeeAmount...)) gasMeter = storetypes.NewGasMeter(500000) - feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, nil) + feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, noopAnteHandler) suite.Require().NotZero(gasMeter.GasConsumed(), "should consume gas for fee deduction") // Case 4. no fee suite.txBuilder.SetFeeAmount(sdk.NewCoins()) gasMeter = storetypes.NewGasMeter(500000) - feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, nil) + feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, false, noopAnteHandler) suite.Require().NotZero(gasMeter.GasConsumed(), "should consume gas for fee deduction") // Case 5. simulate gas consumption suite.txBuilder.SetFeeAmount(sdk.NewCoins()) gasMeter = storetypes.NewGasMeter(500000) - feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, true, nil) - suite.Require().Greater(gasMeter.GasConsumed(), uint64(250000), "should consume gas for fee deduction") + feeAnte.AnteHandle(suite.ctx.WithGasMeter(gasMeter), tx, true, noopAnteHandler) + suite.Require().NotZero(gasMeter.GasConsumed(), "should consume gas for fee deduction") } diff --git a/app/app.go b/app/app.go index ab891646..d8caade0 100644 --- a/app/app.go +++ b/app/app.go @@ -249,18 +249,19 @@ func NewMinitiaApp( } // setup indexer - if evmIndexer, kvIndexerKeeper, kvIndexerModule, streamingManager, err := setupIndexer(app, appOpts, indexerDB, kvindexerDB); err != nil { + evmIndexer, kvIndexerKeeper, kvIndexerModule, streamingManager, err := setupIndexer(app, appOpts, indexerDB, kvindexerDB) + if err != nil { tmos.Exit(err.Error()) - } else if kvIndexerKeeper != nil && kvIndexerModule != nil && streamingManager != nil { + } else if kvIndexerKeeper != nil && kvIndexerModule != nil { // register kvindexer keeper and module, and register services. app.SetKVIndexer(kvIndexerKeeper, kvIndexerModule) + } - // register evm indexer - app.SetEVMIndexer(evmIndexer) + // register evm indexer + app.SetEVMIndexer(evmIndexer) - // override base-app's streaming manager - app.SetStreamingManager(*streamingManager) - } + // override base-app's streaming manager + app.SetStreamingManager(*streamingManager) // register upgrade handler for later use app.RegisterUpgradeHandlers(app.configurator) @@ -602,6 +603,10 @@ func (app *MinitiaApp) Close() error { return err } + if app.evmIndexer != nil { + app.evmIndexer.Stop() + } + return nil } diff --git a/app/const.go b/app/const.go index 09fac7ec..48a5adcf 100644 --- a/app/const.go +++ b/app/const.go @@ -38,7 +38,4 @@ const ( moveMsgPublishModuleBundle = "/initia.move.v1.MsgPublish" moveMsgExecuteEntryFunction = "/initia.move.v1.MsgExecute" moveMsgExecuteScript = "/initia.move.v1.MsgScript" - - // UpgradeName gov proposal name - UpgradeName = "0.0.0" ) diff --git a/app/indexer.go b/app/indexer.go index 61c077b1..bd36f168 100644 --- a/app/indexer.go +++ b/app/indexer.go @@ -25,7 +25,7 @@ func setupIndexer( indexerDB, kvindexerDB dbm.DB, ) (evmindexer.EVMIndexer, *kvindexerkeeper.Keeper, *kvindexermodule.AppModuleBasic, *storetypes.StreamingManager, error) { // setup evm indexer - evmIndexer, err := evmindexer.NewEVMIndexer(indexerDB, app.appCodec, app.Logger(), app.txConfig, app.EVMKeeper, app.OPChildKeeper) + evmIndexer, err := evmindexer.NewEVMIndexer(indexerDB, app.appCodec, app.Logger(), app.txConfig, app.EVMKeeper) if err != nil { return nil, nil, nil, nil, err } diff --git a/app/test_helpers.go b/app/test_helpers.go index fedcd092..c2f3c93d 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -12,6 +12,7 @@ import ( tmtypes "github.com/cometbft/cometbft/types" dbm "github.com/cosmos/cosmos-db" + "cosmossdk.io/math" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" @@ -22,7 +23,9 @@ import ( opchildtypes "github.com/initia-labs/OPinit/x/opchild/types" + "github.com/initia-labs/minievm/types" evmconfig "github.com/initia-labs/minievm/x/evm/config" + evmtypes "github.com/initia-labs/minievm/x/evm/types" ) // defaultConsensusParams defines the default Tendermint consensus params used in @@ -65,7 +68,7 @@ func setup(db *dbm.DB, withGenesis bool) (*MinitiaApp, GenesisState) { ) if withGenesis { - return app, NewDefaultGenesisState(encCdc.Codec, app.BasicModuleManager, sdk.DefaultBondDenom) + return app, NewDefaultGenesisState(encCdc.Codec, app.BasicModuleManager, types.BaseDenom) } return app, GenesisState{} @@ -128,11 +131,18 @@ func SetupWithGenesisAccounts( app.AppCodec().MustUnmarshalJSON(genesisState[opchildtypes.ModuleName], &opchildGenesis) opchildGenesis.Params.Admin = sdk.AccAddress(valSet.Validators[0].Address.Bytes()).String() opchildGenesis.Params.BridgeExecutors = []string{sdk.AccAddress(valSet.Validators[0].Address.Bytes()).String()} + opchildGenesis.Params.MinGasPrices = sdk.NewDecCoins(sdk.NewDecCoin(types.BaseDenom, math.NewInt(1_000_000_000))) // set validators and delegations opchildGenesis = *opchildtypes.NewGenesisState(opchildGenesis.Params, validators, nil) genesisState[opchildtypes.ModuleName] = app.AppCodec().MustMarshalJSON(&opchildGenesis) + // set evm genesis params + var evmGenesis evmtypes.GenesisState + app.AppCodec().MustUnmarshalJSON(genesisState[evmtypes.ModuleName], &evmGenesis) + evmGenesis.Params.GasRefundRatio = math.LegacyZeroDec() + genesisState[evmtypes.ModuleName] = app.AppCodec().MustMarshalJSON(&evmGenesis) + // update total supply bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, sdk.NewCoins(), []banktypes.Metadata{}, []banktypes.SendEnabled{}) genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis) diff --git a/app/upgrade.go b/app/upgrade.go index 6429fd42..ee4e13b0 100644 --- a/app/upgrade.go +++ b/app/upgrade.go @@ -22,7 +22,7 @@ import ( opchildtypes "github.com/initia-labs/OPinit/x/opchild/types" ) -const upgradeName = "0.6.6" +const upgradeName = "0.6.7" // RegisterUpgradeHandlers returns upgrade handlers func (app *MinitiaApp) RegisterUpgradeHandlers(cfg module.Configurator) { @@ -38,11 +38,13 @@ func (app *MinitiaApp) RegisterUpgradeHandlers(cfg module.Configurator) { } // set non-zero default values for new params - params.HookMaxGas = opchildtypes.DefaultHookMaxGas + if params.HookMaxGas == 0 { + params.HookMaxGas = opchildtypes.DefaultHookMaxGas - err = app.OPChildKeeper.SetParams(ctx, params) - if err != nil { - return nil, err + err = app.OPChildKeeper.SetParams(ctx, params) + if err != nil { + return nil, err + } } //////////////////////////// MINIEVM /////////////////////////////////// diff --git a/indexer/abci.go b/indexer/abci.go index e9229c76..1aa68eb4 100644 --- a/indexer/abci.go +++ b/indexer/abci.go @@ -27,6 +27,10 @@ func (e *EVMIndexerImpl) ListenCommit(ctx context.Context, res abci.ResponseComm // IndexBlock implements EVMIndexer. func (e *EVMIndexerImpl) ListenFinalizeBlock(ctx context.Context, req abci.RequestFinalizeBlock, res abci.ResponseFinalizeBlock) error { + if !e.enabled { + return nil + } + sdkCtx := sdk.UnwrapSDKContext(ctx) // load base fee from evm keeper diff --git a/indexer/abci_test.go b/indexer/abci_test.go new file mode 100644 index 00000000..2dab5735 --- /dev/null +++ b/indexer/abci_test.go @@ -0,0 +1,144 @@ +package indexer_test + +import ( + "context" + "math/big" + "sync" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/initia-labs/minievm/tests" + evmtypes "github.com/initia-labs/minievm/x/evm/types" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +func Test_ListenFinalizeBlock(t *testing.T) { + app, addrs, privKeys := tests.CreateApp(t) + indexer := app.EVMIndexer() + defer app.Close() + + tx, evmTxHash := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // 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 = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + finalizeReq, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + // listen finalize block + ctx, err = app.CreateQueryContext(0, false) + require.NoError(t, err) + + // check the tx is indexed + 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, _, privKeys := tests.CreateApp(t) + indexer := app.EVMIndexer() + defer app.Close() + + blockChan, logsChan, pendChan := indexer.Subscribe() + defer close(blockChan) + defer close(logsChan) + defer close(pendChan) + + tx, evmTxHash := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + + ctx, done := context.WithCancel(context.Background()) + reqHeight := app.LastBlockHeight() + 1 + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + for { + select { + case block := <-blockChan: + if block == nil || block.Number.Int64() < reqHeight { + continue + } + + require.NotNil(t, block) + require.Equal(t, reqHeight, block.Number.Int64()) + wg.Done() + case logs := <-logsChan: + require.NotNil(t, logs) + if logs[0].BlockNumber < uint64(reqHeight) { + continue + } + + for _, log := range logs { + require.Equal(t, evmTxHash, log.TxHash) + require.Equal(t, uint64(reqHeight), log.BlockNumber) + } + + wg.Done() + case <-ctx.Done(): + return + } + } + }() + + finalizeReq, finalizeRes := tests.ExecuteTxs(t, app, tx) + require.Equal(t, reqHeight, finalizeReq.Height) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + wg.Wait() + done() +} + +func Test_ListenFinalizeBlock_ContractCreation(t *testing.T) { + app, _, privKeys := tests.CreateApp(t) + indexer := app.EVMIndexer() + defer app.Close() + + tx, evmTxHash := tests.GenerateCreateInitiaERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // check the tx is indexed + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + receipt, err := indexer.TxReceiptByHash(ctx, evmTxHash) + require.NoError(t, err) + require.NotNil(t, receipt) + + // contract creation should have contract address in receipt + require.Equal(t, contractAddr, receipt.ContractAddress.Bytes()) +} diff --git a/indexer/indexer.go b/indexer/indexer.go index e904e4f8..ee50fe43 100644 --- a/indexer/indexer.go +++ b/indexer/indexer.go @@ -19,8 +19,6 @@ import ( "github.com/ethereum/go-ethereum/common" coretypes "github.com/ethereum/go-ethereum/core/types" - opchildkeeper "github.com/initia-labs/OPinit/x/opchild/keeper" - rpctypes "github.com/initia-labs/minievm/jsonrpc/types" evmconfig "github.com/initia-labs/minievm/x/evm/config" evmkeeper "github.com/initia-labs/minievm/x/evm/keeper" @@ -37,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) @@ -53,18 +51,22 @@ type EVMIndexer interface { // mempool MempoolWrapper(mempool mempool.Mempool) mempool.Mempool TxInMempool(hash common.Hash) *rpctypes.RPCTransaction + + // Stop + Stop() } // EVMIndexerImpl implements EVMIndexer. type EVMIndexerImpl struct { + enabled bool + db dbm.DB logger log.Logger txConfig client.TxConfig appCodec codec.Codec - store *CacheStore - evmKeeper *evmkeeper.Keeper - opChildKeeper *opchildkeeper.Keeper + store *CacheStore + evmKeeper *evmkeeper.Keeper schema collections.Schema TxMap collections.Map[[]byte, rpctypes.RPCTransaction] @@ -89,7 +91,6 @@ func NewEVMIndexer( logger log.Logger, txConfig client.TxConfig, evmKeeper *evmkeeper.Keeper, - opChildKeeper *opchildkeeper.Keeper, ) (EVMIndexer, error) { cfg := evmKeeper.Config() if cfg.IndexerCacheSize == 0 { @@ -103,15 +104,17 @@ func NewEVMIndexer( }, ) + logger.Info("EVM Indexer", "enable", !cfg.DisableIndexer) indexer := &EVMIndexerImpl{ + enabled: !cfg.DisableIndexer, + db: db, store: store, logger: logger, txConfig: txConfig, appCodec: appCodec, - evmKeeper: evmKeeper, - opChildKeeper: opChildKeeper, + evmKeeper: evmKeeper, TxMap: collections.NewMap(sb, prefixTx, "tx", collections.BytesKey, CollJsonVal[rpctypes.RPCTransaction]()), TxReceiptMap: collections.NewMap(sb, prefixTxReceipt, "tx_receipt", collections.BytesKey, CollJsonVal[coretypes.Receipt]()), @@ -164,20 +167,27 @@ type blockEvents struct { // blockEventsEmitter emits block events to subscribers. func (e *EVMIndexerImpl) blockEventsEmitter(blockEvents *blockEvents, done chan struct{}) { + defer close(done) if blockEvents == nil { return } + + // emit logs first; use unbuffered channel to ensure logs are emitted before block header for _, logs := range blockEvents.logs { for _, logsChan := range e.logsChans { logsChan <- logs } } - for _, logsChan := range e.logsChans { - logsChan <- []*coretypes.Log{} - } + + // emit block header for _, blockChan := range e.blockChans { blockChan <- blockEvents.header } +} - close(done) +// Stop stops the indexer. +func (e *EVMIndexerImpl) Stop() { + if e.txPendingMap != nil { + e.txPendingMap.Stop() + } } diff --git a/indexer/mempool.go b/indexer/mempool.go index 644582ed..896062f0 100644 --- a/indexer/mempool.go +++ b/indexer/mempool.go @@ -76,3 +76,8 @@ func (m *MempoolWrapper) Remove(tx sdk.Tx) error { func (m *MempoolWrapper) Select(ctx context.Context, txs [][]byte) mempool.Iterator { return m.mempool.Select(ctx, txs) } + +// Inner returns the inner mempool. +func (m *MempoolWrapper) Inner() mempool.Mempool { + return m.mempool +} diff --git a/indexer/mempool_test.go b/indexer/mempool_test.go new file mode 100644 index 00000000..c16c3eeb --- /dev/null +++ b/indexer/mempool_test.go @@ -0,0 +1,54 @@ +package indexer_test + +import ( + "context" + "sync" + "testing" + + "github.com/initia-labs/minievm/tests" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/types/mempool" +) + +func Test_Mempool_Subscribe(t *testing.T) { + app, _, privKeys := tests.CreateApp(t) + indexer := app.EVMIndexer() + defer app.Close() + + blockChan, logsChan, pendChan := indexer.Subscribe() + defer close(blockChan) + defer close(logsChan) + defer close(pendChan) + + tx, evmTxHash := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + + ctx, done := context.WithCancel(context.Background()) + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + select { + case pendingTx := <-pendChan: + require.NotNil(t, pendingTx) + require.Equal(t, evmTxHash, pendingTx.Hash) + wg.Done() + case <-ctx.Done(): + return + } + }() + + 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() + done() +} diff --git a/indexer/reader.go b/indexer/reader.go index a95e04c6..b2bbfe93 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 00000000..7ccfc79c --- /dev/null +++ b/indexer/reader_test.go @@ -0,0 +1,146 @@ +package indexer_test + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + cmttypes "github.com/cometbft/cometbft/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" + "github.com/initia-labs/minievm/tests" + evmtypes "github.com/initia-labs/minievm/x/evm/types" +) + +func Test_Reader(t *testing.T) { + app, addrs, privKeys := tests.CreateApp(t) + indexer := app.EVMIndexer() + defer app.Close() + + tx, evmTxHash := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // check the tx is indexed + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + 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 = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, evmTxHash2 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(2)) + finalizeReq, finalizeRes := tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.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() + + // check the tx is indexed + ctx, err = app.CreateQueryContext(0, false) + require.NoError(t, err) + + 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/jsonrpc/backend/backend_test.go b/jsonrpc/backend/backend_test.go new file mode 100644 index 00000000..6c0739da --- /dev/null +++ b/jsonrpc/backend/backend_test.go @@ -0,0 +1,125 @@ +package backend_test + +import ( + "context" + "crypto/ecdsa" + "math/big" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + + minitiaapp "github.com/initia-labs/minievm/app" + "github.com/initia-labs/minievm/indexer" + "github.com/initia-labs/minievm/jsonrpc/backend" + "github.com/initia-labs/minievm/jsonrpc/config" + "github.com/initia-labs/minievm/tests" + evmtypes "github.com/initia-labs/minievm/x/evm/types" +) + +type testInput struct { + app *minitiaapp.MinitiaApp + indexer indexer.EVMIndexer + backend *backend.JSONRPCBackend + addrs []common.Address + privKeys []*ecdsa.PrivateKey + cometRPC *tests.MockCometRPC +} + +func setupBackend(t *testing.T) testInput { + app, addrs, privKeys := tests.CreateApp(t) + indexer := app.EVMIndexer() + + ctx := context.Background() + svrCtx := server.NewDefaultContext() + clientCtx := client.Context{}.WithCodec(app.AppCodec()). + WithInterfaceRegistry(app.AppCodec().InterfaceRegistry()). + WithTxConfig(app.TxConfig()). + WithLegacyAmino(app.LegacyAmino()). + WithAccountRetriever(authtypes.AccountRetriever{}) + + cfg := config.DefaultJSONRPCConfig() + cfg.Enable = true + + mockCometRPC := tests.NewMockCometRPC(app.BaseApp) + clientCtx = clientCtx.WithClient(mockCometRPC) + + backend, err := backend.NewJSONRPCBackend(ctx, app, app.Logger(), svrCtx, clientCtx, cfg) + require.NoError(t, err) + + return testInput{ + app: app, + indexer: indexer, + backend: backend, + addrs: addrs, + privKeys: privKeys, + cometRPC: mockCometRPC, + } +} + +func Test_FloodingQuery(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + time.Sleep(3 * time.Second) + + ctx, cancel := context.WithCancel(context.Background()) + queryFn := func() { + for { + select { + case <-ctx.Done(): + return + default: + _, err := backend.GetBalance(addrs[0], rpc.BlockNumberOrHashWithNumber(-1)) + require.NoError(t, err) + + time.Sleep(5 * time.Millisecond) + } + } + } + + for i := 0; i < 100; i++ { + go queryFn() + } + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + for i := 0; i < 1000; i++ { + tx, _ = tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + time.Sleep(5 * time.Millisecond) + } + wg.Done() + }() + + wg.Wait() + cancel() +} diff --git a/jsonrpc/backend/block_test.go b/jsonrpc/backend/block_test.go new file mode 100644 index 00000000..c3eb64d1 --- /dev/null +++ b/jsonrpc/backend/block_test.go @@ -0,0 +1,175 @@ +package backend_test + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + + rpctypes "github.com/initia-labs/minievm/jsonrpc/types" + "github.com/initia-labs/minievm/tests" + evmtypes "github.com/initia-labs/minievm/x/evm/types" +) + +func Test_BlockNumber(t *testing.T) { + input := setupBackend(t) + app, _, backend, _, _ := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + bn, err := backend.BlockNumber() + require.NoError(t, err) + require.Equal(t, uint64(app.LastBlockHeight()), uint64(bn)) +} + +func Test_GetHeaderByNumber(t *testing.T) { + input := setupBackend(t) + app, _, backend, _, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + finalizeReq, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + header, err := backend.GetHeaderByNumber(rpc.BlockNumber(finalizeReq.Height)) + require.NoError(t, err) + require.Equal(t, uint64(finalizeReq.Height), header.Number.Uint64()) + + // get latest block header + header2, err := backend.GetHeaderByNumber(rpc.LatestBlockNumber) + require.NoError(t, err) + require.Equal(t, header, header2) + + // pending block number should return the latest block number + header3, err := backend.GetHeaderByNumber(rpc.PendingBlockNumber) + require.NoError(t, err) + require.Equal(t, header, header3) + + // safe block number should return the latest block number + header4, err := backend.GetHeaderByNumber(rpc.SafeBlockNumber) + require.NoError(t, err) + require.Equal(t, header, header4) + + // finalized block number should return the latest block number + header5, err := backend.GetHeaderByNumber(rpc.FinalizedBlockNumber) + require.NoError(t, err) + require.Equal(t, header, header5) + + // 0 block number should return genesis block + header6, err := backend.GetHeaderByNumber(0) + require.NoError(t, err) + + genesisHeader, err := backend.GetHeaderByNumber(rpc.BlockNumber(1)) + require.NoError(t, err) + require.Equal(t, genesisHeader, header6) + + // invalid block number should return nil + header7, err := backend.GetHeaderByNumber(rpc.BlockNumber(1000000)) + require.NoError(t, err) + require.Nil(t, header7) +} + +func Test_GetHeaderByHash(t *testing.T) { + input := setupBackend(t) + app, _, backend, _, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + finalizeReq, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + header, err := backend.GetHeaderByNumber(rpc.BlockNumber(finalizeReq.Height)) + require.NoError(t, err) + + header2, err := backend.GetHeaderByHash(header.Hash()) + require.NoError(t, err) + require.Equal(t, header, header2) +} + +func Test_GetBlockByNumber(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, evmTxHash := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, evmTxHash2 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(2)) + finalizeReq, finalizeRes := tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + header, err := backend.GetHeaderByNumber(rpc.BlockNumber(finalizeReq.Height)) + require.NoError(t, err) + evmTx, err := backend.GetTransactionByHash(evmTxHash) + require.NoError(t, err) + evmTx2, err := backend.GetTransactionByHash(evmTxHash2) + require.NoError(t, err) + + block, err := backend.GetBlockByNumber(rpc.BlockNumber(finalizeReq.Height), true) + require.NoError(t, err) + + require.Equal(t, (*hexutil.Big)(header.Number), block["number"]) + require.Equal(t, header.Hash(), block["hash"]) + require.Equal(t, hexutil.Uint64(header.GasUsed), block["gasUsed"]) + require.Equal(t, hexutil.Uint64(header.GasLimit), block["gasLimit"]) + require.Equal(t, hexutil.Uint64(header.Time), block["timestamp"]) + require.Equal(t, []*rpctypes.RPCTransaction{evmTx, evmTx2}, block["transactions"]) + + block, err = backend.GetBlockByNumber(rpc.BlockNumber(finalizeReq.Height), false) + require.NoError(t, err) + require.Equal(t, []common.Hash{evmTx.Hash, evmTx2.Hash}, block["transactions"]) +} + +func Test_GetBlockByHash(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, evmTxHash := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, evmTxHash2 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(2)) + finalizeReq, finalizeRes := tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + header, err := backend.GetHeaderByNumber(rpc.BlockNumber(finalizeReq.Height)) + require.NoError(t, err) + evmTx, err := backend.GetTransactionByHash(evmTxHash) + require.NoError(t, err) + evmTx2, err := backend.GetTransactionByHash(evmTxHash2) + require.NoError(t, err) + + block, err := backend.GetBlockByHash(header.Hash(), true) + require.NoError(t, err) + + require.Equal(t, (*hexutil.Big)(header.Number), block["number"]) + require.Equal(t, header.Hash(), block["hash"]) + require.Equal(t, hexutil.Uint64(header.GasUsed), block["gasUsed"]) + require.Equal(t, hexutil.Uint64(header.GasLimit), block["gasLimit"]) + require.Equal(t, hexutil.Uint64(header.Time), block["timestamp"]) + require.Equal(t, []*rpctypes.RPCTransaction{evmTx, evmTx2}, block["transactions"]) + + block, err = backend.GetBlockByHash(header.Hash(), false) + require.NoError(t, err) + require.Equal(t, []common.Hash{evmTx.Hash, evmTx2.Hash}, block["transactions"]) +} diff --git a/jsonrpc/backend/cosmos_test.go b/jsonrpc/backend/cosmos_test.go new file mode 100644 index 00000000..cf59a6cb --- /dev/null +++ b/jsonrpc/backend/cosmos_test.go @@ -0,0 +1,98 @@ +package backend_test + +import ( + "math/big" + "testing" + + cmttypes "github.com/cometbft/cometbft/types" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/initia-labs/minievm/tests" + evmtypes "github.com/initia-labs/minievm/x/evm/types" + + "github.com/stretchr/testify/require" +) + +func Test_CosmosTxHashByTxHash(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, evmTxHash := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, evmTxHash2 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.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() + + txHash, err := backend.CosmosTxHashByTxHash(evmTxHash) + require.NoError(t, err) + require.Equal(t, cosmosTxHash, txHash) + + txHash2, err := backend.CosmosTxHashByTxHash(evmTxHash2) + require.NoError(t, err) + require.Equal(t, cosmosTxHash2, txHash2) +} + +func Test_TxHashByCosmosTxHash(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, evmTxHash := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, evmTxHash2 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.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() + + txHash, err := backend.TxHashByCosmosTxHash(cosmosTxHash) + require.NoError(t, err) + require.Equal(t, evmTxHash, txHash) + + txHash2, err := backend.TxHashByCosmosTxHash(cosmosTxHash2) + require.NoError(t, err) + require.Equal(t, evmTxHash2, txHash2) +} diff --git a/jsonrpc/backend/eth_test.go b/jsonrpc/backend/eth_test.go new file mode 100644 index 00000000..696bc622 --- /dev/null +++ b/jsonrpc/backend/eth_test.go @@ -0,0 +1,192 @@ +package backend_test + +import ( + "bytes" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/holiman/uint256" + rpctypes "github.com/initia-labs/minievm/jsonrpc/types" + "github.com/initia-labs/minievm/tests" + "github.com/initia-labs/minievm/x/evm/contracts/erc20" + evmtypes "github.com/initia-labs/minievm/x/evm/types" + "github.com/stretchr/testify/require" +) + +func Test_GetBalance(t *testing.T) { + input := setupBackend(t) + app, _, backend, _, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + // send 1GAS to newly created account + addr := common.Address{1, 2, 3} + tx, _ := tests.GenerateTx(t, app, privKeys[0], &addr, nil, big.NewInt(1_000_000_000_000_000_000)) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + // wait feeFetcher interval + time.Sleep(3 * time.Second) + + // 0 at genesis block + balance, err := backend.GetBalance(addr, rpc.BlockNumberOrHashWithNumber(0)) + require.NoError(t, err) + require.Equal(t, big.NewInt(0), balance.ToInt()) + + // 1GAS at latest block + balance, err = backend.GetBalance(addr, rpc.BlockNumberOrHashWithNumber(-1)) + require.NoError(t, err) + require.Equal(t, big.NewInt(1_000_000_000_000_000_000), balance.ToInt()) +} + +func Test_Call(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + contractEVMAddr := common.BytesToAddress(contractAddr) + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], contractEVMAddr, addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, _ := tests.GenerateTransferERC20Tx(t, app, privKeys[0], contractEVMAddr, addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + // call balanceOf function + abi, err := erc20.Erc20MetaData.GetAbi() + require.NoError(t, err) + + inputBz, err := abi.Pack("balanceOf", addrs[1]) + require.NoError(t, err) + + // query call with genesis block + genesisNumber := rpc.BlockNumberOrHashWithNumber(0) + retBz, err := backend.Call(rpctypes.TransactionArgs{ + From: &addrs[0], + To: &contractEVMAddr, + Input: (*hexutil.Bytes)(&inputBz), + Value: nil, + Nonce: nil, + }, &genesisNumber, nil, nil) + require.NoError(t, err) + require.Empty(t, retBz) + + // query to latest block + retBz, err = backend.Call(rpctypes.TransactionArgs{ + From: &addrs[0], + To: &contractEVMAddr, + Input: (*hexutil.Bytes)(&inputBz), + Value: nil, + Nonce: nil, + }, nil, nil, nil) + require.NoError(t, err) + + res, err := abi.Unpack("balanceOf", retBz) + require.NoError(t, err) + + require.Equal(t, new(big.Int).SetUint64(1_000_000), res[0].(*big.Int)) +} + +func Test_StorageAt(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // slot 0: 0x000000000000000000000000a7cdbd8580affc8dd17c2c28cb7ae891c711fd18 + // slot 1: 0x0000000000000000000000000000000000000000000000000000000000000000 + // slot 2: 0x0000000000000000000000000000000000000000000000000000000000000000 + // slot 3: 0x666f6f0000000000000000000000000000000000000000000000000000000006 (name) + // slot 4: 0x666f6f0000000000000000000000000000000000000000000000000000000006 (symbol) + // slot 5: 0x0000000000000000000000000000000000000000000000000000000000000006 (decimals) + // slot 6: 0x0000000000000000000000000000000000000000000000000000000000000000 (total supply) + slot := common.HexToHash("03") + retBz, err := backend.GetStorageAt(common.BytesToAddress(contractAddr), slot, rpc.BlockNumberOrHashWithNumber(-1)) + require.NoError(t, err) + require.Equal(t, common.Hex2Bytes("666f6f0000000000000000000000000000000000000000000000000000000006"), []byte(retBz)) + + slot = common.HexToHash("04") + retBz, err = backend.GetStorageAt(common.BytesToAddress(contractAddr), slot, rpc.BlockNumberOrHashWithNumber(-1)) + require.NoError(t, err) + require.Equal(t, common.Hex2Bytes("666f6f0000000000000000000000000000000000000000000000000000000006"), []byte(retBz)) + + slot = common.HexToHash("05") + retBz, err = backend.GetStorageAt(common.BytesToAddress(contractAddr), slot, rpc.BlockNumberOrHashWithNumber(-1)) + require.NoError(t, err) + require.Equal(t, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006"), []byte(retBz)) + + slot = common.HexToHash("06") + retBz, err = backend.GetStorageAt(common.BytesToAddress(contractAddr), slot, rpc.BlockNumberOrHashWithNumber(-1)) + require.NoError(t, err) + require.Equal(t, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"), []byte(retBz)) + + // mint 1_000_000 tokens to the first address + contractEVMAddr := common.BytesToAddress(contractAddr) + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], contractEVMAddr, addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + slot = common.HexToHash("06") + retBz, err = backend.GetStorageAt(common.BytesToAddress(contractAddr), slot, rpc.BlockNumberOrHashWithNumber(-1)) + require.NoError(t, err) + expectedTotalSupply := uint256.NewInt(1_000_000_000_000).Bytes32() + require.Equal(t, expectedTotalSupply[:], []byte(retBz)) +} + +func Test_Code(t *testing.T) { + input := setupBackend(t) + app, _, backend, _, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + code, err := backend.GetCode(common.BytesToAddress(contractAddr), rpc.BlockNumberOrHashWithNumber(-1)) + require.NoError(t, err) + require.NotEmpty(t, code) + + erc20Code := hexutil.MustDecode(erc20.Erc20MetaData.Bin) + initCodeOP := common.Hex2Bytes("5ff3fe") + initCodePos := bytes.Index(erc20Code, initCodeOP) + erc20RuntimeCode := erc20Code[initCodePos+3:] + require.True(t, bytes.Equal(erc20RuntimeCode, code)) +} + +func Test_ChainID(t *testing.T) { + input := setupBackend(t) + app, _, backend, _, _ := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + chainID, err := backend.ChainId() + require.NoError(t, err) + + evmChainID := evmtypes.ConvertCosmosChainIDToEthereumChainID(app.ChainID()) + require.Equal(t, evmChainID, chainID.ToInt()) +} diff --git a/jsonrpc/backend/feehistory.go b/jsonrpc/backend/feehistory.go index 86868e48..200cbd38 100644 --- a/jsonrpc/backend/feehistory.go +++ b/jsonrpc/backend/feehistory.go @@ -9,12 +9,10 @@ import ( "sync/atomic" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/misc/eip1559" coretypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" rpctypes "github.com/initia-labs/minievm/jsonrpc/types" - evmtypes "github.com/initia-labs/minievm/x/evm/types" ) const ( @@ -226,19 +224,13 @@ func (b *JSONRPCBackend) resolveBlockRange(reqEnd rpc.BlockNumber, blocks uint64 // the block field filled in, retrieves the block from the backend if not present yet and // fills in the rest of the fields. func (b *JSONRPCBackend) processBlock(bf *blockFees, percentiles []float64) { - ctx, err := b.app.CreateQueryContext(0, false) - if err != nil { - bf.err = err - return - } - - config := evmtypes.DefaultChainConfig(ctx) - // Fill in base fee and next base fee. if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil { bf.results.baseFee = new(big.Int) } - bf.results.nextBaseFee = eip1559.CalcBaseFee(config, bf.header) + + // NOTE: we don't have dynamic base fee calculation yet + bf.results.nextBaseFee = bf.header.BaseFee bf.results.blobBaseFee = new(big.Int) bf.results.nextBlobBaseFee = new(big.Int) diff --git a/jsonrpc/backend/feehistory_test.go b/jsonrpc/backend/feehistory_test.go new file mode 100644 index 00000000..295012c2 --- /dev/null +++ b/jsonrpc/backend/feehistory_test.go @@ -0,0 +1,76 @@ +package backend_test + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/initia-labs/minievm/tests" + evmtypes "github.com/initia-labs/minievm/x/evm/types" + + "github.com/stretchr/testify/require" +) + +func Test_FeeHistory(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + // multiple transfers + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + params, err := app.OPChildKeeper.Params.Get(ctx) + require.NoError(t, err) + + minGasPrice := params.MinGasPrices[0] + + gasLimit := uint64(1_000_000) + baseFee_ := minGasPrice.Amount.TruncateInt().BigInt() + baseFeeCap := new(big.Int).Mul(baseFee_, big.NewInt(int64(gasLimit))) + gasTipCap := new(big.Int).Mul(minGasPrice.Amount.TruncateInt().BigInt(), big.NewInt(1_000)) + gasFeeCap := new(big.Int).Add(baseFeeCap, gasTipCap) // add extra tip + + nonce := 2 + txs := make([]sdk.Tx, 5) + for i := 0; i < 5; i++ { + txs[i], _ = tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(uint64(nonce+i)), tests.SetGasLimit(gasLimit), tests.SetGasFeeCap(gasFeeCap), tests.SetGasTipCap(gasTipCap)) + } + _, finalizeRes = tests.ExecuteTxs(t, app, txs...) + + for i := 0; i < 5; i++ { + tests.CheckTxResult(t, finalizeRes.TxResults[i], true) + } + + header, err := backend.GetHeaderByNumber(rpc.BlockNumber(app.LastBlockHeight())) + require.NoError(t, err) + + gasUsedRatioExp := float64(header.GasUsed) / float64(header.GasLimit) + oldestBlock, reward, baseFee, gasUsedRatio, blobBaseFee, blobGasUsedRatio, err := backend.FeeHistory(1, rpc.BlockNumber(app.LastBlockHeight()), []float64{40, 60}) + require.NoError(t, err) + require.Equal(t, uint64(app.LastBlockHeight()), oldestBlock.Uint64()) + require.Equal(t, [][]*big.Int{{gasTipCap, gasTipCap}}, reward) + require.Equal(t, []*big.Int{baseFee_, baseFee_}, baseFee) + require.Equal(t, []float64{gasUsedRatioExp}, gasUsedRatio) + require.Equal(t, []*big.Int{big.NewInt(0), big.NewInt(0)}, blobBaseFee) + require.Equal(t, []float64{0}, blobGasUsedRatio) +} diff --git a/jsonrpc/backend/filters_test.go b/jsonrpc/backend/filters_test.go new file mode 100644 index 00000000..f3fadbd9 --- /dev/null +++ b/jsonrpc/backend/filters_test.go @@ -0,0 +1,53 @@ +package backend_test + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + coretypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + "github.com/initia-labs/minievm/tests" + evmtypes "github.com/initia-labs/minievm/x/evm/types" + "github.com/stretchr/testify/require" +) + +func Test_GetLogsByHeight(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, _ := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + logs, err := backend.GetLogsByHeight(uint64(app.LastBlockHeight())) + require.NoError(t, err) + + receipts, err := backend.GetBlockReceipts(rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(app.LastBlockHeight()))) + require.NoError(t, err) + + blockLogs := []*coretypes.Log{} + logs0, ok := receipts[0]["logs"].([]*coretypes.Log) + require.True(t, ok) + logs1, ok := receipts[1]["logs"].([]*coretypes.Log) + require.True(t, ok) + + blockLogs = append(blockLogs, logs0...) + blockLogs = append(blockLogs, logs1...) + require.Equal(t, blockLogs, logs) +} diff --git a/jsonrpc/backend/gas_test.go b/jsonrpc/backend/gas_test.go new file mode 100644 index 00000000..1ee496c3 --- /dev/null +++ b/jsonrpc/backend/gas_test.go @@ -0,0 +1,89 @@ +package backend_test + +import ( + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + + rpctypes "github.com/initia-labs/minievm/jsonrpc/types" + "github.com/initia-labs/minievm/tests" + "github.com/initia-labs/minievm/x/evm/contracts/erc20" + evmtypes "github.com/initia-labs/minievm/x/evm/types" +) + +func Test_GasPrice(t *testing.T) { + input := setupBackend(t) + app, _, backend, _, _ := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + params, err := app.OPChildKeeper.Params.Get(ctx) + require.NoError(t, err) + + minGasPrice := params.MinGasPrices[0].Amount.TruncateInt().BigInt() + + time.Sleep(3*time.Second + 500*time.Millisecond) + + gasPrice, err := backend.GasPrice() + require.NoError(t, err) + require.Equal(t, minGasPrice, gasPrice.ToInt()) +} + +func Test_MaxPriorityFeePerGas(t *testing.T) { + input := setupBackend(t) + _, _, backend, _, _ := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + maxPriorityFeePerGas, err := backend.MaxPriorityFeePerGas() + require.NoError(t, err) + require.Equal(t, big.NewInt(0), maxPriorityFeePerGas.ToInt()) +} + +func Test_EstimateGas(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + contractEVMAddr := common.BytesToAddress(contractAddr) + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], contractEVMAddr, addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, _ := tests.GenerateTransferERC20Tx(t, app, privKeys[0], contractEVMAddr, addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + // call transfer function + abi, err := erc20.Erc20MetaData.GetAbi() + require.NoError(t, err) + + inputBz, err := abi.Pack("transfer", addrs[1], big.NewInt(1_000_000)) + require.NoError(t, err) + + time.Sleep(3 * time.Second) + + // query to latest block + gasEstimated, err := backend.EstimateGas(rpctypes.TransactionArgs{ + From: &addrs[0], + To: &contractEVMAddr, + Input: (*hexutil.Bytes)(&inputBz), + Value: nil, + Nonce: nil, + }, nil, nil) + require.NoError(t, err) + require.Greater(t, gasEstimated, uint64(10_000)) + require.Less(t, uint64(gasEstimated), uint64(finalizeRes.TxResults[1].GasUsed)) +} diff --git a/jsonrpc/backend/net_test.go b/jsonrpc/backend/net_test.go new file mode 100644 index 00000000..2867f913 --- /dev/null +++ b/jsonrpc/backend/net_test.go @@ -0,0 +1,36 @@ +package backend_test + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_Version(t *testing.T) { + input := setupBackend(t) + _, _, backend, _, _ := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + version, err := backend.Version() + require.NoError(t, err) + require.Equal(t, "1", version) +} + +func Test_PeerCount(t *testing.T) { + input := setupBackend(t) + backend, cometRPC := input.backend, input.cometRPC + + cometRPC.NPeers = 10 + peerCount, err := backend.PeerCount() + require.NoError(t, err) + require.Equal(t, uint(10), uint(peerCount)) +} + +func Test_Listening(t *testing.T) { + input := setupBackend(t) + backend, cometRPC := input.backend, input.cometRPC + + cometRPC.Listening = true + listening, err := backend.Listening() + require.NoError(t, err) + require.True(t, listening) +} diff --git a/jsonrpc/backend/tx.go b/jsonrpc/backend/tx.go index b1d8f9b6..5c57db60 100644 --- a/jsonrpc/backend/tx.go +++ b/jsonrpc/backend/tx.go @@ -68,7 +68,7 @@ func (b *JSONRPCBackend) SendTx(tx *coretypes.Transaction) error { accSeq := uint64(0) sender := sdk.AccAddress(sig.PubKey.Address().Bytes()) - senderHex := hexutil.Encode(sender.Bytes()) + senderHex := common.BytesToAddress(sender.Bytes()).Hex() // hold mutex for each sender accMut := b.acquireAccMut(senderHex) @@ -328,7 +328,7 @@ func (b *JSONRPCBackend) PendingTransactions() ([]*rpctypes.RPCTransaction, erro return result, nil } -func (b *JSONRPCBackend) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { +func (b *JSONRPCBackend) GetBlockReceipts(blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { blockNumber, err := b.resolveBlockNrOrHash(blockNrOrHash) if err != nil && errors.Is(err, collections.ErrNotFound) { return nil, nil @@ -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 }) diff --git a/jsonrpc/backend/tx_test.go b/jsonrpc/backend/tx_test.go new file mode 100644 index 00000000..2017c588 --- /dev/null +++ b/jsonrpc/backend/tx_test.go @@ -0,0 +1,455 @@ +package backend_test + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/initia-labs/minievm/tests" + evmkeeper "github.com/initia-labs/minievm/x/evm/keeper" + evmtypes "github.com/initia-labs/minievm/x/evm/types" + "github.com/stretchr/testify/require" +) + +func Test_SendRawTransaction(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, _ := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + // Acc: 0, Nonce: 4 + tx04, txHash04 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(4)) + evmTx04, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx04) + require.NoError(t, err) + require.NotNil(t, evmTx04) + + txBz, err := evmTx04.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // Acc: 1, Nonce: 0 + tx10, txHash10 := tests.GenerateTransferERC20Tx(t, app, privKeys[1], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(0)) + evmTx10, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx10) + require.NoError(t, err) + require.NotNil(t, evmTx10) + + txBz, err = evmTx10.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // Acc: 0, Nonce: 6 + tx06, txHash06 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(6)) + evmTx06, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx06) + require.NoError(t, err) + require.NotNil(t, evmTx06) + + txBz, err = evmTx06.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // 1 in pending, and 2 in queued + txPool, err := backend.TxPoolContent() + require.NoError(t, err) + require.Len(t, txPool["pending"][addrs[1].Hex()], 1) + require.Equal(t, txPool["pending"][addrs[1].Hex()]["0"].Hash, txHash10) + require.Len(t, txPool["queued"][addrs[0].Hex()], 2) + require.Equal(t, txPool["queued"][addrs[0].Hex()]["4"].Hash, txHash04) + require.Equal(t, txPool["queued"][addrs[0].Hex()]["6"].Hash, txHash06) + + // sending Nonce 3 should make Nonce 4 to be pending + + // Acc: 0, Nonce: 3 + tx03, txHash03 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(3)) + evmTx03, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx03) + require.NoError(t, err) + require.NotNil(t, evmTx03) + + txBz, err = evmTx03.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // 3 in pending and 1 in queued + txPool, err = backend.TxPoolContent() + require.NoError(t, err) + require.Len(t, txPool["pending"][addrs[0].Hex()], 2) + require.Equal(t, txPool["pending"][addrs[0].Hex()]["3"].Hash, txHash03) + require.Equal(t, txPool["pending"][addrs[0].Hex()]["4"].Hash, txHash04) + require.Len(t, txPool["pending"][addrs[1].Hex()], 1) + require.Equal(t, txPool["pending"][addrs[1].Hex()]["0"].Hash, txHash10) + require.Len(t, txPool["queued"][addrs[0].Hex()], 1) + require.Equal(t, txPool["queued"][addrs[0].Hex()]["6"].Hash, txHash06) + + // sending Nonce 5 should make Nonce 6 to be pending + + // Acc: 0, Nonce: 5 + tx05, txHash05 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(5)) + evmTx05, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx05) + require.NoError(t, err) + require.NotNil(t, evmTx05) + + txBz, err = evmTx05.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // 5 in pending and 0 in queued + txPool, err = backend.TxPoolContent() + require.NoError(t, err) + require.Len(t, txPool["pending"][addrs[0].Hex()], 4) + require.Equal(t, txPool["pending"][addrs[0].Hex()]["3"].Hash, txHash03) + require.Equal(t, txPool["pending"][addrs[0].Hex()]["4"].Hash, txHash04) + require.Equal(t, txPool["pending"][addrs[0].Hex()]["5"].Hash, txHash05) + require.Equal(t, txPool["pending"][addrs[0].Hex()]["6"].Hash, txHash06) + require.Len(t, txPool["pending"][addrs[1].Hex()], 1) + require.Equal(t, txPool["pending"][addrs[1].Hex()]["0"].Hash, txHash10) + require.Empty(t, txPool["queued"]) +} + +func Test_GetTransactionCount(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, _ := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + count, err := backend.GetTransactionCount(common.BytesToAddress(addrs[0].Bytes()), rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(app.LastBlockHeight()-1))) + require.NoError(t, err) + require.Equal(t, "0x1", count.String()) + + count, err = backend.GetTransactionCount(common.BytesToAddress(addrs[0].Bytes()), rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(app.LastBlockHeight()))) + require.NoError(t, err) + require.Equal(t, "0x3", count.String()) + + // try wrong block hash + _, err = backend.GetTransactionCount(common.BytesToAddress(addrs[0].Bytes()), rpc.BlockNumberOrHashWithHash(common.Hash{}, false)) + require.Error(t, err) + + // try with valid block hash + header, err := backend.GetHeaderByNumber(rpc.BlockNumber(app.LastBlockHeight())) + require.NoError(t, err) + + count, err = backend.GetTransactionCount(common.BytesToAddress(addrs[0].Bytes()), rpc.BlockNumberOrHashWithHash(header.Hash(), false)) + require.NoError(t, err) + require.Equal(t, "0x3", count.String()) +} + +func Test_GetTransactionReceipt(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, txHash := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, txHash2 := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + header, err := backend.GetHeaderByNumber(rpc.BlockNumber(app.LastBlockHeight())) + require.NoError(t, err) + + receipt, err := backend.GetTransactionReceipt(txHash) + require.NoError(t, err) + require.NotNil(t, receipt) + require.Equal(t, header.Hash(), *receipt["blockHash"].(*common.Hash)) + require.Equal(t, header.Number.Uint64(), uint64(receipt["blockNumber"].(hexutil.Uint64))) + require.Equal(t, txHash, receipt["transactionHash"]) + require.Equal(t, uint64(1), uint64(receipt["transactionIndex"].(hexutil.Uint64))) + + receipt2, err := backend.GetTransactionReceipt(txHash2) + require.NoError(t, err) + require.NotNil(t, receipt2) + require.Equal(t, header.Hash(), *receipt2["blockHash"].(*common.Hash)) + require.Equal(t, header.Number.Uint64(), uint64(receipt2["blockNumber"].(hexutil.Uint64))) + require.Equal(t, txHash2, receipt2["transactionHash"]) + require.Equal(t, uint64(2), uint64(receipt2["transactionIndex"].(hexutil.Uint64))) +} + +func Test_GetTransaction(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, txHash := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, txHash2 := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + txByHash, err := backend.GetTransactionByHash(txHash) + require.NoError(t, err) + + header, err := backend.GetHeaderByNumber(rpc.BlockNumber(app.LastBlockHeight())) + require.NoError(t, err) + + txByHashAndIndex, err := backend.GetTransactionByBlockHashAndIndex(header.Hash(), 1) + require.NoError(t, err) + require.Equal(t, txByHash, txByHashAndIndex) + txByNumberAndIndex, err := backend.GetTransactionByBlockNumberAndIndex(rpc.BlockNumber(app.LastBlockHeight()), 1) + require.NoError(t, err) + require.Equal(t, txByHash, txByNumberAndIndex) + + txByHash2, err := backend.GetTransactionByHash(txHash2) + require.NoError(t, err) + + txByHashAndIndex2, err := backend.GetTransactionByBlockHashAndIndex(header.Hash(), 2) + require.NoError(t, err) + require.Equal(t, txByHash2, txByHashAndIndex2) + txByNumberAndIndex2, err := backend.GetTransactionByBlockNumberAndIndex(rpc.BlockNumber(app.LastBlockHeight()), 2) + require.NoError(t, err) + require.Equal(t, txByHash2, txByNumberAndIndex2) + + // try with wrong block hash + _, err = backend.GetTransactionByBlockHashAndIndex(common.Hash{}, 1) + require.Error(t, err) +} + +func Test_BlockTransactionCount(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, _ := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + header, err := backend.GetHeaderByNumber(rpc.BlockNumber(app.LastBlockHeight())) + require.NoError(t, err) + + count, err := backend.GetBlockTransactionCountByHash(header.Hash()) + require.NoError(t, err) + require.Equal(t, "0x2", count.String()) + + count, err = backend.GetBlockTransactionCountByNumber(rpc.BlockNumber(app.LastBlockHeight())) + require.NoError(t, err) + require.Equal(t, "0x2", count.String()) +} + +func Test_GetRawTransaction(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, txHash := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, _ := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + txByHash, err := backend.GetRawTransactionByHash(txHash) + require.NoError(t, err) + + header, err := backend.GetHeaderByNumber(rpc.BlockNumber(app.LastBlockHeight())) + require.NoError(t, err) + + txByHashAndIndex, err := backend.GetRawTransactionByBlockHashAndIndex(header.Hash(), 1) + require.NoError(t, err) + require.Equal(t, txByHash, txByHashAndIndex) + + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + evmTx, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx) + require.NoError(t, err) + require.NotNil(t, evmTx) + + txBz, err := evmTx.MarshalBinary() + require.NoError(t, err) + require.Equal(t, txBz, []byte(txByHash)) +} + +func Test_PendingTransactions(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, _ := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + // Acc: 0, Nonce: 3 + tx03, txHash03 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(3)) + evmTx03, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx03) + require.NoError(t, err) + require.NotNil(t, evmTx03) + + txBz, err := evmTx03.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // Acc: 0, Nonce: 4 + tx04, txHash04 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(4)) + evmTx04, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx04) + require.NoError(t, err) + require.NotNil(t, evmTx04) + + txBz, err = evmTx04.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // Acc: 1, Nonce: 0 + tx10, txHash10 := tests.GenerateTransferERC20Tx(t, app, privKeys[1], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(0)) + evmTx10, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx10) + require.NoError(t, err) + require.NotNil(t, evmTx10) + + txBz, err = evmTx10.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + pendingTxs, err := backend.PendingTransactions() + require.NoError(t, err) + require.Len(t, pendingTxs, 3) + + txHashes := []common.Hash{txHash03, txHash04, txHash10} + for _, tx := range pendingTxs { + require.Contains(t, txHashes, tx.Hash) + } +} + +func Test_BlockReceipts(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, txHash := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, txHash2 := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + header, err := backend.GetHeaderByNumber(rpc.BlockNumber(app.LastBlockHeight())) + require.NoError(t, err) + + receipts, err := backend.GetBlockReceipts(rpc.BlockNumberOrHashWithHash(header.Hash(), false)) + require.NoError(t, err) + require.Len(t, receipts, 2) + + receipt := receipts[0] + require.NotNil(t, receipt) + require.Equal(t, header.Hash(), *receipt["blockHash"].(*common.Hash)) + require.Equal(t, header.Number.Uint64(), uint64(receipt["blockNumber"].(hexutil.Uint64))) + require.Equal(t, txHash, receipt["transactionHash"]) + require.Equal(t, uint64(1), uint64(receipt["transactionIndex"].(hexutil.Uint64))) + + receipt2 := receipts[1] + require.NotNil(t, receipt2) + require.Equal(t, header.Hash(), *receipt2["blockHash"].(*common.Hash)) + require.Equal(t, header.Number.Uint64(), uint64(receipt2["blockNumber"].(hexutil.Uint64))) + require.Equal(t, txHash2, receipt2["transactionHash"]) + require.Equal(t, uint64(2), uint64(receipt2["transactionIndex"].(hexutil.Uint64))) +} diff --git a/jsonrpc/backend/txpool_test.go b/jsonrpc/backend/txpool_test.go new file mode 100644 index 00000000..77df1753 --- /dev/null +++ b/jsonrpc/backend/txpool_test.go @@ -0,0 +1,354 @@ +package backend_test + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/initia-labs/minievm/tests" + evmkeeper "github.com/initia-labs/minievm/x/evm/keeper" + evmtypes "github.com/initia-labs/minievm/x/evm/types" + "github.com/stretchr/testify/require" +) + +func Test_TxPoolContextFrom(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, _ := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + // Acc: 0, Nonce: 4 + tx04, txHash04 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(4)) + evmTx04, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx04) + require.NoError(t, err) + require.NotNil(t, evmTx04) + + txBz, err := evmTx04.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // Acc: 1, Nonce: 0 + tx10, txHash10 := tests.GenerateTransferERC20Tx(t, app, privKeys[1], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(0)) + evmTx10, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx10) + require.NoError(t, err) + require.NotNil(t, evmTx10) + + txBz, err = evmTx10.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // Acc: 0, Nonce: 6 + tx06, txHash06 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(6)) + evmTx06, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx06) + require.NoError(t, err) + require.NotNil(t, evmTx06) + + txBz, err = evmTx06.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // 2 in queued and 0 in pending + txPool, err := backend.TxPoolContentFrom(addrs[0]) + require.NoError(t, err) + require.Empty(t, txPool["pending"]) + require.Len(t, txPool["queued"], 2) + require.Equal(t, txPool["queued"]["4"].Hash, txHash04) + require.Equal(t, txPool["queued"]["6"].Hash, txHash06) + + // 1 in pending and 0 in queued + txPool, err = backend.TxPoolContentFrom(addrs[1]) + require.NoError(t, err) + require.Len(t, txPool["pending"], 1) + require.Equal(t, txPool["pending"]["0"].Hash, txHash10) + require.Empty(t, txPool["queued"]) + + // sending Nonce 3 should make Nonce 4 to be pending + + // Acc: 0, Nonce: 3 + tx03, txHash03 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(3)) + evmTx03, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx03) + require.NoError(t, err) + require.NotNil(t, evmTx03) + + txBz, err = evmTx03.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // 2 in pending and 1 in queued + txPool, err = backend.TxPoolContentFrom(addrs[0]) + require.NoError(t, err) + require.Len(t, txPool["pending"], 2) + require.Equal(t, txPool["pending"]["3"].Hash, txHash03) + require.Equal(t, txPool["pending"]["4"].Hash, txHash04) + require.Len(t, txPool["queued"], 1) + require.Equal(t, txPool["queued"]["6"].Hash, txHash06) + + // 1 in pending and 0 in queued + txPool, err = backend.TxPoolContentFrom(addrs[1]) + require.NoError(t, err) + require.Len(t, txPool["pending"], 1) + require.Equal(t, txPool["pending"]["0"].Hash, txHash10) + require.Empty(t, txPool["queued"]) + + // sending Nonce 5 should make Nonce 6 to be pending + + // Acc: 0, Nonce: 5 + tx05, txHash05 := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(5)) + evmTx05, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx05) + require.NoError(t, err) + require.NotNil(t, evmTx05) + + txBz, err = evmTx05.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // 4 in pending and 0 in queued + txPool, err = backend.TxPoolContentFrom(addrs[0]) + require.NoError(t, err) + require.Len(t, txPool["pending"], 4) + require.Equal(t, txPool["pending"]["3"].Hash, txHash03) + require.Equal(t, txPool["pending"]["4"].Hash, txHash04) + require.Equal(t, txPool["pending"]["5"].Hash, txHash05) + require.Equal(t, txPool["pending"]["6"].Hash, txHash06) + require.Empty(t, txPool["queued"]) + + // 1 in pending + txPool, err = backend.TxPoolContentFrom(addrs[1]) + require.NoError(t, err) + require.Len(t, txPool["pending"], 1) + require.Equal(t, txPool["pending"]["0"].Hash, txHash10) + require.Empty(t, txPool["queued"]) +} + +func Test_TxPoolStatus(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, _ := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + // Acc: 0, Nonce: 4 + tx04, _ := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(4)) + evmTx04, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx04) + require.NoError(t, err) + require.NotNil(t, evmTx04) + + txBz, err := evmTx04.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // Acc: 1, Nonce: 0 + tx10, _ := tests.GenerateTransferERC20Tx(t, app, privKeys[1], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(0)) + evmTx10, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx10) + require.NoError(t, err) + require.NotNil(t, evmTx10) + + txBz, err = evmTx10.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // Acc: 0, Nonce: 6 + tx06, _ := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(6)) + evmTx06, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx06) + require.NoError(t, err) + require.NotNil(t, evmTx06) + + txBz, err = evmTx06.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // 2 in queued and 0 in pending + status, err := backend.TxPoolStatus() + require.NoError(t, err) + require.Equal(t, 1, int(status["pending"])) + require.Equal(t, 2, int(status["queued"])) + + // sending Nonce 3 should make Nonce 4 to be pending + + // Acc: 0, Nonce: 3 + tx03, _ := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(3)) + evmTx03, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx03) + require.NoError(t, err) + require.NotNil(t, evmTx03) + + txBz, err = evmTx03.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // 3 in pending and 1 in queued + status, err = backend.TxPoolStatus() + require.NoError(t, err) + require.Equal(t, 3, int(status["pending"])) + require.Equal(t, 1, int(status["queued"])) + + // sending Nonce 5 should make Nonce 6 to be pending + + // Acc: 0, Nonce: 5 + tx05, _ := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(5)) + evmTx05, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx05) + require.NoError(t, err) + require.NotNil(t, evmTx05) + + txBz, err = evmTx05.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // 5 in pending and 0 in queued + status, err = backend.TxPoolStatus() + require.NoError(t, err) + require.Equal(t, 5, int(status["pending"])) + require.Equal(t, 0, int(status["queued"])) +} + +func Test_TxPoolInspect(t *testing.T) { + input := setupBackend(t) + app, _, backend, addrs, privKeys := input.app, input.addrs, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx, _ = tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + tx2, _ := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000), tests.SetNonce(2)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + tests.CheckTxResult(t, finalizeRes.TxResults[1], true) + + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + // Acc: 0, Nonce: 4 + tx04, _ := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(4)) + evmTx04, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx04) + require.NoError(t, err) + require.NotNil(t, evmTx04) + + txBz, err := evmTx04.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // Acc: 1, Nonce: 0 + tx10, _ := tests.GenerateTransferERC20Tx(t, app, privKeys[1], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(0)) + evmTx10, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx10) + require.NoError(t, err) + require.NotNil(t, evmTx10) + + txBz, err = evmTx10.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // Acc: 0, Nonce: 6 + tx06, _ := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(6)) + evmTx06, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx06) + require.NoError(t, err) + require.NotNil(t, evmTx06) + + txBz, err = evmTx06.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // 1 in pending, and 2 in queued + txPool, err := backend.TxPoolInspect() + require.NoError(t, err) + require.Len(t, txPool["pending"][addrs[1].Hex()], 1) + require.Len(t, txPool["queued"][addrs[0].Hex()], 2) + + // sending Nonce 3 should make Nonce 4 to be pending + + // Acc: 0, Nonce: 3 + tx03, _ := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(3)) + evmTx03, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx03) + require.NoError(t, err) + require.NotNil(t, evmTx03) + + txBz, err = evmTx03.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // 3 in pending and 1 in queued + txPool, err = backend.TxPoolInspect() + require.NoError(t, err) + require.Len(t, txPool["pending"][addrs[0].Hex()], 2) + require.Len(t, txPool["pending"][addrs[1].Hex()], 1) + require.Len(t, txPool["queued"][addrs[0].Hex()], 1) + + // sending Nonce 5 should make Nonce 6 to be pending + + // Acc: 0, Nonce: 5 + tx05, _ := tests.GenerateTransferERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[1], new(big.Int).SetUint64(1_000_000), tests.SetNonce(5)) + evmTx05, _, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertCosmosTxToEthereumTx(ctx, tx05) + require.NoError(t, err) + require.NotNil(t, evmTx05) + + txBz, err = evmTx05.MarshalBinary() + require.NoError(t, err) + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + // 5 in pending and 0 in queued + txPool, err = backend.TxPoolInspect() + require.NoError(t, err) + require.Len(t, txPool["pending"][addrs[0].Hex()], 4) + require.Len(t, txPool["pending"][addrs[1].Hex()], 1) + require.Empty(t, txPool["queued"]) +} diff --git a/jsonrpc/backend/web3_test.go b/jsonrpc/backend/web3_test.go new file mode 100644 index 00000000..ba47181a --- /dev/null +++ b/jsonrpc/backend/web3_test.go @@ -0,0 +1,17 @@ +package backend_test + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_ClientVersion(t *testing.T) { + test := setupBackend(t) + backend, cometRPC := test.backend, test.cometRPC + + cometRPC.ClientVersion = "v0.6.2" + version, err := backend.ClientVersion() + require.NoError(t, err) + require.Equal(t, "v0.6.2", version) +} diff --git a/jsonrpc/namespaces/eth/api.go b/jsonrpc/namespaces/eth/api.go index 9b9a6635..f963728d 100644 --- a/jsonrpc/namespaces/eth/api.go +++ b/jsonrpc/namespaces/eth/api.go @@ -144,9 +144,9 @@ func (api *EthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]int return api.backend.GetBlockByHash(hash, fullTx) } -func (api *EthAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { +func (api *EthAPI) GetBlockReceipts(blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { api.logger.Debug("eth_getBlockReceipts", "block number or hash", blockNrOrHash) - return api.backend.GetBlockReceipts(ctx, blockNrOrHash) + return api.backend.GetBlockReceipts(blockNrOrHash) } // ************************************* diff --git a/jsonrpc/namespaces/eth/filters/api.go b/jsonrpc/namespaces/eth/filters/api.go index 9332a06f..3c016045 100644 --- a/jsonrpc/namespaces/eth/filters/api.go +++ b/jsonrpc/namespaces/eth/filters/api.go @@ -49,7 +49,7 @@ type FilterAPI struct { logger log.Logger - filtersMut sync.Mutex + filtersMut sync.RWMutex filters map[rpc.ID]*filter subscriptions map[rpc.ID]*subscription @@ -126,6 +126,8 @@ func (api *FilterAPI) clearUnusedFilters() { } } +// eventLoop is the main loop for the filter API. +// NOTE: api.subscriptions is not thread-safe and should only be accessed from this goroutine. func (api *FilterAPI) eventLoop() { for { select { @@ -208,18 +210,18 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) (rpc.ID, error) for { select { case rpcTx := <-txChan: - api.filtersMut.Lock() + api.filtersMut.RLock() if f, found := api.filters[id]; found { f.txs = append(f.txs, rpcTx) } - api.filtersMut.Unlock() + api.filtersMut.RUnlock() case hash := <-hashChan: - api.filtersMut.Lock() + api.filtersMut.RLock() if f, found := api.filters[id]; found { f.hashes = append(f.hashes, hash) } - api.filtersMut.Unlock() - case <-s.err: // subsciprtion is uninstalled + api.filtersMut.RUnlock() + case <-s.err: // subscription is uninstalled return } } @@ -260,12 +262,12 @@ func (api *FilterAPI) NewBlockFilter() (rpc.ID, error) { for { select { case header := <-headerChan: - api.filtersMut.Lock() + api.filtersMut.RLock() if f, found := api.filters[id]; found { f.hashes = append(f.hashes, header.Hash()) } - api.filtersMut.Unlock() - case <-s.err: // subsciprtion is uninstalled + api.filtersMut.RUnlock() + case <-s.err: // subscription is uninstalled return } } @@ -338,12 +340,12 @@ func (api *FilterAPI) NewFilter(crit ethfilters.FilterCriteria) (rpc.ID, error) select { case logs := <-logsChan: logs = filterLogs(logs, s.crit.FromBlock, s.crit.ToBlock, s.crit.Addresses, s.crit.Topics) - api.filtersMut.Lock() + api.filtersMut.RLock() if f, found := api.filters[id]; found { f.logs = append(f.logs, logs...) } - api.filtersMut.Unlock() - case <-s.err: // subsciprtion is uninstalled + api.filtersMut.RUnlock() + case <-s.err: // subscription is uninstalled return } } @@ -404,9 +406,9 @@ func (api *FilterAPI) UninstallFilter(id rpc.ID) bool { // GetFilterLogs returns the logs for the filter with the given id. // If the filter could not be found an empty array of logs is returned. func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*coretypes.Log, error) { - api.filtersMut.Lock() + api.filtersMut.RLock() f, found := api.filters[id] - api.filtersMut.Unlock() + api.filtersMut.RUnlock() if !found || f.s.ty != ethfilters.LogsSubscription { return nil, errFilterNotFound @@ -445,10 +447,10 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*coretype // For pending transaction and block filters the result is []common.Hash. // (pending)Log filters return []Log. func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { - api.filtersMut.Lock() - defer api.filtersMut.Unlock() - + api.filtersMut.RLock() f, ok := api.filters[id] + api.filtersMut.RUnlock() + if !ok { return []interface{}{}, errFilterNotFound } diff --git a/jsonrpc/namespaces/eth/filters/api_test.go b/jsonrpc/namespaces/eth/filters/api_test.go new file mode 100644 index 00000000..21210383 --- /dev/null +++ b/jsonrpc/namespaces/eth/filters/api_test.go @@ -0,0 +1,472 @@ +package filters_test + +import ( + "context" + "crypto/ecdsa" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + coretypes "github.com/ethereum/go-ethereum/core/types" + ethfilters "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/rpc" + + minitiaapp "github.com/initia-labs/minievm/app" + "github.com/initia-labs/minievm/indexer" + "github.com/initia-labs/minievm/jsonrpc/backend" + "github.com/initia-labs/minievm/jsonrpc/config" + "github.com/initia-labs/minievm/jsonrpc/namespaces/eth/filters" + rpctypes "github.com/initia-labs/minievm/jsonrpc/types" + "github.com/initia-labs/minievm/tests" + evmtypes "github.com/initia-labs/minievm/x/evm/types" +) + +type testInput struct { + app *minitiaapp.MinitiaApp + indexer indexer.EVMIndexer + backend *backend.JSONRPCBackend + addrs []common.Address + privKeys []*ecdsa.PrivateKey + cometRPC *tests.MockCometRPC + filterAPI *filters.FilterAPI +} + +func setupFilterAPI(t *testing.T) testInput { + app, addrs, privKeys := tests.CreateApp(t) + indexer := app.EVMIndexer() + + ctx := context.Background() + svrCtx := server.NewDefaultContext() + clientCtx := client.Context{}.WithCodec(app.AppCodec()). + WithInterfaceRegistry(app.AppCodec().InterfaceRegistry()). + WithTxConfig(app.TxConfig()). + WithLegacyAmino(app.LegacyAmino()). + WithAccountRetriever(authtypes.AccountRetriever{}) + + cfg := config.DefaultJSONRPCConfig() + cfg.Enable = true + cfg.FilterTimeout = 3 * time.Second + + mockCometRPC := tests.NewMockCometRPC(app.BaseApp) + clientCtx = clientCtx.WithClient(mockCometRPC) + + backend, err := backend.NewJSONRPCBackend(ctx, app, app.Logger(), svrCtx, clientCtx, cfg) + require.NoError(t, err) + + filterAPI := filters.NewFilterAPI(ctx, app, backend, app.Logger()) + + return testInput{ + app: app, + indexer: indexer, + backend: backend, + addrs: addrs, + privKeys: privKeys, + cometRPC: mockCometRPC, + filterAPI: filterAPI, + } +} + +func Test_NewPendingTransactionFilter_FullTx(t *testing.T) { + input := setupFilterAPI(t) + defer input.app.Close() + + // wait indexer to be ready + time.Sleep(3 * time.Second) + + fullTx := true + filterID, err := input.filterAPI.NewPendingTransactionFilter(&fullTx) + require.NoError(t, err) + require.NotEmpty(t, filterID) + + app, backend, addrs, privKeys := input.app, input.backend, input.addrs, input.privKeys + + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + evmTx, _, err := app.EVMKeeper.TxUtils().ConvertCosmosTxToEthereumTx(ctx, tx) + require.NoError(t, err) + + txBz, err := evmTx.MarshalBinary() + require.NoError(t, err) + txHash1, err := backend.SendRawTransaction(txBz) + require.NoError(t, err) + + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx2, _ := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + evmTx2, _, err := app.EVMKeeper.TxUtils().ConvertCosmosTxToEthereumTx(ctx, tx2) + require.NoError(t, err) + + txBz, err = evmTx2.MarshalBinary() + require.NoError(t, err) + txHash2, err := backend.SendRawTransaction(txBz) + require.NoError(t, err) + + _, finalizeRes = tests.ExecuteTxs(t, app, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + rpcTx1, err := backend.GetTransactionByHash(txHash1) + require.NoError(t, err) + rpcTx2, err := backend.GetTransactionByHash(txHash2) + require.NoError(t, err) + + // wait txs to be indexed + time.Sleep(1 * time.Second) + + // there should be 2 changes + changes, err := input.filterAPI.GetFilterChanges(filterID) + require.NoError(t, err) + require.Len(t, changes, 2) + + // to compare with pending tx filter, we need to remove block hash, block number, and transaction index + rpcTx1.BlockHash = nil + rpcTx1.BlockNumber = nil + rpcTx1.TransactionIndex = nil + rpcTx2.BlockHash = nil + rpcTx2.BlockNumber = nil + rpcTx2.TransactionIndex = nil + + res := []string{} + for i := 0; i < 2; i++ { + rpcTx := changes.([]*rpctypes.RPCTransaction)[i] + res = append(res, rpcTx.String()) + } + + require.Equal(t, []string{rpcTx1.String(), rpcTx2.String()}, res) + + // uninstall filter + found := input.filterAPI.UninstallFilter(filterID) + require.True(t, found) + + // more changes should not be returned + _, err = input.filterAPI.GetFilterChanges(filterID) + require.ErrorContains(t, err, "filter not found") +} + +func Test_NewPendingTransactionFilter(t *testing.T) { + input := setupFilterAPI(t) + defer input.app.Close() + + // wait indexer to be ready + time.Sleep(3 * time.Second) + + fullTx := false + filterID, err := input.filterAPI.NewPendingTransactionFilter(&fullTx) + require.NoError(t, err) + require.NotEmpty(t, filterID) + + app, backend, addrs, privKeys := input.app, input.backend, input.addrs, input.privKeys + + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + evmTx, _, err := app.EVMKeeper.TxUtils().ConvertCosmosTxToEthereumTx(ctx, tx) + require.NoError(t, err) + + txBz, err := evmTx.MarshalBinary() + require.NoError(t, err) + txHash1, err := backend.SendRawTransaction(txBz) + require.NoError(t, err) + + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx2, _ := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + evmTx2, _, err := app.EVMKeeper.TxUtils().ConvertCosmosTxToEthereumTx(ctx, tx2) + require.NoError(t, err) + + txBz, err = evmTx2.MarshalBinary() + require.NoError(t, err) + txHash2, err := backend.SendRawTransaction(txBz) + require.NoError(t, err) + + _, finalizeRes = tests.ExecuteTxs(t, app, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + // wait txs to be indexed + time.Sleep(1 * time.Second) + + // there should be 2 changes + changes, err := input.filterAPI.GetFilterChanges(filterID) + require.NoError(t, err) + require.Len(t, changes, 2) + require.Equal(t, []common.Hash{txHash1, txHash2}, changes.([]common.Hash)) +} + +func Test_NewBlockFilter(t *testing.T) { + input := setupFilterAPI(t) + defer input.app.Close() + + // wait indexer to be ready + time.Sleep(3 * time.Second) + + filterID, err := input.filterAPI.NewBlockFilter() + require.NoError(t, err) + require.NotEmpty(t, filterID) + + app, backend, addrs, privKeys := input.app, input.backend, input.addrs, input.privKeys + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx2, _ := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + // wait txs to be indexed + time.Sleep(1 * time.Second) + + // there should be 2 changes + changes, err := input.filterAPI.GetFilterChanges(filterID) + require.NoError(t, err) + require.Len(t, changes, 2) + + blockHash := changes.([]common.Hash)[0] + header, err := backend.GetHeaderByHash(blockHash) + require.NoError(t, err) + require.Equal(t, app.LastBlockHeight()-1, header.Number.Int64()) + + blockHash = changes.([]common.Hash)[1] + header, err = backend.GetHeaderByHash(blockHash) + require.NoError(t, err) + require.Equal(t, app.LastBlockHeight(), header.Number.Int64()) +} + +func Test_NewFilter(t *testing.T) { + input := setupFilterAPI(t) + defer input.app.Close() + + // wait indexer to be ready + time.Sleep(3 * time.Second) + + app, addrs, privKeys := input.app, input.addrs, input.privKeys + + // invalid block range + _, err := input.filterAPI.NewFilter(ethfilters.FilterCriteria{ + FromBlock: big.NewInt(100), + ToBlock: big.NewInt(10), + }) + require.Error(t, err) + + // start tracking after 2 blocks + filterID, err := input.filterAPI.NewFilter(ethfilters.FilterCriteria{ + FromBlock: big.NewInt(app.LastBlockHeight() + 2), + ToBlock: big.NewInt(int64(rpc.LatestBlockNumber)), + }) + require.NoError(t, err) + require.NotEmpty(t, filterID) + + // track all blocks + filterID2, err := input.filterAPI.NewFilter(ethfilters.FilterCriteria{}) + require.NoError(t, err) + require.NotEmpty(t, filterID2) + + // this should not tracked by filter 1 but by filter 2 + tx, txHash1 := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // this should be tracked by both filters + // mint 1_000_000 tokens to the first address + tx2, txHash2 := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + // wait txs to be indexed + time.Sleep(1 * time.Second) + + changes, err := input.filterAPI.GetFilterChanges(filterID) + require.NoError(t, err) + require.Len(t, changes, 1) + for _, change := range changes.([]*coretypes.Log) { + require.Equal(t, txHash2, change.TxHash) + } + + changes, err = input.filterAPI.GetFilterChanges(filterID2) + require.NoError(t, err) + require.Len(t, changes, 3) + require.Equal(t, txHash1, changes.([]*coretypes.Log)[0].TxHash) + require.Equal(t, txHash1, changes.([]*coretypes.Log)[1].TxHash) + require.Equal(t, txHash2, changes.([]*coretypes.Log)[2].TxHash) +} + +func Test_GetLogs(t *testing.T) { + input := setupFilterAPI(t) + defer input.app.Close() + + // wait indexer to be ready + time.Sleep(3 * time.Second) + + app, addrs, privKeys := input.app, input.addrs, input.privKeys + + // invalid block range + _, err := input.filterAPI.NewFilter(ethfilters.FilterCriteria{ + FromBlock: big.NewInt(100), + ToBlock: big.NewInt(10), + }) + require.Error(t, err) + + tx, txHash1 := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx2, txHash2 := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + // wait txs to be indexed + time.Sleep(1 * time.Second) + + logs, err := input.filterAPI.GetLogs(context.Background(), ethfilters.FilterCriteria{}) + require.NoError(t, err) + require.NotEmpty(t, logs) + for _, log := range logs { + require.Equal(t, txHash2, log.TxHash) + } + + logs, err = input.filterAPI.GetLogs(context.Background(), ethfilters.FilterCriteria{ + FromBlock: big.NewInt(app.LastBlockHeight() - 1), + ToBlock: big.NewInt(app.LastBlockHeight() - 1), + }) + require.NoError(t, err) + require.NotEmpty(t, logs) + for _, log := range logs { + require.Equal(t, txHash1, log.TxHash) + } + + // by block hash + header, err := input.backend.GetHeaderByNumber(rpc.BlockNumber(app.LastBlockHeight())) + require.NoError(t, err) + blockHash := header.Hash() + logs, err = input.filterAPI.GetLogs(context.Background(), ethfilters.FilterCriteria{ + BlockHash: &blockHash, + }) + require.NoError(t, err) + require.NotEmpty(t, logs) + for _, log := range logs { + require.Equal(t, txHash2, log.TxHash) + } + + header, err = input.backend.GetHeaderByNumber(rpc.BlockNumber(app.LastBlockHeight() - 1)) + require.NoError(t, err) + blockHash = header.Hash() + logs, err = input.filterAPI.GetLogs(context.Background(), ethfilters.FilterCriteria{ + BlockHash: &blockHash, + }) + require.NoError(t, err) + require.NotEmpty(t, logs) + for _, log := range logs { + require.Equal(t, txHash1, log.TxHash) + } +} + +func Test_GetFilterLogs(t *testing.T) { + input := setupFilterAPI(t) + defer input.app.Close() + + // wait indexer to be ready + time.Sleep(3 * time.Second) + + app, addrs, privKeys := input.app, input.addrs, input.privKeys + + // start tracking after 2 blocks + filterID, err := input.filterAPI.NewFilter(ethfilters.FilterCriteria{ + FromBlock: big.NewInt(app.LastBlockHeight() + 2), + ToBlock: big.NewInt(int64(rpc.LatestBlockNumber)), + }) + require.NoError(t, err) + require.NotEmpty(t, filterID) + + // track all blocks from the last block + filterID2, err := input.filterAPI.NewFilter(ethfilters.FilterCriteria{ + FromBlock: big.NewInt(app.LastBlockHeight()), + }) + require.NoError(t, err) + require.NotEmpty(t, filterID2) + + tx, txHash1 := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.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) + + // mint 1_000_000 tokens to the first address + tx2, txHash2 := tests.GenerateMintERC20Tx(t, app, privKeys[0], common.BytesToAddress(contractAddr), addrs[0], new(big.Int).SetUint64(1_000_000_000_000)) + _, finalizeRes = tests.ExecuteTxs(t, app, tx2) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + // wait txs to be indexed + time.Sleep(1 * time.Second) + + // there should be 1 changes + logs, err := input.filterAPI.GetFilterLogs(context.Background(), filterID) + require.NoError(t, err) + require.Len(t, logs, 1) + for _, change := range logs { + require.Equal(t, txHash2, change.TxHash) + } + + // there should be 3 changes + logs, err = input.filterAPI.GetFilterLogs(context.Background(), filterID2) + require.NoError(t, err) + require.Len(t, logs, 3) + require.Equal(t, txHash1, logs[0].TxHash) + require.Equal(t, txHash1, logs[1].TxHash) + require.Equal(t, txHash2, logs[2].TxHash) +} diff --git a/jsonrpc/namespaces/eth/filters/subscriptions_test.go b/jsonrpc/namespaces/eth/filters/subscriptions_test.go new file mode 100644 index 00000000..c766a229 --- /dev/null +++ b/jsonrpc/namespaces/eth/filters/subscriptions_test.go @@ -0,0 +1,178 @@ +package filters_test + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + + coretypes "github.com/ethereum/go-ethereum/core/types" + ethfilters "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/rpc" + + rpctypes "github.com/initia-labs/minievm/jsonrpc/types" + "github.com/initia-labs/minievm/tests" + evmtypes "github.com/initia-labs/minievm/x/evm/types" +) + +func Test_NewPendingTransactions_FullTx(t *testing.T) { + input := tests.CreateAppWithJSONRPC(t) + defer input.App.Close() + + // wait indexer to be ready + time.Sleep(3 * time.Second) + + client, err := rpc.Dial("ws://" + input.AddressWS) + require.NoError(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ch := make(chan *rpctypes.RPCTransaction) + sub, err := client.EthSubscribe(ctx, ch, "newPendingTransactions", true) + require.NoError(t, err) + require.NotEmpty(t, sub) + + app, backend, privKeys := input.App, input.Backend, input.PrivKeys + + queryCtx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + tx, txHash1 := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + evmTx, _, err := app.EVMKeeper.TxUtils().ConvertCosmosTxToEthereumTx(queryCtx, tx) + require.NoError(t, err) + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + for { + select { + case <-ctx.Done(): + return + case tx := <-ch: + require.NotNil(t, tx) + require.Equal(t, txHash1, tx.Hash) + wg.Done() + } + } + }() + + txBz, err := evmTx.MarshalBinary() + require.NoError(t, err) + + _, err = backend.SendRawTransaction(txBz) + require.NoError(t, err) + + wg.Wait() + sub.Unsubscribe() +} + +func Test_NewHeads(t *testing.T) { + input := tests.CreateAppWithJSONRPC(t) + defer input.App.Close() + + // wait indexer to be ready + time.Sleep(3 * time.Second) + + client, err := rpc.Dial("ws://" + input.AddressWS) + require.NoError(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ch := make(chan *coretypes.Header) + sub, err := client.EthSubscribe(ctx, ch, "newHeads") + require.NoError(t, err) + require.NotEmpty(t, sub) + + app, backend, privKeys := input.App, input.Backend, input.PrivKeys + + wg := sync.WaitGroup{} // wait to receive header from channel + wg2 := sync.WaitGroup{} // wait for header to be set + wg.Add(1) + wg2.Add(1) + var expectedHeader *coretypes.Header + go func() { + for { + select { + case <-ctx.Done(): + return + case header := <-ch: + wg2.Wait() + require.NotNil(t, header) + require.Equal(t, expectedHeader, header) + wg.Done() + } + } + }() + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + header, err := backend.GetHeaderByNumber(rpc.BlockNumber(app.LastBlockHeight())) + require.NoError(t, err) + expectedHeader = header + wg2.Done() + wg.Wait() + sub.Unsubscribe() +} + +func Test_Logs(t *testing.T) { + input := tests.CreateAppWithJSONRPC(t) + defer input.App.Close() + + // wait indexer to be ready + time.Sleep(3 * time.Second) + + client, err := rpc.Dial("ws://" + input.AddressWS) + require.NoError(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ch := make(chan *coretypes.Log) + sub, err := client.EthSubscribe(ctx, ch, "logs", ethfilters.FilterCriteria{}) + require.NoError(t, err) + require.NotEmpty(t, sub) + + app, backend, privKeys := input.App, input.Backend, input.PrivKeys + + wg := sync.WaitGroup{} // wait to receive logs from channel + wg2 := sync.WaitGroup{} // wait for logs to be set + wg.Add(1) + wg2.Add(1) + var expectedLogs []*coretypes.Log + var logs []*coretypes.Log + go func() { + for { + select { + case <-ctx.Done(): + return + case log := <-ch: + wg2.Wait() + logs = append(logs, log) + if len(logs) == len(expectedLogs) { + require.Equal(t, expectedLogs, logs) + wg.Done() + } + } + } + }() + + tx, _ := tests.GenerateCreateERC20Tx(t, app, privKeys[0]) + _, finalizeRes := tests.ExecuteTxs(t, app, tx) + tests.CheckTxResult(t, finalizeRes.TxResults[0], true) + + events := finalizeRes.TxResults[0].Events + createEvent := events[len(events)-3] + require.Equal(t, evmtypes.EventTypeContractCreated, createEvent.GetType()) + + expectedLogs, err = backend.GetLogsByHeight(uint64(app.LastBlockHeight())) + require.NoError(t, err) + + wg2.Done() + wg.Wait() + sub.Unsubscribe() +} diff --git a/jsonrpc/types/tx.go b/jsonrpc/types/tx.go index fdcef0ac..875d5360 100644 --- a/jsonrpc/types/tx.go +++ b/jsonrpc/types/tx.go @@ -3,6 +3,8 @@ package types import ( "math/big" + "gopkg.in/yaml.v3" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" coretypes "github.com/ethereum/go-ethereum/core/types" @@ -133,3 +135,9 @@ func (rpcTx RPCTransaction) ToTransaction() *coretypes.Transaction { return nil } } + +// String implements the fmt.Stringer interface +func (rpcTx RPCTransaction) String() string { + yamlBytes, _ := yaml.Marshal(rpcTx) + return string(yamlBytes) +} diff --git a/jsonrpc/types/tx_test.go b/jsonrpc/types/tx_test.go index d6b1e3c2..9aa63c2b 100644 --- a/jsonrpc/types/tx_test.go +++ b/jsonrpc/types/tx_test.go @@ -110,6 +110,8 @@ func TestDynamicFeeTxTypeRPCTransaction(t *testing.T) { err = matchTx(signedTx, ethTx) require.NoError(t, err) + + _ = rpcTx.String() } func matchTx(signedTx *coretypes.Transaction, ethTx *coretypes.Transaction) error { diff --git a/tests/app_creator.go b/tests/app_creator.go new file mode 100644 index 00000000..22a20b02 --- /dev/null +++ b/tests/app_creator.go @@ -0,0 +1,58 @@ +package tests + +import ( + "crypto/ecdsa" + "testing" + + "github.com/ethereum/go-ethereum/common" + minitiaapp "github.com/initia-labs/minievm/app" + minievmtypes "github.com/initia-labs/minievm/types" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + abci "github.com/cometbft/cometbft/abci/types" +) + +// Bond denom should be set for staking test +const baseDenom = minievmtypes.BaseDenom + +var ( + genCoins = sdk.NewCoins(sdk.NewCoin(baseDenom, math.NewInt(1_000_000_000_000_000_000).MulRaw(1_000_000))).Sort() +) + +func checkBalance(t *testing.T, app *minitiaapp.MinitiaApp, addr common.Address, balances sdk.Coins) { + ctxCheck := app.BaseApp.NewContext(true) + require.True(t, balances.Equal(app.BankKeeper.GetAllBalances(ctxCheck, addr.Bytes()))) +} + +func CreateApp(t *testing.T) (*minitiaapp.MinitiaApp, []common.Address, []*ecdsa.PrivateKey) { + addrs, privKeys := GenerateKeys(t, 2) + genAccs := authtypes.GenesisAccounts{} + for _, addr := range addrs { + + genAccs = append(genAccs, &authtypes.BaseAccount{Address: sdk.AccAddress(addr.Bytes()).String()}) + } + + genBalances := []banktypes.Balance{} + for _, addr := range addrs { + genBalances = append(genBalances, banktypes.Balance{Address: sdk.AccAddress(addr.Bytes()).String(), Coins: genCoins}) + } + + app := minitiaapp.SetupWithGenesisAccounts(nil, genAccs, genBalances...) + for _, addr := range addrs { + checkBalance(t, app, addr, genCoins) + } + + _, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: app.LastBlockHeight() + 1}) + require.NoError(t, err) + + _, err = app.Commit() + require.NoError(t, err) + + return app, addrs, privKeys +} diff --git a/tests/jsonrpc_creator.go b/tests/jsonrpc_creator.go new file mode 100644 index 00000000..e8cefe36 --- /dev/null +++ b/tests/jsonrpc_creator.go @@ -0,0 +1,90 @@ +package tests + +import ( + "context" + "crypto/ecdsa" + "fmt" + "net" + "testing" + "time" + + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + "github.com/ethereum/go-ethereum/common" + + minitiaapp "github.com/initia-labs/minievm/app" + "github.com/initia-labs/minievm/indexer" + "github.com/initia-labs/minievm/jsonrpc" + "github.com/initia-labs/minievm/jsonrpc/backend" + "github.com/initia-labs/minievm/jsonrpc/config" +) + +type TestInput struct { + App *minitiaapp.MinitiaApp + Indexer indexer.EVMIndexer + Backend *backend.JSONRPCBackend + Addrs []common.Address + PrivKeys []*ecdsa.PrivateKey + CometRPC *MockCometRPC + Address string + AddressWS string +} + +// getFreePort asks the kernel for a free open port that is ready to use. +func getFreePort(t *testing.T) (port int) { + a, err := net.ResolveTCPAddr("tcp", "localhost:0") + require.NoError(t, err) + + l, err := net.ListenTCP("tcp", a) + require.NoError(t, err) + + defer l.Close() + return l.Addr().(*net.TCPAddr).Port +} + +func CreateAppWithJSONRPC(t *testing.T) TestInput { + app, addrs, privKeys := CreateApp(t) + indexer := app.EVMIndexer() + + ctx := context.Background() + svrCtx := server.NewDefaultContext() + clientCtx := client.Context{}.WithCodec(app.AppCodec()). + WithInterfaceRegistry(app.AppCodec().InterfaceRegistry()). + WithTxConfig(app.TxConfig()). + WithLegacyAmino(app.LegacyAmino()). + WithAccountRetriever(authtypes.AccountRetriever{}) + + cfg := config.DefaultJSONRPCConfig() + cfg.Enable = true + cfg.FilterTimeout = 3 * time.Second + cfg.Address = fmt.Sprintf("localhost:%d", getFreePort(t)) + cfg.AddressWS = fmt.Sprintf("localhost:%d", getFreePort(t)) + + mockCometRPC := NewMockCometRPC(app.BaseApp) + clientCtx = clientCtx.WithClient(mockCometRPC) + + backend, err := backend.NewJSONRPCBackend(ctx, app, app.Logger(), svrCtx, clientCtx, cfg) + require.NoError(t, err) + + g, ctx := errgroup.WithContext(ctx) + err = jsonrpc.StartJSONRPC(ctx, g, app, svrCtx, clientCtx, cfg, false) + require.NoError(t, err) + err = jsonrpc.StartJSONRPC(ctx, g, app, svrCtx, clientCtx, cfg, true) + require.NoError(t, err) + + return TestInput{ + App: app, + Indexer: indexer, + Backend: backend, + Addrs: addrs, + PrivKeys: privKeys, + CometRPC: mockCometRPC, + Address: cfg.Address, + AddressWS: cfg.AddressWS, + } +} diff --git a/tests/mock_comet.go b/tests/mock_comet.go new file mode 100644 index 00000000..b41ad67f --- /dev/null +++ b/tests/mock_comet.go @@ -0,0 +1,188 @@ +package tests + +import ( + "context" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/libs/bytes" + "github.com/cometbft/cometbft/p2p" + rpcclient "github.com/cometbft/cometbft/rpc/client" + ctypes "github.com/cometbft/cometbft/rpc/core/types" + "github.com/cometbft/cometbft/types" + + "github.com/skip-mev/block-sdk/v2/block" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/types/mempool" + + "github.com/initia-labs/minievm/indexer" +) + +var _ client.CometRPC = &MockCometRPC{} +var _ rpcclient.MempoolClient = &MockCometRPC{} +var _ rpcclient.NetworkClient = &MockCometRPC{} + +type MockCometRPC struct { + app *baseapp.BaseApp + + NPeers int + Listening bool + ClientVersion string +} + +func NewMockCometRPC(app *baseapp.BaseApp) *MockCometRPC { + return &MockCometRPC{app: app} +} + +// setters +func (m *MockCometRPC) WithNPeers(n int) *MockCometRPC { + m.NPeers = n + return m +} +func (m *MockCometRPC) WithListening(listening bool) *MockCometRPC { + m.Listening = listening + return m +} +func (m *MockCometRPC) WithClientVersion(version string) *MockCometRPC { + m.ClientVersion = version + return m +} + +// CometRPC methods +func (m *MockCometRPC) Status(context.Context) (*ctypes.ResultStatus, error) { + return &ctypes.ResultStatus{ + NodeInfo: p2p.DefaultNodeInfo{ + Version: m.ClientVersion, + }, + }, nil +} +func (m *MockCometRPC) BroadcastTxSync(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + res, err := m.app.CheckTx(&abci.RequestCheckTx{ + Tx: tx, + Type: abci.CheckTxType_New, + }) + if err != nil { + return nil, err + } + return &ctypes.ResultBroadcastTx{ + Code: res.Code, + Log: res.Log, + Data: res.Data, + Codespace: res.Codespace, + Hash: tx.Hash(), + }, nil +} +func (m *MockCometRPC) UnconfirmedTxs(ctx context.Context, limit *int) (*ctypes.ResultUnconfirmedTxs, error) { + var ok bool + var mempool mempool.Mempool + if mempool, ok = m.app.Mempool().(*indexer.MempoolWrapper); ok { + mempool = mempool.(*indexer.MempoolWrapper).Inner() + } else { + mempool = m.app.Mempool() + } + + laneMempool := mempool.(*block.LanedMempool) + lanes := laneMempool.Registry() + txs := make([]types.Tx, 0) + for _, lane := range lanes { + iter := lane.Select(ctx, nil) + for ; iter != nil; iter = iter.Next() { + tx, err := m.app.TxEncode(iter.Tx()) + if err != nil { + return nil, err + } + + txs = append(txs, tx) + } + } + + return &ctypes.ResultUnconfirmedTxs{ + Txs: txs, + }, nil +} +func (m *MockCometRPC) NumUnconfirmedTxs(context.Context) (*ctypes.ResultUnconfirmedTxs, error) { + count := m.app.Mempool().CountTx() + return &ctypes.ResultUnconfirmedTxs{ + Count: count, + Total: count, + }, nil +} +func (m *MockCometRPC) NetInfo(context.Context) (*ctypes.ResultNetInfo, error) { + return &ctypes.ResultNetInfo{ + NPeers: m.NPeers, + Listening: m.Listening, + }, nil +} + +// unused methods +func (m *MockCometRPC) DumpConsensusState(context.Context) (*ctypes.ResultDumpConsensusState, error) { + panic("implement me") +} +func (m *MockCometRPC) ConsensusState(context.Context) (*ctypes.ResultConsensusState, error) { + panic("implement me") +} +func (m *MockCometRPC) ConsensusParams(ctx context.Context, height *int64) (*ctypes.ResultConsensusParams, error) { + panic("implement me") +} +func (m *MockCometRPC) Health(context.Context) (*ctypes.ResultHealth, error) { + panic("implement me") +} +func (m *MockCometRPC) CheckTx(context.Context, types.Tx) (*ctypes.ResultCheckTx, error) { + panic("implement me") +} +func (m *MockCometRPC) BroadcastTxCommit(context.Context, types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + panic("implement me") +} +func (m *MockCometRPC) BroadcastTxAsync(context.Context, types.Tx) (*ctypes.ResultBroadcastTx, error) { + panic("implement me") +} +func (m *MockCometRPC) ABCIInfo(context.Context) (*ctypes.ResultABCIInfo, error) { + panic("implement me") +} +func (m *MockCometRPC) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*ctypes.ResultABCIQuery, error) { + panic("implement me") +} +func (m *MockCometRPC) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, + opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + panic("implement me") +} +func (m *MockCometRPC) Validators(ctx context.Context, height *int64, page, perPage *int) (*ctypes.ResultValidators, error) { + panic("implement me") +} + +func (m *MockCometRPC) Block(ctx context.Context, height *int64) (*ctypes.ResultBlock, error) { + panic("implement me") +} +func (m *MockCometRPC) BlockByHash(ctx context.Context, hash []byte) (*ctypes.ResultBlock, error) { + panic("implement me") +} +func (m *MockCometRPC) BlockResults(ctx context.Context, height *int64) (*ctypes.ResultBlockResults, error) { + panic("implement me") +} +func (m *MockCometRPC) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { + panic("implement me") +} +func (m *MockCometRPC) Commit(ctx context.Context, height *int64) (*ctypes.ResultCommit, error) { + panic("implement me") +} +func (m *MockCometRPC) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) { + panic("implement me") +} +func (m *MockCometRPC) TxSearch( + ctx context.Context, + query string, + prove bool, + page, perPage *int, + orderBy string, +) (*ctypes.ResultTxSearch, error) { + panic("implement me") +} +func (m *MockCometRPC) BlockSearch( + ctx context.Context, + query string, + page, perPage *int, + orderBy string, +) (*ctypes.ResultBlockSearch, error) { + panic("implement me") +} diff --git a/tests/tx_helper.go b/tests/tx_helper.go new file mode 100644 index 00000000..7e3572c0 --- /dev/null +++ b/tests/tx_helper.go @@ -0,0 +1,218 @@ +package tests + +import ( + "bytes" + "crypto/ecdsa" + "crypto/rand" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + coretypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + + abcitypes "github.com/cometbft/cometbft/abci/types" + + "github.com/initia-labs/initia/crypto/ethsecp256k1" + + minitiaapp "github.com/initia-labs/minievm/app" + "github.com/initia-labs/minievm/x/evm/contracts/erc20" + "github.com/initia-labs/minievm/x/evm/contracts/erc20_factory" + "github.com/initia-labs/minievm/x/evm/contracts/initia_erc20" + evmkeeper "github.com/initia-labs/minievm/x/evm/keeper" + evmtypes "github.com/initia-labs/minievm/x/evm/types" +) + +func GenerateKeys(t *testing.T, n int) ([]common.Address, []*ecdsa.PrivateKey) { + addrs := make([]common.Address, n) + privKeys := make([]*ecdsa.PrivateKey, n) + for i := 0; i < n; i++ { + randBytes := make([]byte, 64) + _, err := rand.Read(randBytes) + require.NoError(t, err) + reader := bytes.NewReader(randBytes) + privKey, err := ecdsa.GenerateKey(crypto.S256(), reader) + require.NoError(t, err) + + cosmosKey := ethsecp256k1.PrivKey{ + Key: crypto.FromECDSA(privKey), + } + addrBz := cosmosKey.PubKey().Address() + addr := common.BytesToAddress(addrBz) + + addrs[i] = addr + privKeys[i] = privKey + } + + return addrs, privKeys +} + +// Opt is a function that modifies the tx +type Opt func(tx *coretypes.DynamicFeeTx) + +// SetNonce sets the nonce of the tx +func SetNonce(nonce uint64) Opt { + return func(tx *coretypes.DynamicFeeTx) { + tx.Nonce = nonce + } +} + +// SetGasFeeCap sets the gas fee cap of the tx +func SetGasFeeCap(gasFeeCap *big.Int) Opt { + return func(tx *coretypes.DynamicFeeTx) { + tx.GasFeeCap = gasFeeCap + } +} + +// SetGasLimit sets the gas limit of the tx +func SetGasLimit(gasLimit uint64) Opt { + return func(tx *coretypes.DynamicFeeTx) { + tx.Gas = gasLimit + } +} + +// SetGasTipCap sets the gas tip cap of the tx +func SetGasTipCap(gasTipCap *big.Int) Opt { + return func(tx *coretypes.DynamicFeeTx) { + tx.GasTipCap = gasTipCap + } +} + +// GenerateTx generates a tx with the given parameters +func GenerateTx( + t *testing.T, + app *minitiaapp.MinitiaApp, + privKey *ecdsa.PrivateKey, + to *common.Address, + inputBz []byte, + value *big.Int, + opts ...Opt, +) (sdk.Tx, common.Hash) { + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + gasLimit := new(big.Int).SetUint64(1_000_000) + gasPrice := new(big.Int).SetUint64(1_000_000_000) + + ethChainID := evmtypes.ConvertCosmosChainIDToEthereumChainID(ctx.ChainID()) + dynamicFeeTx := &coretypes.DynamicFeeTx{ + ChainID: ethChainID, + Nonce: 0, + GasTipCap: big.NewInt(0), + GasFeeCap: gasPrice, + Gas: gasLimit.Uint64(), + To: to, + Data: inputBz, + Value: value, + AccessList: coretypes.AccessList{}, + } + for _, opt := range opts { + opt(dynamicFeeTx) + } + if dynamicFeeTx.Nonce == 0 { + cosmosKey := ethsecp256k1.PrivKey{Key: crypto.FromECDSA(privKey)} + addrBz := cosmosKey.PubKey().Address() + dynamicFeeTx.Nonce, err = app.AccountKeeper.GetSequence(ctx, sdk.AccAddress(addrBz)) + require.NoError(t, err) + } + + ethTx := coretypes.NewTx(dynamicFeeTx) + signer := coretypes.LatestSignerForChainID(ethChainID) + signedTx, err := coretypes.SignTx(ethTx, signer, privKey) + require.NoError(t, err) + + // Convert to cosmos tx + sdkTx, err := evmkeeper.NewTxUtils(app.EVMKeeper).ConvertEthereumTxToCosmosTx(ctx, signedTx) + require.NoError(t, err) + + return sdkTx, signedTx.Hash() +} + +func GenerateCreateInitiaERC20Tx(t *testing.T, app *minitiaapp.MinitiaApp, privKey *ecdsa.PrivateKey, opts ...Opt) (sdk.Tx, common.Hash) { + abi, err := initia_erc20.InitiaErc20MetaData.GetAbi() + require.NoError(t, err) + + bin, err := hexutil.Decode(initia_erc20.InitiaErc20MetaData.Bin) + require.NoError(t, err) + + inputBz, err := abi.Pack("", "foo", "foo", uint8(6)) + require.NoError(t, err) + + return GenerateTx(t, app, privKey, nil, append(bin, inputBz...), new(big.Int).SetUint64(0), opts...) +} + +func GenerateCreateERC20Tx(t *testing.T, app *minitiaapp.MinitiaApp, privKey *ecdsa.PrivateKey, opts ...Opt) (sdk.Tx, common.Hash) { + ctx, err := app.CreateQueryContext(0, false) + require.NoError(t, err) + + ethFactoryAddr, err := app.EVMKeeper.GetERC20FactoryAddr(ctx) + require.NoError(t, err) + + abi, err := erc20_factory.Erc20FactoryMetaData.GetAbi() + require.NoError(t, err) + + inputBz, err := abi.Pack("createERC20", "foo", "foo", uint8(6)) + require.NoError(t, err) + + return GenerateTx(t, app, privKey, ðFactoryAddr, inputBz, new(big.Int).SetUint64(0), opts...) +} + +func GenerateMintERC20Tx(t *testing.T, app *minitiaapp.MinitiaApp, privKey *ecdsa.PrivateKey, erc20Addr, recipient common.Address, amount *big.Int, opts ...Opt) (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, new(big.Int).SetUint64(0), opts...) +} + +func GenerateTransferERC20Tx(t *testing.T, app *minitiaapp.MinitiaApp, privKey *ecdsa.PrivateKey, erc20Addr, recipient common.Address, amount *big.Int, opts ...Opt) (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, new(big.Int).SetUint64(0), opts...) +} + +// execute txs and finalize block and commit block +func ExecuteTxs(t *testing.T, app *minitiaapp.MinitiaApp, txs ...sdk.Tx) (*abcitypes.RequestFinalizeBlock, *abcitypes.ResponseFinalizeBlock) { + txsBytes := make([][]byte, len(txs)) + for i, tx := range txs { + txBytes, err := app.TxConfig().TxEncoder()(tx) + require.NoError(t, err) + + txsBytes[i] = txBytes + } + + finalizeReq := &abcitypes.RequestFinalizeBlock{ + Txs: txsBytes, + Height: app.LastBlockHeight() + 1, + } + resBlock, err := app.FinalizeBlock(finalizeReq) + require.NoError(t, err) + + finalizeRes := &abcitypes.ResponseFinalizeBlock{ + TxResults: resBlock.TxResults, + } + + _, err = app.Commit() + require.NoError(t, err) + + return finalizeReq, finalizeRes +} + +func CheckTxResult(t *testing.T, txResult *abcitypes.ExecTxResult, expectSuccess bool) { + if expectSuccess { + require.Equal(t, abcitypes.CodeTypeOK, txResult.Code) + } else { + require.NotEqual(t, abcitypes.CodeTypeOK, txResult.Code) + } +} diff --git a/x/evm/config/config.go b/x/evm/config/config.go index 72d477d9..1cde2a3f 100644 --- a/x/evm/config/config.go +++ b/x/evm/config/config.go @@ -10,12 +10,15 @@ import ( const ( // DefaultContractSimulationGasLimit - default max simulation gas DefaultContractSimulationGasLimit = uint64(3_000_000) + // DefaultDisableIndexer is the default flag to disable indexer + DefaultDisableIndexer = false // DefaultIndexerCacheSize is the default maximum size (MiB) of the cache. DefaultIndexerCacheSize = 100 ) const ( flagContractSimulationGasLimit = "evm.contract-simulation-gas-limit" + flagDisableIndexer = "evm.disable-indexer" flagIndexerCacheSize = "evm.indexer-cache-size" ) @@ -23,6 +26,8 @@ const ( type EVMConfig struct { // ContractSimulationGasLimit is the maximum gas amount can be used in a tx simulation call. ContractSimulationGasLimit uint64 `mapstructure:"contract-simulation-gas-limit"` + // DisableIndexer is the flag to disable indexer + DisableIndexer bool `mapstructure:"disable-indexer"` // IndexerCacheSize is the maximum size (MiB) of the cache. IndexerCacheSize int `mapstructure:"indexer-cache-size"` } @@ -31,6 +36,7 @@ type EVMConfig struct { func DefaultEVMConfig() EVMConfig { return EVMConfig{ ContractSimulationGasLimit: DefaultContractSimulationGasLimit, + DisableIndexer: DefaultDisableIndexer, IndexerCacheSize: DefaultIndexerCacheSize, } } @@ -39,6 +45,7 @@ func DefaultEVMConfig() EVMConfig { func GetConfig(appOpts servertypes.AppOptions) EVMConfig { return EVMConfig{ ContractSimulationGasLimit: cast.ToUint64(appOpts.Get(flagContractSimulationGasLimit)), + DisableIndexer: cast.ToBool(appOpts.Get(flagDisableIndexer)), IndexerCacheSize: cast.ToInt(appOpts.Get(flagIndexerCacheSize)), } } @@ -46,6 +53,7 @@ func GetConfig(appOpts servertypes.AppOptions) EVMConfig { // AddConfigFlags implements servertypes.EVMConfigFlags interface. func AddConfigFlags(startCmd *cobra.Command) { startCmd.Flags().Uint64(flagContractSimulationGasLimit, DefaultContractSimulationGasLimit, "Maximum simulation gas amount for evm contract execution") + startCmd.Flags().Bool(flagDisableIndexer, DefaultDisableIndexer, "Disable evm indexer") startCmd.Flags().Int(flagIndexerCacheSize, DefaultIndexerCacheSize, "Maximum size (MiB) of the indexer cache") } @@ -60,6 +68,10 @@ const DefaultConfigTemplate = ` # The maximum gas amount can be used in a tx simulation call. contract-simulation-gas-limit = "{{ .EVMConfig.ContractSimulationGasLimit }}" +# DisableIndexer is the flag to disable indexer. If true, evm jsonrpc queries will return +# empty results for block, tx, and receipt queries. +disable-indexer = {{ .EVMConfig.DisableIndexer }} + # IndexerCacheSize is the maximum size (MiB) of the cache for evm indexer. indexer-cache-size = {{ .EVMConfig.IndexerCacheSize }} ` diff --git a/x/evm/keeper/erc20_stores_test.go b/x/evm/keeper/erc20_stores_test.go index ad3ef168..6b215449 100644 --- a/x/evm/keeper/erc20_stores_test.go +++ b/x/evm/keeper/erc20_stores_test.go @@ -3,11 +3,13 @@ package keeper_test import ( "testing" + "github.com/stretchr/testify/require" + + "github.com/holiman/uint256" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" - "github.com/holiman/uint256" - "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" diff --git a/x/evm/keeper/txutils_test.go b/x/evm/keeper/txutils_test.go index 20e1bb83..d0aeea04 100644 --- a/x/evm/keeper/txutils_test.go +++ b/x/evm/keeper/txutils_test.go @@ -15,6 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" coretypes "github.com/ethereum/go-ethereum/core/types"