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 }