From df3f303771529e42a5c3f087f0cc7c861fd08773 Mon Sep 17 00:00:00 2001 From: tnasu Date: Fri, 17 Dec 2021 23:31:43 +0900 Subject: [PATCH] Fix codecov for `rpc: index block events to support block event queries (bp #6226) (#6261)` --- light/rpc/client_test.go | 58 +++++++++++ node/node_test.go | 36 +++++++ rpc/core/blocks_test.go | 204 +++++++++++++++++++++++++++++++++++++++ rpc/core/tx_test.go | 194 +++++++++++++++++++++++++++++++++++++ 4 files changed, 492 insertions(+) create mode 100644 rpc/core/tx_test.go diff --git a/light/rpc/client_test.go b/light/rpc/client_test.go index 2505fc7bd..1a650a929 100644 --- a/light/rpc/client_test.go +++ b/light/rpc/client_test.go @@ -96,6 +96,64 @@ func TestABCIQuery(t *testing.T) { assert.NotNil(t, res) } +func TestTxSearch(t *testing.T) { + + query := "query/test" + prove := false + page := 0 + perPage := 1 + orderBy := "" + + next := &rpcmock.Client{} + next.On( + "TxSearch", + context.Background(), + query, + prove, + &page, + &perPage, + orderBy, + ).Return(&ctypes.ResultTxSearch{ + Txs: nil, + TotalCount: 0, + }, nil) + + lc := &lcmock.LightClient{} + + c := NewClient(next, lc) + res, err := c.TxSearch(context.Background(), query, prove, &page, &perPage, orderBy) + require.NoError(t, err) + assert.NotNil(t, res) +} + +func TestBlockSearch(t *testing.T) { + + query := "query/test" + page := 0 + perPage := 1 + orderBy := "" + + next := &rpcmock.Client{} + next.On( + "BlockSearch", + context.Background(), + query, + &page, + &perPage, + orderBy, + ).Return(&ctypes.ResultBlockSearch{ + Blocks: nil, + TotalCount: 0, + }, nil) + + lc := &lcmock.LightClient{} + + c := NewClient(next, lc) + res, err := c.BlockSearch(context.Background(), query, &page, &perPage, orderBy) + require.NoError(t, err) + assert.NotNil(t, res) +} + type testOp struct { Spec *ics23.ProofSpec Key []byte diff --git a/node/node_test.go b/node/node_test.go index 913a1a937..53902514e 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -418,6 +418,42 @@ func TestNodeNewNodeCustomReactors(t *testing.T) { assert.Equal(t, customBlockchainReactor, n.Switch().Reactor("BLOCKCHAIN")) } +func TestNodeNewNodeTxIndexIndexer(t *testing.T) { + config := cfg.ResetTestRoot("node_new_node_tx_index_indexer_test") + defer os.RemoveAll(config.RootDir) + + doTest := func(doProvider func(ctx *DBContext) (dbm.DB, error)) (*Node, error) { + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + require.NoError(t, err) + + pvKey, _ := privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile(), + config.PrivValidatorKeyType()) + return NewNode(config, + pvKey, + nodeKey, + proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()), + DefaultGenesisDocProviderFunc(config), + doProvider, + DefaultMetricsProvider(config.Instrumentation), + log.TestingLogger(), + ) + } + + { + // Change to panic-provider for test + n, err := doTest(func(ctx *DBContext) (dbm.DB, error) { return nil, fmt.Errorf("test error") }) + require.Error(t, err) + require.Nil(t, n) + } + { + // Change to non-default-value for test + config.TxIndex.Indexer = "" + n, err := doTest(DefaultDBProvider) + require.NoError(t, err) + require.NotNil(t, n) + } +} + func state(nVals int, height int64) (sm.State, dbm.DB, []types.PrivValidator) { privVals := make([]types.PrivValidator, nVals) vals := make([]types.GenesisValidator, nVals) diff --git a/rpc/core/blocks_test.go b/rpc/core/blocks_test.go index 7d22a5d6e..636d4b6e4 100644 --- a/rpc/core/blocks_test.go +++ b/rpc/core/blocks_test.go @@ -1,8 +1,20 @@ package core import ( + errors "errors" "fmt" + "os" "testing" + "time" + + txidxkv "github.com/line/ostracon/state/txindex/kv" + + cfg "github.com/line/ostracon/config" + "github.com/line/ostracon/crypto" + tmrand "github.com/line/ostracon/libs/rand" + blockidxkv "github.com/line/ostracon/state/indexer/block/kv" + blockidxnull "github.com/line/ostracon/state/indexer/block/null" + "github.com/line/ostracon/store" "github.com/line/tm-db/v2/memdb" "github.com/stretchr/testify/assert" @@ -114,6 +126,198 @@ func TestBlockResults(t *testing.T) { } } +func TestBlockSearchByBlockHeightQuery(t *testing.T) { + height := int64(1) + ctx := &rpctypes.Context{} + + q := fmt.Sprintf("%s=%d", types.BlockHeightKey, height) + page := 1 + perPage := 10 + orderBy := TestOrderByDefault + + state, cleanup := makeTestState() + defer cleanup() + + { + // Get by block.height (not search/range) + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, 0, res.TotalCount) // Don't have height in db + require.Equal(t, 0, len(res.Blocks)) + } + + numToMakeBlocks := 1 + numToGet := 1 + // Save blocks + storeTestBlocks(height, int64(numToMakeBlocks), 0, state, time.Now()) + + { + // Get by block.height (not search/range) + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeBlocks, res.TotalCount) // Get + require.Equal(t, numToGet, len(res.Blocks)) + } +} + +func TestBlockSearchByRangeQuery(t *testing.T) { + height := int64(1) + ctx := &rpctypes.Context{} + + q := fmt.Sprintf("%s>=%d", types.BlockHeightKey, height) + page := 1 + perPage := 10 + orderBy := TestOrderByDefault + + state, cleanup := makeTestState() + defer cleanup() + + numToMakeBlocks := 15 + numToGet := perPage + // Save blocks + storeTestBlocks(height, int64(numToMakeBlocks), 0, state, time.Now()) + + { + // Search blocks by range query with desc (default) + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeBlocks, res.TotalCount) + require.Equal(t, numToGet, len(res.Blocks)) + require.Equal(t, int64(numToMakeBlocks), res.Blocks[0].Block.Height) + require.Equal(t, height+int64(numToMakeBlocks-numToGet), res.Blocks[numToGet-1].Block.Height) + } + { + orderBy = TestOrderByAsc + // Search blocks by range query with asc + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeBlocks, res.TotalCount) + require.Equal(t, numToGet, len(res.Blocks)) + require.Equal(t, height, res.Blocks[0].Block.Height) + require.Equal(t, int64(numToGet), res.Blocks[numToGet-1].Block.Height) + } +} + +func TestBlockSearch_errors(t *testing.T) { + ctx := &rpctypes.Context{} + + q := "" + page := 0 + perPage := 1 + orderBy := "error" + + { + // error: env.BlockIndexer.(*blockidxnull.BlockerIndexer) + env = &Environment{} + env.BlockIndexer = &blockidxnull.BlockerIndexer{} + + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, errors.New("block indexing is disabled"), err) + require.Nil(t, res) + } + { + // error: tmquery.New(query) + env = &Environment{} + + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "\nparse error near Unknown (line 1 symbol 1 - line 1 symbol 1):\n\"\"\n", + err.Error()) + require.Nil(t, res) + } + { + // error: switch orderBy + env = &Environment{} + env.BlockIndexer = blockidxkv.New(memdb.NewDB()) + q = fmt.Sprintf("%s>%d", types.BlockHeightKey, 1) + + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "expected order_by to be either `asc` or `desc` or empty", + err.Error()) + require.Nil(t, res) + } + { + // error: validatePage(pagePtr, perPage, totalCount) + env = &Environment{} + env.BlockIndexer = blockidxkv.New(memdb.NewDB()) + q = fmt.Sprintf("%s>%d", types.BlockHeightKey, 1) + orderBy = TestOrderByDesc + + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "page should be within [1, 1] range, given 0", + err.Error()) + require.Nil(t, res) + } +} + +func makeTestState() (sm.State, func()) { + config := cfg.ResetTestRoot("rpc_core_test") + env = &Environment{} + env.StateStore = sm.NewStore(memdb.NewDB()) + env.BlockStore = store.NewBlockStore(memdb.NewDB()) + env.BlockIndexer = blockidxkv.New(memdb.NewDB()) + env.TxIndexer = txidxkv.NewTxIndex(memdb.NewDB()) + + state, _ := env.StateStore.LoadFromDBOrGenesisFile(config.GenesisFile()) + return state, func() { os.RemoveAll(config.RootDir) } +} + +func storeTestBlocks(startHeight, numToMakeBlocks, numToMakeTxs int64, state sm.State, timestamp time.Time) { + for i := int64(0); i < numToMakeBlocks; i++ { + commitSigs := []types.CommitSig{{ + BlockIDFlag: types.BlockIDFlagCommit, + ValidatorAddress: tmrand.Bytes(crypto.AddressSize), + Timestamp: timestamp, + Signature: []byte("Signature"), + }} + height := startHeight + i + lastHeight := startHeight - 1 + round := int32(0) + hash := []byte("") + partSize := uint32(2) + blockID := types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: partSize}} + proposer := state.Validators.SelectProposer(state.LastProofHash, startHeight, round) + txs := make([]types.Tx, numToMakeTxs) + for txIndex := int64(0); txIndex < numToMakeTxs; txIndex++ { + tx := []byte{byte(height), byte(txIndex)} + txs[txIndex] = tx + // Indexing + env.TxIndexer.Index(&abci.TxResult{Height: height, Index: uint32(txIndex), Tx: tx}) // nolint:errcheck + } + lastCommit := types.NewCommit(lastHeight, round, blockID, commitSigs) + block, _ := state.MakeBlock(height, txs, lastCommit, nil, proposer.Address, round, nil) + blockPart := block.MakePartSet(partSize) + // Indexing + env.BlockIndexer.Index(types.EventDataNewBlockHeader{Header: block.Header}) // nolint:errcheck + // Save + env.BlockStore.SaveBlock(block, blockPart, lastCommit) + } +} + +const ( + TestOrderByDefault = "" + TestOrderByDesc = "desc" + TestOrderByAsc = "asc" +) + type mockBlockStore struct { height int64 } diff --git a/rpc/core/tx_test.go b/rpc/core/tx_test.go new file mode 100644 index 000000000..fe2cdfc7f --- /dev/null +++ b/rpc/core/tx_test.go @@ -0,0 +1,194 @@ +package core + +import ( + "encoding/hex" + "errors" + "fmt" + "math" + "testing" + "time" + + txidxkv "github.com/line/ostracon/state/txindex/kv" + txidxnull "github.com/line/ostracon/state/txindex/null" + "github.com/line/tm-db/v2/memdb" + "github.com/stretchr/testify/require" + + rpctypes "github.com/line/ostracon/rpc/jsonrpc/types" + "github.com/line/ostracon/types" +) + +func TestTxSearchByTxHashQuery(t *testing.T) { + height := int64(1) + txIndex := 0 + tx := []byte{byte(height), byte(txIndex)} + hash := hex.EncodeToString(types.Tx(tx).Hash()) + ctx := &rpctypes.Context{} + + q := fmt.Sprintf("%s='%s'", types.TxHashKey, hash) + prove := false + page := 1 + perPage := 10 + orderBy := TestOrderByDefault + + state, cleanup := makeTestState() + defer cleanup() + + { + // Get by tx.hash (not search/range) + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, 0, res.TotalCount) // Don't have tx in db + require.Equal(t, 0, len(res.Txs)) + } + + numToMakeBlocks := 1 + numToMakeTxs := 1 + numOfGet := 1 + // SaveBlock + storeTestBlocks(height, int64(numToMakeBlocks), int64(numToMakeTxs), state, time.Now()) + + { + // Get by block.height (not search/range) + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeTxs, res.TotalCount) // Get + require.Equal(t, numOfGet, len(res.Txs)) + } +} + +func TestTxSearchByTxHeightQuery(t *testing.T) { + height := int64(1) + ctx := &rpctypes.Context{} + + q := fmt.Sprintf("%s>=%d", types.TxHeightKey, height) + prove := false + page := 1 + perPage := 10 + orderBy := TestOrderByDefault + + state, cleanup := makeTestState() + defer cleanup() + + numToMakeBlocks := 5 + numToMakeTxs := 3 + numToGet := perPage + // SaveBlock + storeTestBlocks(height, int64(numToMakeBlocks), int64(numToMakeTxs), state, time.Now()) + + { + // Search blocks by range query with asc (default) + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeBlocks*numToMakeTxs, res.TotalCount) + require.Equal(t, numToGet, len(res.Txs)) + // check first tx + first := res.Txs[0] + require.Equal(t, height, first.Height) + require.Equal(t, uint32(0), first.Index) + // check last tx + last := res.Txs[numToGet-1] + require.Equal(t, int64(math.Ceil(float64(numToGet)/float64(numToMakeTxs))), last.Height) + require.Equal(t, uint32(numToGet%numToMakeTxs-1), last.Index) + } + { + orderBy = TestOrderByDesc + // Search blocks by range query with desc + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeBlocks*numToMakeTxs, res.TotalCount) + require.Equal(t, numToGet, len(res.Txs)) + // check first tx + first := res.Txs[0] + require.Equal(t, int64(numToMakeBlocks), first.Height) + require.Equal(t, uint32(numToMakeTxs-1), first.Index) + // check last tx + last := res.Txs[numToGet-1] + require.Equal(t, int64(numToMakeBlocks-numToGet/numToMakeTxs), last.Height) + require.Equal(t, uint32(numToMakeTxs-numToGet%numToMakeTxs), last.Index) + } +} + +func TestTxSearch_errors(t *testing.T) { + ctx := &rpctypes.Context{} + + q := "" + prove := false + page := 0 + perPage := 1 + orderBy := "error" + + { + // error: env.TxIndexer.(*txidxnull.TxIndex) + env = &Environment{} + env.TxIndexer = &txidxnull.TxIndex{} + + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, errors.New("transaction indexing is disabled"), err) + require.Nil(t, res) + } + { + // error: tmquery.New(query) + env = &Environment{} + + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "\nparse error near Unknown (line 1 symbol 1 - line 1 symbol 1):\n\"\"\n", + err.Error()) + require.Nil(t, res) + } + { + // error: lookForHash + env = &Environment{} + env.TxIndexer = txidxkv.NewTxIndex(memdb.NewDB()) + q = fmt.Sprintf("%s=%s", types.TxHashKey, "'1'") + + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "error during searching for a hash in the query: encoding/hex: odd length hex string", + err.Error()) + require.Nil(t, res) + } + { + // error: switch orderBy + env = &Environment{} + env.TxIndexer = txidxkv.NewTxIndex(memdb.NewDB()) + q = fmt.Sprintf("%s=%s", types.TxHashKey, "'1234567890abcdef'") + + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "expected order_by to be either `asc` or `desc` or empty", + err.Error()) + require.Nil(t, res) + } + { + // error: validatePage(pagePtr, perPage, totalCount) + env = &Environment{} + env.TxIndexer = txidxkv.NewTxIndex(memdb.NewDB()) + q = fmt.Sprintf("%s=%s", types.TxHashKey, "'1234567890abcdef'") + orderBy = TestOrderByAsc + + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "page should be within [1, 1] range, given 0", + err.Error()) + require.Nil(t, res) + } +}