github.com/evdatsion/aphelion-dpos-bft@v0.32.1/state/txindex/kv/kv.go (about) 1 package kv 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "fmt" 7 "sort" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/pkg/errors" 13 14 cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common" 15 dbm "github.com/evdatsion/aphelion-dpos-bft/libs/db" 16 "github.com/evdatsion/aphelion-dpos-bft/libs/pubsub/query" 17 "github.com/evdatsion/aphelion-dpos-bft/state/txindex" 18 "github.com/evdatsion/aphelion-dpos-bft/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 tagsToIndex []string 31 indexAllTags bool 32 } 33 34 // NewTxIndex creates new KV indexer. 35 func NewTxIndex(store dbm.DB, options ...func(*TxIndex)) *TxIndex { 36 txi := &TxIndex{store: store, tagsToIndex: make([]string, 0), indexAllTags: false} 37 for _, o := range options { 38 o(txi) 39 } 40 return txi 41 } 42 43 // IndexTags is an option for setting which tags to index. 44 func IndexTags(tags []string) func(*TxIndex) { 45 return func(txi *TxIndex) { 46 txi.tagsToIndex = tags 47 } 48 } 49 50 // IndexAllTags is an option for indexing all tags. 51 func IndexAllTags() func(*TxIndex) { 52 return func(txi *TxIndex) { 53 txi.indexAllTags = true 54 } 55 } 56 57 // Get gets transaction from the TxIndex storage and returns it or nil if the 58 // transaction is not found. 59 func (txi *TxIndex) Get(hash []byte) (*types.TxResult, error) { 60 if len(hash) == 0 { 61 return nil, txindex.ErrorEmptyHash 62 } 63 64 rawBytes := txi.store.Get(hash) 65 if rawBytes == nil { 66 return nil, nil 67 } 68 69 txResult := new(types.TxResult) 70 err := cdc.UnmarshalBinaryBare(rawBytes, &txResult) 71 if err != nil { 72 return nil, fmt.Errorf("Error reading TxResult: %v", err) 73 } 74 75 return txResult, nil 76 } 77 78 // AddBatch indexes a batch of transactions using the given list of events. Each 79 // key that indexed from the tx's events is a composite of the event type and 80 // the respective attribute's key delimited by a "." (eg. "account.number"). 81 // Any event with an empty type is not indexed. 82 func (txi *TxIndex) AddBatch(b *txindex.Batch) error { 83 storeBatch := txi.store.NewBatch() 84 defer storeBatch.Close() 85 86 for _, result := range b.Ops { 87 hash := result.Tx.Hash() 88 89 // index tx by events 90 txi.indexEvents(result, hash, storeBatch) 91 92 // index tx by height 93 if txi.indexAllTags || cmn.StringInSlice(types.TxHeightKey, txi.tagsToIndex) { 94 storeBatch.Set(keyForHeight(result), hash) 95 } 96 97 // index tx by hash 98 rawBytes, err := cdc.MarshalBinaryBare(result) 99 if err != nil { 100 return err 101 } 102 storeBatch.Set(hash, rawBytes) 103 } 104 105 storeBatch.Write() 106 return nil 107 } 108 109 // Index indexes a single transaction using the given list of events. Each key 110 // that indexed from the tx's events is a composite of the event type and the 111 // respective attribute's key delimited by a "." (eg. "account.number"). 112 // Any event with an empty type is not indexed. 113 func (txi *TxIndex) Index(result *types.TxResult) error { 114 b := txi.store.NewBatch() 115 defer b.Close() 116 117 hash := result.Tx.Hash() 118 119 // index tx by events 120 txi.indexEvents(result, hash, b) 121 122 // index tx by height 123 if txi.indexAllTags || cmn.StringInSlice(types.TxHeightKey, txi.tagsToIndex) { 124 b.Set(keyForHeight(result), hash) 125 } 126 127 // index tx by hash 128 rawBytes, err := cdc.MarshalBinaryBare(result) 129 if err != nil { 130 return err 131 } 132 133 b.Set(hash, rawBytes) 134 b.Write() 135 136 return nil 137 } 138 139 func (txi *TxIndex) indexEvents(result *types.TxResult, hash []byte, store dbm.SetDeleter) { 140 for _, event := range result.Result.Events { 141 // only index events with a non-empty type 142 if len(event.Type) == 0 { 143 continue 144 } 145 146 for _, attr := range event.Attributes { 147 if len(attr.Key) == 0 { 148 continue 149 } 150 151 compositeTag := fmt.Sprintf("%s.%s", event.Type, string(attr.Key)) 152 if txi.indexAllTags || cmn.StringInSlice(compositeTag, txi.tagsToIndex) { 153 store.Set(keyForEvent(compositeTag, attr.Value, result), hash) 154 } 155 } 156 } 157 } 158 159 // Search performs a search using the given query. It breaks the query into 160 // conditions (like "tx.height > 5"). For each condition, it queries the DB 161 // index. One special use cases here: (1) if "tx.hash" is found, it returns tx 162 // result for it (2) for range queries it is better for the client to provide 163 // both lower and upper bounds, so we are not performing a full scan. Results 164 // from querying indexes are then intersected and returned to the caller. 165 func (txi *TxIndex) Search(q *query.Query) ([]*types.TxResult, error) { 166 var hashes [][]byte 167 var hashesInitialized bool 168 169 // get a list of conditions (like "tx.height > 5") 170 conditions := q.Conditions() 171 172 // if there is a hash condition, return the result immediately 173 hash, err, ok := lookForHash(conditions) 174 if err != nil { 175 return nil, errors.Wrap(err, "error during searching for a hash in the query") 176 } else if ok { 177 res, err := txi.Get(hash) 178 if res == nil { 179 return []*types.TxResult{}, nil 180 } 181 return []*types.TxResult{res}, errors.Wrap(err, "error while retrieving the result") 182 } 183 184 // conditions to skip because they're handled before "everything else" 185 skipIndexes := make([]int, 0) 186 187 // extract ranges 188 // if both upper and lower bounds exist, it's better to get them in order not 189 // no iterate over kvs that are not within range. 190 ranges, rangeIndexes := lookForRanges(conditions) 191 if len(ranges) > 0 { 192 skipIndexes = append(skipIndexes, rangeIndexes...) 193 194 for _, r := range ranges { 195 if !hashesInitialized { 196 hashes = txi.matchRange(r, startKey(r.key)) 197 hashesInitialized = true 198 } else { 199 hashes = intersect(hashes, txi.matchRange(r, startKey(r.key))) 200 } 201 } 202 } 203 204 // if there is a height condition ("tx.height=3"), extract it 205 height := lookForHeight(conditions) 206 207 // for all other conditions 208 for i, c := range conditions { 209 if cmn.IntInSlice(i, skipIndexes) { 210 continue 211 } 212 213 if !hashesInitialized { 214 hashes = txi.match(c, startKeyForCondition(c, height)) 215 hashesInitialized = true 216 } else { 217 hashes = intersect(hashes, txi.match(c, startKeyForCondition(c, height))) 218 } 219 } 220 221 results := make([]*types.TxResult, len(hashes)) 222 i := 0 223 for _, h := range hashes { 224 results[i], err = txi.Get(h) 225 if err != nil { 226 return nil, errors.Wrapf(err, "failed to get Tx{%X}", h) 227 } 228 i++ 229 } 230 231 // sort by height & index by default 232 sort.Slice(results, func(i, j int) bool { 233 if results[i].Height == results[j].Height { 234 return results[i].Index < results[j].Index 235 } 236 return results[i].Height < results[j].Height 237 }) 238 239 return results, nil 240 } 241 242 func lookForHash(conditions []query.Condition) (hash []byte, err error, ok bool) { 243 for _, c := range conditions { 244 if c.Tag == types.TxHashKey { 245 decoded, err := hex.DecodeString(c.Operand.(string)) 246 return decoded, err, true 247 } 248 } 249 return 250 } 251 252 // lookForHeight returns a height if there is an "height=X" condition. 253 func lookForHeight(conditions []query.Condition) (height int64) { 254 for _, c := range conditions { 255 if c.Tag == types.TxHeightKey && c.Op == query.OpEqual { 256 return c.Operand.(int64) 257 } 258 } 259 return 0 260 } 261 262 // special map to hold range conditions 263 // Example: account.number => queryRange{lowerBound: 1, upperBound: 5} 264 type queryRanges map[string]queryRange 265 266 type queryRange struct { 267 key string 268 lowerBound interface{} // int || time.Time 269 includeLowerBound bool 270 upperBound interface{} // int || time.Time 271 includeUpperBound bool 272 } 273 274 func (r queryRange) lowerBoundValue() interface{} { 275 if r.lowerBound == nil { 276 return nil 277 } 278 279 if r.includeLowerBound { 280 return r.lowerBound 281 } else { 282 switch t := r.lowerBound.(type) { 283 case int64: 284 return t + 1 285 case time.Time: 286 return t.Unix() + 1 287 default: 288 panic("not implemented") 289 } 290 } 291 } 292 293 func (r queryRange) AnyBound() interface{} { 294 if r.lowerBound != nil { 295 return r.lowerBound 296 } else { 297 return r.upperBound 298 } 299 } 300 301 func (r queryRange) upperBoundValue() interface{} { 302 if r.upperBound == nil { 303 return nil 304 } 305 306 if r.includeUpperBound { 307 return r.upperBound 308 } else { 309 switch t := r.upperBound.(type) { 310 case int64: 311 return t - 1 312 case time.Time: 313 return t.Unix() - 1 314 default: 315 panic("not implemented") 316 } 317 } 318 } 319 320 func lookForRanges(conditions []query.Condition) (ranges queryRanges, indexes []int) { 321 ranges = make(queryRanges) 322 for i, c := range conditions { 323 if isRangeOperation(c.Op) { 324 r, ok := ranges[c.Tag] 325 if !ok { 326 r = queryRange{key: c.Tag} 327 } 328 switch c.Op { 329 case query.OpGreater: 330 r.lowerBound = c.Operand 331 case query.OpGreaterEqual: 332 r.includeLowerBound = true 333 r.lowerBound = c.Operand 334 case query.OpLess: 335 r.upperBound = c.Operand 336 case query.OpLessEqual: 337 r.includeUpperBound = true 338 r.upperBound = c.Operand 339 } 340 ranges[c.Tag] = r 341 indexes = append(indexes, i) 342 } 343 } 344 return ranges, indexes 345 } 346 347 func isRangeOperation(op query.Operator) bool { 348 switch op { 349 case query.OpGreater, query.OpGreaterEqual, query.OpLess, query.OpLessEqual: 350 return true 351 default: 352 return false 353 } 354 } 355 356 func (txi *TxIndex) match(c query.Condition, startKeyBz []byte) (hashes [][]byte) { 357 if c.Op == query.OpEqual { 358 it := dbm.IteratePrefix(txi.store, startKeyBz) 359 defer it.Close() 360 for ; it.Valid(); it.Next() { 361 hashes = append(hashes, it.Value()) 362 } 363 } else if c.Op == query.OpContains { 364 // XXX: startKey does not apply here. 365 // For example, if startKey = "account.owner/an/" and search query = "account.owner CONTAINS an" 366 // we can't iterate with prefix "account.owner/an/" because we might miss keys like "account.owner/Ulan/" 367 it := dbm.IteratePrefix(txi.store, startKey(c.Tag)) 368 defer it.Close() 369 for ; it.Valid(); it.Next() { 370 if !isTagKey(it.Key()) { 371 continue 372 } 373 if strings.Contains(extractValueFromKey(it.Key()), c.Operand.(string)) { 374 hashes = append(hashes, it.Value()) 375 } 376 } 377 } else { 378 panic("other operators should be handled already") 379 } 380 return 381 } 382 383 func (txi *TxIndex) matchRange(r queryRange, startKey []byte) (hashes [][]byte) { 384 // create a map to prevent duplicates 385 hashesMap := make(map[string][]byte) 386 387 lowerBound := r.lowerBoundValue() 388 upperBound := r.upperBoundValue() 389 390 it := dbm.IteratePrefix(txi.store, startKey) 391 defer it.Close() 392 LOOP: 393 for ; it.Valid(); it.Next() { 394 if !isTagKey(it.Key()) { 395 continue 396 } 397 switch r.AnyBound().(type) { 398 case int64: 399 v, err := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64) 400 if err != nil { 401 continue LOOP 402 } 403 include := true 404 if lowerBound != nil && v < lowerBound.(int64) { 405 include = false 406 } 407 if upperBound != nil && v > upperBound.(int64) { 408 include = false 409 } 410 if include { 411 hashesMap[fmt.Sprintf("%X", it.Value())] = it.Value() 412 } 413 // XXX: passing time in a ABCI Tags is not yet implemented 414 // case time.Time: 415 // v := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64) 416 // if v == r.upperBound { 417 // break 418 // } 419 } 420 } 421 hashes = make([][]byte, len(hashesMap)) 422 i := 0 423 for _, h := range hashesMap { 424 hashes[i] = h 425 i++ 426 } 427 return 428 } 429 430 /////////////////////////////////////////////////////////////////////////////// 431 // Keys 432 433 func isTagKey(key []byte) bool { 434 return strings.Count(string(key), tagKeySeparator) == 3 435 } 436 437 func extractValueFromKey(key []byte) string { 438 parts := strings.SplitN(string(key), tagKeySeparator, 3) 439 return parts[1] 440 } 441 442 func keyForEvent(key string, value []byte, result *types.TxResult) []byte { 443 return []byte(fmt.Sprintf("%s/%s/%d/%d", 444 key, 445 value, 446 result.Height, 447 result.Index, 448 )) 449 } 450 451 func keyForHeight(result *types.TxResult) []byte { 452 return []byte(fmt.Sprintf("%s/%d/%d/%d", 453 types.TxHeightKey, 454 result.Height, 455 result.Height, 456 result.Index, 457 )) 458 } 459 460 func startKeyForCondition(c query.Condition, height int64) []byte { 461 if height > 0 { 462 return startKey(c.Tag, c.Operand, height) 463 } 464 return startKey(c.Tag, c.Operand) 465 } 466 467 func startKey(fields ...interface{}) []byte { 468 var b bytes.Buffer 469 for _, f := range fields { 470 b.Write([]byte(fmt.Sprintf("%v", f) + tagKeySeparator)) 471 } 472 return b.Bytes() 473 } 474 475 /////////////////////////////////////////////////////////////////////////////// 476 // Utils 477 478 func intersect(as, bs [][]byte) [][]byte { 479 i := make([][]byte, 0, cmn.MinInt(len(as), len(bs))) 480 for _, a := range as { 481 for _, b := range bs { 482 if bytes.Equal(a, b) { 483 i = append(i, a) 484 } 485 } 486 } 487 return i 488 }