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