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