github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/state/txindex/kv/kv.go (about) 1 package kv 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/hex" 7 "fmt" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/gogo/protobuf/proto" 13 14 abci "github.com/lazyledger/lazyledger-core/abci/types" 15 dbm "github.com/lazyledger/lazyledger-core/libs/db" 16 "github.com/lazyledger/lazyledger-core/libs/pubsub/query" 17 "github.com/lazyledger/lazyledger-core/state/txindex" 18 "github.com/lazyledger/lazyledger-core/types" 19 ) 20 21 const ( 22 tagKeySeparator = "/" 23 ) 24 25 var _ txindex.TxIndexer = (*TxIndex)(nil) 26 27 // TxIndex is the simplest possible indexer, backed by key-value storage (levelDB). 28 type TxIndex struct { 29 store dbm.DB 30 } 31 32 // NewTxIndex creates new KV indexer. 33 func NewTxIndex(store dbm.DB) *TxIndex { 34 return &TxIndex{ 35 store: store, 36 } 37 } 38 39 // Get gets transaction from the TxIndex storage and returns it or nil if the 40 // transaction is not found. 41 func (txi *TxIndex) Get(hash []byte) (*abci.TxResult, error) { 42 if len(hash) == 0 { 43 return nil, txindex.ErrorEmptyHash 44 } 45 46 rawBytes, err := txi.store.Get(hash) 47 if err != nil { 48 panic(err) 49 } 50 if rawBytes == nil { 51 return nil, nil 52 } 53 54 txResult := new(abci.TxResult) 55 err = proto.Unmarshal(rawBytes, txResult) 56 if err != nil { 57 return nil, fmt.Errorf("error reading TxResult: %v", err) 58 } 59 60 return txResult, nil 61 } 62 63 // AddBatch indexes a batch of transactions using the given list of events. Each 64 // key that indexed from the tx's events is a composite of the event type and 65 // the respective attribute's key delimited by a "." (eg. "account.number"). 66 // Any event with an empty type is not indexed. 67 func (txi *TxIndex) AddBatch(b *txindex.Batch) error { 68 storeBatch := txi.store.NewBatch() 69 defer storeBatch.Close() 70 71 for _, result := range b.Ops { 72 hash := types.Tx(result.Tx).Hash() 73 74 // index tx by events 75 err := txi.indexEvents(result, hash, storeBatch) 76 if err != nil { 77 return err 78 } 79 80 // index by height (always) 81 err = storeBatch.Set(keyForHeight(result), hash) 82 if err != nil { 83 return err 84 } 85 86 rawBytes, err := proto.Marshal(result) 87 if err != nil { 88 return err 89 } 90 // index by hash (always) 91 err = storeBatch.Set(hash, rawBytes) 92 if err != nil { 93 return err 94 } 95 } 96 97 return storeBatch.WriteSync() 98 } 99 100 // Index indexes a single transaction using the given list of events. Each key 101 // that indexed from the tx's events is a composite of the event type and the 102 // respective attribute's key delimited by a "." (eg. "account.number"). 103 // Any event with an empty type is not indexed. 104 func (txi *TxIndex) Index(result *abci.TxResult) error { 105 b := txi.store.NewBatch() 106 defer b.Close() 107 108 hash := types.Tx(result.Tx).Hash() 109 110 // index tx by events 111 err := txi.indexEvents(result, hash, b) 112 if err != nil { 113 return err 114 } 115 116 // index by height (always) 117 err = b.Set(keyForHeight(result), hash) 118 if err != nil { 119 return err 120 } 121 122 rawBytes, err := proto.Marshal(result) 123 if err != nil { 124 return err 125 } 126 // index by hash (always) 127 err = b.Set(hash, rawBytes) 128 if err != nil { 129 return err 130 } 131 132 return b.WriteSync() 133 } 134 135 func (txi *TxIndex) indexEvents(result *abci.TxResult, hash []byte, store dbm.Batch) error { 136 for _, event := range result.Result.Events { 137 // only index events with a non-empty type 138 if len(event.Type) == 0 { 139 continue 140 } 141 142 for _, attr := range event.Attributes { 143 if len(attr.Key) == 0 { 144 continue 145 } 146 147 // index if `index: true` is set 148 compositeTag := fmt.Sprintf("%s.%s", event.Type, string(attr.Key)) 149 if attr.GetIndex() { 150 err := store.Set(keyForEvent(compositeTag, attr.Value, result), hash) 151 if err != nil { 152 return err 153 } 154 } 155 } 156 } 157 158 return nil 159 } 160 161 // Search performs a search using the given query. 162 // 163 // It breaks the query into conditions (like "tx.height > 5"). For each 164 // condition, it queries the DB index. One special use cases here: (1) if 165 // "tx.hash" is found, it returns tx result for it (2) for range queries it is 166 // better for the client to provide both lower and upper bounds, so we are not 167 // performing a full scan. Results from querying indexes are then intersected 168 // and returned to the caller, in no particular order. 169 // 170 // Search will exit early and return any result fetched so far, 171 // when a message is received on the context chan. 172 func (txi *TxIndex) Search(ctx context.Context, q *query.Query) ([]*abci.TxResult, error) { 173 // Potentially exit early. 174 select { 175 case <-ctx.Done(): 176 results := make([]*abci.TxResult, 0) 177 return results, nil 178 default: 179 } 180 181 var hashesInitialized bool 182 filteredHashes := make(map[string][]byte) 183 184 // get a list of conditions (like "tx.height > 5") 185 conditions, err := q.Conditions() 186 if err != nil { 187 return nil, fmt.Errorf("error during parsing conditions from query: %w", err) 188 } 189 190 // if there is a hash condition, return the result immediately 191 hash, ok, err := lookForHash(conditions) 192 if err != nil { 193 return nil, fmt.Errorf("error during searching for a hash in the query: %w", err) 194 } else if ok { 195 res, err := txi.Get(hash) 196 switch { 197 case err != nil: 198 return []*abci.TxResult{}, fmt.Errorf("error while retrieving the result: %w", err) 199 case res == nil: 200 return []*abci.TxResult{}, nil 201 default: 202 return []*abci.TxResult{res}, nil 203 } 204 } 205 206 // conditions to skip because they're handled before "everything else" 207 skipIndexes := make([]int, 0) 208 209 // extract ranges 210 // if both upper and lower bounds exist, it's better to get them in order not 211 // no iterate over kvs that are not within range. 212 ranges, rangeIndexes := lookForRanges(conditions) 213 if len(ranges) > 0 { 214 skipIndexes = append(skipIndexes, rangeIndexes...) 215 216 for _, r := range ranges { 217 if !hashesInitialized { 218 filteredHashes = txi.matchRange(ctx, r, startKey(r.key), filteredHashes, true) 219 hashesInitialized = true 220 221 // Ignore any remaining conditions if the first condition resulted 222 // in no matches (assuming implicit AND operand). 223 if len(filteredHashes) == 0 { 224 break 225 } 226 } else { 227 filteredHashes = txi.matchRange(ctx, r, startKey(r.key), filteredHashes, false) 228 } 229 } 230 } 231 232 // if there is a height condition ("tx.height=3"), extract it 233 height := lookForHeight(conditions) 234 235 // for all other conditions 236 for i, c := range conditions { 237 if intInSlice(i, skipIndexes) { 238 continue 239 } 240 241 if !hashesInitialized { 242 filteredHashes = txi.match(ctx, c, startKeyForCondition(c, height), filteredHashes, true) 243 hashesInitialized = true 244 245 // Ignore any remaining conditions if the first condition resulted 246 // in no matches (assuming implicit AND operand). 247 if len(filteredHashes) == 0 { 248 break 249 } 250 } else { 251 filteredHashes = txi.match(ctx, c, startKeyForCondition(c, height), filteredHashes, false) 252 } 253 } 254 255 results := make([]*abci.TxResult, 0, len(filteredHashes)) 256 for _, h := range filteredHashes { 257 res, err := txi.Get(h) 258 if err != nil { 259 return nil, fmt.Errorf("failed to get Tx{%X}: %w", h, err) 260 } 261 results = append(results, res) 262 263 // Potentially exit early. 264 select { 265 case <-ctx.Done(): 266 break 267 default: 268 } 269 } 270 271 return results, nil 272 } 273 274 func lookForHash(conditions []query.Condition) (hash []byte, ok bool, err error) { 275 for _, c := range conditions { 276 if c.CompositeKey == types.TxHashKey { 277 decoded, err := hex.DecodeString(c.Operand.(string)) 278 return decoded, true, err 279 } 280 } 281 return 282 } 283 284 // lookForHeight returns a height if there is an "height=X" condition. 285 func lookForHeight(conditions []query.Condition) (height int64) { 286 for _, c := range conditions { 287 if c.CompositeKey == types.TxHeightKey && c.Op == query.OpEqual { 288 return c.Operand.(int64) 289 } 290 } 291 return 0 292 } 293 294 // special map to hold range conditions 295 // Example: account.number => queryRange{lowerBound: 1, upperBound: 5} 296 type queryRanges map[string]queryRange 297 298 type queryRange struct { 299 lowerBound interface{} // int || time.Time 300 upperBound interface{} // int || time.Time 301 key string 302 includeLowerBound bool 303 includeUpperBound bool 304 } 305 306 func (r queryRange) lowerBoundValue() interface{} { 307 if r.lowerBound == nil { 308 return nil 309 } 310 311 if r.includeLowerBound { 312 return r.lowerBound 313 } 314 315 switch t := r.lowerBound.(type) { 316 case int64: 317 return t + 1 318 case time.Time: 319 return t.Unix() + 1 320 default: 321 panic("not implemented") 322 } 323 } 324 325 func (r queryRange) AnyBound() interface{} { 326 if r.lowerBound != nil { 327 return r.lowerBound 328 } 329 330 return r.upperBound 331 } 332 333 func (r queryRange) upperBoundValue() interface{} { 334 if r.upperBound == nil { 335 return nil 336 } 337 338 if r.includeUpperBound { 339 return r.upperBound 340 } 341 342 switch t := r.upperBound.(type) { 343 case int64: 344 return t - 1 345 case time.Time: 346 return t.Unix() - 1 347 default: 348 panic("not implemented") 349 } 350 } 351 352 func lookForRanges(conditions []query.Condition) (ranges queryRanges, indexes []int) { 353 ranges = make(queryRanges) 354 for i, c := range conditions { 355 if isRangeOperation(c.Op) { 356 r, ok := ranges[c.CompositeKey] 357 if !ok { 358 r = queryRange{key: c.CompositeKey} 359 } 360 switch c.Op { 361 case query.OpGreater: 362 r.lowerBound = c.Operand 363 case query.OpGreaterEqual: 364 r.includeLowerBound = true 365 r.lowerBound = c.Operand 366 case query.OpLess: 367 r.upperBound = c.Operand 368 case query.OpLessEqual: 369 r.includeUpperBound = true 370 r.upperBound = c.Operand 371 } 372 ranges[c.CompositeKey] = r 373 indexes = append(indexes, i) 374 } 375 } 376 return ranges, indexes 377 } 378 379 func isRangeOperation(op query.Operator) bool { 380 switch op { 381 case query.OpGreater, query.OpGreaterEqual, query.OpLess, query.OpLessEqual: 382 return true 383 default: 384 return false 385 } 386 } 387 388 // match returns all matching txs by hash that meet a given condition and start 389 // key. An already filtered result (filteredHashes) is provided such that any 390 // non-intersecting matches are removed. 391 // 392 // NOTE: filteredHashes may be empty if no previous condition has matched. 393 func (txi *TxIndex) match( 394 ctx context.Context, 395 c query.Condition, 396 startKeyBz []byte, 397 filteredHashes map[string][]byte, 398 firstRun bool, 399 ) map[string][]byte { 400 // A previous match was attempted but resulted in no matches, so we return 401 // no matches (assuming AND operand). 402 if !firstRun && len(filteredHashes) == 0 { 403 return filteredHashes 404 } 405 406 tmpHashes := make(map[string][]byte) 407 408 switch { 409 case c.Op == query.OpEqual: 410 it, err := dbm.IteratePrefix(txi.store, startKeyBz) 411 if err != nil { 412 panic(err) 413 } 414 defer it.Close() 415 416 for ; it.Valid(); it.Next() { 417 tmpHashes[string(it.Value())] = it.Value() 418 419 // Potentially exit early. 420 select { 421 case <-ctx.Done(): 422 break 423 default: 424 } 425 } 426 if err := it.Error(); err != nil { 427 panic(err) 428 } 429 430 case c.Op == query.OpExists: 431 // XXX: can't use startKeyBz here because c.Operand is nil 432 // (e.g. "account.owner/<nil>/" won't match w/ a single row) 433 it, err := dbm.IteratePrefix(txi.store, startKey(c.CompositeKey)) 434 if err != nil { 435 panic(err) 436 } 437 defer it.Close() 438 439 for ; it.Valid(); it.Next() { 440 tmpHashes[string(it.Value())] = it.Value() 441 442 // Potentially exit early. 443 select { 444 case <-ctx.Done(): 445 break 446 default: 447 } 448 } 449 if err := it.Error(); err != nil { 450 panic(err) 451 } 452 453 case c.Op == query.OpContains: 454 // XXX: startKey does not apply here. 455 // For example, if startKey = "account.owner/an/" and search query = "account.owner CONTAINS an" 456 // we can't iterate with prefix "account.owner/an/" because we might miss keys like "account.owner/Ulan/" 457 it, err := dbm.IteratePrefix(txi.store, startKey(c.CompositeKey)) 458 if err != nil { 459 panic(err) 460 } 461 defer it.Close() 462 463 for ; it.Valid(); it.Next() { 464 if !isTagKey(it.Key()) { 465 continue 466 } 467 468 if strings.Contains(extractValueFromKey(it.Key()), c.Operand.(string)) { 469 tmpHashes[string(it.Value())] = it.Value() 470 } 471 472 // Potentially exit early. 473 select { 474 case <-ctx.Done(): 475 break 476 default: 477 } 478 } 479 if err := it.Error(); err != nil { 480 panic(err) 481 } 482 default: 483 panic("other operators should be handled already") 484 } 485 486 if len(tmpHashes) == 0 || firstRun { 487 // Either: 488 // 489 // 1. Regardless if a previous match was attempted, which may have had 490 // results, but no match was found for the current condition, then we 491 // return no matches (assuming AND operand). 492 // 493 // 2. A previous match was not attempted, so we return all results. 494 return tmpHashes 495 } 496 497 // Remove/reduce matches in filteredHashes that were not found in this 498 // match (tmpHashes). 499 for k := range filteredHashes { 500 if tmpHashes[k] == nil { 501 delete(filteredHashes, k) 502 503 // Potentially exit early. 504 select { 505 case <-ctx.Done(): 506 break 507 default: 508 } 509 } 510 } 511 512 return filteredHashes 513 } 514 515 // matchRange returns all matching txs by hash that meet a given queryRange and 516 // start key. An already filtered result (filteredHashes) is provided such that 517 // any non-intersecting matches are removed. 518 // 519 // NOTE: filteredHashes may be empty if no previous condition has matched. 520 func (txi *TxIndex) matchRange( 521 ctx context.Context, 522 r queryRange, 523 startKey []byte, 524 filteredHashes map[string][]byte, 525 firstRun bool, 526 ) map[string][]byte { 527 // A previous match was attempted but resulted in no matches, so we return 528 // no matches (assuming AND operand). 529 if !firstRun && len(filteredHashes) == 0 { 530 return filteredHashes 531 } 532 533 tmpHashes := make(map[string][]byte) 534 lowerBound := r.lowerBoundValue() 535 upperBound := r.upperBoundValue() 536 537 it, err := dbm.IteratePrefix(txi.store, startKey) 538 if err != nil { 539 panic(err) 540 } 541 defer it.Close() 542 543 LOOP: 544 for ; it.Valid(); it.Next() { 545 if !isTagKey(it.Key()) { 546 continue 547 } 548 549 if _, ok := r.AnyBound().(int64); ok { 550 v, err := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64) 551 if err != nil { 552 continue LOOP 553 } 554 555 include := true 556 if lowerBound != nil && v < lowerBound.(int64) { 557 include = false 558 } 559 560 if upperBound != nil && v > upperBound.(int64) { 561 include = false 562 } 563 564 if include { 565 tmpHashes[string(it.Value())] = it.Value() 566 } 567 568 // XXX: passing time in a ABCI Events is not yet implemented 569 // case time.Time: 570 // v := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64) 571 // if v == r.upperBound { 572 // break 573 // } 574 } 575 576 // Potentially exit early. 577 select { 578 case <-ctx.Done(): 579 break 580 default: 581 } 582 } 583 if err := it.Error(); err != nil { 584 panic(err) 585 } 586 587 if len(tmpHashes) == 0 || firstRun { 588 // Either: 589 // 590 // 1. Regardless if a previous match was attempted, which may have had 591 // results, but no match was found for the current condition, then we 592 // return no matches (assuming AND operand). 593 // 594 // 2. A previous match was not attempted, so we return all results. 595 return tmpHashes 596 } 597 598 // Remove/reduce matches in filteredHashes that were not found in this 599 // match (tmpHashes). 600 for k := range filteredHashes { 601 if tmpHashes[k] == nil { 602 delete(filteredHashes, k) 603 604 // Potentially exit early. 605 select { 606 case <-ctx.Done(): 607 break 608 default: 609 } 610 } 611 } 612 613 return filteredHashes 614 } 615 616 // Keys 617 618 func isTagKey(key []byte) bool { 619 return strings.Count(string(key), tagKeySeparator) == 3 620 } 621 622 func extractValueFromKey(key []byte) string { 623 parts := strings.SplitN(string(key), tagKeySeparator, 3) 624 return parts[1] 625 } 626 627 func keyForEvent(key string, value []byte, result *abci.TxResult) []byte { 628 return []byte(fmt.Sprintf("%s/%s/%d/%d", 629 key, 630 value, 631 result.Height, 632 result.Index, 633 )) 634 } 635 636 func keyForHeight(result *abci.TxResult) []byte { 637 return []byte(fmt.Sprintf("%s/%d/%d/%d", 638 types.TxHeightKey, 639 result.Height, 640 result.Height, 641 result.Index, 642 )) 643 } 644 645 func startKeyForCondition(c query.Condition, height int64) []byte { 646 if height > 0 { 647 return startKey(c.CompositeKey, c.Operand, height) 648 } 649 return startKey(c.CompositeKey, c.Operand) 650 } 651 652 func startKey(fields ...interface{}) []byte { 653 var b bytes.Buffer 654 for _, f := range fields { 655 b.Write([]byte(fmt.Sprintf("%v", f) + tagKeySeparator)) 656 } 657 return b.Bytes() 658 }