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 }