github.com/franono/tendermint@v0.32.2-0.20200527150959-749313264ce9/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  	"time"
    11  
    12  	dbm "github.com/tendermint/tm-db"
    13  
    14  	"github.com/franono/tendermint/libs/pubsub/query"
    15  	tmstring "github.com/franono/tendermint/libs/strings"
    16  	"github.com/franono/tendermint/state/txindex"
    17  	"github.com/franono/tendermint/types"
    18  )
    19  
    20  const (
    21  	tagKeySeparator = "/"
    22  )
    23  
    24  var _ txindex.TxIndexer = (*TxIndex)(nil)
    25  
    26  // TxIndex is the simplest possible indexer, backed by key-value storage (levelDB).
    27  type TxIndex struct {
    28  	store                dbm.DB
    29  	compositeKeysToIndex []string
    30  	indexAllEvents       bool
    31  }
    32  
    33  // NewTxIndex creates new KV indexer.
    34  func NewTxIndex(store dbm.DB, options ...func(*TxIndex)) *TxIndex {
    35  	txi := &TxIndex{store: store, compositeKeysToIndex: make([]string, 0), indexAllEvents: false}
    36  	for _, o := range options {
    37  		o(txi)
    38  	}
    39  	return txi
    40  }
    41  
    42  // IndexEvents is an option for setting which composite keys to index.
    43  func IndexEvents(compositeKeys []string) func(*TxIndex) {
    44  	return func(txi *TxIndex) {
    45  		txi.compositeKeysToIndex = compositeKeys
    46  	}
    47  }
    48  
    49  // IndexAllEvents is an option for indexing all events.
    50  func IndexAllEvents() func(*TxIndex) {
    51  	return func(txi *TxIndex) {
    52  		txi.indexAllEvents = true
    53  	}
    54  }
    55  
    56  // Get gets transaction from the TxIndex storage and returns it or nil if the
    57  // transaction is not found.
    58  func (txi *TxIndex) Get(hash []byte) (*types.TxResult, error) {
    59  	if len(hash) == 0 {
    60  		return nil, txindex.ErrorEmptyHash
    61  	}
    62  
    63  	rawBytes, err := txi.store.Get(hash)
    64  	if err != nil {
    65  		panic(err)
    66  	}
    67  	if rawBytes == nil {
    68  		return nil, nil
    69  	}
    70  
    71  	txResult := new(types.TxResult)
    72  	err = cdc.UnmarshalBinaryBare(rawBytes, &txResult)
    73  	if err != nil {
    74  		return nil, fmt.Errorf("error reading TxResult: %v", err)
    75  	}
    76  
    77  	return txResult, nil
    78  }
    79  
    80  // AddBatch indexes a batch of transactions using the given list of events. Each
    81  // key that indexed from the tx's events is a composite of the event type and
    82  // the respective attribute's key delimited by a "." (eg. "account.number").
    83  // Any event with an empty type is not indexed.
    84  func (txi *TxIndex) AddBatch(b *txindex.Batch) error {
    85  	storeBatch := txi.store.NewBatch()
    86  	defer storeBatch.Close()
    87  
    88  	for _, result := range b.Ops {
    89  		hash := result.Tx.Hash()
    90  
    91  		// index tx by events
    92  		txi.indexEvents(result, hash, storeBatch)
    93  
    94  		// index tx by height
    95  		if txi.indexAllEvents || tmstring.StringInSlice(types.TxHeightKey, txi.compositeKeysToIndex) {
    96  			storeBatch.Set(keyForHeight(result), hash)
    97  		}
    98  
    99  		// index tx by hash
   100  		rawBytes, err := cdc.MarshalBinaryBare(result)
   101  		if err != nil {
   102  			return err
   103  		}
   104  		storeBatch.Set(hash, rawBytes)
   105  	}
   106  
   107  	storeBatch.WriteSync()
   108  	return nil
   109  }
   110  
   111  // Index indexes a single transaction using the given list of events. Each key
   112  // that indexed from the tx's events is a composite of the event type and the
   113  // respective attribute's key delimited by a "." (eg. "account.number").
   114  // Any event with an empty type is not indexed.
   115  func (txi *TxIndex) Index(result *types.TxResult) error {
   116  	b := txi.store.NewBatch()
   117  	defer b.Close()
   118  
   119  	hash := result.Tx.Hash()
   120  
   121  	// index tx by events
   122  	txi.indexEvents(result, hash, b)
   123  
   124  	// index tx by height
   125  	if txi.indexAllEvents || tmstring.StringInSlice(types.TxHeightKey, txi.compositeKeysToIndex) {
   126  		b.Set(keyForHeight(result), hash)
   127  	}
   128  
   129  	// index tx by hash
   130  	rawBytes, err := cdc.MarshalBinaryBare(result)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	b.Set(hash, rawBytes)
   136  	b.WriteSync()
   137  
   138  	return nil
   139  }
   140  
   141  func (txi *TxIndex) indexEvents(result *types.TxResult, hash []byte, store dbm.SetDeleter) {
   142  	for _, event := range result.Result.Events {
   143  		// only index events with a non-empty type
   144  		if len(event.Type) == 0 {
   145  			continue
   146  		}
   147  
   148  		for _, attr := range event.Attributes {
   149  			if len(attr.Key) == 0 {
   150  				continue
   151  			}
   152  
   153  			compositeTag := fmt.Sprintf("%s.%s", event.Type, string(attr.Key))
   154  			if txi.indexAllEvents || tmstring.StringInSlice(compositeTag, txi.compositeKeysToIndex) || attr.GetIndex() {
   155  				store.Set(keyForEvent(compositeTag, attr.Value, result), hash)
   156  			}
   157  		}
   158  	}
   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) ([]*types.TxResult, error) {
   173  	// Potentially exit early.
   174  	select {
   175  	case <-ctx.Done():
   176  		results := make([]*types.TxResult, 0)
   177  		return results, nil
   178  	default:
   179  	}
   180  
   181  	var hashesInitialized bool
   182  	filteredHashes := make(map[string][]byte)
   183  
   184  	// get a list of conditions (like "tx.height > 5")
   185  	conditions, err := q.Conditions()
   186  	if err != nil {
   187  		return nil, fmt.Errorf("error during parsing conditions from query: %w", err)
   188  	}
   189  
   190  	// if there is a hash condition, return the result immediately
   191  	hash, ok, err := lookForHash(conditions)
   192  	if err != nil {
   193  		return nil, fmt.Errorf("error during searching for a hash in the query: %w", err)
   194  	} else if ok {
   195  		res, err := txi.Get(hash)
   196  		switch {
   197  		case err != nil:
   198  			return []*types.TxResult{}, fmt.Errorf("error while retrieving the result: %w", err)
   199  		case res == nil:
   200  			return []*types.TxResult{}, nil
   201  		default:
   202  			return []*types.TxResult{res}, nil
   203  		}
   204  	}
   205  
   206  	// conditions to skip because they're handled before "everything else"
   207  	skipIndexes := make([]int, 0)
   208  
   209  	// extract ranges
   210  	// if both upper and lower bounds exist, it's better to get them in order not
   211  	// no iterate over kvs that are not within range.
   212  	ranges, rangeIndexes := lookForRanges(conditions)
   213  	if len(ranges) > 0 {
   214  		skipIndexes = append(skipIndexes, rangeIndexes...)
   215  
   216  		for _, r := range ranges {
   217  			if !hashesInitialized {
   218  				filteredHashes = txi.matchRange(ctx, r, startKey(r.key), filteredHashes, true)
   219  				hashesInitialized = true
   220  
   221  				// Ignore any remaining conditions if the first condition resulted
   222  				// in no matches (assuming implicit AND operand).
   223  				if len(filteredHashes) == 0 {
   224  					break
   225  				}
   226  			} else {
   227  				filteredHashes = txi.matchRange(ctx, r, startKey(r.key), filteredHashes, false)
   228  			}
   229  		}
   230  	}
   231  
   232  	// if there is a height condition ("tx.height=3"), extract it
   233  	height := lookForHeight(conditions)
   234  
   235  	// for all other conditions
   236  	for i, c := range conditions {
   237  		if intInSlice(i, skipIndexes) {
   238  			continue
   239  		}
   240  
   241  		if !hashesInitialized {
   242  			filteredHashes = txi.match(ctx, c, startKeyForCondition(c, height), filteredHashes, true)
   243  			hashesInitialized = true
   244  
   245  			// Ignore any remaining conditions if the first condition resulted
   246  			// in no matches (assuming implicit AND operand).
   247  			if len(filteredHashes) == 0 {
   248  				break
   249  			}
   250  		} else {
   251  			filteredHashes = txi.match(ctx, c, startKeyForCondition(c, height), filteredHashes, false)
   252  		}
   253  	}
   254  
   255  	results := make([]*types.TxResult, 0, len(filteredHashes))
   256  	for _, h := range filteredHashes {
   257  		res, err := txi.Get(h)
   258  		if err != nil {
   259  			return nil, fmt.Errorf("failed to get Tx{%X}: %w", h, err)
   260  		}
   261  		results = append(results, res)
   262  
   263  		// Potentially exit early.
   264  		select {
   265  		case <-ctx.Done():
   266  			break
   267  		default:
   268  		}
   269  	}
   270  
   271  	return results, nil
   272  }
   273  
   274  func lookForHash(conditions []query.Condition) (hash []byte, ok bool, err error) {
   275  	for _, c := range conditions {
   276  		if c.CompositeKey == types.TxHashKey {
   277  			decoded, err := hex.DecodeString(c.Operand.(string))
   278  			return decoded, true, err
   279  		}
   280  	}
   281  	return
   282  }
   283  
   284  // lookForHeight returns a height if there is an "height=X" condition.
   285  func lookForHeight(conditions []query.Condition) (height int64) {
   286  	for _, c := range conditions {
   287  		if c.CompositeKey == types.TxHeightKey && c.Op == query.OpEqual {
   288  			return c.Operand.(int64)
   289  		}
   290  	}
   291  	return 0
   292  }
   293  
   294  // special map to hold range conditions
   295  // Example: account.number => queryRange{lowerBound: 1, upperBound: 5}
   296  type queryRanges map[string]queryRange
   297  
   298  type queryRange struct {
   299  	lowerBound        interface{} // int || time.Time
   300  	upperBound        interface{} // int || time.Time
   301  	key               string
   302  	includeLowerBound bool
   303  	includeUpperBound bool
   304  }
   305  
   306  func (r queryRange) lowerBoundValue() interface{} {
   307  	if r.lowerBound == nil {
   308  		return nil
   309  	}
   310  
   311  	if r.includeLowerBound {
   312  		return r.lowerBound
   313  	}
   314  
   315  	switch t := r.lowerBound.(type) {
   316  	case int64:
   317  		return t + 1
   318  	case time.Time:
   319  		return t.Unix() + 1
   320  	default:
   321  		panic("not implemented")
   322  	}
   323  }
   324  
   325  func (r queryRange) AnyBound() interface{} {
   326  	if r.lowerBound != nil {
   327  		return r.lowerBound
   328  	}
   329  
   330  	return r.upperBound
   331  }
   332  
   333  func (r queryRange) upperBoundValue() interface{} {
   334  	if r.upperBound == nil {
   335  		return nil
   336  	}
   337  
   338  	if r.includeUpperBound {
   339  		return r.upperBound
   340  	}
   341  
   342  	switch t := r.upperBound.(type) {
   343  	case int64:
   344  		return t - 1
   345  	case time.Time:
   346  		return t.Unix() - 1
   347  	default:
   348  		panic("not implemented")
   349  	}
   350  }
   351  
   352  func lookForRanges(conditions []query.Condition) (ranges queryRanges, indexes []int) {
   353  	ranges = make(queryRanges)
   354  	for i, c := range conditions {
   355  		if isRangeOperation(c.Op) {
   356  			r, ok := ranges[c.CompositeKey]
   357  			if !ok {
   358  				r = queryRange{key: c.CompositeKey}
   359  			}
   360  			switch c.Op {
   361  			case query.OpGreater:
   362  				r.lowerBound = c.Operand
   363  			case query.OpGreaterEqual:
   364  				r.includeLowerBound = true
   365  				r.lowerBound = c.Operand
   366  			case query.OpLess:
   367  				r.upperBound = c.Operand
   368  			case query.OpLessEqual:
   369  				r.includeUpperBound = true
   370  				r.upperBound = c.Operand
   371  			}
   372  			ranges[c.CompositeKey] = r
   373  			indexes = append(indexes, i)
   374  		}
   375  	}
   376  	return ranges, indexes
   377  }
   378  
   379  func isRangeOperation(op query.Operator) bool {
   380  	switch op {
   381  	case query.OpGreater, query.OpGreaterEqual, query.OpLess, query.OpLessEqual:
   382  		return true
   383  	default:
   384  		return false
   385  	}
   386  }
   387  
   388  // match returns all matching txs by hash that meet a given condition and start
   389  // key. An already filtered result (filteredHashes) is provided such that any
   390  // non-intersecting matches are removed.
   391  //
   392  // NOTE: filteredHashes may be empty if no previous condition has matched.
   393  func (txi *TxIndex) match(
   394  	ctx context.Context,
   395  	c query.Condition,
   396  	startKeyBz []byte,
   397  	filteredHashes map[string][]byte,
   398  	firstRun bool,
   399  ) map[string][]byte {
   400  	// A previous match was attempted but resulted in no matches, so we return
   401  	// no matches (assuming AND operand).
   402  	if !firstRun && len(filteredHashes) == 0 {
   403  		return filteredHashes
   404  	}
   405  
   406  	tmpHashes := make(map[string][]byte)
   407  
   408  	switch {
   409  	case c.Op == query.OpEqual:
   410  		it, err := dbm.IteratePrefix(txi.store, startKeyBz)
   411  		if err != nil {
   412  			panic(err)
   413  		}
   414  		defer it.Close()
   415  
   416  		for ; it.Valid(); it.Next() {
   417  			tmpHashes[string(it.Value())] = it.Value()
   418  
   419  			// Potentially exit early.
   420  			select {
   421  			case <-ctx.Done():
   422  				break
   423  			default:
   424  			}
   425  		}
   426  
   427  	case c.Op == query.OpContains:
   428  		// XXX: startKey does not apply here.
   429  		// For example, if startKey = "account.owner/an/" and search query = "account.owner CONTAINS an"
   430  		// we can't iterate with prefix "account.owner/an/" because we might miss keys like "account.owner/Ulan/"
   431  		it, err := dbm.IteratePrefix(txi.store, startKey(c.CompositeKey))
   432  		if err != nil {
   433  			panic(err)
   434  		}
   435  		defer it.Close()
   436  
   437  		for ; it.Valid(); it.Next() {
   438  			if !isTagKey(it.Key()) {
   439  				continue
   440  			}
   441  
   442  			if strings.Contains(extractValueFromKey(it.Key()), c.Operand.(string)) {
   443  				tmpHashes[string(it.Value())] = it.Value()
   444  			}
   445  
   446  			// Potentially exit early.
   447  			select {
   448  			case <-ctx.Done():
   449  				break
   450  			default:
   451  			}
   452  		}
   453  	default:
   454  		panic("other operators should be handled already")
   455  	}
   456  
   457  	if len(tmpHashes) == 0 || firstRun {
   458  		// Either:
   459  		//
   460  		// 1. Regardless if a previous match was attempted, which may have had
   461  		// results, but no match was found for the current condition, then we
   462  		// return no matches (assuming AND operand).
   463  		//
   464  		// 2. A previous match was not attempted, so we return all results.
   465  		return tmpHashes
   466  	}
   467  
   468  	// Remove/reduce matches in filteredHashes that were not found in this
   469  	// match (tmpHashes).
   470  	for k := range filteredHashes {
   471  		if tmpHashes[k] == nil {
   472  			delete(filteredHashes, k)
   473  
   474  			// Potentially exit early.
   475  			select {
   476  			case <-ctx.Done():
   477  				break
   478  			default:
   479  			}
   480  		}
   481  	}
   482  
   483  	return filteredHashes
   484  }
   485  
   486  // matchRange returns all matching txs by hash that meet a given queryRange and
   487  // start key. An already filtered result (filteredHashes) is provided such that
   488  // any non-intersecting matches are removed.
   489  //
   490  // NOTE: filteredHashes may be empty if no previous condition has matched.
   491  func (txi *TxIndex) matchRange(
   492  	ctx context.Context,
   493  	r queryRange,
   494  	startKey []byte,
   495  	filteredHashes map[string][]byte,
   496  	firstRun bool,
   497  ) map[string][]byte {
   498  	// A previous match was attempted but resulted in no matches, so we return
   499  	// no matches (assuming AND operand).
   500  	if !firstRun && len(filteredHashes) == 0 {
   501  		return filteredHashes
   502  	}
   503  
   504  	tmpHashes := make(map[string][]byte)
   505  	lowerBound := r.lowerBoundValue()
   506  	upperBound := r.upperBoundValue()
   507  
   508  	it, err := dbm.IteratePrefix(txi.store, startKey)
   509  	if err != nil {
   510  		panic(err)
   511  	}
   512  	defer it.Close()
   513  
   514  LOOP:
   515  	for ; it.Valid(); it.Next() {
   516  		if !isTagKey(it.Key()) {
   517  			continue
   518  		}
   519  
   520  		if _, ok := r.AnyBound().(int64); ok {
   521  			v, err := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64)
   522  			if err != nil {
   523  				continue LOOP
   524  			}
   525  
   526  			include := true
   527  			if lowerBound != nil && v < lowerBound.(int64) {
   528  				include = false
   529  			}
   530  
   531  			if upperBound != nil && v > upperBound.(int64) {
   532  				include = false
   533  			}
   534  
   535  			if include {
   536  				tmpHashes[string(it.Value())] = it.Value()
   537  			}
   538  
   539  			// XXX: passing time in a ABCI Events is not yet implemented
   540  			// case time.Time:
   541  			// 	v := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64)
   542  			// 	if v == r.upperBound {
   543  			// 		break
   544  			// 	}
   545  		}
   546  
   547  		// Potentially exit early.
   548  		select {
   549  		case <-ctx.Done():
   550  			break
   551  		default:
   552  		}
   553  	}
   554  
   555  	if len(tmpHashes) == 0 || firstRun {
   556  		// Either:
   557  		//
   558  		// 1. Regardless if a previous match was attempted, which may have had
   559  		// results, but no match was found for the current condition, then we
   560  		// return no matches (assuming AND operand).
   561  		//
   562  		// 2. A previous match was not attempted, so we return all results.
   563  		return tmpHashes
   564  	}
   565  
   566  	// Remove/reduce matches in filteredHashes that were not found in this
   567  	// match (tmpHashes).
   568  	for k := range filteredHashes {
   569  		if tmpHashes[k] == nil {
   570  			delete(filteredHashes, k)
   571  
   572  			// Potentially exit early.
   573  			select {
   574  			case <-ctx.Done():
   575  				break
   576  			default:
   577  			}
   578  		}
   579  	}
   580  
   581  	return filteredHashes
   582  }
   583  
   584  ///////////////////////////////////////////////////////////////////////////////
   585  // Keys
   586  
   587  func isTagKey(key []byte) bool {
   588  	return strings.Count(string(key), tagKeySeparator) == 3
   589  }
   590  
   591  func extractValueFromKey(key []byte) string {
   592  	parts := strings.SplitN(string(key), tagKeySeparator, 3)
   593  	return parts[1]
   594  }
   595  
   596  func keyForEvent(key string, value []byte, result *types.TxResult) []byte {
   597  	return []byte(fmt.Sprintf("%s/%s/%d/%d",
   598  		key,
   599  		value,
   600  		result.Height,
   601  		result.Index,
   602  	))
   603  }
   604  
   605  func keyForHeight(result *types.TxResult) []byte {
   606  	return []byte(fmt.Sprintf("%s/%d/%d/%d",
   607  		types.TxHeightKey,
   608  		result.Height,
   609  		result.Height,
   610  		result.Index,
   611  	))
   612  }
   613  
   614  func startKeyForCondition(c query.Condition, height int64) []byte {
   615  	if height > 0 {
   616  		return startKey(c.CompositeKey, c.Operand, height)
   617  	}
   618  	return startKey(c.CompositeKey, c.Operand)
   619  }
   620  
   621  func startKey(fields ...interface{}) []byte {
   622  	var b bytes.Buffer
   623  	for _, f := range fields {
   624  		b.Write([]byte(fmt.Sprintf("%v", f) + tagKeySeparator))
   625  	}
   626  	return b.Bytes()
   627  }