github.com/franono/tendermint@v0.32.2-0.20200527150959-749313264ce9/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 dbm "github.com/tendermint/tm-db" 13 14 "github.com/franono/tendermint/libs/pubsub/query" 15 tmstring "github.com/franono/tendermint/libs/strings" 16 "github.com/franono/tendermint/state/txindex" 17 "github.com/franono/tendermint/types" 18 ) 19 20 const ( 21 tagKeySeparator = "/" 22 ) 23 24 var _ txindex.TxIndexer = (*TxIndex)(nil) 25 26 // TxIndex is the simplest possible indexer, backed by key-value storage (levelDB). 27 type TxIndex struct { 28 store dbm.DB 29 compositeKeysToIndex []string 30 indexAllEvents bool 31 } 32 33 // NewTxIndex creates new KV indexer. 34 func NewTxIndex(store dbm.DB, options ...func(*TxIndex)) *TxIndex { 35 txi := &TxIndex{store: store, compositeKeysToIndex: make([]string, 0), indexAllEvents: false} 36 for _, o := range options { 37 o(txi) 38 } 39 return txi 40 } 41 42 // IndexEvents is an option for setting which composite keys to index. 43 func IndexEvents(compositeKeys []string) func(*TxIndex) { 44 return func(txi *TxIndex) { 45 txi.compositeKeysToIndex = compositeKeys 46 } 47 } 48 49 // IndexAllEvents is an option for indexing all events. 50 func IndexAllEvents() func(*TxIndex) { 51 return func(txi *TxIndex) { 52 txi.indexAllEvents = true 53 } 54 } 55 56 // Get gets transaction from the TxIndex storage and returns it or nil if the 57 // transaction is not found. 58 func (txi *TxIndex) Get(hash []byte) (*types.TxResult, error) { 59 if len(hash) == 0 { 60 return nil, txindex.ErrorEmptyHash 61 } 62 63 rawBytes, err := txi.store.Get(hash) 64 if err != nil { 65 panic(err) 66 } 67 if rawBytes == nil { 68 return nil, nil 69 } 70 71 txResult := new(types.TxResult) 72 err = cdc.UnmarshalBinaryBare(rawBytes, &txResult) 73 if err != nil { 74 return nil, fmt.Errorf("error reading TxResult: %v", err) 75 } 76 77 return txResult, nil 78 } 79 80 // AddBatch indexes a batch of transactions using the given list of events. Each 81 // key that indexed from the tx's events is a composite of the event type and 82 // the respective attribute's key delimited by a "." (eg. "account.number"). 83 // Any event with an empty type is not indexed. 84 func (txi *TxIndex) AddBatch(b *txindex.Batch) error { 85 storeBatch := txi.store.NewBatch() 86 defer storeBatch.Close() 87 88 for _, result := range b.Ops { 89 hash := result.Tx.Hash() 90 91 // index tx by events 92 txi.indexEvents(result, hash, storeBatch) 93 94 // index tx by height 95 if txi.indexAllEvents || tmstring.StringInSlice(types.TxHeightKey, txi.compositeKeysToIndex) { 96 storeBatch.Set(keyForHeight(result), hash) 97 } 98 99 // index tx by hash 100 rawBytes, err := cdc.MarshalBinaryBare(result) 101 if err != nil { 102 return err 103 } 104 storeBatch.Set(hash, rawBytes) 105 } 106 107 storeBatch.WriteSync() 108 return nil 109 } 110 111 // Index indexes a single transaction using the given list of events. Each key 112 // that indexed from the tx's events is a composite of the event type and the 113 // respective attribute's key delimited by a "." (eg. "account.number"). 114 // Any event with an empty type is not indexed. 115 func (txi *TxIndex) Index(result *types.TxResult) error { 116 b := txi.store.NewBatch() 117 defer b.Close() 118 119 hash := result.Tx.Hash() 120 121 // index tx by events 122 txi.indexEvents(result, hash, b) 123 124 // index tx by height 125 if txi.indexAllEvents || tmstring.StringInSlice(types.TxHeightKey, txi.compositeKeysToIndex) { 126 b.Set(keyForHeight(result), hash) 127 } 128 129 // index tx by hash 130 rawBytes, err := cdc.MarshalBinaryBare(result) 131 if err != nil { 132 return err 133 } 134 135 b.Set(hash, rawBytes) 136 b.WriteSync() 137 138 return nil 139 } 140 141 func (txi *TxIndex) indexEvents(result *types.TxResult, hash []byte, store dbm.SetDeleter) { 142 for _, event := range result.Result.Events { 143 // only index events with a non-empty type 144 if len(event.Type) == 0 { 145 continue 146 } 147 148 for _, attr := range event.Attributes { 149 if len(attr.Key) == 0 { 150 continue 151 } 152 153 compositeTag := fmt.Sprintf("%s.%s", event.Type, string(attr.Key)) 154 if txi.indexAllEvents || tmstring.StringInSlice(compositeTag, txi.compositeKeysToIndex) || attr.GetIndex() { 155 store.Set(keyForEvent(compositeTag, attr.Value, result), hash) 156 } 157 } 158 } 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) ([]*types.TxResult, error) { 173 // Potentially exit early. 174 select { 175 case <-ctx.Done(): 176 results := make([]*types.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 []*types.TxResult{}, fmt.Errorf("error while retrieving the result: %w", err) 199 case res == nil: 200 return []*types.TxResult{}, nil 201 default: 202 return []*types.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([]*types.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 427 case c.Op == query.OpContains: 428 // XXX: startKey does not apply here. 429 // For example, if startKey = "account.owner/an/" and search query = "account.owner CONTAINS an" 430 // we can't iterate with prefix "account.owner/an/" because we might miss keys like "account.owner/Ulan/" 431 it, err := dbm.IteratePrefix(txi.store, startKey(c.CompositeKey)) 432 if err != nil { 433 panic(err) 434 } 435 defer it.Close() 436 437 for ; it.Valid(); it.Next() { 438 if !isTagKey(it.Key()) { 439 continue 440 } 441 442 if strings.Contains(extractValueFromKey(it.Key()), c.Operand.(string)) { 443 tmpHashes[string(it.Value())] = it.Value() 444 } 445 446 // Potentially exit early. 447 select { 448 case <-ctx.Done(): 449 break 450 default: 451 } 452 } 453 default: 454 panic("other operators should be handled already") 455 } 456 457 if len(tmpHashes) == 0 || firstRun { 458 // Either: 459 // 460 // 1. Regardless if a previous match was attempted, which may have had 461 // results, but no match was found for the current condition, then we 462 // return no matches (assuming AND operand). 463 // 464 // 2. A previous match was not attempted, so we return all results. 465 return tmpHashes 466 } 467 468 // Remove/reduce matches in filteredHashes that were not found in this 469 // match (tmpHashes). 470 for k := range filteredHashes { 471 if tmpHashes[k] == nil { 472 delete(filteredHashes, k) 473 474 // Potentially exit early. 475 select { 476 case <-ctx.Done(): 477 break 478 default: 479 } 480 } 481 } 482 483 return filteredHashes 484 } 485 486 // matchRange returns all matching txs by hash that meet a given queryRange and 487 // start key. An already filtered result (filteredHashes) is provided such that 488 // any non-intersecting matches are removed. 489 // 490 // NOTE: filteredHashes may be empty if no previous condition has matched. 491 func (txi *TxIndex) matchRange( 492 ctx context.Context, 493 r queryRange, 494 startKey []byte, 495 filteredHashes map[string][]byte, 496 firstRun bool, 497 ) map[string][]byte { 498 // A previous match was attempted but resulted in no matches, so we return 499 // no matches (assuming AND operand). 500 if !firstRun && len(filteredHashes) == 0 { 501 return filteredHashes 502 } 503 504 tmpHashes := make(map[string][]byte) 505 lowerBound := r.lowerBoundValue() 506 upperBound := r.upperBoundValue() 507 508 it, err := dbm.IteratePrefix(txi.store, startKey) 509 if err != nil { 510 panic(err) 511 } 512 defer it.Close() 513 514 LOOP: 515 for ; it.Valid(); it.Next() { 516 if !isTagKey(it.Key()) { 517 continue 518 } 519 520 if _, ok := r.AnyBound().(int64); ok { 521 v, err := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64) 522 if err != nil { 523 continue LOOP 524 } 525 526 include := true 527 if lowerBound != nil && v < lowerBound.(int64) { 528 include = false 529 } 530 531 if upperBound != nil && v > upperBound.(int64) { 532 include = false 533 } 534 535 if include { 536 tmpHashes[string(it.Value())] = it.Value() 537 } 538 539 // XXX: passing time in a ABCI Events is not yet implemented 540 // case time.Time: 541 // v := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64) 542 // if v == r.upperBound { 543 // break 544 // } 545 } 546 547 // Potentially exit early. 548 select { 549 case <-ctx.Done(): 550 break 551 default: 552 } 553 } 554 555 if len(tmpHashes) == 0 || firstRun { 556 // Either: 557 // 558 // 1. Regardless if a previous match was attempted, which may have had 559 // results, but no match was found for the current condition, then we 560 // return no matches (assuming AND operand). 561 // 562 // 2. A previous match was not attempted, so we return all results. 563 return tmpHashes 564 } 565 566 // Remove/reduce matches in filteredHashes that were not found in this 567 // match (tmpHashes). 568 for k := range filteredHashes { 569 if tmpHashes[k] == nil { 570 delete(filteredHashes, k) 571 572 // Potentially exit early. 573 select { 574 case <-ctx.Done(): 575 break 576 default: 577 } 578 } 579 } 580 581 return filteredHashes 582 } 583 584 /////////////////////////////////////////////////////////////////////////////// 585 // Keys 586 587 func isTagKey(key []byte) bool { 588 return strings.Count(string(key), tagKeySeparator) == 3 589 } 590 591 func extractValueFromKey(key []byte) string { 592 parts := strings.SplitN(string(key), tagKeySeparator, 3) 593 return parts[1] 594 } 595 596 func keyForEvent(key string, value []byte, result *types.TxResult) []byte { 597 return []byte(fmt.Sprintf("%s/%s/%d/%d", 598 key, 599 value, 600 result.Height, 601 result.Index, 602 )) 603 } 604 605 func keyForHeight(result *types.TxResult) []byte { 606 return []byte(fmt.Sprintf("%s/%d/%d/%d", 607 types.TxHeightKey, 608 result.Height, 609 result.Height, 610 result.Index, 611 )) 612 } 613 614 func startKeyForCondition(c query.Condition, height int64) []byte { 615 if height > 0 { 616 return startKey(c.CompositeKey, c.Operand, height) 617 } 618 return startKey(c.CompositeKey, c.Operand) 619 } 620 621 func startKey(fields ...interface{}) []byte { 622 var b bytes.Buffer 623 for _, f := range fields { 624 b.Write([]byte(fmt.Sprintf("%v", f) + tagKeySeparator)) 625 } 626 return b.Bytes() 627 }