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  }