diff --git a/jsonrpc/backend/backend_test.go b/jsonrpc/backend/backend_test.go index 8014f5f..6c0739d 100644 --- a/jsonrpc/backend/backend_test.go +++ b/jsonrpc/backend/backend_test.go @@ -3,7 +3,10 @@ package backend_test import ( "context" "crypto/ecdsa" + "math/big" + "sync" "testing" + "time" "github.com/stretchr/testify/require" @@ -12,12 +15,15 @@ import ( 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 { @@ -59,3 +65,61 @@ func setupBackend(t *testing.T) testInput { 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/namespaces/eth/filters/api.go b/jsonrpc/namespaces/eth/filters/api.go index 4e6b28e..4b3346e 100644 --- a/jsonrpc/namespaces/eth/filters/api.go +++ b/jsonrpc/namespaces/eth/filters/api.go @@ -3,6 +3,7 @@ package filters import ( "context" "errors" + "fmt" "sync" "time" @@ -339,7 +340,9 @@ func (api *FilterAPI) NewFilter(crit ethfilters.FilterCriteria) (rpc.ID, error) for { select { case logs := <-logsChan: + fmt.Println(logs) logs = filterLogs(logs, s.crit.FromBlock, s.crit.ToBlock, s.crit.Addresses, s.crit.Topics) + fmt.Println(logs) api.filtersMut.Lock() if f, found := api.filters[id]; found { f.logs = append(f.logs, logs...) diff --git a/jsonrpc/namespaces/eth/filters/api_test.go b/jsonrpc/namespaces/eth/filters/api_test.go index c9538f0..3dbb763 100644 --- a/jsonrpc/namespaces/eth/filters/api_test.go +++ b/jsonrpc/namespaces/eth/filters/api_test.go @@ -3,6 +3,7 @@ package filters_test import ( "context" "crypto/ecdsa" + "math/big" "testing" "time" @@ -13,13 +14,19 @@ import ( 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 { @@ -67,6 +74,79 @@ func setupFilterAPI(t *testing.T) testInput { } } +func Test_NewPendingTransactionFilter_FullTx(t *testing.T) { + input := setupFilterAPI(t) + defer input.app.Close() + + 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) + + // 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) +} + func Test_NewPendingTransactionFilter(t *testing.T) { input := setupFilterAPI(t) defer input.app.Close() @@ -75,4 +155,137 @@ func Test_NewPendingTransactionFilter(t *testing.T) { 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) + + // 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() + + 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) + + // 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() + + 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) + + // this should not tracked + 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) + + // this should be tracked + // 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) + + changes, err := input.filterAPI.GetFilterChanges(filterID) + require.NoError(t, err) + require.NotEmpty(t, changes) + for _, change := range changes.([]*coretypes.Log) { + require.Equal(t, txHash2, change.TxHash) + t.Logf("%v", change) + } } diff --git a/jsonrpc/types/tx.go b/jsonrpc/types/tx.go index fdcef0a..875d536 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 d6b1e3c..9aa63c2 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 {