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