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