github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/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/cosmos/gogoproto/proto"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	db "github.com/badrootd/nibiru-db"
    14  
    15  	abci "github.com/badrootd/nibiru-cometbft/abci/types"
    16  	"github.com/badrootd/nibiru-cometbft/libs/pubsub/query"
    17  	cmtrand "github.com/badrootd/nibiru-cometbft/libs/rand"
    18  	"github.com/badrootd/nibiru-cometbft/state/txindex"
    19  	"github.com/badrootd/nibiru-cometbft/types"
    20  )
    21  
    22  func TestBigInt(t *testing.T) {
    23  	indexer := NewTxIndex(db.NewMemDB())
    24  
    25  	bigInt := "10000000000000000000"
    26  	bigIntPlus1 := "10000000000000000001"
    27  	bigFloat := bigInt + ".76"
    28  	bigFloatLower := bigInt + ".1"
    29  
    30  	txResult := txResultWithEvents([]abci.Event{
    31  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigInt, Index: true}}},
    32  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigIntPlus1, Index: true}}},
    33  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigFloatLower, Index: true}}},
    34  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "owner", Value: "/Ivan/", Index: true}}},
    35  		{Type: "", Attributes: []abci.EventAttribute{{Key: "not_allowed", Value: "Vlad", Index: true}}},
    36  	})
    37  	hash := types.Tx(txResult.Tx).Hash()
    38  
    39  	err := indexer.Index(txResult)
    40  
    41  	require.NoError(t, err)
    42  
    43  	txResult2 := txResultWithEvents([]abci.Event{
    44  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigFloat, Index: true}}},
    45  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: bigFloat, Index: true}, {Key: "amount", Value: "5", Index: true}}},
    46  	})
    47  
    48  	txResult2.Tx = types.Tx("NEW TX")
    49  	txResult2.Height = 2
    50  	txResult2.Index = 2
    51  
    52  	hash2 := types.Tx(txResult2.Tx).Hash()
    53  
    54  	err = indexer.Index(txResult2)
    55  	require.NoError(t, err)
    56  	testCases := []struct {
    57  		q             string
    58  		txRes         *abci.TxResult
    59  		resultsLength int
    60  	}{
    61  		//	search by hash
    62  		{fmt.Sprintf("tx.hash = '%X'", hash), txResult, 1},
    63  		// search by hash (lower)
    64  		{fmt.Sprintf("tx.hash = '%x'", hash), txResult, 1},
    65  		{fmt.Sprintf("tx.hash = '%x'", hash2), txResult2, 1},
    66  		// search by exact match (one key) - bigint
    67  		{"account.number >= " + bigInt, nil, 1},
    68  		// search by exact match (one key) - bigint range
    69  		{"account.number >= " + bigInt + " AND tx.height > 0", nil, 1},
    70  		{"account.number >= " + bigInt + " AND tx.height > 0 AND account.owner = '/Ivan/'", nil, 0},
    71  		// Floats are not parsed
    72  		{"account.number >= " + bigInt + " AND tx.height > 0 AND account.amount > 4", txResult2, 0},
    73  		{"account.number >= " + bigInt + " AND tx.height > 0 AND account.amount = 5", txResult2, 0},
    74  		{"account.number >= " + bigInt + " AND account.amount <= 5", txResult2, 0},
    75  		{"account.number < " + bigInt + " AND tx.height = 1", nil, 0},
    76  	}
    77  
    78  	ctx := context.Background()
    79  
    80  	for _, tc := range testCases {
    81  		tc := tc
    82  		t.Run(tc.q, func(t *testing.T) {
    83  			results, err := indexer.Search(ctx, query.MustParse(tc.q))
    84  			assert.NoError(t, err)
    85  			assert.Len(t, results, tc.resultsLength)
    86  			if tc.resultsLength > 0 && tc.txRes != nil {
    87  				assert.True(t, proto.Equal(results[0], tc.txRes))
    88  			}
    89  		})
    90  	}
    91  }
    92  
    93  func TestTxIndex(t *testing.T) {
    94  	indexer := NewTxIndex(db.NewMemDB())
    95  
    96  	tx := types.Tx("HELLO WORLD")
    97  	txResult := &abci.TxResult{
    98  		Height: 1,
    99  		Index:  0,
   100  		Tx:     tx,
   101  		Result: abci.ResponseDeliverTx{
   102  			Data: []byte{0},
   103  			Code: abci.CodeTypeOK, Log: "", Events: nil,
   104  		},
   105  	}
   106  	hash := tx.Hash()
   107  
   108  	batch := txindex.NewBatch(1)
   109  	if err := batch.Add(txResult); err != nil {
   110  		t.Error(err)
   111  	}
   112  	err := indexer.AddBatch(batch)
   113  	require.NoError(t, err)
   114  
   115  	loadedTxResult, err := indexer.Get(hash)
   116  	require.NoError(t, err)
   117  	assert.True(t, proto.Equal(txResult, loadedTxResult))
   118  
   119  	tx2 := types.Tx("BYE BYE WORLD")
   120  	txResult2 := &abci.TxResult{
   121  		Height: 1,
   122  		Index:  0,
   123  		Tx:     tx2,
   124  		Result: abci.ResponseDeliverTx{
   125  			Data: []byte{0},
   126  			Code: abci.CodeTypeOK, Log: "", Events: nil,
   127  		},
   128  	}
   129  	hash2 := tx2.Hash()
   130  
   131  	err = indexer.Index(txResult2)
   132  	require.NoError(t, err)
   133  
   134  	loadedTxResult2, err := indexer.Get(hash2)
   135  	require.NoError(t, err)
   136  	assert.True(t, proto.Equal(txResult2, loadedTxResult2))
   137  }
   138  
   139  func TestTxSearch(t *testing.T) {
   140  	indexer := NewTxIndex(db.NewMemDB())
   141  
   142  	txResult := txResultWithEvents([]abci.Event{
   143  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}},
   144  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "owner", Value: "/Ivan/", Index: true}}},
   145  		{Type: "", Attributes: []abci.EventAttribute{{Key: "not_allowed", Value: "Vlad", Index: true}}},
   146  	})
   147  	hash := types.Tx(txResult.Tx).Hash()
   148  
   149  	err := indexer.Index(txResult)
   150  	require.NoError(t, err)
   151  
   152  	testCases := []struct {
   153  		q             string
   154  		resultsLength int
   155  	}{
   156  		//	search by hash
   157  		{fmt.Sprintf("tx.hash = '%X'", hash), 1},
   158  		// search by hash (lower)
   159  		{fmt.Sprintf("tx.hash = '%x'", hash), 1},
   160  		// search by exact match (one key)
   161  		{"account.number = 1", 1},
   162  		// search by exact match (two keys)
   163  		{"account.number = 1 AND account.owner = 'Ivan'", 0},
   164  		{"account.owner = 'Ivan' AND account.number = 1", 0},
   165  		{"account.owner = '/Ivan/'", 1},
   166  		// search by exact match (two keys)
   167  		{"account.number = 1 AND account.owner = 'Vlad'", 0},
   168  		{"account.owner = 'Vlad' AND account.number = 1", 0},
   169  		{"account.number >= 1 AND account.owner = 'Vlad'", 0},
   170  		{"account.owner = 'Vlad' AND account.number >= 1", 0},
   171  		{"account.number <= 0", 0},
   172  		{"account.number <= 0 AND account.owner = 'Ivan'", 0},
   173  		{"account.number < 10000 AND account.owner = 'Ivan'", 0},
   174  		// search using a prefix of the stored value
   175  		{"account.owner = 'Iv'", 0},
   176  		// search by range
   177  		{"account.number >= 1 AND account.number <= 5", 1},
   178  		// search by range and another key
   179  		{"account.number >= 1 AND account.owner = 'Ivan' AND account.number <= 5", 0},
   180  		// search by range (lower bound)
   181  		{"account.number >= 1", 1},
   182  		// search by range (upper bound)
   183  		{"account.number <= 5", 1},
   184  		{"account.number <= 1", 1},
   185  		// search using not allowed key
   186  		{"not_allowed = 'boom'", 0},
   187  		{"not_allowed = 'Vlad'", 0},
   188  		// search for not existing tx result
   189  		{"account.number >= 2 AND account.number <= 5 AND tx.height > 0", 0},
   190  		// search using not existing key
   191  		{"account.date >= TIME 2013-05-03T14:45:00Z", 0},
   192  		// search using CONTAINS
   193  		{"account.owner CONTAINS 'an'", 1},
   194  		//	search for non existing value using CONTAINS
   195  		{"account.owner CONTAINS 'Vlad'", 0},
   196  		{"account.owner CONTAINS 'Ivann'", 0},
   197  		{"account.owner CONTAINS 'IIvan'", 0},
   198  		{"account.owner CONTAINS 'Iva n'", 0},
   199  		{"account.owner CONTAINS ' Ivan'", 0},
   200  		{"account.owner CONTAINS 'Ivan '", 0},
   201  		// search using the wrong key (of numeric type) using CONTAINS
   202  		{"account.number CONTAINS 'Iv'", 0},
   203  		// search using EXISTS
   204  		{"account.number EXISTS", 1},
   205  		// search using EXISTS for non existing key
   206  		{"account.date EXISTS", 0},
   207  		{"not_allowed EXISTS", 0},
   208  	}
   209  
   210  	ctx := context.Background()
   211  
   212  	for _, tc := range testCases {
   213  		tc := tc
   214  		t.Run(tc.q, func(t *testing.T) {
   215  			results, err := indexer.Search(ctx, query.MustParse(tc.q))
   216  			assert.NoError(t, err)
   217  
   218  			assert.Len(t, results, tc.resultsLength)
   219  			if tc.resultsLength > 0 {
   220  				for _, txr := range results {
   221  					assert.True(t, proto.Equal(txResult, txr))
   222  				}
   223  			}
   224  		})
   225  	}
   226  }
   227  
   228  func TestTxSearchEventMatch(t *testing.T) {
   229  
   230  	indexer := NewTxIndex(db.NewMemDB())
   231  
   232  	txResult := txResultWithEvents([]abci.Event{
   233  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}, {Key: "owner", Value: "Ana", Index: true}}},
   234  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "2", Index: true}, {Key: "owner", Value: "/Ivan/.test", Index: true}}},
   235  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "3", Index: false}, {Key: "owner", Value: "Mickey", Index: false}}},
   236  		{Type: "", Attributes: []abci.EventAttribute{{Key: "not_allowed", Value: "Vlad", Index: true}}},
   237  	})
   238  
   239  	err := indexer.Index(txResult)
   240  	require.NoError(t, err)
   241  
   242  	testCases := map[string]struct {
   243  		q             string
   244  		resultsLength int
   245  	}{
   246  		"Return all events from a height": {
   247  			q:             "tx.height = 1",
   248  			resultsLength: 1,
   249  		},
   250  		"Don't match non-indexed events": {
   251  			q:             "account.number = 3 AND account.owner = 'Mickey'",
   252  			resultsLength: 0,
   253  		},
   254  		"Return all events from a height with range": {
   255  			q:             "tx.height > 0",
   256  			resultsLength: 1,
   257  		},
   258  		"Return all events from a height with range 2": {
   259  			q:             "tx.height <= 1",
   260  			resultsLength: 1,
   261  		},
   262  		"Return all events from a height (deduplicate height)": {
   263  			q:             "tx.height = 1 AND tx.height = 1",
   264  			resultsLength: 1,
   265  		},
   266  		"Match attributes with height range and event": {
   267  			q:             "tx.height < 2 AND tx.height > 0 AND account.number > 0 AND account.number <= 1 AND account.owner CONTAINS 'Ana'",
   268  			resultsLength: 1,
   269  		},
   270  		"Match attributes with multiple CONTAIN and height range": {
   271  			q:             "tx.height < 2 AND tx.height > 0 AND account.number = 1 AND account.owner CONTAINS 'Ana' AND account.owner CONTAINS 'An'",
   272  			resultsLength: 1,
   273  		},
   274  		"Match attributes with height range and event - no match": {
   275  			q:             "tx.height < 2 AND tx.height > 0 AND account.number = 2 AND account.owner = 'Ana'",
   276  			resultsLength: 0,
   277  		},
   278  		"Match attributes with event": {
   279  			q:             "account.number = 2 AND account.owner = 'Ana' AND tx.height = 1",
   280  			resultsLength: 0,
   281  		},
   282  		"Deduplication test - should return nothing if attribute repeats multiple times": {
   283  			q:             "tx.height < 2 AND account.number = 3 AND account.number = 2 AND account.number = 5",
   284  			resultsLength: 0,
   285  		},
   286  		"Deduplication test - should return nothing if attribute repeats multiple times with match events": {
   287  			q:             "tx.height < 2 AND account.number = 3 AND account.number = 2 AND account.number = 5",
   288  			resultsLength: 0,
   289  		},
   290  		" Match range with match events": {
   291  			q:             "account.number < 2 AND account.owner = '/Ivan/.test'",
   292  			resultsLength: 0,
   293  		},
   294  		" Match range with match events 2": {
   295  			q:             "account.number <= 2 AND account.owner = '/Ivan/.test' AND tx.height > 0",
   296  			resultsLength: 1,
   297  		},
   298  		" Match range with match events contains with multiple items": {
   299  			q:             "account.number <= 2 AND account.owner CONTAINS '/Iv' AND account.owner CONTAINS 'an' AND tx.height = 1",
   300  			resultsLength: 1,
   301  		},
   302  		" Match range with match events contains": {
   303  			q:             "account.number <= 2 AND account.owner CONTAINS 'an' AND tx.height > 0",
   304  			resultsLength: 1,
   305  		},
   306  	}
   307  
   308  	ctx := context.Background()
   309  
   310  	for _, tc := range testCases {
   311  		tc := tc
   312  		t.Run(tc.q, func(t *testing.T) {
   313  			results, err := indexer.Search(ctx, query.MustParse(tc.q))
   314  			assert.NoError(t, err)
   315  
   316  			assert.Len(t, results, tc.resultsLength)
   317  			if tc.resultsLength > 0 {
   318  				for _, txr := range results {
   319  					assert.True(t, proto.Equal(txResult, txr))
   320  				}
   321  			}
   322  		})
   323  	}
   324  }
   325  
   326  func TestTxSearchEventMatchByHeight(t *testing.T) {
   327  
   328  	indexer := NewTxIndex(db.NewMemDB())
   329  
   330  	txResult := txResultWithEvents([]abci.Event{
   331  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}, {Key: "owner", Value: "Ana", Index: true}}},
   332  	})
   333  
   334  	err := indexer.Index(txResult)
   335  	require.NoError(t, err)
   336  
   337  	txResult10 := txResultWithEvents([]abci.Event{
   338  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}, {Key: "owner", Value: "/Ivan/.test", Index: true}}},
   339  	})
   340  	txResult10.Tx = types.Tx("HELLO WORLD 10")
   341  	txResult10.Height = 10
   342  
   343  	err = indexer.Index(txResult10)
   344  	require.NoError(t, err)
   345  
   346  	testCases := map[string]struct {
   347  		q             string
   348  		resultsLength int
   349  	}{
   350  		"Return all events from a height 1": {
   351  			q:             "tx.height = 1",
   352  			resultsLength: 1,
   353  		},
   354  		"Return all events from a height 10": {
   355  			q:             "tx.height = 10",
   356  			resultsLength: 1,
   357  		},
   358  		"Return all events from a height 5": {
   359  			q:             "tx.height = 5",
   360  			resultsLength: 0,
   361  		},
   362  		"Return all events from a height in [2; 5]": {
   363  			q:             "tx.height >= 2 AND tx.height <= 5",
   364  			resultsLength: 0,
   365  		},
   366  		"Return all events from a height in [1; 5]": {
   367  			q:             "tx.height >= 1 AND tx.height <= 5",
   368  			resultsLength: 1,
   369  		},
   370  		"Return all events from a height in [1; 10]": {
   371  			q:             "tx.height >= 1 AND tx.height <= 10",
   372  			resultsLength: 2,
   373  		},
   374  		"Return all events from a height in [1; 5] by account.number": {
   375  			q:             "tx.height >= 1 AND tx.height <= 5 AND account.number=1",
   376  			resultsLength: 1,
   377  		},
   378  		"Return all events from a height in [1; 10] by account.number 2": {
   379  			q:             "tx.height >= 1 AND tx.height <= 10 AND account.number=1",
   380  			resultsLength: 2,
   381  		},
   382  	}
   383  
   384  	ctx := context.Background()
   385  
   386  	for _, tc := range testCases {
   387  		tc := tc
   388  		t.Run(tc.q, func(t *testing.T) {
   389  			results, err := indexer.Search(ctx, query.MustParse(tc.q))
   390  			assert.NoError(t, err)
   391  
   392  			assert.Len(t, results, tc.resultsLength)
   393  			if tc.resultsLength > 0 {
   394  				for _, txr := range results {
   395  					if txr.Height == 1 {
   396  						assert.True(t, proto.Equal(txResult, txr))
   397  					} else if txr.Height == 10 {
   398  						assert.True(t, proto.Equal(txResult10, txr))
   399  					} else {
   400  						assert.True(t, false)
   401  					}
   402  				}
   403  			}
   404  		})
   405  	}
   406  }
   407  
   408  func TestTxSearchWithCancelation(t *testing.T) {
   409  	indexer := NewTxIndex(db.NewMemDB())
   410  
   411  	txResult := txResultWithEvents([]abci.Event{
   412  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}},
   413  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "owner", Value: "Ivan", Index: true}}},
   414  		{Type: "", Attributes: []abci.EventAttribute{{Key: "not_allowed", Value: "Vlad", Index: true}}},
   415  	})
   416  	err := indexer.Index(txResult)
   417  	require.NoError(t, err)
   418  
   419  	ctx, cancel := context.WithCancel(context.Background())
   420  	cancel()
   421  	results, err := indexer.Search(ctx, query.MustParse("account.number = 1"))
   422  	assert.NoError(t, err)
   423  	assert.Empty(t, results)
   424  }
   425  
   426  func TestTxSearchDeprecatedIndexing(t *testing.T) {
   427  	indexer := NewTxIndex(db.NewMemDB())
   428  
   429  	// index tx using events indexing (composite key)
   430  	txResult1 := txResultWithEvents([]abci.Event{
   431  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}},
   432  	})
   433  	hash1 := types.Tx(txResult1.Tx).Hash()
   434  
   435  	err := indexer.Index(txResult1)
   436  	require.NoError(t, err)
   437  
   438  	// index tx also using deprecated indexing (event as key)
   439  	txResult2 := txResultWithEvents(nil)
   440  	txResult2.Tx = types.Tx("HELLO WORLD 2")
   441  
   442  	hash2 := types.Tx(txResult2.Tx).Hash()
   443  	b := indexer.store.NewBatch()
   444  
   445  	rawBytes, err := proto.Marshal(txResult2)
   446  	require.NoError(t, err)
   447  
   448  	depKey := []byte(fmt.Sprintf("%s/%s/%d/%d",
   449  		"sender",
   450  		"addr1",
   451  		txResult2.Height,
   452  		txResult2.Index,
   453  	))
   454  
   455  	err = b.Set(depKey, hash2)
   456  	require.NoError(t, err)
   457  	err = b.Set(keyForHeight(txResult2), hash2)
   458  	require.NoError(t, err)
   459  	err = b.Set(hash2, rawBytes)
   460  	require.NoError(t, err)
   461  	err = b.Write()
   462  	require.NoError(t, err)
   463  
   464  	testCases := []struct {
   465  		q       string
   466  		results []*abci.TxResult
   467  	}{
   468  		// search by hash
   469  		{fmt.Sprintf("tx.hash = '%X'", hash1), []*abci.TxResult{txResult1}},
   470  		// search by hash
   471  		{fmt.Sprintf("tx.hash = '%X'", hash2), []*abci.TxResult{txResult2}},
   472  		// search by exact match (one key)
   473  		{"account.number = 1", []*abci.TxResult{txResult1}},
   474  		{"account.number >= 1 AND account.number <= 5", []*abci.TxResult{txResult1}},
   475  		// search by range (lower bound)
   476  		{"account.number >= 1", []*abci.TxResult{txResult1}},
   477  		// search by range (upper bound)
   478  		{"account.number <= 5", []*abci.TxResult{txResult1}},
   479  		// search using not allowed key
   480  		{"not_allowed = 'boom'", []*abci.TxResult{}},
   481  		// search for not existing tx result
   482  		{"account.number >= 2 AND account.number <= 5", []*abci.TxResult{}},
   483  		// search using not existing key
   484  		{"account.date >= TIME 2013-05-03T14:45:00Z", []*abci.TxResult{}},
   485  		// search by deprecated key
   486  		{"sender = 'addr1'", []*abci.TxResult{txResult2}},
   487  	}
   488  
   489  	ctx := context.Background()
   490  
   491  	for _, tc := range testCases {
   492  		tc := tc
   493  		t.Run(tc.q, func(t *testing.T) {
   494  			results, err := indexer.Search(ctx, query.MustParse(tc.q))
   495  			require.NoError(t, err)
   496  			for _, txr := range results {
   497  				for _, tr := range tc.results {
   498  					assert.True(t, proto.Equal(tr, txr))
   499  				}
   500  			}
   501  		})
   502  	}
   503  }
   504  
   505  func TestTxSearchOneTxWithMultipleSameTagsButDifferentValues(t *testing.T) {
   506  	indexer := NewTxIndex(db.NewMemDB())
   507  
   508  	txResult := txResultWithEvents([]abci.Event{
   509  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}},
   510  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "2", Index: true}}},
   511  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "3", Index: false}}},
   512  	})
   513  
   514  	err := indexer.Index(txResult)
   515  	require.NoError(t, err)
   516  
   517  	testCases := []struct {
   518  		q     string
   519  		found bool
   520  	}{
   521  		{
   522  			q:     "account.number >= 1",
   523  			found: true,
   524  		},
   525  		{
   526  			q:     "account.number > 2",
   527  			found: false,
   528  		},
   529  		{
   530  			q:     "account.number >= 1 AND tx.height = 3 AND tx.height > 0",
   531  			found: true,
   532  		},
   533  		{
   534  			q:     "account.number >= 1 AND tx.height > 0 AND tx.height = 3",
   535  			found: true,
   536  		},
   537  
   538  		{
   539  			q:     "account.number >= 1 AND tx.height = 1  AND tx.height = 2 AND tx.height = 3",
   540  			found: true,
   541  		},
   542  
   543  		{
   544  			q:     "account.number >= 1 AND tx.height = 3  AND tx.height = 2 AND tx.height = 1",
   545  			found: false,
   546  		},
   547  		{
   548  			q:     "account.number >= 1 AND tx.height = 3",
   549  			found: false,
   550  		},
   551  		{
   552  			q:     "account.number > 1 AND tx.height < 2",
   553  			found: true,
   554  		},
   555  		{
   556  			q:     "account.number >= 2",
   557  			found: true,
   558  		},
   559  		{
   560  			q:     "account.number <= 1",
   561  			found: true,
   562  		},
   563  		{
   564  			q:     "account.number = 'something'",
   565  			found: false,
   566  		},
   567  		{
   568  			q:     "account.number CONTAINS 'bla'",
   569  			found: false,
   570  		},
   571  	}
   572  
   573  	ctx := context.Background()
   574  
   575  	for _, tc := range testCases {
   576  		results, err := indexer.Search(ctx, query.MustParse(tc.q))
   577  		assert.NoError(t, err)
   578  		len := 0
   579  		if tc.found {
   580  			len = 1
   581  		}
   582  		assert.Len(t, results, len)
   583  		assert.True(t, !tc.found || proto.Equal(txResult, results[0]))
   584  
   585  	}
   586  }
   587  
   588  func TestTxIndexDuplicatePreviouslySuccessful(t *testing.T) {
   589  	var mockTx = types.Tx("MOCK_TX_HASH")
   590  
   591  	testCases := []struct {
   592  		name         string
   593  		tx1          *abci.TxResult
   594  		tx2          *abci.TxResult
   595  		expOverwrite bool // do we expect the second tx to overwrite the first tx
   596  	}{
   597  		{
   598  			"don't overwrite as a non-zero code was returned and the previous tx was successful",
   599  			&abci.TxResult{
   600  				Height: 1,
   601  				Index:  0,
   602  				Tx:     mockTx,
   603  				Result: abci.ResponseDeliverTx{
   604  					Code: abci.CodeTypeOK,
   605  				},
   606  			},
   607  			&abci.TxResult{
   608  				Height: 2,
   609  				Index:  0,
   610  				Tx:     mockTx,
   611  				Result: abci.ResponseDeliverTx{
   612  					Code: abci.CodeTypeOK + 1,
   613  				},
   614  			},
   615  			false,
   616  		},
   617  		{
   618  			"overwrite as the previous tx was also unsuccessful",
   619  			&abci.TxResult{
   620  				Height: 1,
   621  				Index:  0,
   622  				Tx:     mockTx,
   623  				Result: abci.ResponseDeliverTx{
   624  					Code: abci.CodeTypeOK + 1,
   625  				},
   626  			},
   627  			&abci.TxResult{
   628  				Height: 2,
   629  				Index:  0,
   630  				Tx:     mockTx,
   631  				Result: abci.ResponseDeliverTx{
   632  					Code: abci.CodeTypeOK + 1,
   633  				},
   634  			},
   635  			true,
   636  		},
   637  		{
   638  			"overwrite as the most recent tx was successful",
   639  			&abci.TxResult{
   640  				Height: 1,
   641  				Index:  0,
   642  				Tx:     mockTx,
   643  				Result: abci.ResponseDeliverTx{
   644  					Code: abci.CodeTypeOK,
   645  				},
   646  			},
   647  			&abci.TxResult{
   648  				Height: 2,
   649  				Index:  0,
   650  				Tx:     mockTx,
   651  				Result: abci.ResponseDeliverTx{
   652  					Code: abci.CodeTypeOK,
   653  				},
   654  			},
   655  			true,
   656  		},
   657  	}
   658  
   659  	hash := mockTx.Hash()
   660  
   661  	for _, tc := range testCases {
   662  		t.Run(tc.name, func(t *testing.T) {
   663  			indexer := NewTxIndex(db.NewMemDB())
   664  
   665  			// index the first tx
   666  			err := indexer.Index(tc.tx1)
   667  			require.NoError(t, err)
   668  
   669  			// index the same tx with different results
   670  			err = indexer.Index(tc.tx2)
   671  			require.NoError(t, err)
   672  
   673  			res, err := indexer.Get(hash)
   674  			require.NoError(t, err)
   675  
   676  			if tc.expOverwrite {
   677  				require.Equal(t, tc.tx2, res)
   678  			} else {
   679  				require.Equal(t, tc.tx1, res)
   680  			}
   681  		})
   682  	}
   683  }
   684  
   685  func TestTxSearchMultipleTxs(t *testing.T) {
   686  	indexer := NewTxIndex(db.NewMemDB())
   687  
   688  	// indexed first, but bigger height (to test the order of transactions)
   689  	txResult := txResultWithEvents([]abci.Event{
   690  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}},
   691  	})
   692  
   693  	txResult.Tx = types.Tx("Bob's account")
   694  	txResult.Height = 2
   695  	txResult.Index = 1
   696  	err := indexer.Index(txResult)
   697  	require.NoError(t, err)
   698  
   699  	// indexed second, but smaller height (to test the order of transactions)
   700  	txResult2 := txResultWithEvents([]abci.Event{
   701  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "2", Index: true}}},
   702  	})
   703  	txResult2.Tx = types.Tx("Alice's account")
   704  	txResult2.Height = 1
   705  	txResult2.Index = 2
   706  
   707  	err = indexer.Index(txResult2)
   708  	require.NoError(t, err)
   709  
   710  	// indexed third (to test the order of transactions)
   711  	txResult3 := txResultWithEvents([]abci.Event{
   712  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "3", Index: true}}},
   713  	})
   714  	txResult3.Tx = types.Tx("Jack's account")
   715  	txResult3.Height = 1
   716  	txResult3.Index = 1
   717  	err = indexer.Index(txResult3)
   718  	require.NoError(t, err)
   719  
   720  	// indexed fourth (to test we don't include txs with similar events)
   721  	// https://github.com/tendermint/tendermint/issues/2908
   722  	txResult4 := txResultWithEvents([]abci.Event{
   723  		{Type: "account", Attributes: []abci.EventAttribute{{Key: "number.id", Value: "1", Index: true}}},
   724  	})
   725  	txResult4.Tx = types.Tx("Mike's account")
   726  	txResult4.Height = 2
   727  	txResult4.Index = 2
   728  	err = indexer.Index(txResult4)
   729  	require.NoError(t, err)
   730  
   731  	ctx := context.Background()
   732  
   733  	results, err := indexer.Search(ctx, query.MustParse("account.number >= 1"))
   734  	assert.NoError(t, err)
   735  
   736  	require.Len(t, results, 3)
   737  }
   738  
   739  func txResultWithEvents(events []abci.Event) *abci.TxResult {
   740  	tx := types.Tx("HELLO WORLD")
   741  	return &abci.TxResult{
   742  		Height: 1,
   743  		Index:  0,
   744  		Tx:     tx,
   745  		Result: abci.ResponseDeliverTx{
   746  			Data:   []byte{0},
   747  			Code:   abci.CodeTypeOK,
   748  			Log:    "",
   749  			Events: events,
   750  		},
   751  	}
   752  }
   753  
   754  func benchmarkTxIndex(txsCount int64, b *testing.B) {
   755  	dir, err := os.MkdirTemp("", "tx_index_db")
   756  	require.NoError(b, err)
   757  	defer os.RemoveAll(dir)
   758  
   759  	store, err := db.NewDB("tx_index", "goleveldb", dir)
   760  	require.NoError(b, err)
   761  	indexer := NewTxIndex(store)
   762  
   763  	batch := txindex.NewBatch(txsCount)
   764  	txIndex := uint32(0)
   765  	for i := int64(0); i < txsCount; i++ {
   766  		tx := cmtrand.Bytes(250)
   767  		txResult := &abci.TxResult{
   768  			Height: 1,
   769  			Index:  txIndex,
   770  			Tx:     tx,
   771  			Result: abci.ResponseDeliverTx{
   772  				Data:   []byte{0},
   773  				Code:   abci.CodeTypeOK,
   774  				Log:    "",
   775  				Events: []abci.Event{},
   776  			},
   777  		}
   778  		if err := batch.Add(txResult); err != nil {
   779  			b.Fatal(err)
   780  		}
   781  		txIndex++
   782  	}
   783  
   784  	b.ResetTimer()
   785  
   786  	for n := 0; n < b.N; n++ {
   787  		err = indexer.AddBatch(batch)
   788  	}
   789  	if err != nil {
   790  		b.Fatal(err)
   791  	}
   792  }
   793  
   794  func BenchmarkTxIndex1(b *testing.B)     { benchmarkTxIndex(1, b) }
   795  func BenchmarkTxIndex500(b *testing.B)   { benchmarkTxIndex(500, b) }
   796  func BenchmarkTxIndex1000(b *testing.B)  { benchmarkTxIndex(1000, b) }
   797  func BenchmarkTxIndex2000(b *testing.B)  { benchmarkTxIndex(2000, b) }
   798  func BenchmarkTxIndex10000(b *testing.B) { benchmarkTxIndex(10000, b) }