github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/state/indexer/block/kv/kv_test.go (about)

     1  package kv_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/require"
     9  
    10  	db "github.com/badrootd/nibiru-db"
    11  
    12  	abci "github.com/badrootd/nibiru-cometbft/abci/types"
    13  	"github.com/badrootd/nibiru-cometbft/libs/pubsub/query"
    14  	blockidxkv "github.com/badrootd/nibiru-cometbft/state/indexer/block/kv"
    15  	"github.com/badrootd/nibiru-cometbft/types"
    16  )
    17  
    18  func TestBlockIndexer(t *testing.T) {
    19  	store := db.NewPrefixDB(db.NewMemDB(), []byte("block_events"))
    20  	indexer := blockidxkv.New(store)
    21  
    22  	require.NoError(t, indexer.Index(types.EventDataNewBlockHeader{
    23  		Header: types.Header{Height: 1},
    24  		ResultBeginBlock: abci.ResponseBeginBlock{
    25  			Events: []abci.Event{
    26  				{
    27  					Type: "begin_event",
    28  					Attributes: []abci.EventAttribute{
    29  						{
    30  							Key:   "proposer",
    31  							Value: "FCAA001",
    32  							Index: true,
    33  						},
    34  					},
    35  				},
    36  			},
    37  		},
    38  		ResultEndBlock: abci.ResponseEndBlock{
    39  			Events: []abci.Event{
    40  				{
    41  					Type: "end_event",
    42  					Attributes: []abci.EventAttribute{
    43  						{
    44  							Key:   "foo",
    45  							Value: "100",
    46  							Index: true,
    47  						},
    48  					},
    49  				},
    50  			},
    51  		},
    52  	}))
    53  
    54  	for i := 2; i < 12; i++ {
    55  		var index bool
    56  		if i%2 == 0 {
    57  			index = true
    58  		}
    59  
    60  		require.NoError(t, indexer.Index(types.EventDataNewBlockHeader{
    61  			Header: types.Header{Height: int64(i)},
    62  			ResultBeginBlock: abci.ResponseBeginBlock{
    63  				Events: []abci.Event{
    64  					{
    65  						Type: "begin_event",
    66  						Attributes: []abci.EventAttribute{
    67  							{
    68  								Key:   "proposer",
    69  								Value: "FCAA001",
    70  								Index: true,
    71  							},
    72  						},
    73  					},
    74  				},
    75  			},
    76  			ResultEndBlock: abci.ResponseEndBlock{
    77  				Events: []abci.Event{
    78  					{
    79  						Type: "end_event",
    80  						Attributes: []abci.EventAttribute{
    81  							{
    82  								Key:   "foo",
    83  								Value: fmt.Sprintf("%d", i),
    84  								Index: index,
    85  							},
    86  						},
    87  					},
    88  				},
    89  			},
    90  		}))
    91  	}
    92  
    93  	testCases := map[string]struct {
    94  		q       *query.Query
    95  		results []int64
    96  	}{
    97  		"block.height = 100": {
    98  			q:       query.MustParse("block.height = 100"),
    99  			results: []int64{},
   100  		},
   101  		"block.height = 5": {
   102  			q:       query.MustParse("block.height = 5"),
   103  			results: []int64{5},
   104  		},
   105  		"begin_event.key1 = 'value1'": {
   106  			q:       query.MustParse("begin_event.key1 = 'value1'"),
   107  			results: []int64{},
   108  		},
   109  		"begin_event.proposer = 'FCAA001'": {
   110  			q:       query.MustParse("begin_event.proposer = 'FCAA001'"),
   111  			results: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
   112  		},
   113  		"end_event.foo <= 5": {
   114  			q:       query.MustParse("end_event.foo <= 5"),
   115  			results: []int64{2, 4},
   116  		},
   117  		"end_event.foo >= 100": {
   118  			q:       query.MustParse("end_event.foo >= 100"),
   119  			results: []int64{1},
   120  		},
   121  		"block.height > 2 AND end_event.foo <= 8": {
   122  			q:       query.MustParse("block.height > 2 AND end_event.foo <= 8"),
   123  			results: []int64{4, 6, 8},
   124  		},
   125  		"end_event.foo > 100": {
   126  			q:       query.MustParse("end_event.foo > 100"),
   127  			results: []int64{},
   128  		},
   129  		"block.height >= 2 AND end_event.foo < 8": {
   130  			q:       query.MustParse("block.height >= 2 AND end_event.foo < 8"),
   131  			results: []int64{2, 4, 6},
   132  		},
   133  		"begin_event.proposer CONTAINS 'FFFFFFF'": {
   134  			q:       query.MustParse("begin_event.proposer CONTAINS 'FFFFFFF'"),
   135  			results: []int64{},
   136  		},
   137  		"begin_event.proposer CONTAINS 'FCAA001'": {
   138  			q:       query.MustParse("begin_event.proposer CONTAINS 'FCAA001'"),
   139  			results: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
   140  		},
   141  		"end_event.foo CONTAINS '1'": {
   142  			q:       query.MustParse("end_event.foo CONTAINS '1'"),
   143  			results: []int64{1, 10},
   144  		},
   145  	}
   146  
   147  	for name, tc := range testCases {
   148  		tc := tc
   149  		t.Run(name, func(t *testing.T) {
   150  			results, err := indexer.Search(context.Background(), tc.q)
   151  			require.NoError(t, err)
   152  			require.Equal(t, tc.results, results)
   153  		})
   154  	}
   155  }
   156  
   157  func TestBlockIndexerMulti(t *testing.T) {
   158  	store := db.NewPrefixDB(db.NewMemDB(), []byte("block_events"))
   159  	indexer := blockidxkv.New(store)
   160  
   161  	require.NoError(t, indexer.Index(types.EventDataNewBlockHeader{
   162  		Header: types.Header{Height: 1},
   163  		ResultBeginBlock: abci.ResponseBeginBlock{
   164  			Events: []abci.Event{},
   165  		},
   166  		ResultEndBlock: abci.ResponseEndBlock{
   167  			Events: []abci.Event{
   168  				{
   169  					Type: "end_event",
   170  					Attributes: []abci.EventAttribute{
   171  						{
   172  							Key:   "foo",
   173  							Value: "100",
   174  							Index: true,
   175  						},
   176  						{
   177  							Key:   "bar",
   178  							Value: "200",
   179  							Index: true,
   180  						},
   181  					},
   182  				},
   183  				{
   184  					Type: "end_event",
   185  					Attributes: []abci.EventAttribute{
   186  						{
   187  							Key:   "foo",
   188  							Value: "300",
   189  							Index: true,
   190  						},
   191  						{
   192  							Key:   "bar",
   193  							Value: "500",
   194  							Index: true,
   195  						},
   196  					},
   197  				},
   198  			},
   199  		},
   200  	}))
   201  
   202  	require.NoError(t, indexer.Index(types.EventDataNewBlockHeader{
   203  		Header: types.Header{Height: 2},
   204  		ResultBeginBlock: abci.ResponseBeginBlock{
   205  			Events: []abci.Event{},
   206  		},
   207  		ResultEndBlock: abci.ResponseEndBlock{
   208  			Events: []abci.Event{
   209  				{
   210  					Type: "end_event",
   211  					Attributes: []abci.EventAttribute{
   212  						{
   213  							Key:   "foo",
   214  							Value: "100",
   215  							Index: true,
   216  						},
   217  						{
   218  							Key:   "bar",
   219  							Value: "200",
   220  							Index: true,
   221  						},
   222  					},
   223  				},
   224  				{
   225  					Type: "end_event",
   226  					Attributes: []abci.EventAttribute{
   227  						{
   228  							Key:   "foo",
   229  							Value: "300",
   230  							Index: true,
   231  						},
   232  						{
   233  							Key:   "bar",
   234  							Value: "400",
   235  							Index: true,
   236  						},
   237  					},
   238  				},
   239  			},
   240  		},
   241  	}))
   242  
   243  	testCases := map[string]struct {
   244  		q       *query.Query
   245  		results []int64
   246  	}{
   247  
   248  		"query return all events from a height - exact": {
   249  			q:       query.MustParse("block.height = 1"),
   250  			results: []int64{1},
   251  		},
   252  		"query return all events from a height - exact (deduplicate height)": {
   253  			q:       query.MustParse("block.height = 1 AND block.height = 2"),
   254  			results: []int64{1},
   255  		},
   256  		"query return all events from a height - range": {
   257  			q:       query.MustParse("block.height < 2 AND block.height > 0 AND block.height > 0"),
   258  			results: []int64{1},
   259  		},
   260  		"query return all events from a height - range 2": {
   261  			q:       query.MustParse("block.height < 3 AND block.height < 2 AND block.height > 0 AND block.height > 0"),
   262  			results: []int64{1},
   263  		},
   264  		"query return all events from a height - range 3": {
   265  			q:       query.MustParse("block.height < 1 AND block.height > 1"),
   266  			results: []int64{},
   267  		},
   268  		"query matches fields from same event": {
   269  			q:       query.MustParse("end_event.bar < 300 AND end_event.foo = 100 AND block.height > 0 AND block.height <= 2"),
   270  			results: []int64{1, 2},
   271  		},
   272  		"query matches fields from multiple events": {
   273  			q:       query.MustParse("end_event.foo = 100 AND end_event.bar = 400 AND block.height = 2"),
   274  			results: []int64{},
   275  		},
   276  		"query matches fields from multiple events 2": {
   277  			q:       query.MustParse("end_event.foo = 100 AND end_event.bar > 200 AND block.height > 0 AND block.height < 3"),
   278  			results: []int64{},
   279  		},
   280  		"query matches fields from multiple events allowed": {
   281  			q:       query.MustParse("end_event.foo = 100 AND end_event.bar = 400"),
   282  			results: []int64{},
   283  		},
   284  		"query matches fields from all events whose attribute is within range": {
   285  			q:       query.MustParse("block.height  = 2 AND end_event.foo < 300"),
   286  			results: []int64{2},
   287  		},
   288  		"deduplication test - match.events multiple 2": {
   289  			q:       query.MustParse("end_event.foo = 100 AND end_event.bar = 400 AND block.height = 2"),
   290  			results: []int64{},
   291  		},
   292  		"query using CONTAINS matches fields from all events whose attribute is within range": {
   293  			q:       query.MustParse("block.height  = 2 AND end_event.foo CONTAINS '30'"),
   294  			results: []int64{2},
   295  		},
   296  		"query matches all fields from multiple events": {
   297  			q:       query.MustParse("end_event.bar > 100 AND end_event.bar <= 500"),
   298  			results: []int64{1, 2},
   299  		},
   300  		"query with height range and height equality - should ignore equality": {
   301  			q:       query.MustParse("block.height = 2 AND end_event.foo >= 100 AND block.height < 2"),
   302  			results: []int64{1},
   303  		},
   304  		"query with non-existent field": {
   305  			q:       query.MustParse("end_event.bar = 100"),
   306  			results: []int64{},
   307  		},
   308  	}
   309  
   310  	for name, tc := range testCases {
   311  		tc := tc
   312  		t.Run(name, func(t *testing.T) {
   313  			results, err := indexer.Search(context.Background(), tc.q)
   314  			require.NoError(t, err)
   315  			require.Equal(t, tc.results, results)
   316  		})
   317  	}
   318  }
   319  
   320  func TestBigInt(t *testing.T) {
   321  
   322  	bigInt := "10000000000000000000"
   323  	store := db.NewPrefixDB(db.NewMemDB(), []byte("block_events"))
   324  	indexer := blockidxkv.New(store)
   325  
   326  	require.NoError(t, indexer.Index(types.EventDataNewBlockHeader{
   327  		Header: types.Header{Height: 1},
   328  		ResultBeginBlock: abci.ResponseBeginBlock{
   329  			Events: []abci.Event{},
   330  		},
   331  		ResultEndBlock: abci.ResponseEndBlock{
   332  			Events: []abci.Event{
   333  				{
   334  					Type: "end_event",
   335  					Attributes: []abci.EventAttribute{
   336  						{
   337  							Key:   "foo",
   338  							Value: "100",
   339  							Index: true,
   340  						},
   341  						{
   342  							Key:   "bar",
   343  							Value: "10000000000000000000.76",
   344  							Index: true,
   345  						},
   346  						{
   347  							Key:   "bar_lower",
   348  							Value: "10000000000000000000.1",
   349  							Index: true,
   350  						},
   351  					},
   352  				},
   353  				{
   354  					Type: "end_event",
   355  					Attributes: []abci.EventAttribute{
   356  						{
   357  							Key:   "foo",
   358  							Value: bigInt,
   359  							Index: true,
   360  						},
   361  						{
   362  							Key:   "bar",
   363  							Value: "500",
   364  							Index: true,
   365  						},
   366  						{
   367  							Key:   "bla",
   368  							Value: "500.5",
   369  							Index: true,
   370  						},
   371  					},
   372  				},
   373  			},
   374  		},
   375  	}))
   376  
   377  	testCases := map[string]struct {
   378  		q       *query.Query
   379  		results []int64
   380  	}{
   381  
   382  		"query return all events from a height - exact": {
   383  			q:       query.MustParse("block.height = 1"),
   384  			results: []int64{1},
   385  		},
   386  		"query return all events from a height - exact (deduplicate height)": {
   387  			q:       query.MustParse("block.height = 1 AND block.height = 2"),
   388  			results: []int64{1},
   389  		},
   390  		"query return all events from a height - range": {
   391  			q:       query.MustParse("block.height < 2 AND block.height > 0 AND block.height > 0"),
   392  			results: []int64{1},
   393  		},
   394  		"query matches fields with big int and height - no match": {
   395  			q:       query.MustParse("end_event.foo = " + bigInt + " AND end_event.bar = 500 AND block.height = 2"),
   396  			results: []int64{},
   397  		},
   398  		"query matches fields with big int with less and height - no match": {
   399  			q:       query.MustParse("end_event.foo <= " + bigInt + " AND end_event.bar = 500 AND block.height = 2"),
   400  			results: []int64{},
   401  		},
   402  		"query matches fields with big int and height - match": {
   403  			q:       query.MustParse("end_event.foo = " + bigInt + " AND end_event.bar = 500 AND block.height = 1"),
   404  			results: []int64{1},
   405  		},
   406  		"query matches big int in range": {
   407  			q:       query.MustParse("end_event.foo = " + bigInt),
   408  			results: []int64{1},
   409  		},
   410  		"query matches big int in range with float - does not pass as float is not converted to int": {
   411  			q:       query.MustParse("end_event.bar >= " + bigInt),
   412  			results: []int64{},
   413  		},
   414  		"query matches big int in range with float - fails because float is converted to int": {
   415  			q:       query.MustParse("end_event.bar > " + bigInt),
   416  			results: []int64{},
   417  		},
   418  		"query matches big int in range with float lower dec point - fails because float is converted to int": {
   419  			q:       query.MustParse("end_event.bar_lower > " + bigInt),
   420  			results: []int64{},
   421  		},
   422  		"query matches big int in range with float with less - found": {
   423  			q:       query.MustParse("end_event.foo <= " + bigInt),
   424  			results: []int64{1},
   425  		},
   426  		"query matches big int in range with float with less with height range - found": {
   427  			q:       query.MustParse("end_event.foo <= " + bigInt + " AND block.height > 0"),
   428  			results: []int64{1},
   429  		},
   430  		"query matches big int in range with float with less - not found": {
   431  			q:       query.MustParse("end_event.foo < " + bigInt + " AND end_event.foo > 100"),
   432  			results: []int64{},
   433  		},
   434  		"query does not parse float": {
   435  			q:       query.MustParse("end_event.bla >= 500"),
   436  			results: []int64{},
   437  		},
   438  	}
   439  
   440  	for name, tc := range testCases {
   441  		tc := tc
   442  		t.Run(name, func(t *testing.T) {
   443  			results, err := indexer.Search(context.Background(), tc.q)
   444  			require.NoError(t, err)
   445  			require.Equal(t, tc.results, results)
   446  		})
   447  	}
   448  }