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