github.com/Finschia/ostracon@v1.1.5/state/txindex/kv/kv_test.go (about)

     1  package kv
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  
     9  	"github.com/gogo/protobuf/proto"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	abci "github.com/tendermint/tendermint/abci/types"
    14  	db "github.com/tendermint/tm-db"
    15  
    16  	ocabci "github.com/Finschia/ostracon/abci/types"
    17  	"github.com/Finschia/ostracon/libs/pubsub/query"
    18  	tmrand "github.com/Finschia/ostracon/libs/rand"
    19  	"github.com/Finschia/ostracon/state/txindex"
    20  	"github.com/Finschia/ostracon/types"
    21  )
    22  
    23  func TestTxIndex(t *testing.T) {
    24  	indexer := NewTxIndex(db.NewMemDB())
    25  
    26  	tx := types.Tx("HELLO WORLD")
    27  	txResult := &abci.TxResult{
    28  		Height: 1,
    29  		Index:  0,
    30  		Tx:     tx,
    31  		Result: abci.ResponseDeliverTx{
    32  			Data: []byte{0},
    33  			Code: ocabci.CodeTypeOK, Log: "", Events: nil,
    34  		},
    35  	}
    36  	hash := tx.Hash()
    37  
    38  	batch := txindex.NewBatch(1)
    39  	if err := batch.Add(txResult); err != nil {
    40  		t.Error(err)
    41  	}
    42  	err := indexer.AddBatch(batch)
    43  	require.NoError(t, err)
    44  
    45  	loadedTxResult, err := indexer.Get(hash)
    46  	require.NoError(t, err)
    47  	assert.True(t, proto.Equal(txResult, loadedTxResult))
    48  
    49  	tx2 := types.Tx("BYE BYE WORLD")
    50  	txResult2 := &abci.TxResult{
    51  		Height: 1,
    52  		Index:  0,
    53  		Tx:     tx2,
    54  		Result: abci.ResponseDeliverTx{
    55  			Data: []byte{0},
    56  			Code: ocabci.CodeTypeOK, Log: "", Events: nil,
    57  		},
    58  	}
    59  	hash2 := tx2.Hash()
    60  
    61  	err = indexer.Index(txResult2)
    62  	require.NoError(t, err)
    63  
    64  	loadedTxResult2, err := indexer.Get(hash2)
    65  	require.NoError(t, err)
    66  	assert.True(t, proto.Equal(txResult2, loadedTxResult2))
    67  }
    68  
    69  func TestTxSearch(t *testing.T) {
    70  	indexer := NewTxIndex(db.NewMemDB())
    71  
    72  	txResult := txResultWithEvents([]abci.Event{
    73  		{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("1"), Index: true}}},
    74  		{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("owner"), Value: []byte("Ivan"), Index: true}}},
    75  		{Type: "", Attributes: []abci.EventAttribute{{Key: []byte("not_allowed"), Value: []byte("Vlad"), Index: true}}},
    76  	})
    77  	hash := types.Tx(txResult.Tx).Hash()
    78  
    79  	err := indexer.Index(txResult)
    80  	require.NoError(t, err)
    81  
    82  	testCases := []struct {
    83  		q             string
    84  		resultsLength int
    85  	}{
    86  		// search by hash
    87  		{fmt.Sprintf("tx.hash = '%X'", hash), 1},
    88  		// search by exact match (one key)
    89  		{"account.number = 1", 1},
    90  		// search by exact match (two keys)
    91  		{"account.number = 1 AND account.owner = 'Ivan'", 1},
    92  		// search by exact match (two keys)
    93  		{"account.number = 1 AND account.owner = 'Vlad'", 0},
    94  		{"account.owner = 'Vlad' AND account.number = 1", 0},
    95  		{"account.number >= 1 AND account.owner = 'Vlad'", 0},
    96  		{"account.owner = 'Vlad' AND account.number >= 1", 0},
    97  		{"account.number <= 0", 0},
    98  		{"account.number <= 0 AND account.owner = 'Ivan'", 0},
    99  		// search using a prefix of the stored value
   100  		{"account.owner = 'Iv'", 0},
   101  		// search by range
   102  		{"account.number >= 1 AND account.number <= 5", 1},
   103  		// search by range (lower bound)
   104  		{"account.number >= 1", 1},
   105  		// search by range (upper bound)
   106  		{"account.number <= 5", 1},
   107  		// search using not allowed key
   108  		{"not_allowed = 'boom'", 0},
   109  		// search for not existing tx result
   110  		{"account.number >= 2 AND account.number <= 5", 0},
   111  		// search using not existing key
   112  		{"account.date >= TIME 2013-05-03T14:45:00Z", 0},
   113  		// search using CONTAINS
   114  		{"account.owner CONTAINS 'an'", 1},
   115  		// search for non existing value using CONTAINS
   116  		{"account.owner CONTAINS 'Vlad'", 0},
   117  		// search using the wrong key (of numeric type) using CONTAINS
   118  		{"account.number CONTAINS 'Iv'", 0},
   119  		// search using EXISTS
   120  		{"account.number EXISTS", 1},
   121  		// search using EXISTS for non existing key
   122  		{"account.date EXISTS", 0},
   123  	}
   124  
   125  	ctx := context.Background()
   126  
   127  	for _, tc := range testCases {
   128  		tc := tc
   129  		t.Run(tc.q, func(t *testing.T) {
   130  			results, err := indexer.Search(ctx, query.MustParse(tc.q))
   131  			assert.NoError(t, err)
   132  
   133  			assert.Len(t, results, tc.resultsLength)
   134  			if tc.resultsLength > 0 {
   135  				for _, txr := range results {
   136  					assert.True(t, proto.Equal(txResult, txr))
   137  				}
   138  			}
   139  		})
   140  	}
   141  }
   142  
   143  func TestTxSearchWithCancelation(t *testing.T) {
   144  	indexer := NewTxIndex(db.NewMemDB())
   145  
   146  	txResult := txResultWithEvents([]abci.Event{
   147  		{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("1"), Index: true}}},
   148  		{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("owner"), Value: []byte("Ivan"), Index: true}}},
   149  		{Type: "", Attributes: []abci.EventAttribute{{Key: []byte("not_allowed"), Value: []byte("Vlad"), Index: true}}},
   150  	})
   151  	err := indexer.Index(txResult)
   152  	require.NoError(t, err)
   153  
   154  	ctx, cancel := context.WithCancel(context.Background())
   155  	cancel()
   156  	results, err := indexer.Search(ctx, query.MustParse("account.number = 1"))
   157  	assert.NoError(t, err)
   158  	assert.Empty(t, results)
   159  }
   160  
   161  func TestTxSearchDeprecatedIndexing(t *testing.T) {
   162  	indexer := NewTxIndex(db.NewMemDB())
   163  
   164  	// index tx using events indexing (composite key)
   165  	txResult1 := txResultWithEvents([]abci.Event{
   166  		{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("1"), Index: true}}},
   167  	})
   168  	hash1 := types.Tx(txResult1.Tx).Hash()
   169  
   170  	err := indexer.Index(txResult1)
   171  	require.NoError(t, err)
   172  
   173  	// index tx also using deprecated indexing (event as key)
   174  	txResult2 := txResultWithEvents(nil)
   175  	txResult2.Tx = types.Tx("HELLO WORLD 2")
   176  
   177  	hash2 := types.Tx(txResult2.Tx).Hash()
   178  	b := indexer.store.NewBatch()
   179  
   180  	rawBytes, err := proto.Marshal(txResult2)
   181  	require.NoError(t, err)
   182  
   183  	depKey := []byte(fmt.Sprintf("%s/%s/%d/%d",
   184  		"sender",
   185  		"addr1",
   186  		txResult2.Height,
   187  		txResult2.Index,
   188  	))
   189  
   190  	err = b.Set(depKey, hash2)
   191  	require.NoError(t, err)
   192  	err = b.Set(keyForHeight(txResult2), hash2)
   193  	require.NoError(t, err)
   194  	err = b.Set(hash2, rawBytes)
   195  	require.NoError(t, err)
   196  	err = b.Write()
   197  	require.NoError(t, err)
   198  
   199  	testCases := []struct {
   200  		q       string
   201  		results []*abci.TxResult
   202  	}{
   203  		// search by hash
   204  		{fmt.Sprintf("tx.hash = '%X'", hash1), []*abci.TxResult{txResult1}},
   205  		// search by hash
   206  		{fmt.Sprintf("tx.hash = '%X'", hash2), []*abci.TxResult{txResult2}},
   207  		// search by exact match (one key)
   208  		{"account.number = 1", []*abci.TxResult{txResult1}},
   209  		{"account.number >= 1 AND account.number <= 5", []*abci.TxResult{txResult1}},
   210  		// search by range (lower bound)
   211  		{"account.number >= 1", []*abci.TxResult{txResult1}},
   212  		// search by range (upper bound)
   213  		{"account.number <= 5", []*abci.TxResult{txResult1}},
   214  		// search using not allowed key
   215  		{"not_allowed = 'boom'", []*abci.TxResult{}},
   216  		// search for not existing tx result
   217  		{"account.number >= 2 AND account.number <= 5", []*abci.TxResult{}},
   218  		// search using not existing key
   219  		{"account.date >= TIME 2013-05-03T14:45:00Z", []*abci.TxResult{}},
   220  		// search by deprecated key
   221  		{"sender = 'addr1'", []*abci.TxResult{txResult2}},
   222  	}
   223  
   224  	ctx := context.Background()
   225  
   226  	for _, tc := range testCases {
   227  		tc := tc
   228  		t.Run(tc.q, func(t *testing.T) {
   229  			results, err := indexer.Search(ctx, query.MustParse(tc.q))
   230  			require.NoError(t, err)
   231  			for _, txr := range results {
   232  				for _, tr := range tc.results {
   233  					assert.True(t, proto.Equal(tr, txr))
   234  				}
   235  			}
   236  		})
   237  	}
   238  }
   239  
   240  func TestTxSearchOneTxWithMultipleSameTagsButDifferentValues(t *testing.T) {
   241  	indexer := NewTxIndex(db.NewMemDB())
   242  
   243  	txResult := txResultWithEvents([]abci.Event{
   244  		{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("1"), Index: true}}},
   245  		{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("2"), Index: true}}},
   246  	})
   247  
   248  	err := indexer.Index(txResult)
   249  	require.NoError(t, err)
   250  
   251  	ctx := context.Background()
   252  
   253  	results, err := indexer.Search(ctx, query.MustParse("account.number >= 1"))
   254  	assert.NoError(t, err)
   255  
   256  	assert.Len(t, results, 1)
   257  	for _, txr := range results {
   258  		assert.True(t, proto.Equal(txResult, txr))
   259  	}
   260  }
   261  
   262  func TestTxIndexDuplicatePreviouslySuccessful(t *testing.T) {
   263  	mockTx := types.Tx("MOCK_TX_HASH")
   264  
   265  	testCases := []struct {
   266  		name         string
   267  		tx1          *abci.TxResult
   268  		tx2          *abci.TxResult
   269  		expOverwrite bool // do we expect the second tx to overwrite the first tx
   270  	}{
   271  		{
   272  			"don't overwrite as a non-zero code was returned and the previous tx was successful",
   273  			&abci.TxResult{
   274  				Height: 1,
   275  				Index:  0,
   276  				Tx:     mockTx,
   277  				Result: abci.ResponseDeliverTx{
   278  					Code: abci.CodeTypeOK,
   279  				},
   280  			},
   281  			&abci.TxResult{
   282  				Height: 2,
   283  				Index:  0,
   284  				Tx:     mockTx,
   285  				Result: abci.ResponseDeliverTx{
   286  					Code: abci.CodeTypeOK + 1,
   287  				},
   288  			},
   289  			false,
   290  		},
   291  		{
   292  			"overwrite as the previous tx was also unsuccessful",
   293  			&abci.TxResult{
   294  				Height: 1,
   295  				Index:  0,
   296  				Tx:     mockTx,
   297  				Result: abci.ResponseDeliverTx{
   298  					Code: abci.CodeTypeOK + 1,
   299  				},
   300  			},
   301  			&abci.TxResult{
   302  				Height: 2,
   303  				Index:  0,
   304  				Tx:     mockTx,
   305  				Result: abci.ResponseDeliverTx{
   306  					Code: abci.CodeTypeOK + 1,
   307  				},
   308  			},
   309  			true,
   310  		},
   311  		{
   312  			"overwrite as the most recent tx was successful",
   313  			&abci.TxResult{
   314  				Height: 1,
   315  				Index:  0,
   316  				Tx:     mockTx,
   317  				Result: abci.ResponseDeliverTx{
   318  					Code: abci.CodeTypeOK,
   319  				},
   320  			},
   321  			&abci.TxResult{
   322  				Height: 2,
   323  				Index:  0,
   324  				Tx:     mockTx,
   325  				Result: abci.ResponseDeliverTx{
   326  					Code: abci.CodeTypeOK,
   327  				},
   328  			},
   329  			true,
   330  		},
   331  	}
   332  
   333  	hash := mockTx.Hash()
   334  
   335  	for _, tc := range testCases {
   336  		t.Run(tc.name, func(t *testing.T) {
   337  			indexer := NewTxIndex(db.NewMemDB())
   338  
   339  			// index the first tx
   340  			err := indexer.Index(tc.tx1)
   341  			require.NoError(t, err)
   342  
   343  			// index the same tx with different results
   344  			err = indexer.Index(tc.tx2)
   345  			require.NoError(t, err)
   346  
   347  			res, err := indexer.Get(hash)
   348  			require.NoError(t, err)
   349  
   350  			if tc.expOverwrite {
   351  				require.Equal(t, tc.tx2, res)
   352  			} else {
   353  				require.Equal(t, tc.tx1, res)
   354  			}
   355  		})
   356  	}
   357  }
   358  
   359  func TestTxSearchMultipleTxs(t *testing.T) {
   360  	indexer := NewTxIndex(db.NewMemDB())
   361  
   362  	// indexed first, but bigger height (to test the order of transactions)
   363  	txResult := txResultWithEvents([]abci.Event{
   364  		{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("1"), Index: true}}},
   365  	})
   366  
   367  	txResult.Tx = types.Tx("Bob's account")
   368  	txResult.Height = 2
   369  	txResult.Index = 1
   370  	err := indexer.Index(txResult)
   371  	require.NoError(t, err)
   372  
   373  	// indexed second, but smaller height (to test the order of transactions)
   374  	txResult2 := txResultWithEvents([]abci.Event{
   375  		{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("2"), Index: true}}},
   376  	})
   377  	txResult2.Tx = types.Tx("Alice's account")
   378  	txResult2.Height = 1
   379  	txResult2.Index = 2
   380  
   381  	err = indexer.Index(txResult2)
   382  	require.NoError(t, err)
   383  
   384  	// indexed third (to test the order of transactions)
   385  	txResult3 := txResultWithEvents([]abci.Event{
   386  		{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number"), Value: []byte("3"), Index: true}}},
   387  	})
   388  	txResult3.Tx = types.Tx("Jack's account")
   389  	txResult3.Height = 1
   390  	txResult3.Index = 1
   391  	err = indexer.Index(txResult3)
   392  	require.NoError(t, err)
   393  
   394  	// indexed fourth (to test we don't include txs with similar events)
   395  	// https://github.com/tendermint/tendermint/issues/2908
   396  	txResult4 := txResultWithEvents([]abci.Event{
   397  		{Type: "account", Attributes: []abci.EventAttribute{{Key: []byte("number.id"), Value: []byte("1"), Index: true}}},
   398  	})
   399  	txResult4.Tx = types.Tx("Mike's account")
   400  	txResult4.Height = 2
   401  	txResult4.Index = 2
   402  	err = indexer.Index(txResult4)
   403  	require.NoError(t, err)
   404  
   405  	ctx := context.Background()
   406  
   407  	results, err := indexer.Search(ctx, query.MustParse("account.number >= 1"))
   408  	assert.NoError(t, err)
   409  
   410  	require.Len(t, results, 3)
   411  }
   412  
   413  func txResultWithEvents(events []abci.Event) *abci.TxResult {
   414  	tx := types.Tx("HELLO WORLD")
   415  	return &abci.TxResult{
   416  		Height: 1,
   417  		Index:  0,
   418  		Tx:     tx,
   419  		Result: abci.ResponseDeliverTx{
   420  			Data:   []byte{0},
   421  			Code:   ocabci.CodeTypeOK,
   422  			Log:    "",
   423  			Events: events,
   424  		},
   425  	}
   426  }
   427  
   428  func benchmarkTxIndex(txsCount int64, b *testing.B) {
   429  	dir, err := os.MkdirTemp("", "tx_index_db")
   430  	require.NoError(b, err)
   431  	defer os.RemoveAll(dir)
   432  
   433  	store, err := db.NewDB("tx_index", "goleveldb", dir)
   434  	require.NoError(b, err)
   435  	indexer := NewTxIndex(store)
   436  
   437  	batch := txindex.NewBatch(txsCount)
   438  	txIndex := uint32(0)
   439  	for i := int64(0); i < txsCount; i++ {
   440  		tx := tmrand.Bytes(250)
   441  		txResult := &abci.TxResult{
   442  			Height: 1,
   443  			Index:  txIndex,
   444  			Tx:     tx,
   445  			Result: abci.ResponseDeliverTx{
   446  				Data:   []byte{0},
   447  				Code:   ocabci.CodeTypeOK,
   448  				Log:    "",
   449  				Events: []abci.Event{},
   450  			},
   451  		}
   452  		if err := batch.Add(txResult); err != nil {
   453  			b.Fatal(err)
   454  		}
   455  		txIndex++
   456  	}
   457  
   458  	b.ResetTimer()
   459  
   460  	for n := 0; n < b.N; n++ {
   461  		err = indexer.AddBatch(batch)
   462  	}
   463  	if err != nil {
   464  		b.Fatal(err)
   465  	}
   466  }
   467  
   468  func BenchmarkTxIndex1(b *testing.B)     { benchmarkTxIndex(1, b) }
   469  func BenchmarkTxIndex500(b *testing.B)   { benchmarkTxIndex(500, b) }
   470  func BenchmarkTxIndex1000(b *testing.B)  { benchmarkTxIndex(1000, b) }
   471  func BenchmarkTxIndex2000(b *testing.B)  { benchmarkTxIndex(2000, b) }
   472  func BenchmarkTxIndex10000(b *testing.B) { benchmarkTxIndex(10000, b) }