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