github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/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/gogo/protobuf/proto"
    13  
    14  	abci "github.com/lazyledger/lazyledger-core/abci/types"
    15  	dbm "github.com/lazyledger/lazyledger-core/libs/db"
    16  	"github.com/lazyledger/lazyledger-core/libs/pubsub/query"
    17  	"github.com/lazyledger/lazyledger-core/state/txindex"
    18  	"github.com/lazyledger/lazyledger-core/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  	// Potentially exit early.
   174  	select {
   175  	case <-ctx.Done():
   176  		results := make([]*abci.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 []*abci.TxResult{}, fmt.Errorf("error while retrieving the result: %w", err)
   199  		case res == nil:
   200  			return []*abci.TxResult{}, nil
   201  		default:
   202  			return []*abci.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([]*abci.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  		if err := it.Error(); err != nil {
   427  			panic(err)
   428  		}
   429  
   430  	case c.Op == query.OpExists:
   431  		// XXX: can't use startKeyBz here because c.Operand is nil
   432  		// (e.g. "account.owner/<nil>/" won't match w/ a single row)
   433  		it, err := dbm.IteratePrefix(txi.store, startKey(c.CompositeKey))
   434  		if err != nil {
   435  			panic(err)
   436  		}
   437  		defer it.Close()
   438  
   439  		for ; it.Valid(); it.Next() {
   440  			tmpHashes[string(it.Value())] = it.Value()
   441  
   442  			// Potentially exit early.
   443  			select {
   444  			case <-ctx.Done():
   445  				break
   446  			default:
   447  			}
   448  		}
   449  		if err := it.Error(); err != nil {
   450  			panic(err)
   451  		}
   452  
   453  	case c.Op == query.OpContains:
   454  		// XXX: startKey does not apply here.
   455  		// For example, if startKey = "account.owner/an/" and search query = "account.owner CONTAINS an"
   456  		// we can't iterate with prefix "account.owner/an/" because we might miss keys like "account.owner/Ulan/"
   457  		it, err := dbm.IteratePrefix(txi.store, startKey(c.CompositeKey))
   458  		if err != nil {
   459  			panic(err)
   460  		}
   461  		defer it.Close()
   462  
   463  		for ; it.Valid(); it.Next() {
   464  			if !isTagKey(it.Key()) {
   465  				continue
   466  			}
   467  
   468  			if strings.Contains(extractValueFromKey(it.Key()), c.Operand.(string)) {
   469  				tmpHashes[string(it.Value())] = it.Value()
   470  			}
   471  
   472  			// Potentially exit early.
   473  			select {
   474  			case <-ctx.Done():
   475  				break
   476  			default:
   477  			}
   478  		}
   479  		if err := it.Error(); err != nil {
   480  			panic(err)
   481  		}
   482  	default:
   483  		panic("other operators should be handled already")
   484  	}
   485  
   486  	if len(tmpHashes) == 0 || firstRun {
   487  		// Either:
   488  		//
   489  		// 1. Regardless if a previous match was attempted, which may have had
   490  		// results, but no match was found for the current condition, then we
   491  		// return no matches (assuming AND operand).
   492  		//
   493  		// 2. A previous match was not attempted, so we return all results.
   494  		return tmpHashes
   495  	}
   496  
   497  	// Remove/reduce matches in filteredHashes that were not found in this
   498  	// match (tmpHashes).
   499  	for k := range filteredHashes {
   500  		if tmpHashes[k] == nil {
   501  			delete(filteredHashes, k)
   502  
   503  			// Potentially exit early.
   504  			select {
   505  			case <-ctx.Done():
   506  				break
   507  			default:
   508  			}
   509  		}
   510  	}
   511  
   512  	return filteredHashes
   513  }
   514  
   515  // matchRange returns all matching txs by hash that meet a given queryRange and
   516  // start key. An already filtered result (filteredHashes) is provided such that
   517  // any non-intersecting matches are removed.
   518  //
   519  // NOTE: filteredHashes may be empty if no previous condition has matched.
   520  func (txi *TxIndex) matchRange(
   521  	ctx context.Context,
   522  	r queryRange,
   523  	startKey []byte,
   524  	filteredHashes map[string][]byte,
   525  	firstRun bool,
   526  ) map[string][]byte {
   527  	// A previous match was attempted but resulted in no matches, so we return
   528  	// no matches (assuming AND operand).
   529  	if !firstRun && len(filteredHashes) == 0 {
   530  		return filteredHashes
   531  	}
   532  
   533  	tmpHashes := make(map[string][]byte)
   534  	lowerBound := r.lowerBoundValue()
   535  	upperBound := r.upperBoundValue()
   536  
   537  	it, err := dbm.IteratePrefix(txi.store, startKey)
   538  	if err != nil {
   539  		panic(err)
   540  	}
   541  	defer it.Close()
   542  
   543  LOOP:
   544  	for ; it.Valid(); it.Next() {
   545  		if !isTagKey(it.Key()) {
   546  			continue
   547  		}
   548  
   549  		if _, ok := r.AnyBound().(int64); ok {
   550  			v, err := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64)
   551  			if err != nil {
   552  				continue LOOP
   553  			}
   554  
   555  			include := true
   556  			if lowerBound != nil && v < lowerBound.(int64) {
   557  				include = false
   558  			}
   559  
   560  			if upperBound != nil && v > upperBound.(int64) {
   561  				include = false
   562  			}
   563  
   564  			if include {
   565  				tmpHashes[string(it.Value())] = it.Value()
   566  			}
   567  
   568  			// XXX: passing time in a ABCI Events is not yet implemented
   569  			// case time.Time:
   570  			// 	v := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64)
   571  			// 	if v == r.upperBound {
   572  			// 		break
   573  			// 	}
   574  		}
   575  
   576  		// Potentially exit early.
   577  		select {
   578  		case <-ctx.Done():
   579  			break
   580  		default:
   581  		}
   582  	}
   583  	if err := it.Error(); err != nil {
   584  		panic(err)
   585  	}
   586  
   587  	if len(tmpHashes) == 0 || firstRun {
   588  		// Either:
   589  		//
   590  		// 1. Regardless if a previous match was attempted, which may have had
   591  		// results, but no match was found for the current condition, then we
   592  		// return no matches (assuming AND operand).
   593  		//
   594  		// 2. A previous match was not attempted, so we return all results.
   595  		return tmpHashes
   596  	}
   597  
   598  	// Remove/reduce matches in filteredHashes that were not found in this
   599  	// match (tmpHashes).
   600  	for k := range filteredHashes {
   601  		if tmpHashes[k] == nil {
   602  			delete(filteredHashes, k)
   603  
   604  			// Potentially exit early.
   605  			select {
   606  			case <-ctx.Done():
   607  				break
   608  			default:
   609  			}
   610  		}
   611  	}
   612  
   613  	return filteredHashes
   614  }
   615  
   616  // Keys
   617  
   618  func isTagKey(key []byte) bool {
   619  	return strings.Count(string(key), tagKeySeparator) == 3
   620  }
   621  
   622  func extractValueFromKey(key []byte) string {
   623  	parts := strings.SplitN(string(key), tagKeySeparator, 3)
   624  	return parts[1]
   625  }
   626  
   627  func keyForEvent(key string, value []byte, result *abci.TxResult) []byte {
   628  	return []byte(fmt.Sprintf("%s/%s/%d/%d",
   629  		key,
   630  		value,
   631  		result.Height,
   632  		result.Index,
   633  	))
   634  }
   635  
   636  func keyForHeight(result *abci.TxResult) []byte {
   637  	return []byte(fmt.Sprintf("%s/%d/%d/%d",
   638  		types.TxHeightKey,
   639  		result.Height,
   640  		result.Height,
   641  		result.Index,
   642  	))
   643  }
   644  
   645  func startKeyForCondition(c query.Condition, height int64) []byte {
   646  	if height > 0 {
   647  		return startKey(c.CompositeKey, c.Operand, height)
   648  	}
   649  	return startKey(c.CompositeKey, c.Operand)
   650  }
   651  
   652  func startKey(fields ...interface{}) []byte {
   653  	var b bytes.Buffer
   654  	for _, f := range fields {
   655  		b.Write([]byte(fmt.Sprintf("%v", f) + tagKeySeparator))
   656  	}
   657  	return b.Bytes()
   658  }