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