github.com/noirx94/tendermintmp@v0.0.1/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 11 "github.com/gogo/protobuf/proto" 12 dbm "github.com/tendermint/tm-db" 13 14 abci "github.com/tendermint/tendermint/abci/types" 15 "github.com/tendermint/tendermint/libs/pubsub/query" 16 "github.com/tendermint/tendermint/state/indexer" 17 "github.com/tendermint/tendermint/state/txindex" 18 "github.com/tendermint/tendermint/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 select { 174 case <-ctx.Done(): 175 return make([]*abci.TxResult, 0), nil 176 177 default: 178 } 179 180 var hashesInitialized bool 181 filteredHashes := make(map[string][]byte) 182 183 // get a list of conditions (like "tx.height > 5") 184 conditions, err := q.Conditions() 185 if err != nil { 186 return nil, fmt.Errorf("error during parsing conditions from query: %w", err) 187 } 188 189 // if there is a hash condition, return the result immediately 190 hash, ok, err := lookForHash(conditions) 191 if err != nil { 192 return nil, fmt.Errorf("error during searching for a hash in the query: %w", err) 193 } else if ok { 194 res, err := txi.Get(hash) 195 switch { 196 case err != nil: 197 return []*abci.TxResult{}, fmt.Errorf("error while retrieving the result: %w", err) 198 case res == nil: 199 return []*abci.TxResult{}, nil 200 default: 201 return []*abci.TxResult{res}, nil 202 } 203 } 204 205 // conditions to skip because they're handled before "everything else" 206 skipIndexes := make([]int, 0) 207 208 // extract ranges 209 // if both upper and lower bounds exist, it's better to get them in order not 210 // no iterate over kvs that are not within range. 211 ranges, rangeIndexes := indexer.LookForRanges(conditions) 212 if len(ranges) > 0 { 213 skipIndexes = append(skipIndexes, rangeIndexes...) 214 215 for _, qr := range ranges { 216 if !hashesInitialized { 217 filteredHashes = txi.matchRange(ctx, qr, startKey(qr.Key), filteredHashes, true) 218 hashesInitialized = true 219 220 // Ignore any remaining conditions if the first condition resulted 221 // in no matches (assuming implicit AND operand). 222 if len(filteredHashes) == 0 { 223 break 224 } 225 } else { 226 filteredHashes = txi.matchRange(ctx, qr, startKey(qr.Key), filteredHashes, false) 227 } 228 } 229 } 230 231 // if there is a height condition ("tx.height=3"), extract it 232 height := lookForHeight(conditions) 233 234 // for all other conditions 235 for i, c := range conditions { 236 if intInSlice(i, skipIndexes) { 237 continue 238 } 239 240 if !hashesInitialized { 241 filteredHashes = txi.match(ctx, c, startKeyForCondition(c, height), filteredHashes, true) 242 hashesInitialized = true 243 244 // Ignore any remaining conditions if the first condition resulted 245 // in no matches (assuming implicit AND operand). 246 if len(filteredHashes) == 0 { 247 break 248 } 249 } else { 250 filteredHashes = txi.match(ctx, c, startKeyForCondition(c, height), filteredHashes, false) 251 } 252 } 253 254 results := make([]*abci.TxResult, 0, len(filteredHashes)) 255 for _, h := range filteredHashes { 256 res, err := txi.Get(h) 257 if err != nil { 258 return nil, fmt.Errorf("failed to get Tx{%X}: %w", h, err) 259 } 260 results = append(results, res) 261 262 // Potentially exit early. 263 select { 264 case <-ctx.Done(): 265 break 266 default: 267 } 268 } 269 270 return results, nil 271 } 272 273 func lookForHash(conditions []query.Condition) (hash []byte, ok bool, err error) { 274 for _, c := range conditions { 275 if c.CompositeKey == types.TxHashKey { 276 decoded, err := hex.DecodeString(c.Operand.(string)) 277 return decoded, true, err 278 } 279 } 280 return 281 } 282 283 // lookForHeight returns a height if there is an "height=X" condition. 284 func lookForHeight(conditions []query.Condition) (height int64) { 285 for _, c := range conditions { 286 if c.CompositeKey == types.TxHeightKey && c.Op == query.OpEqual { 287 return c.Operand.(int64) 288 } 289 } 290 return 0 291 } 292 293 // match returns all matching txs by hash that meet a given condition and start 294 // key. An already filtered result (filteredHashes) is provided such that any 295 // non-intersecting matches are removed. 296 // 297 // NOTE: filteredHashes may be empty if no previous condition has matched. 298 func (txi *TxIndex) match( 299 ctx context.Context, 300 c query.Condition, 301 startKeyBz []byte, 302 filteredHashes map[string][]byte, 303 firstRun bool, 304 ) map[string][]byte { 305 // A previous match was attempted but resulted in no matches, so we return 306 // no matches (assuming AND operand). 307 if !firstRun && len(filteredHashes) == 0 { 308 return filteredHashes 309 } 310 311 tmpHashes := make(map[string][]byte) 312 313 switch { 314 case c.Op == query.OpEqual: 315 it, err := dbm.IteratePrefix(txi.store, startKeyBz) 316 if err != nil { 317 panic(err) 318 } 319 defer it.Close() 320 321 for ; it.Valid(); it.Next() { 322 tmpHashes[string(it.Value())] = it.Value() 323 324 // Potentially exit early. 325 select { 326 case <-ctx.Done(): 327 break 328 default: 329 } 330 } 331 if err := it.Error(); err != nil { 332 panic(err) 333 } 334 335 case c.Op == query.OpExists: 336 // XXX: can't use startKeyBz here because c.Operand is nil 337 // (e.g. "account.owner/<nil>/" won't match w/ a single row) 338 it, err := dbm.IteratePrefix(txi.store, startKey(c.CompositeKey)) 339 if err != nil { 340 panic(err) 341 } 342 defer it.Close() 343 344 for ; it.Valid(); it.Next() { 345 tmpHashes[string(it.Value())] = it.Value() 346 347 // Potentially exit early. 348 select { 349 case <-ctx.Done(): 350 break 351 default: 352 } 353 } 354 if err := it.Error(); err != nil { 355 panic(err) 356 } 357 358 case c.Op == query.OpContains: 359 // XXX: startKey does not apply here. 360 // For example, if startKey = "account.owner/an/" and search query = "account.owner CONTAINS an" 361 // we can't iterate with prefix "account.owner/an/" because we might miss keys like "account.owner/Ulan/" 362 it, err := dbm.IteratePrefix(txi.store, startKey(c.CompositeKey)) 363 if err != nil { 364 panic(err) 365 } 366 defer it.Close() 367 368 for ; it.Valid(); it.Next() { 369 if !isTagKey(it.Key()) { 370 continue 371 } 372 373 if strings.Contains(extractValueFromKey(it.Key()), c.Operand.(string)) { 374 tmpHashes[string(it.Value())] = it.Value() 375 } 376 377 // Potentially exit early. 378 select { 379 case <-ctx.Done(): 380 break 381 default: 382 } 383 } 384 if err := it.Error(); err != nil { 385 panic(err) 386 } 387 default: 388 panic("other operators should be handled already") 389 } 390 391 if len(tmpHashes) == 0 || firstRun { 392 // Either: 393 // 394 // 1. Regardless if a previous match was attempted, which may have had 395 // results, but no match was found for the current condition, then we 396 // return no matches (assuming AND operand). 397 // 398 // 2. A previous match was not attempted, so we return all results. 399 return tmpHashes 400 } 401 402 // Remove/reduce matches in filteredHashes that were not found in this 403 // match (tmpHashes). 404 for k := range filteredHashes { 405 if tmpHashes[k] == nil { 406 delete(filteredHashes, k) 407 408 // Potentially exit early. 409 select { 410 case <-ctx.Done(): 411 break 412 default: 413 } 414 } 415 } 416 417 return filteredHashes 418 } 419 420 // matchRange returns all matching txs by hash that meet a given queryRange and 421 // start key. An already filtered result (filteredHashes) is provided such that 422 // any non-intersecting matches are removed. 423 // 424 // NOTE: filteredHashes may be empty if no previous condition has matched. 425 func (txi *TxIndex) matchRange( 426 ctx context.Context, 427 qr indexer.QueryRange, 428 startKey []byte, 429 filteredHashes map[string][]byte, 430 firstRun bool, 431 ) map[string][]byte { 432 // A previous match was attempted but resulted in no matches, so we return 433 // no matches (assuming AND operand). 434 if !firstRun && len(filteredHashes) == 0 { 435 return filteredHashes 436 } 437 438 tmpHashes := make(map[string][]byte) 439 lowerBound := qr.LowerBoundValue() 440 upperBound := qr.UpperBoundValue() 441 442 it, err := dbm.IteratePrefix(txi.store, startKey) 443 if err != nil { 444 panic(err) 445 } 446 defer it.Close() 447 448 LOOP: 449 for ; it.Valid(); it.Next() { 450 if !isTagKey(it.Key()) { 451 continue 452 } 453 454 if _, ok := qr.AnyBound().(int64); ok { 455 v, err := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64) 456 if err != nil { 457 continue LOOP 458 } 459 460 include := true 461 if lowerBound != nil && v < lowerBound.(int64) { 462 include = false 463 } 464 465 if upperBound != nil && v > upperBound.(int64) { 466 include = false 467 } 468 469 if include { 470 tmpHashes[string(it.Value())] = it.Value() 471 } 472 473 // XXX: passing time in a ABCI Events is not yet implemented 474 // case time.Time: 475 // v := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64) 476 // if v == r.upperBound { 477 // break 478 // } 479 } 480 481 // Potentially exit early. 482 select { 483 case <-ctx.Done(): 484 break 485 default: 486 } 487 } 488 if err := it.Error(); err != nil { 489 panic(err) 490 } 491 492 if len(tmpHashes) == 0 || firstRun { 493 // Either: 494 // 495 // 1. Regardless if a previous match was attempted, which may have had 496 // results, but no match was found for the current condition, then we 497 // return no matches (assuming AND operand). 498 // 499 // 2. A previous match was not attempted, so we return all results. 500 return tmpHashes 501 } 502 503 // Remove/reduce matches in filteredHashes that were not found in this 504 // match (tmpHashes). 505 for k := range filteredHashes { 506 if tmpHashes[k] == nil { 507 delete(filteredHashes, k) 508 509 // Potentially exit early. 510 select { 511 case <-ctx.Done(): 512 break 513 default: 514 } 515 } 516 } 517 518 return filteredHashes 519 } 520 521 // Keys 522 523 func isTagKey(key []byte) bool { 524 return strings.Count(string(key), tagKeySeparator) == 3 525 } 526 527 func extractValueFromKey(key []byte) string { 528 parts := strings.SplitN(string(key), tagKeySeparator, 3) 529 return parts[1] 530 } 531 532 func keyForEvent(key string, value []byte, result *abci.TxResult) []byte { 533 return []byte(fmt.Sprintf("%s/%s/%d/%d", 534 key, 535 value, 536 result.Height, 537 result.Index, 538 )) 539 } 540 541 func keyForHeight(result *abci.TxResult) []byte { 542 return []byte(fmt.Sprintf("%s/%d/%d/%d", 543 types.TxHeightKey, 544 result.Height, 545 result.Height, 546 result.Index, 547 )) 548 } 549 550 func startKeyForCondition(c query.Condition, height int64) []byte { 551 if height > 0 { 552 return startKey(c.CompositeKey, c.Operand, height) 553 } 554 return startKey(c.CompositeKey, c.Operand) 555 } 556 557 func startKey(fields ...interface{}) []byte { 558 var b bytes.Buffer 559 for _, f := range fields { 560 b.Write([]byte(fmt.Sprintf("%v", f) + tagKeySeparator)) 561 } 562 return b.Bytes() 563 }