github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/state/indexer/block/kv/kv.go (about)

     1  package kv
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  
    12  	dbm "github.com/cometbft/cometbft-db"
    13  	"github.com/google/orderedcode"
    14  
    15  	abci "github.com/badrootd/celestia-core/abci/types"
    16  	"github.com/badrootd/celestia-core/libs/pubsub/query"
    17  	"github.com/badrootd/celestia-core/state/indexer"
    18  	"github.com/badrootd/celestia-core/types"
    19  )
    20  
    21  var _ indexer.BlockIndexer = (*BlockerIndexer)(nil)
    22  
    23  // BlockerIndexer implements a block indexer, indexing BeginBlock and EndBlock
    24  // events with an underlying KV store. Block events are indexed by their height,
    25  // such that matching search criteria returns the respective block height(s).
    26  type BlockerIndexer struct {
    27  	store dbm.DB
    28  
    29  	// Add unique event identifier to use when querying
    30  	// Matching will be done both on height AND eventSeq
    31  	eventSeq int64
    32  }
    33  
    34  func New(store dbm.DB) *BlockerIndexer {
    35  	return &BlockerIndexer{
    36  		store: store,
    37  	}
    38  }
    39  
    40  // Has returns true if the given height has been indexed. An error is returned
    41  // upon database query failure.
    42  func (idx *BlockerIndexer) Has(height int64) (bool, error) {
    43  	key, err := heightKey(height)
    44  	if err != nil {
    45  		return false, fmt.Errorf("failed to create block height index key: %w", err)
    46  	}
    47  
    48  	return idx.store.Has(key)
    49  }
    50  
    51  // Index indexes BeginBlock and EndBlock events for a given block by its height.
    52  // The following is indexed:
    53  //
    54  // primary key: encode(block.height | height) => encode(height)
    55  // BeginBlock events: encode(eventType.eventAttr|eventValue|height|begin_block) => encode(height)
    56  // EndBlock events: encode(eventType.eventAttr|eventValue|height|end_block) => encode(height)
    57  func (idx *BlockerIndexer) Index(bh types.EventDataNewBlockHeader) error {
    58  	batch := idx.store.NewBatch()
    59  	defer batch.Close()
    60  
    61  	height := bh.Header.Height
    62  
    63  	// 1. index by height
    64  	key, err := heightKey(height)
    65  	if err != nil {
    66  		return fmt.Errorf("failed to create block height index key: %w", err)
    67  	}
    68  	if err := batch.Set(key, int64ToBytes(height)); err != nil {
    69  		return err
    70  	}
    71  
    72  	// 2. index BeginBlock events
    73  	if err := idx.indexEvents(batch, bh.ResultBeginBlock.Events, "begin_block", height); err != nil {
    74  		return fmt.Errorf("failed to index BeginBlock events: %w", err)
    75  	}
    76  
    77  	// 3. index EndBlock events
    78  	if err := idx.indexEvents(batch, bh.ResultEndBlock.Events, "end_block", height); err != nil {
    79  		return fmt.Errorf("failed to index EndBlock events: %w", err)
    80  	}
    81  
    82  	return batch.WriteSync()
    83  }
    84  
    85  // Search performs a query for block heights that match a given BeginBlock
    86  // and Endblock event search criteria. The given query can match against zero,
    87  // one or more block heights. In the case of height queries, i.e. block.height=H,
    88  // if the height is indexed, that height alone will be returned. An error and
    89  // nil slice is returned. Otherwise, a non-nil slice and nil error is returned.
    90  func (idx *BlockerIndexer) Search(ctx context.Context, q *query.Query) ([]int64, error) {
    91  	results := make([]int64, 0)
    92  	select {
    93  	case <-ctx.Done():
    94  		return results, nil
    95  
    96  	default:
    97  	}
    98  
    99  	conditions, err := q.Conditions()
   100  	if err != nil {
   101  		return nil, fmt.Errorf("failed to parse query conditions: %w", err)
   102  	}
   103  	// conditions to skip because they're handled before "everything else"
   104  	skipIndexes := make([]int, 0)
   105  
   106  	var matchEvents bool
   107  	var matchEventIdx int
   108  
   109  	// If the match.events keyword is at the beginning of the query, we will only
   110  	// return heights where the conditions are true within the same event
   111  	// and set the matchEvents to true
   112  	conditions, matchEvents = dedupMatchEvents(conditions)
   113  
   114  	if matchEvents {
   115  		matchEventIdx = 0
   116  		skipIndexes = append(skipIndexes, matchEventIdx)
   117  	}
   118  
   119  	// If there is an exact height query, return the result immediately
   120  	// (if it exists).
   121  	var ok bool
   122  	var heightInfo HeightInfo
   123  	if matchEvents {
   124  		// If we are not matching events and block.height = 3 occurs more than once, the later value will
   125  		// overwrite the first one. For match.events it will create problems. If we have a height range, we
   126  		// should ignore height equality.
   127  		conditions, heightInfo, ok = dedupHeight(conditions)
   128  	} else {
   129  		heightInfo.height, ok, heightInfo.heightEqIdx = lookForHeight(conditions)
   130  	}
   131  
   132  	// Extract ranges. If both upper and lower bounds exist, it's better to get
   133  	// them in order as to not iterate over kvs that are not within range.
   134  	// If we have a query range over height and want to still look for
   135  	// specific event values we do not want to simply return all
   136  	// blocks in this height range. We remember the height range info
   137  	// and pass it on to match() to take into account when processing events.
   138  	ranges, rangeIndexes, heightRange := indexer.LookForRangesWithHeight(conditions)
   139  	heightInfo.heightRange = heightRange
   140  
   141  	// If we have additional constraints and want to query per event
   142  	// attributes, we cannot simply return all blocks for a height.
   143  	// But we remember the height we want to find and forward it to
   144  	// match(). If we only have the height constraint and match.events keyword
   145  	// in the query (the second part of the ||), we don't need to query
   146  	// per event conditions and return all events within the height range.
   147  	if ok && (!matchEvents || (matchEvents && heightInfo.onlyHeightEq)) {
   148  		ok, err := idx.Has(heightInfo.height)
   149  		if err != nil {
   150  			return nil, err
   151  		}
   152  
   153  		if ok {
   154  			return []int64{heightInfo.height}, nil
   155  		}
   156  
   157  		return results, nil
   158  	}
   159  
   160  	var heightsInitialized bool
   161  	filteredHeights := make(map[string][]byte)
   162  	if matchEvents && heightInfo.heightEqIdx != -1 {
   163  		skipIndexes = append(skipIndexes, heightInfo.heightEqIdx)
   164  	}
   165  
   166  	if len(ranges) > 0 {
   167  		skipIndexes = append(skipIndexes, rangeIndexes...)
   168  
   169  		for _, qr := range ranges {
   170  			if qr.Key == types.BlockHeightKey && matchEvents && !heightInfo.onlyHeightRange {
   171  				// If the query contains ranges other than the height then we need to treat the height
   172  				// range when querying the conditions of the other range.
   173  				// Otherwise we can just return all the blocks within the height range (as there is no
   174  				// additional constraint on events)
   175  				continue
   176  
   177  			}
   178  			prefix, err := orderedcode.Append(nil, qr.Key)
   179  			if err != nil {
   180  				return nil, fmt.Errorf("failed to create prefix key: %w", err)
   181  			}
   182  
   183  			if !heightsInitialized {
   184  				filteredHeights, err = idx.matchRange(ctx, qr, prefix, filteredHeights, true, matchEvents, heightInfo)
   185  				if err != nil {
   186  					return nil, err
   187  				}
   188  
   189  				heightsInitialized = true
   190  
   191  				// Ignore any remaining conditions if the first condition resulted in no
   192  				// matches (assuming implicit AND operand).
   193  				if len(filteredHeights) == 0 {
   194  					break
   195  				}
   196  			} else {
   197  				filteredHeights, err = idx.matchRange(ctx, qr, prefix, filteredHeights, false, matchEvents, heightInfo)
   198  				if err != nil {
   199  					return nil, err
   200  				}
   201  			}
   202  		}
   203  	}
   204  
   205  	// for all other conditions
   206  	for i, c := range conditions {
   207  		if intInSlice(i, skipIndexes) {
   208  			continue
   209  		}
   210  
   211  		startKey, err := orderedcode.Append(nil, c.CompositeKey, fmt.Sprintf("%v", c.Operand))
   212  
   213  		if err != nil {
   214  			return nil, err
   215  		}
   216  
   217  		if !heightsInitialized {
   218  			filteredHeights, err = idx.match(ctx, c, startKey, filteredHeights, true, matchEvents, heightInfo)
   219  			if err != nil {
   220  				return nil, err
   221  			}
   222  
   223  			heightsInitialized = true
   224  
   225  			// Ignore any remaining conditions if the first condition resulted in no
   226  			// matches (assuming implicit AND operand).
   227  			if len(filteredHeights) == 0 {
   228  				break
   229  			}
   230  		} else {
   231  			filteredHeights, err = idx.match(ctx, c, startKey, filteredHeights, false, matchEvents, heightInfo)
   232  			if err != nil {
   233  				return nil, err
   234  			}
   235  		}
   236  	}
   237  
   238  	// fetch matching heights
   239  	results = make([]int64, 0, len(filteredHeights))
   240  	resultMap := make(map[int64]struct{})
   241  	for _, hBz := range filteredHeights {
   242  		h := int64FromBytes(hBz)
   243  
   244  		ok, err := idx.Has(h)
   245  		if err != nil {
   246  			return nil, err
   247  		}
   248  		_, okHeight := resultMap[h]
   249  		if ok && !okHeight {
   250  			resultMap[h] = struct{}{}
   251  			results = append(results, h)
   252  		}
   253  
   254  		select {
   255  		case <-ctx.Done():
   256  			break
   257  
   258  		default:
   259  		}
   260  	}
   261  
   262  	sort.Slice(results, func(i, j int) bool { return results[i] < results[j] })
   263  
   264  	return results, nil
   265  }
   266  
   267  // matchRange returns all matching block heights that match a given QueryRange
   268  // and start key. An already filtered result (filteredHeights) is provided such
   269  // that any non-intersecting matches are removed.
   270  //
   271  // NOTE: The provided filteredHeights may be empty if no previous condition has
   272  // matched.
   273  func (idx *BlockerIndexer) matchRange(
   274  	ctx context.Context,
   275  	qr indexer.QueryRange,
   276  	startKey []byte,
   277  	filteredHeights map[string][]byte,
   278  	firstRun bool,
   279  	matchEvents bool,
   280  	heightInfo HeightInfo,
   281  ) (map[string][]byte, error) {
   282  
   283  	// A previous match was attempted but resulted in no matches, so we return
   284  	// no matches (assuming AND operand).
   285  	if !firstRun && len(filteredHeights) == 0 {
   286  		return filteredHeights, nil
   287  	}
   288  
   289  	tmpHeights := make(map[string][]byte)
   290  
   291  	it, err := dbm.IteratePrefix(idx.store, startKey)
   292  	if err != nil {
   293  		return nil, fmt.Errorf("failed to create prefix iterator: %w", err)
   294  	}
   295  	defer it.Close()
   296  
   297  LOOP:
   298  	for ; it.Valid(); it.Next() {
   299  		var (
   300  			eventValue string
   301  			err        error
   302  		)
   303  
   304  		if qr.Key == types.BlockHeightKey {
   305  			eventValue, err = parseValueFromPrimaryKey(it.Key())
   306  		} else {
   307  			eventValue, err = parseValueFromEventKey(it.Key())
   308  		}
   309  
   310  		if err != nil {
   311  			continue
   312  		}
   313  
   314  		if _, ok := qr.AnyBound().(int64); ok {
   315  			v, err := strconv.ParseInt(eventValue, 10, 64)
   316  			if err != nil {
   317  				continue LOOP
   318  			}
   319  
   320  			if matchEvents && qr.Key != types.BlockHeightKey {
   321  				keyHeight, err := parseHeightFromEventKey(it.Key())
   322  				if err != nil || !checkHeightConditions(heightInfo, keyHeight) {
   323  					continue LOOP
   324  				}
   325  			}
   326  			if checkBounds(qr, v) {
   327  				idx.setTmpHeights(tmpHeights, it, matchEvents)
   328  			}
   329  		}
   330  
   331  		select {
   332  		case <-ctx.Done():
   333  			break
   334  
   335  		default:
   336  		}
   337  	}
   338  
   339  	if err := it.Error(); err != nil {
   340  		return nil, err
   341  	}
   342  
   343  	if len(tmpHeights) == 0 || firstRun {
   344  		// Either:
   345  		//
   346  		// 1. Regardless if a previous match was attempted, which may have had
   347  		// results, but no match was found for the current condition, then we
   348  		// return no matches (assuming AND operand).
   349  		//
   350  		// 2. A previous match was not attempted, so we return all results.
   351  		return tmpHeights, nil
   352  	}
   353  
   354  	// Remove/reduce matches in filteredHashes that were not found in this
   355  	// match (tmpHashes).
   356  	for k, v := range filteredHeights {
   357  		tmpHeight := tmpHeights[k]
   358  
   359  		// Check whether in this iteration we have not found an overlapping height (tmpHeight == nil)
   360  		// or whether the events in which the attributed occurred do not match (first part of the condition)
   361  		if tmpHeight == nil || !bytes.Equal(tmpHeight, v) {
   362  			delete(filteredHeights, k)
   363  
   364  			select {
   365  			case <-ctx.Done():
   366  				break
   367  
   368  			default:
   369  			}
   370  		}
   371  	}
   372  
   373  	return filteredHeights, nil
   374  }
   375  
   376  func (idx *BlockerIndexer) setTmpHeights(tmpHeights map[string][]byte, it dbm.Iterator, matchEvents bool) {
   377  	// If we return attributes that occur within the same events, then store the event sequence in the
   378  	// result map as well
   379  	if matchEvents {
   380  		eventSeq, _ := parseEventSeqFromEventKey(it.Key())
   381  		retVal := it.Value()
   382  		tmpHeights[string(retVal)+strconv.FormatInt(eventSeq, 10)] = it.Value()
   383  	} else {
   384  		tmpHeights[string(it.Value())] = it.Value()
   385  	}
   386  }
   387  
   388  func checkBounds(ranges indexer.QueryRange, v int64) bool {
   389  	include := true
   390  	lowerBound := ranges.LowerBoundValue()
   391  	upperBound := ranges.UpperBoundValue()
   392  	if lowerBound != nil && v < lowerBound.(int64) {
   393  		include = false
   394  	}
   395  
   396  	if upperBound != nil && v > upperBound.(int64) {
   397  		include = false
   398  	}
   399  
   400  	return include
   401  }
   402  
   403  // match returns all matching heights that meet a given query condition and start
   404  // key. An already filtered result (filteredHeights) is provided such that any
   405  // non-intersecting matches are removed.
   406  //
   407  // NOTE: The provided filteredHeights may be empty if no previous condition has
   408  // matched.
   409  func (idx *BlockerIndexer) match(
   410  	ctx context.Context,
   411  	c query.Condition,
   412  	startKeyBz []byte,
   413  	filteredHeights map[string][]byte,
   414  	firstRun bool,
   415  	matchEvents bool,
   416  	heightInfo HeightInfo,
   417  ) (map[string][]byte, error) {
   418  
   419  	// A previous match was attempted but resulted in no matches, so we return
   420  	// no matches (assuming AND operand).
   421  	if !firstRun && len(filteredHeights) == 0 {
   422  		return filteredHeights, nil
   423  	}
   424  
   425  	tmpHeights := make(map[string][]byte)
   426  
   427  	switch {
   428  	case c.Op == query.OpEqual:
   429  		it, err := dbm.IteratePrefix(idx.store, startKeyBz)
   430  		if err != nil {
   431  			return nil, fmt.Errorf("failed to create prefix iterator: %w", err)
   432  		}
   433  		defer it.Close()
   434  
   435  		for ; it.Valid(); it.Next() {
   436  			if matchEvents {
   437  				keyHeight, err := parseHeightFromEventKey(it.Key())
   438  				if err != nil || !checkHeightConditions(heightInfo, keyHeight) {
   439  					continue
   440  				}
   441  
   442  			}
   443  			idx.setTmpHeights(tmpHeights, it, matchEvents)
   444  
   445  			if err := ctx.Err(); err != nil {
   446  				break
   447  			}
   448  		}
   449  
   450  		if err := it.Error(); err != nil {
   451  			return nil, err
   452  		}
   453  
   454  	case c.Op == query.OpExists:
   455  		prefix, err := orderedcode.Append(nil, c.CompositeKey)
   456  		if err != nil {
   457  			return nil, err
   458  		}
   459  
   460  		it, err := dbm.IteratePrefix(idx.store, prefix)
   461  		if err != nil {
   462  			return nil, fmt.Errorf("failed to create prefix iterator: %w", err)
   463  		}
   464  		defer it.Close()
   465  
   466  		for ; it.Valid(); it.Next() {
   467  			keyHeight, err := parseHeightFromEventKey(it.Key())
   468  			if err != nil || !checkHeightConditions(heightInfo, keyHeight) {
   469  				continue
   470  			}
   471  
   472  			idx.setTmpHeights(tmpHeights, it, matchEvents)
   473  
   474  			select {
   475  			case <-ctx.Done():
   476  				break
   477  
   478  			default:
   479  			}
   480  		}
   481  
   482  		if err := it.Error(); err != nil {
   483  			return nil, err
   484  		}
   485  
   486  	case c.Op == query.OpContains:
   487  		prefix, err := orderedcode.Append(nil, c.CompositeKey)
   488  		if err != nil {
   489  			return nil, err
   490  		}
   491  
   492  		it, err := dbm.IteratePrefix(idx.store, prefix)
   493  		if err != nil {
   494  			return nil, fmt.Errorf("failed to create prefix iterator: %w", err)
   495  		}
   496  		defer it.Close()
   497  
   498  		for ; it.Valid(); it.Next() {
   499  			eventValue, err := parseValueFromEventKey(it.Key())
   500  			if err != nil {
   501  				continue
   502  			}
   503  
   504  			if strings.Contains(eventValue, c.Operand.(string)) {
   505  				keyHeight, err := parseHeightFromEventKey(it.Key())
   506  				if err != nil || !checkHeightConditions(heightInfo, keyHeight) {
   507  					continue
   508  				}
   509  				idx.setTmpHeights(tmpHeights, it, matchEvents)
   510  			}
   511  
   512  			select {
   513  			case <-ctx.Done():
   514  				break
   515  
   516  			default:
   517  			}
   518  		}
   519  		if err := it.Error(); err != nil {
   520  			return nil, err
   521  		}
   522  
   523  	default:
   524  		return nil, errors.New("other operators should be handled already")
   525  	}
   526  
   527  	if len(tmpHeights) == 0 || firstRun {
   528  		// Either:
   529  		//
   530  		// 1. Regardless if a previous match was attempted, which may have had
   531  		// results, but no match was found for the current condition, then we
   532  		// return no matches (assuming AND operand).
   533  		//
   534  		// 2. A previous match was not attempted, so we return all results.
   535  		return tmpHeights, nil
   536  	}
   537  
   538  	// Remove/reduce matches in filteredHeights that were not found in this
   539  	// match (tmpHeights).
   540  	for k, v := range filteredHeights {
   541  		tmpHeight := tmpHeights[k]
   542  		if tmpHeight == nil || !bytes.Equal(tmpHeight, v) {
   543  			delete(filteredHeights, k)
   544  
   545  			select {
   546  			case <-ctx.Done():
   547  				break
   548  
   549  			default:
   550  			}
   551  		}
   552  	}
   553  
   554  	return filteredHeights, nil
   555  }
   556  
   557  func (idx *BlockerIndexer) indexEvents(batch dbm.Batch, events []abci.Event, typ string, height int64) error {
   558  	heightBz := int64ToBytes(height)
   559  
   560  	for _, event := range events {
   561  		idx.eventSeq = idx.eventSeq + 1
   562  		// only index events with a non-empty type
   563  		if len(event.Type) == 0 {
   564  			continue
   565  		}
   566  
   567  		for _, attr := range event.Attributes {
   568  			if len(attr.Key) == 0 {
   569  				continue
   570  			}
   571  
   572  			// index iff the event specified index:true and it's not a reserved event
   573  			compositeKey := fmt.Sprintf("%s.%s", event.Type, attr.Key)
   574  			if compositeKey == types.BlockHeightKey {
   575  				return fmt.Errorf("event type and attribute key \"%s\" is reserved; please use a different key", compositeKey)
   576  			}
   577  
   578  			if attr.GetIndex() {
   579  				key, err := eventKey(compositeKey, typ, attr.Value, height, idx.eventSeq)
   580  				if err != nil {
   581  					return fmt.Errorf("failed to create block index key: %w", err)
   582  				}
   583  
   584  				if err := batch.Set(key, heightBz); err != nil {
   585  					return err
   586  				}
   587  			}
   588  		}
   589  	}
   590  
   591  	return nil
   592  }