github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/internal/state/indexer/tx/kv/kv.go (about)

     1  package kv
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/gogo/protobuf/proto"
    11  	"github.com/google/orderedcode"
    12  	dbm "github.com/tendermint/tm-db"
    13  
    14  	abci "github.com/ari-anchor/sei-tendermint/abci/types"
    15  	"github.com/ari-anchor/sei-tendermint/internal/pubsub/query"
    16  	"github.com/ari-anchor/sei-tendermint/internal/pubsub/query/syntax"
    17  	indexer "github.com/ari-anchor/sei-tendermint/internal/state/indexer"
    18  	"github.com/ari-anchor/sei-tendermint/types"
    19  )
    20  
    21  var _ indexer.TxIndexer = (*TxIndex)(nil)
    22  
    23  // TxIndex is the simplest possible indexer
    24  // It is backed by two kv stores:
    25  // 1. txhash - result  (primary key)
    26  // 2. event - txhash   (secondary key)
    27  type TxIndex struct {
    28  	store dbm.DB
    29  }
    30  
    31  // NewTxIndex creates new KV indexer.
    32  func NewTxIndex(store dbm.DB) *TxIndex {
    33  	return &TxIndex{
    34  		store: store,
    35  	}
    36  }
    37  
    38  // Get gets transaction from the TxIndex storage and returns it or nil if the
    39  // transaction is not found.
    40  func (txi *TxIndex) Get(hash []byte) (*abci.TxResult, error) {
    41  	if len(hash) == 0 {
    42  		return nil, indexer.ErrorEmptyHash
    43  	}
    44  
    45  	rawBytes, err := txi.store.Get(primaryKey(hash))
    46  	if err != nil {
    47  		panic(err)
    48  	}
    49  	if rawBytes == nil {
    50  		return nil, nil
    51  	}
    52  
    53  	txResult := new(abci.TxResult)
    54  	err = proto.Unmarshal(rawBytes, txResult)
    55  	if err != nil {
    56  		return nil, fmt.Errorf("error reading TxResult: %w", err)
    57  	}
    58  
    59  	return txResult, nil
    60  }
    61  
    62  // Index indexes transactions using the given list of events. Each key
    63  // that indexed from the tx's events is a composite of the event type and the
    64  // respective attribute's key delimited by a "." (eg. "account.number").
    65  // Any event with an empty type is not indexed.
    66  func (txi *TxIndex) Index(results []*abci.TxResult) error {
    67  	b := txi.store.NewBatch()
    68  	defer b.Close()
    69  
    70  	for _, result := range results {
    71  		hash := types.Tx(result.Tx).Hash()
    72  
    73  		// index tx by events
    74  		err := txi.indexEvents(result, hash, b)
    75  		if err != nil {
    76  			return err
    77  		}
    78  
    79  		// index by height (always)
    80  		err = b.Set(KeyFromHeight(result), hash)
    81  		if err != nil {
    82  			return err
    83  		}
    84  
    85  		rawBytes, err := proto.Marshal(result)
    86  		if err != nil {
    87  			return err
    88  		}
    89  		// index by hash (always)
    90  		err = b.Set(primaryKey(hash), rawBytes)
    91  		if err != nil {
    92  			return err
    93  		}
    94  	}
    95  
    96  	return b.WriteSync()
    97  }
    98  
    99  func (txi *TxIndex) indexEvents(result *abci.TxResult, hash []byte, store dbm.Batch) error {
   100  	for _, event := range result.Result.Events {
   101  		// only index events with a non-empty type
   102  		if len(event.Type) == 0 {
   103  			continue
   104  		}
   105  
   106  		for _, attr := range event.Attributes {
   107  			if len(attr.Key) == 0 {
   108  				continue
   109  			}
   110  
   111  			// index if `index: true` is set
   112  			compositeTag := fmt.Sprintf("%s.%s", event.Type, string(attr.Key))
   113  			// ensure event does not conflict with a reserved prefix key
   114  			if compositeTag == types.TxHashKey || compositeTag == types.TxHeightKey {
   115  				return fmt.Errorf("event type and attribute key \"%s\" is reserved; please use a different key", compositeTag)
   116  			}
   117  			if attr.GetIndex() {
   118  				err := store.Set(keyFromEvent(compositeTag, string(attr.Value), result), hash)
   119  				if err != nil {
   120  					return err
   121  				}
   122  			}
   123  		}
   124  	}
   125  
   126  	return nil
   127  }
   128  
   129  // Search performs a search using the given query.
   130  //
   131  // It breaks the query into conditions (like "tx.height > 5"). For each
   132  // condition, it queries the DB index. One special use cases here: (1) if
   133  // "tx.hash" is found, it returns tx result for it (2) for range queries it is
   134  // better for the client to provide both lower and upper bounds, so we are not
   135  // performing a full scan. Results from querying indexes are then intersected
   136  // and returned to the caller, in no particular order.
   137  //
   138  // Search will exit early and return any result fetched so far,
   139  // when a message is received on the context chan.
   140  func (txi *TxIndex) Search(ctx context.Context, q *query.Query) ([]*abci.TxResult, error) {
   141  	select {
   142  	case <-ctx.Done():
   143  		return make([]*abci.TxResult, 0), nil
   144  
   145  	default:
   146  	}
   147  
   148  	var hashesInitialized bool
   149  	filteredHashes := make(map[string][]byte)
   150  
   151  	// get a list of conditions (like "tx.height > 5")
   152  	conditions := q.Syntax()
   153  
   154  	// if there is a hash condition, return the result immediately
   155  	hash, ok, err := lookForHash(conditions)
   156  	if err != nil {
   157  		return nil, fmt.Errorf("error during searching for a hash in the query: %w", err)
   158  	} else if ok {
   159  		res, err := txi.Get(hash)
   160  		switch {
   161  		case err != nil:
   162  			return []*abci.TxResult{}, fmt.Errorf("error while retrieving the result: %w", err)
   163  		case res == nil:
   164  			return []*abci.TxResult{}, nil
   165  		default:
   166  			return []*abci.TxResult{res}, nil
   167  		}
   168  	}
   169  
   170  	// conditions to skip because they're handled before "everything else"
   171  	skipIndexes := make([]int, 0)
   172  
   173  	// extract ranges
   174  	// if both upper and lower bounds exist, it's better to get them in order not
   175  	// no iterate over kvs that are not within range.
   176  	ranges, rangeIndexes := indexer.LookForRanges(conditions)
   177  	if len(ranges) > 0 {
   178  		skipIndexes = append(skipIndexes, rangeIndexes...)
   179  
   180  		for _, qr := range ranges {
   181  			if !hashesInitialized {
   182  				filteredHashes = txi.matchRange(ctx, qr, prefixFromCompositeKey(qr.Key), filteredHashes, true)
   183  				hashesInitialized = true
   184  
   185  				// Ignore any remaining conditions if the first condition resulted
   186  				// in no matches (assuming implicit AND operand).
   187  				if len(filteredHashes) == 0 {
   188  					break
   189  				}
   190  			} else {
   191  				filteredHashes = txi.matchRange(ctx, qr, prefixFromCompositeKey(qr.Key), filteredHashes, false)
   192  			}
   193  		}
   194  	}
   195  
   196  	// if there is a height condition ("tx.height=3"), extract it
   197  	height := lookForHeight(conditions)
   198  
   199  	// for all other conditions
   200  	for i, c := range conditions {
   201  		if intInSlice(i, skipIndexes) {
   202  			continue
   203  		}
   204  
   205  		if !hashesInitialized {
   206  			filteredHashes = txi.match(ctx, c, prefixForCondition(c, height), filteredHashes, true)
   207  			hashesInitialized = true
   208  
   209  			// Ignore any remaining conditions if the first condition resulted
   210  			// in no matches (assuming implicit AND operand).
   211  			if len(filteredHashes) == 0 {
   212  				break
   213  			}
   214  		} else {
   215  			filteredHashes = txi.match(ctx, c, prefixForCondition(c, height), filteredHashes, false)
   216  		}
   217  	}
   218  
   219  	results := make([]*abci.TxResult, 0, len(filteredHashes))
   220  hashes:
   221  	for _, h := range filteredHashes {
   222  		res, err := txi.Get(h)
   223  		if err != nil {
   224  			return nil, fmt.Errorf("failed to get Tx{%X}: %w", h, err)
   225  		}
   226  		results = append(results, res)
   227  
   228  		// Potentially exit early.
   229  		select {
   230  		case <-ctx.Done():
   231  			break hashes
   232  		default:
   233  		}
   234  	}
   235  
   236  	return results, nil
   237  }
   238  
   239  func lookForHash(conditions []syntax.Condition) (hash []byte, ok bool, err error) {
   240  	for _, c := range conditions {
   241  		if c.Tag == types.TxHashKey {
   242  			decoded, err := hex.DecodeString(c.Arg.Value())
   243  			return decoded, true, err
   244  		}
   245  	}
   246  	return
   247  }
   248  
   249  // lookForHeight returns a height if there is an "height=X" condition.
   250  func lookForHeight(conditions []syntax.Condition) (height int64) {
   251  	for _, c := range conditions {
   252  		if c.Tag == types.TxHeightKey && c.Op == syntax.TEq {
   253  			return int64(c.Arg.Number())
   254  		}
   255  	}
   256  	return 0
   257  }
   258  
   259  // match returns all matching txs by hash that meet a given condition and start
   260  // key. An already filtered result (filteredHashes) is provided such that any
   261  // non-intersecting matches are removed.
   262  //
   263  // NOTE: filteredHashes may be empty if no previous condition has matched.
   264  func (txi *TxIndex) match(
   265  	ctx context.Context,
   266  	c syntax.Condition,
   267  	startKeyBz []byte,
   268  	filteredHashes map[string][]byte,
   269  	firstRun bool,
   270  ) map[string][]byte {
   271  	// A previous match was attempted but resulted in no matches, so we return
   272  	// no matches (assuming AND operand).
   273  	if !firstRun && len(filteredHashes) == 0 {
   274  		return filteredHashes
   275  	}
   276  
   277  	tmpHashes := make(map[string][]byte)
   278  
   279  	switch {
   280  	case c.Op == syntax.TEq:
   281  		it, err := dbm.IteratePrefix(txi.store, startKeyBz)
   282  		if err != nil {
   283  			panic(err)
   284  		}
   285  		defer it.Close()
   286  
   287  	iterEqual:
   288  		for ; it.Valid(); it.Next() {
   289  			tmpHashes[string(it.Value())] = it.Value()
   290  
   291  			// Potentially exit early.
   292  			select {
   293  			case <-ctx.Done():
   294  				break iterEqual
   295  			default:
   296  			}
   297  		}
   298  		if err := it.Error(); err != nil {
   299  			panic(err)
   300  		}
   301  
   302  	case c.Op == syntax.TExists:
   303  		// XXX: can't use startKeyBz here because c.Operand is nil
   304  		// (e.g. "account.owner/<nil>/" won't match w/ a single row)
   305  		it, err := dbm.IteratePrefix(txi.store, prefixFromCompositeKey(c.Tag))
   306  		if err != nil {
   307  			panic(err)
   308  		}
   309  		defer it.Close()
   310  
   311  	iterExists:
   312  		for ; it.Valid(); it.Next() {
   313  			tmpHashes[string(it.Value())] = it.Value()
   314  
   315  			// Potentially exit early.
   316  			select {
   317  			case <-ctx.Done():
   318  				break iterExists
   319  			default:
   320  			}
   321  		}
   322  		if err := it.Error(); err != nil {
   323  			panic(err)
   324  		}
   325  
   326  	case c.Op == syntax.TContains:
   327  		// XXX: startKey does not apply here.
   328  		// For example, if startKey = "account.owner/an/" and search query = "account.owner CONTAINS an"
   329  		// we can't iterate with prefix "account.owner/an/" because we might miss keys like "account.owner/Ulan/"
   330  		it, err := dbm.IteratePrefix(txi.store, prefixFromCompositeKey(c.Tag))
   331  		if err != nil {
   332  			panic(err)
   333  		}
   334  		defer it.Close()
   335  
   336  	iterContains:
   337  		for ; it.Valid(); it.Next() {
   338  			value, err := parseValueFromKey(it.Key())
   339  			if err != nil {
   340  				continue
   341  			}
   342  			if strings.Contains(value, c.Arg.Value()) {
   343  				tmpHashes[string(it.Value())] = it.Value()
   344  			}
   345  
   346  			// Potentially exit early.
   347  			select {
   348  			case <-ctx.Done():
   349  				break iterContains
   350  			default:
   351  			}
   352  		}
   353  		if err := it.Error(); err != nil {
   354  			panic(err)
   355  		}
   356  	default:
   357  		panic("other operators should be handled already")
   358  	}
   359  
   360  	if len(tmpHashes) == 0 || firstRun {
   361  		// Either:
   362  		//
   363  		// 1. Regardless if a previous match was attempted, which may have had
   364  		// results, but no match was found for the current condition, then we
   365  		// return no matches (assuming AND operand).
   366  		//
   367  		// 2. A previous match was not attempted, so we return all results.
   368  		return tmpHashes
   369  	}
   370  
   371  	// Remove/reduce matches in filteredHashes that were not found in this
   372  	// match (tmpHashes).
   373  	for k := range filteredHashes {
   374  		if tmpHashes[k] == nil {
   375  			delete(filteredHashes, k)
   376  
   377  			// Potentially exit early.
   378  			select {
   379  			case <-ctx.Done():
   380  				break
   381  			default:
   382  			}
   383  		}
   384  	}
   385  
   386  	return filteredHashes
   387  }
   388  
   389  // matchRange returns all matching txs by hash that meet a given queryRange and
   390  // start key. An already filtered result (filteredHashes) is provided such that
   391  // any non-intersecting matches are removed.
   392  //
   393  // NOTE: filteredHashes may be empty if no previous condition has matched.
   394  func (txi *TxIndex) matchRange(
   395  	ctx context.Context,
   396  	qr indexer.QueryRange,
   397  	startKey []byte,
   398  	filteredHashes map[string][]byte,
   399  	firstRun bool,
   400  ) map[string][]byte {
   401  	// A previous match was attempted but resulted in no matches, so we return
   402  	// no matches (assuming AND operand).
   403  	if !firstRun && len(filteredHashes) == 0 {
   404  		return filteredHashes
   405  	}
   406  
   407  	tmpHashes := make(map[string][]byte)
   408  	lowerBound := qr.LowerBoundValue()
   409  	upperBound := qr.UpperBoundValue()
   410  
   411  	it, err := dbm.IteratePrefix(txi.store, startKey)
   412  	if err != nil {
   413  		panic(err)
   414  	}
   415  	defer it.Close()
   416  
   417  iter:
   418  	for ; it.Valid(); it.Next() {
   419  		value, err := parseValueFromKey(it.Key())
   420  		if err != nil {
   421  			continue
   422  		}
   423  		if _, ok := qr.AnyBound().(int64); ok {
   424  			v, err := strconv.ParseInt(value, 10, 64)
   425  			if err != nil {
   426  				continue iter
   427  			}
   428  
   429  			include := true
   430  			if lowerBound != nil && v < lowerBound.(int64) {
   431  				include = false
   432  			}
   433  
   434  			if upperBound != nil && v > upperBound.(int64) {
   435  				include = false
   436  			}
   437  
   438  			if include {
   439  				tmpHashes[string(it.Value())] = it.Value()
   440  			}
   441  
   442  			// XXX: passing time in a ABCI Events is not yet implemented
   443  			// case time.Time:
   444  			// 	v := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64)
   445  			// 	if v == r.upperBound {
   446  			// 		break
   447  			// 	}
   448  		}
   449  
   450  		// Potentially exit early.
   451  		select {
   452  		case <-ctx.Done():
   453  			break iter
   454  		default:
   455  		}
   456  	}
   457  	if err := it.Error(); err != nil {
   458  		panic(err)
   459  	}
   460  
   461  	if len(tmpHashes) == 0 || firstRun {
   462  		// Either:
   463  		//
   464  		// 1. Regardless if a previous match was attempted, which may have had
   465  		// results, but no match was found for the current condition, then we
   466  		// return no matches (assuming AND operand).
   467  		//
   468  		// 2. A previous match was not attempted, so we return all results.
   469  		return tmpHashes
   470  	}
   471  
   472  	// Remove/reduce matches in filteredHashes that were not found in this
   473  	// match (tmpHashes).
   474  	for k := range filteredHashes {
   475  		if tmpHashes[k] == nil {
   476  			delete(filteredHashes, k)
   477  
   478  			// Potentially exit early.
   479  			select {
   480  			case <-ctx.Done():
   481  				break
   482  			default:
   483  			}
   484  		}
   485  	}
   486  
   487  	return filteredHashes
   488  }
   489  
   490  // ##########################  Keys  #############################
   491  //
   492  // The indexer has two types of kv stores:
   493  // 1. txhash - result  (primary key)
   494  // 2. event - txhash   (secondary key)
   495  //
   496  // The event key can be decomposed into 4 parts.
   497  // 1. A composite key which can be any string.
   498  // Usually something like "tx.height" or "account.owner"
   499  // 2. A value. That corresponds to the key. In the above
   500  // example the value could be "5" or "Ivan"
   501  // 3. The height of the Tx that aligns with the key and value.
   502  // 4. The index of the Tx that aligns with the key and value
   503  
   504  // the hash/primary key
   505  func primaryKey(hash []byte) []byte {
   506  	key, err := orderedcode.Append(
   507  		nil,
   508  		types.TxHashKey,
   509  		string(hash),
   510  	)
   511  	if err != nil {
   512  		panic(err)
   513  	}
   514  	return key
   515  }
   516  
   517  // The event/secondary key
   518  func secondaryKey(compositeKey, value string, height int64, index uint32) []byte {
   519  	key, err := orderedcode.Append(
   520  		nil,
   521  		compositeKey,
   522  		value,
   523  		height,
   524  		int64(index),
   525  	)
   526  	if err != nil {
   527  		panic(err)
   528  	}
   529  	return key
   530  }
   531  
   532  // parseValueFromKey parses an event key and extracts out the value, returning an error if one arises.
   533  // This will also involve ensuring that the key has the correct format.
   534  // CONTRACT: function doesn't check that the prefix is correct. This should have already been done by the iterator
   535  func parseValueFromKey(key []byte) (string, error) {
   536  	var (
   537  		compositeKey, value string
   538  		height, index       int64
   539  	)
   540  	remaining, err := orderedcode.Parse(string(key), &compositeKey, &value, &height, &index)
   541  	if err != nil {
   542  		return "", err
   543  	}
   544  	if len(remaining) != 0 {
   545  		return "", fmt.Errorf("unexpected remainder in key: %s", remaining)
   546  	}
   547  	return value, nil
   548  }
   549  
   550  func keyFromEvent(compositeKey string, value string, result *abci.TxResult) []byte {
   551  	return secondaryKey(compositeKey, value, result.Height, result.Index)
   552  }
   553  
   554  func KeyFromHeight(result *abci.TxResult) []byte {
   555  	return secondaryKey(types.TxHeightKey, fmt.Sprintf("%d", result.Height), result.Height, result.Index)
   556  }
   557  
   558  // Prefixes: these represent an initial part of the key and are used by iterators to iterate over a small
   559  // section of the kv store during searches.
   560  
   561  func prefixFromCompositeKey(compositeKey string) []byte {
   562  	key, err := orderedcode.Append(nil, compositeKey)
   563  	if err != nil {
   564  		panic(err)
   565  	}
   566  	return key
   567  }
   568  
   569  func prefixFromCompositeKeyAndValue(compositeKey, value string) []byte {
   570  	key, err := orderedcode.Append(nil, compositeKey, value)
   571  	if err != nil {
   572  		panic(err)
   573  	}
   574  	return key
   575  }
   576  
   577  // a small utility function for getting a keys prefix based on a condition and a height
   578  func prefixForCondition(c syntax.Condition, height int64) []byte {
   579  	key := prefixFromCompositeKeyAndValue(c.Tag, c.Arg.Value())
   580  	if height > 0 {
   581  		var err error
   582  		key, err = orderedcode.Append(key, height)
   583  		if err != nil {
   584  			panic(err)
   585  		}
   586  	}
   587  	return key
   588  }