gitlab.com/gpdionisio/tendermint@v0.34.19-dev2/state/indexer/block/kv/kv.go (about)

     1  package kv
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/google/orderedcode"
    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/types"
    18  )
    19  
    20  var _ indexer.BlockIndexer = (*BlockerIndexer)(nil)
    21  
    22  // BlockerIndexer implements a block indexer, indexing BeginBlock and EndBlock
    23  // events with an underlying KV store. Block events are indexed by their height,
    24  // such that matching search criteria returns the respective block height(s).
    25  type BlockerIndexer struct {
    26  	store dbm.DB
    27  }
    28  
    29  func New(store dbm.DB) *BlockerIndexer {
    30  	return &BlockerIndexer{
    31  		store: store,
    32  	}
    33  }
    34  
    35  // Has returns true if the given height has been indexed. An error is returned
    36  // upon database query failure.
    37  func (idx *BlockerIndexer) Has(height int64) (bool, error) {
    38  	key, err := heightKey(height)
    39  	if err != nil {
    40  		return false, fmt.Errorf("failed to create block height index key: %w", err)
    41  	}
    42  
    43  	return idx.store.Has(key)
    44  }
    45  
    46  // Index indexes BeginBlock and EndBlock events for a given block by its height.
    47  // The following is indexed:
    48  //
    49  // primary key: encode(block.height | height) => encode(height)
    50  // BeginBlock events: encode(eventType.eventAttr|eventValue|height|begin_block) => encode(height)
    51  // EndBlock events: encode(eventType.eventAttr|eventValue|height|end_block) => encode(height)
    52  func (idx *BlockerIndexer) Index(bh types.EventDataNewBlockHeader) error {
    53  	batch := idx.store.NewBatch()
    54  	defer batch.Close()
    55  
    56  	height := bh.Header.Height
    57  
    58  	// 1. index by height
    59  	key, err := heightKey(height)
    60  	if err != nil {
    61  		return fmt.Errorf("failed to create block height index key: %w", err)
    62  	}
    63  	if err := batch.Set(key, int64ToBytes(height)); err != nil {
    64  		return err
    65  	}
    66  
    67  	// 2. index BeginBlock events
    68  	if err := idx.indexEvents(batch, bh.ResultBeginBlock.Events, "begin_block", height); err != nil {
    69  		return fmt.Errorf("failed to index BeginBlock events: %w", err)
    70  	}
    71  
    72  	// 3. index EndBlock events
    73  	if err := idx.indexEvents(batch, bh.ResultEndBlock.Events, "end_block", height); err != nil {
    74  		return fmt.Errorf("failed to index EndBlock events: %w", err)
    75  	}
    76  
    77  	return batch.WriteSync()
    78  }
    79  
    80  // Search performs a query for block heights that match a given BeginBlock
    81  // and Endblock event search criteria. The given query can match against zero,
    82  // one or more block heights. In the case of height queries, i.e. block.height=H,
    83  // if the height is indexed, that height alone will be returned. An error and
    84  // nil slice is returned. Otherwise, a non-nil slice and nil error is returned.
    85  func (idx *BlockerIndexer) Search(ctx context.Context, q *query.Query) ([]int64, error) {
    86  	results := make([]int64, 0)
    87  	select {
    88  	case <-ctx.Done():
    89  		return results, nil
    90  
    91  	default:
    92  	}
    93  
    94  	conditions, err := q.Conditions()
    95  	if err != nil {
    96  		return nil, fmt.Errorf("failed to parse query conditions: %w", err)
    97  	}
    98  
    99  	// If there is an exact height query, return the result immediately
   100  	// (if it exists).
   101  	height, ok := lookForHeight(conditions)
   102  	if ok {
   103  		ok, err := idx.Has(height)
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  
   108  		if ok {
   109  			return []int64{height}, nil
   110  		}
   111  
   112  		return results, nil
   113  	}
   114  
   115  	var heightsInitialized bool
   116  	filteredHeights := make(map[string][]byte)
   117  
   118  	// conditions to skip because they're handled before "everything else"
   119  	skipIndexes := make([]int, 0)
   120  
   121  	// Extract ranges. If both upper and lower bounds exist, it's better to get
   122  	// them in order as to not iterate over kvs that are not within range.
   123  	ranges, rangeIndexes := indexer.LookForRanges(conditions)
   124  	if len(ranges) > 0 {
   125  		skipIndexes = append(skipIndexes, rangeIndexes...)
   126  
   127  		for _, qr := range ranges {
   128  			prefix, err := orderedcode.Append(nil, qr.Key)
   129  			if err != nil {
   130  				return nil, fmt.Errorf("failed to create prefix key: %w", err)
   131  			}
   132  
   133  			if !heightsInitialized {
   134  				filteredHeights, err = idx.matchRange(ctx, qr, prefix, filteredHeights, true)
   135  				if err != nil {
   136  					return nil, err
   137  				}
   138  
   139  				heightsInitialized = true
   140  
   141  				// Ignore any remaining conditions if the first condition resulted in no
   142  				// matches (assuming implicit AND operand).
   143  				if len(filteredHeights) == 0 {
   144  					break
   145  				}
   146  			} else {
   147  				filteredHeights, err = idx.matchRange(ctx, qr, prefix, filteredHeights, false)
   148  				if err != nil {
   149  					return nil, err
   150  				}
   151  			}
   152  		}
   153  	}
   154  
   155  	// for all other conditions
   156  	for i, c := range conditions {
   157  		if intInSlice(i, skipIndexes) {
   158  			continue
   159  		}
   160  
   161  		startKey, err := orderedcode.Append(nil, c.CompositeKey, fmt.Sprintf("%v", c.Operand))
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  
   166  		if !heightsInitialized {
   167  			filteredHeights, err = idx.match(ctx, c, startKey, filteredHeights, true)
   168  			if err != nil {
   169  				return nil, err
   170  			}
   171  
   172  			heightsInitialized = true
   173  
   174  			// Ignore any remaining conditions if the first condition resulted in no
   175  			// matches (assuming implicit AND operand).
   176  			if len(filteredHeights) == 0 {
   177  				break
   178  			}
   179  		} else {
   180  			filteredHeights, err = idx.match(ctx, c, startKey, filteredHeights, false)
   181  			if err != nil {
   182  				return nil, err
   183  			}
   184  		}
   185  	}
   186  
   187  	// fetch matching heights
   188  	results = make([]int64, 0, len(filteredHeights))
   189  	for _, hBz := range filteredHeights {
   190  		h := int64FromBytes(hBz)
   191  
   192  		ok, err := idx.Has(h)
   193  		if err != nil {
   194  			return nil, err
   195  		}
   196  		if ok {
   197  			results = append(results, h)
   198  		}
   199  
   200  		select {
   201  		case <-ctx.Done():
   202  			break
   203  
   204  		default:
   205  		}
   206  	}
   207  
   208  	sort.Slice(results, func(i, j int) bool { return results[i] < results[j] })
   209  
   210  	return results, nil
   211  }
   212  
   213  // matchRange returns all matching block heights that match a given QueryRange
   214  // and start key. An already filtered result (filteredHeights) is provided such
   215  // that any non-intersecting matches are removed.
   216  //
   217  // NOTE: The provided filteredHeights may be empty if no previous condition has
   218  // matched.
   219  func (idx *BlockerIndexer) matchRange(
   220  	ctx context.Context,
   221  	qr indexer.QueryRange,
   222  	startKey []byte,
   223  	filteredHeights map[string][]byte,
   224  	firstRun bool,
   225  ) (map[string][]byte, error) {
   226  
   227  	// A previous match was attempted but resulted in no matches, so we return
   228  	// no matches (assuming AND operand).
   229  	if !firstRun && len(filteredHeights) == 0 {
   230  		return filteredHeights, nil
   231  	}
   232  
   233  	tmpHeights := make(map[string][]byte)
   234  	lowerBound := qr.LowerBoundValue()
   235  	upperBound := qr.UpperBoundValue()
   236  
   237  	it, err := dbm.IteratePrefix(idx.store, startKey)
   238  	if err != nil {
   239  		return nil, fmt.Errorf("failed to create prefix iterator: %w", err)
   240  	}
   241  	defer it.Close()
   242  
   243  LOOP:
   244  	for ; it.Valid(); it.Next() {
   245  		var (
   246  			eventValue string
   247  			err        error
   248  		)
   249  
   250  		if qr.Key == types.BlockHeightKey {
   251  			eventValue, err = parseValueFromPrimaryKey(it.Key())
   252  		} else {
   253  			eventValue, err = parseValueFromEventKey(it.Key())
   254  		}
   255  
   256  		if err != nil {
   257  			continue
   258  		}
   259  
   260  		if _, ok := qr.AnyBound().(int64); ok {
   261  			v, err := strconv.ParseInt(eventValue, 10, 64)
   262  			if err != nil {
   263  				continue LOOP
   264  			}
   265  
   266  			include := true
   267  			if lowerBound != nil && v < lowerBound.(int64) {
   268  				include = false
   269  			}
   270  
   271  			if upperBound != nil && v > upperBound.(int64) {
   272  				include = false
   273  			}
   274  
   275  			if include {
   276  				tmpHeights[string(it.Value())] = it.Value()
   277  			}
   278  		}
   279  
   280  		select {
   281  		case <-ctx.Done():
   282  			break
   283  
   284  		default:
   285  		}
   286  	}
   287  
   288  	if err := it.Error(); err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	if len(tmpHeights) == 0 || firstRun {
   293  		// Either:
   294  		//
   295  		// 1. Regardless if a previous match was attempted, which may have had
   296  		// results, but no match was found for the current condition, then we
   297  		// return no matches (assuming AND operand).
   298  		//
   299  		// 2. A previous match was not attempted, so we return all results.
   300  		return tmpHeights, nil
   301  	}
   302  
   303  	// Remove/reduce matches in filteredHashes that were not found in this
   304  	// match (tmpHashes).
   305  	for k := range filteredHeights {
   306  		if tmpHeights[k] == nil {
   307  			delete(filteredHeights, k)
   308  
   309  			select {
   310  			case <-ctx.Done():
   311  				break
   312  
   313  			default:
   314  			}
   315  		}
   316  	}
   317  
   318  	return filteredHeights, nil
   319  }
   320  
   321  // match returns all matching heights that meet a given query condition and start
   322  // key. An already filtered result (filteredHeights) is provided such that any
   323  // non-intersecting matches are removed.
   324  //
   325  // NOTE: The provided filteredHeights may be empty if no previous condition has
   326  // matched.
   327  func (idx *BlockerIndexer) match(
   328  	ctx context.Context,
   329  	c query.Condition,
   330  	startKeyBz []byte,
   331  	filteredHeights map[string][]byte,
   332  	firstRun bool,
   333  ) (map[string][]byte, error) {
   334  
   335  	// A previous match was attempted but resulted in no matches, so we return
   336  	// no matches (assuming AND operand).
   337  	if !firstRun && len(filteredHeights) == 0 {
   338  		return filteredHeights, nil
   339  	}
   340  
   341  	tmpHeights := make(map[string][]byte)
   342  
   343  	switch {
   344  	case c.Op == query.OpEqual:
   345  		it, err := dbm.IteratePrefix(idx.store, startKeyBz)
   346  		if err != nil {
   347  			return nil, fmt.Errorf("failed to create prefix iterator: %w", err)
   348  		}
   349  		defer it.Close()
   350  
   351  		for ; it.Valid(); it.Next() {
   352  			tmpHeights[string(it.Value())] = it.Value()
   353  
   354  			if err := ctx.Err(); err != nil {
   355  				break
   356  			}
   357  		}
   358  
   359  		if err := it.Error(); err != nil {
   360  			return nil, err
   361  		}
   362  
   363  	case c.Op == query.OpExists:
   364  		prefix, err := orderedcode.Append(nil, c.CompositeKey)
   365  		if err != nil {
   366  			return nil, err
   367  		}
   368  
   369  		it, err := dbm.IteratePrefix(idx.store, prefix)
   370  		if err != nil {
   371  			return nil, fmt.Errorf("failed to create prefix iterator: %w", err)
   372  		}
   373  		defer it.Close()
   374  
   375  		for ; it.Valid(); it.Next() {
   376  			tmpHeights[string(it.Value())] = it.Value()
   377  
   378  			select {
   379  			case <-ctx.Done():
   380  				break
   381  
   382  			default:
   383  			}
   384  		}
   385  
   386  		if err := it.Error(); err != nil {
   387  			return nil, err
   388  		}
   389  
   390  	case c.Op == query.OpContains:
   391  		prefix, err := orderedcode.Append(nil, c.CompositeKey)
   392  		if err != nil {
   393  			return nil, err
   394  		}
   395  
   396  		it, err := dbm.IteratePrefix(idx.store, prefix)
   397  		if err != nil {
   398  			return nil, fmt.Errorf("failed to create prefix iterator: %w", err)
   399  		}
   400  		defer it.Close()
   401  
   402  		for ; it.Valid(); it.Next() {
   403  			eventValue, err := parseValueFromEventKey(it.Key())
   404  			if err != nil {
   405  				continue
   406  			}
   407  
   408  			if strings.Contains(eventValue, c.Operand.(string)) {
   409  				tmpHeights[string(it.Value())] = it.Value()
   410  			}
   411  
   412  			select {
   413  			case <-ctx.Done():
   414  				break
   415  
   416  			default:
   417  			}
   418  		}
   419  		if err := it.Error(); err != nil {
   420  			return nil, err
   421  		}
   422  
   423  	default:
   424  		return nil, errors.New("other operators should be handled already")
   425  	}
   426  
   427  	if len(tmpHeights) == 0 || firstRun {
   428  		// Either:
   429  		//
   430  		// 1. Regardless if a previous match was attempted, which may have had
   431  		// results, but no match was found for the current condition, then we
   432  		// return no matches (assuming AND operand).
   433  		//
   434  		// 2. A previous match was not attempted, so we return all results.
   435  		return tmpHeights, nil
   436  	}
   437  
   438  	// Remove/reduce matches in filteredHeights that were not found in this
   439  	// match (tmpHeights).
   440  	for k := range filteredHeights {
   441  		if tmpHeights[k] == nil {
   442  			delete(filteredHeights, k)
   443  
   444  			select {
   445  			case <-ctx.Done():
   446  				break
   447  
   448  			default:
   449  			}
   450  		}
   451  	}
   452  
   453  	return filteredHeights, nil
   454  }
   455  
   456  func (idx *BlockerIndexer) indexEvents(batch dbm.Batch, events []abci.Event, typ string, height int64) error {
   457  	heightBz := int64ToBytes(height)
   458  
   459  	for _, event := range events {
   460  		// only index events with a non-empty type
   461  		if len(event.Type) == 0 {
   462  			continue
   463  		}
   464  
   465  		for _, attr := range event.Attributes {
   466  			if len(attr.Key) == 0 {
   467  				continue
   468  			}
   469  
   470  			// index iff the event specified index:true and it's not a reserved event
   471  			compositeKey := fmt.Sprintf("%s.%s", event.Type, string(attr.Key))
   472  			if compositeKey == types.BlockHeightKey {
   473  				return fmt.Errorf("event type and attribute key \"%s\" is reserved; please use a different key", compositeKey)
   474  			}
   475  
   476  			if attr.GetIndex() {
   477  				key, err := eventKey(compositeKey, typ, string(attr.Value), height)
   478  				if err != nil {
   479  					return fmt.Errorf("failed to create block index key: %w", err)
   480  				}
   481  
   482  				if err := batch.Set(key, heightBz); err != nil {
   483  					return err
   484  				}
   485  			}
   486  		}
   487  	}
   488  
   489  	return nil
   490  }