github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/state/indexer/block/kv/kv_test.go (about)

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