github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/state/indexer/block/kv/kv.go (about) 1 package kv 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "math/big" 9 "sort" 10 "strconv" 11 "strings" 12 13 "github.com/google/orderedcode" 14 15 dbm "github.com/badrootd/nibiru-db" 16 17 abci "github.com/badrootd/nibiru-cometbft/abci/types" 18 "github.com/badrootd/nibiru-cometbft/libs/pubsub/query" 19 "github.com/badrootd/nibiru-cometbft/state/indexer" 20 "github.com/badrootd/nibiru-cometbft/types" 21 ) 22 23 var _ indexer.BlockIndexer = (*BlockerIndexer)(nil) 24 25 // BlockerIndexer implements a block indexer, indexing BeginBlock and EndBlock 26 // events with an underlying KV store. Block events are indexed by their height, 27 // such that matching search criteria returns the respective block height(s). 28 type BlockerIndexer struct { 29 store dbm.DB 30 31 // Add unique event identifier to use when querying 32 // Matching will be done both on height AND eventSeq 33 eventSeq int64 34 } 35 36 func New(store dbm.DB) *BlockerIndexer { 37 return &BlockerIndexer{ 38 store: store, 39 } 40 } 41 42 // Has returns true if the given height has been indexed. An error is returned 43 // upon database query failure. 44 func (idx *BlockerIndexer) Has(height int64) (bool, error) { 45 key, err := heightKey(height) 46 if err != nil { 47 return false, fmt.Errorf("failed to create block height index key: %w", err) 48 } 49 50 return idx.store.Has(key) 51 } 52 53 // Index indexes BeginBlock and EndBlock events for a given block by its height. 54 // The following is indexed: 55 // 56 // primary key: encode(block.height | height) => encode(height) 57 // BeginBlock events: encode(eventType.eventAttr|eventValue|height|begin_block) => encode(height) 58 // EndBlock events: encode(eventType.eventAttr|eventValue|height|end_block) => encode(height) 59 func (idx *BlockerIndexer) Index(bh types.EventDataNewBlockHeader) error { 60 batch := idx.store.NewBatch() 61 defer batch.Close() 62 63 height := bh.Header.Height 64 65 // 1. index by height 66 key, err := heightKey(height) 67 if err != nil { 68 return fmt.Errorf("failed to create block height index key: %w", err) 69 } 70 if err := batch.Set(key, int64ToBytes(height)); err != nil { 71 return err 72 } 73 74 // 2. index BeginBlock events 75 if err := idx.indexEvents(batch, bh.ResultBeginBlock.Events, "begin_block", height); err != nil { 76 return fmt.Errorf("failed to index BeginBlock events: %w", err) 77 } 78 79 // 3. index EndBlock events 80 if err := idx.indexEvents(batch, bh.ResultEndBlock.Events, "end_block", height); err != nil { 81 return fmt.Errorf("failed to index EndBlock events: %w", err) 82 } 83 84 return batch.WriteSync() 85 } 86 87 // Search performs a query for block heights that match a given BeginBlock 88 // and Endblock event search criteria. The given query can match against zero, 89 // one or more block heights. In the case of height queries, i.e. block.height=H, 90 // if the height is indexed, that height alone will be returned. An error and 91 // nil slice is returned. Otherwise, a non-nil slice and nil error is returned. 92 func (idx *BlockerIndexer) Search(ctx context.Context, q *query.Query) ([]int64, error) { 93 results := make([]int64, 0) 94 select { 95 case <-ctx.Done(): 96 return results, nil 97 98 default: 99 } 100 101 conditions, err := q.Conditions() 102 if err != nil { 103 return nil, fmt.Errorf("failed to parse query conditions: %w", err) 104 } 105 // conditions to skip because they're handled before "everything else" 106 skipIndexes := make([]int, 0) 107 108 var ok bool 109 110 var heightInfo HeightInfo 111 // If we are not matching events and block.height occurs more than once, the later value will 112 // overwrite the first one. 113 conditions, heightInfo, ok = dedupHeight(conditions) 114 115 // Extract ranges. If both upper and lower bounds exist, it's better to get 116 // them in order as to not iterate over kvs that are not within range. 117 ranges, rangeIndexes, heightRange := indexer.LookForRangesWithHeight(conditions) 118 heightInfo.heightRange = heightRange 119 120 // If we have additional constraints and want to query per event 121 // attributes, we cannot simply return all blocks for a height. 122 // But we remember the height we want to find and forward it to 123 // match(). If we only have the height constraint 124 // in the query (the second part of the ||), we don't need to query 125 // per event conditions and return all events within the height range. 126 if ok && heightInfo.onlyHeightEq { 127 ok, err := idx.Has(heightInfo.height) 128 if err != nil { 129 return nil, err 130 } 131 132 if ok { 133 return []int64{heightInfo.height}, nil 134 } 135 136 return results, nil 137 } 138 139 var heightsInitialized bool 140 filteredHeights := make(map[string][]byte) 141 if heightInfo.heightEqIdx != -1 { 142 skipIndexes = append(skipIndexes, heightInfo.heightEqIdx) 143 } 144 145 if len(ranges) > 0 { 146 skipIndexes = append(skipIndexes, rangeIndexes...) 147 148 for _, qr := range ranges { 149 // If we have a query range over height and want to still look for 150 // specific event values we do not want to simply return all 151 // blocks in this height range. We remember the height range info 152 // and pass it on to match() to take into account when processing events. 153 if qr.Key == types.BlockHeightKey && !heightInfo.onlyHeightRange { 154 // If the query contains ranges other than the height then we need to treat the height 155 // range when querying the conditions of the other range. 156 // Otherwise we can just return all the blocks within the height range (as there is no 157 // additional constraint on events) 158 159 continue 160 161 } 162 prefix, err := orderedcode.Append(nil, qr.Key) 163 if err != nil { 164 return nil, fmt.Errorf("failed to create prefix key: %w", err) 165 } 166 167 if !heightsInitialized { 168 filteredHeights, err = idx.matchRange(ctx, qr, prefix, filteredHeights, true, heightInfo) 169 if err != nil { 170 return nil, err 171 } 172 173 heightsInitialized = true 174 175 // Ignore any remaining conditions if the first condition resulted in no 176 // matches (assuming implicit AND operand). 177 if len(filteredHeights) == 0 { 178 break 179 } 180 } else { 181 filteredHeights, err = idx.matchRange(ctx, qr, prefix, filteredHeights, false, heightInfo) 182 if err != nil { 183 return nil, err 184 } 185 } 186 } 187 } 188 189 // for all other conditions 190 for i, c := range conditions { 191 if intInSlice(i, skipIndexes) { 192 continue 193 } 194 195 startKey, err := orderedcode.Append(nil, c.CompositeKey, fmt.Sprintf("%v", c.Operand)) 196 197 if err != nil { 198 return nil, err 199 } 200 201 if !heightsInitialized { 202 filteredHeights, err = idx.match(ctx, c, startKey, filteredHeights, true, heightInfo) 203 if err != nil { 204 return nil, err 205 } 206 207 heightsInitialized = true 208 209 // Ignore any remaining conditions if the first condition resulted in no 210 // matches (assuming implicit AND operand). 211 if len(filteredHeights) == 0 { 212 break 213 } 214 } else { 215 filteredHeights, err = idx.match(ctx, c, startKey, filteredHeights, false, heightInfo) 216 if err != nil { 217 return nil, err 218 } 219 } 220 } 221 222 // fetch matching heights 223 results = make([]int64, 0, len(filteredHeights)) 224 resultMap := make(map[int64]struct{}) 225 for _, hBz := range filteredHeights { 226 h := int64FromBytes(hBz) 227 228 ok, err := idx.Has(h) 229 if err != nil { 230 return nil, err 231 } 232 if ok { 233 if _, ok := resultMap[h]; !ok { 234 resultMap[h] = struct{}{} 235 results = append(results, h) 236 } 237 } 238 239 select { 240 case <-ctx.Done(): 241 break 242 243 default: 244 } 245 } 246 247 sort.Slice(results, func(i, j int) bool { return results[i] < results[j] }) 248 249 return results, nil 250 } 251 252 // matchRange returns all matching block heights that match a given QueryRange 253 // and start key. An already filtered result (filteredHeights) is provided such 254 // that any non-intersecting matches are removed. 255 // 256 // NOTE: The provided filteredHeights may be empty if no previous condition has 257 // matched. 258 func (idx *BlockerIndexer) matchRange( 259 ctx context.Context, 260 qr indexer.QueryRange, 261 startKey []byte, 262 filteredHeights map[string][]byte, 263 firstRun bool, 264 heightInfo HeightInfo, 265 ) (map[string][]byte, error) { 266 // A previous match was attempted but resulted in no matches, so we return 267 // no matches (assuming AND operand). 268 if !firstRun && len(filteredHeights) == 0 { 269 return filteredHeights, nil 270 } 271 272 tmpHeights := make(map[string][]byte) 273 274 it, err := dbm.IteratePrefix(idx.store, startKey) 275 if err != nil { 276 return nil, fmt.Errorf("failed to create prefix iterator: %w", err) 277 } 278 defer it.Close() 279 280 LOOP: 281 for ; it.Valid(); it.Next() { 282 var ( 283 eventValue string 284 err error 285 ) 286 287 if qr.Key == types.BlockHeightKey { 288 eventValue, err = parseValueFromPrimaryKey(it.Key()) 289 } else { 290 eventValue, err = parseValueFromEventKey(it.Key()) 291 } 292 293 if err != nil { 294 continue 295 } 296 297 if _, ok := qr.AnyBound().(*big.Int); ok { 298 v := new(big.Int) 299 v, ok := v.SetString(eventValue, 10) 300 if !ok { // If the number was not int it might be a float but this behavior is kept the same as before the patch 301 continue LOOP 302 } 303 304 if qr.Key != types.BlockHeightKey { 305 keyHeight, err := parseHeightFromEventKey(it.Key()) 306 if err != nil || !checkHeightConditions(heightInfo, keyHeight) { 307 continue LOOP 308 } 309 } 310 if checkBounds(qr, v) { 311 idx.setTmpHeights(tmpHeights, it) 312 } 313 } 314 315 select { 316 case <-ctx.Done(): 317 break 318 319 default: 320 } 321 } 322 323 if err := it.Error(); err != nil { 324 return nil, err 325 } 326 327 if len(tmpHeights) == 0 || firstRun { 328 // Either: 329 // 330 // 1. Regardless if a previous match was attempted, which may have had 331 // results, but no match was found for the current condition, then we 332 // return no matches (assuming AND operand). 333 // 334 // 2. A previous match was not attempted, so we return all results. 335 return tmpHeights, nil 336 } 337 338 // Remove/reduce matches in filteredHashes that were not found in this 339 // match (tmpHashes). 340 for k, v := range filteredHeights { 341 tmpHeight := tmpHeights[k] 342 343 // Check whether in this iteration we have not found an overlapping height (tmpHeight == nil) 344 // or whether the events in which the attributed occurred do not match (first part of the condition) 345 if tmpHeight == nil || !bytes.Equal(tmpHeight, v) { 346 delete(filteredHeights, k) 347 348 select { 349 case <-ctx.Done(): 350 break 351 352 default: 353 } 354 } 355 } 356 357 return filteredHeights, nil 358 } 359 360 func (idx *BlockerIndexer) setTmpHeights(tmpHeights map[string][]byte, it dbm.Iterator) { 361 // If we return attributes that occur within the same events, then store the event sequence in the 362 // result map as well 363 eventSeq, _ := parseEventSeqFromEventKey(it.Key()) 364 retVal := it.Value() 365 tmpHeights[string(retVal)+strconv.FormatInt(eventSeq, 10)] = it.Value() 366 367 } 368 369 func checkBounds(ranges indexer.QueryRange, v *big.Int) bool { 370 include := true 371 lowerBound := ranges.LowerBoundValue() 372 upperBound := ranges.UpperBoundValue() 373 374 if lowerBound != nil && v.Cmp(lowerBound.(*big.Int)) == -1 { 375 include = false 376 } 377 378 if upperBound != nil && v.Cmp(upperBound.(*big.Int)) == 1 { 379 include = false 380 } 381 382 return include 383 } 384 385 // match returns all matching heights that meet a given query condition and start 386 // key. An already filtered result (filteredHeights) is provided such that any 387 // non-intersecting matches are removed. 388 // 389 // NOTE: The provided filteredHeights may be empty if no previous condition has 390 // matched. 391 func (idx *BlockerIndexer) match( 392 ctx context.Context, 393 c query.Condition, 394 startKeyBz []byte, 395 filteredHeights map[string][]byte, 396 firstRun bool, 397 heightInfo HeightInfo, 398 ) (map[string][]byte, error) { 399 // A previous match was attempted but resulted in no matches, so we return 400 // no matches (assuming AND operand). 401 if !firstRun && len(filteredHeights) == 0 { 402 return filteredHeights, nil 403 } 404 405 tmpHeights := make(map[string][]byte) 406 407 switch { 408 case c.Op == query.OpEqual: 409 it, err := dbm.IteratePrefix(idx.store, startKeyBz) 410 if err != nil { 411 return nil, fmt.Errorf("failed to create prefix iterator: %w", err) 412 } 413 defer it.Close() 414 415 for ; it.Valid(); it.Next() { 416 417 keyHeight, err := parseHeightFromEventKey(it.Key()) 418 if err != nil || !checkHeightConditions(heightInfo, keyHeight) { 419 continue 420 } 421 422 idx.setTmpHeights(tmpHeights, it) 423 424 if err := ctx.Err(); err != nil { 425 break 426 } 427 } 428 429 if err := it.Error(); err != nil { 430 return nil, err 431 } 432 433 case c.Op == query.OpExists: 434 prefix, err := orderedcode.Append(nil, c.CompositeKey) 435 if err != nil { 436 return nil, err 437 } 438 439 it, err := dbm.IteratePrefix(idx.store, prefix) 440 if err != nil { 441 return nil, fmt.Errorf("failed to create prefix iterator: %w", err) 442 } 443 defer it.Close() 444 445 for ; it.Valid(); it.Next() { 446 keyHeight, err := parseHeightFromEventKey(it.Key()) 447 if err != nil || !checkHeightConditions(heightInfo, keyHeight) { 448 continue 449 } 450 idx.setTmpHeights(tmpHeights, it) 451 452 select { 453 case <-ctx.Done(): 454 break 455 456 default: 457 } 458 } 459 460 if err := it.Error(); err != nil { 461 return nil, err 462 } 463 464 case c.Op == query.OpContains: 465 prefix, err := orderedcode.Append(nil, c.CompositeKey) 466 if err != nil { 467 return nil, err 468 } 469 470 it, err := dbm.IteratePrefix(idx.store, prefix) 471 if err != nil { 472 return nil, fmt.Errorf("failed to create prefix iterator: %w", err) 473 } 474 defer it.Close() 475 476 for ; it.Valid(); it.Next() { 477 eventValue, err := parseValueFromEventKey(it.Key()) 478 if err != nil { 479 continue 480 } 481 482 if strings.Contains(eventValue, c.Operand.(string)) { 483 keyHeight, err := parseHeightFromEventKey(it.Key()) 484 if err != nil || !checkHeightConditions(heightInfo, keyHeight) { 485 continue 486 } 487 idx.setTmpHeights(tmpHeights, it) 488 } 489 490 select { 491 case <-ctx.Done(): 492 break 493 494 default: 495 } 496 } 497 if err := it.Error(); err != nil { 498 return nil, err 499 } 500 501 default: 502 return nil, errors.New("other operators should be handled already") 503 } 504 505 if len(tmpHeights) == 0 || firstRun { 506 // Either: 507 // 508 // 1. Regardless if a previous match was attempted, which may have had 509 // results, but no match was found for the current condition, then we 510 // return no matches (assuming AND operand). 511 // 512 // 2. A previous match was not attempted, so we return all results. 513 return tmpHeights, nil 514 } 515 516 // Remove/reduce matches in filteredHeights that were not found in this 517 // match (tmpHeights). 518 for k, v := range filteredHeights { 519 tmpHeight := tmpHeights[k] 520 if tmpHeight == nil || !bytes.Equal(tmpHeight, v) { 521 delete(filteredHeights, k) 522 523 select { 524 case <-ctx.Done(): 525 break 526 527 default: 528 } 529 } 530 } 531 532 return filteredHeights, nil 533 } 534 535 func (idx *BlockerIndexer) indexEvents(batch dbm.Batch, events []abci.Event, typ string, height int64) error { 536 heightBz := int64ToBytes(height) 537 538 for _, event := range events { 539 idx.eventSeq = idx.eventSeq + 1 540 // only index events with a non-empty type 541 if len(event.Type) == 0 { 542 continue 543 } 544 545 for _, attr := range event.Attributes { 546 if len(attr.Key) == 0 { 547 continue 548 } 549 550 // index iff the event specified index:true and it's not a reserved event 551 compositeKey := fmt.Sprintf("%s.%s", event.Type, attr.Key) 552 if compositeKey == types.BlockHeightKey { 553 return fmt.Errorf("event type and attribute key \"%s\" is reserved; please use a different key", compositeKey) 554 } 555 556 if attr.GetIndex() { 557 key, err := eventKey(compositeKey, typ, attr.Value, height, idx.eventSeq) 558 if err != nil { 559 return fmt.Errorf("failed to create block index key: %w", err) 560 } 561 562 if err := batch.Set(key, heightBz); err != nil { 563 return err 564 } 565 } 566 } 567 } 568 569 return nil 570 }