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