github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/index/events_index.go (about)

     1  package index
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"go.uber.org/atomic"
     8  
     9  	"github.com/onflow/flow-go/model/flow"
    10  	"github.com/onflow/flow-go/module/state_synchronization"
    11  	"github.com/onflow/flow-go/module/state_synchronization/indexer"
    12  	"github.com/onflow/flow-go/storage"
    13  )
    14  
    15  var _ state_synchronization.IndexReporter = (*EventsIndex)(nil)
    16  
    17  // EventsIndex implements a wrapper around `storage.Events` ensuring that needed data has been synced and is available to the client.
    18  // Note: `EventsIndex` is created with empty report due to the next reasoning:
    19  // When the index is initially bootstrapped, the indexer needs to load an execution state checkpoint from
    20  // disk and index all the data. This process can take more than 1 hour on some systems. Consequently, the Initialize
    21  // pattern is implemented to enable the Access API to start up and serve queries before the index is fully ready. During
    22  // the initialization phase, all calls to retrieve data from this struct should return indexer.ErrIndexNotInitialized.
    23  // The caller is responsible for handling this error appropriately for the method.
    24  type EventsIndex struct {
    25  	events   storage.Events
    26  	reporter *atomic.Pointer[state_synchronization.IndexReporter]
    27  }
    28  
    29  func NewEventsIndex(events storage.Events) *EventsIndex {
    30  	return &EventsIndex{
    31  		events:   events,
    32  		reporter: atomic.NewPointer[state_synchronization.IndexReporter](nil),
    33  	}
    34  }
    35  
    36  // Initialize replaces a previously non-initialized reporter. Can be called once.
    37  // No errors are expected during normal operations.
    38  func (e *EventsIndex) Initialize(indexReporter state_synchronization.IndexReporter) error {
    39  	if e.reporter.CompareAndSwap(nil, &indexReporter) {
    40  		return nil
    41  	}
    42  	return fmt.Errorf("index reporter already initialized")
    43  }
    44  
    45  // ByBlockID checks data availability and returns events for a block
    46  // Expected errors:
    47  //   - indexer.ErrIndexNotInitialized if the `EventsIndex` has not been initialized
    48  //   - storage.ErrHeightNotIndexed when data is unavailable
    49  //   - codes.NotFound if result cannot be provided by storage due to the absence of data.
    50  func (e *EventsIndex) ByBlockID(blockID flow.Identifier, height uint64) ([]flow.Event, error) {
    51  	if err := e.checkDataAvailability(height); err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	events, err := e.events.ByBlockID(blockID)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	// events are keyed/sorted by [blockID, txID, txIndex, eventIndex]
    61  	// we need to resort them by tx index then event index so the output is in execution order
    62  	sort.Slice(events, func(i, j int) bool {
    63  		if events[i].TransactionIndex == events[j].TransactionIndex {
    64  			return events[i].EventIndex < events[j].EventIndex
    65  		}
    66  		return events[i].TransactionIndex < events[j].TransactionIndex
    67  	})
    68  
    69  	return events, nil
    70  }
    71  
    72  // ByBlockIDTransactionID checks data availability and return events for the given block ID and transaction ID
    73  // Expected errors:
    74  //   - indexer.ErrIndexNotInitialized if the `EventsIndex` has not been initialized
    75  //   - storage.ErrHeightNotIndexed when data is unavailable
    76  //   - codes.NotFound if result cannot be provided by storage due to the absence of data.
    77  func (e *EventsIndex) ByBlockIDTransactionID(blockID flow.Identifier, height uint64, transactionID flow.Identifier) ([]flow.Event, error) {
    78  	if err := e.checkDataAvailability(height); err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	return e.events.ByBlockIDTransactionID(blockID, transactionID)
    83  }
    84  
    85  // ByBlockIDTransactionIndex checks data availability and return events for the transaction at given index in a given block
    86  // Expected errors:
    87  //   - indexer.ErrIndexNotInitialized if the `EventsIndex` has not been initialized
    88  //   - storage.ErrHeightNotIndexed when data is unavailable
    89  //   - codes.NotFound if result cannot be provided by storage due to the absence of data.
    90  func (e *EventsIndex) ByBlockIDTransactionIndex(blockID flow.Identifier, height uint64, txIndex uint32) ([]flow.Event, error) {
    91  	if err := e.checkDataAvailability(height); err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	return e.events.ByBlockIDTransactionIndex(blockID, txIndex)
    96  }
    97  
    98  // LowestIndexedHeight returns the lowest height indexed by the execution state indexer.
    99  // Expected errors:
   100  // - indexer.ErrIndexNotInitialized if the EventsIndex has not been initialized
   101  func (e *EventsIndex) LowestIndexedHeight() (uint64, error) {
   102  	reporter, err := e.getReporter()
   103  	if err != nil {
   104  		return 0, err
   105  	}
   106  
   107  	return reporter.LowestIndexedHeight()
   108  }
   109  
   110  // HighestIndexedHeight returns the highest height indexed by the execution state indexer.
   111  // Expected errors:
   112  // - indexer.ErrIndexNotInitialized if the EventsIndex has not been initialized
   113  func (e *EventsIndex) HighestIndexedHeight() (uint64, error) {
   114  	reporter, err := e.getReporter()
   115  	if err != nil {
   116  		return 0, err
   117  	}
   118  
   119  	return reporter.HighestIndexedHeight()
   120  }
   121  
   122  // checkDataAvailability checks the availability of data at the given height by comparing it with the highest and lowest
   123  // indexed heights. If the height is beyond the indexed range, an error is returned.
   124  // Expected errors:
   125  //   - indexer.ErrIndexNotInitialized if the `TransactionResultsIndex` has not been initialized
   126  //   - storage.ErrHeightNotIndexed if the block at the provided height is not indexed yet
   127  //   - fmt.Errorf with custom message if the highest or lowest indexed heights cannot be retrieved from the reporter
   128  func (e *EventsIndex) checkDataAvailability(height uint64) error {
   129  	reporter, err := e.getReporter()
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	highestHeight, err := reporter.HighestIndexedHeight()
   135  	if err != nil {
   136  		return fmt.Errorf("could not get highest indexed height: %w", err)
   137  	}
   138  	if height > highestHeight {
   139  		return fmt.Errorf("%w: block not indexed yet", storage.ErrHeightNotIndexed)
   140  	}
   141  
   142  	lowestHeight, err := reporter.LowestIndexedHeight()
   143  	if err != nil {
   144  		return fmt.Errorf("could not get lowest indexed height: %w", err)
   145  	}
   146  	if height < lowestHeight {
   147  		return fmt.Errorf("%w: block is before lowest indexed height", storage.ErrHeightNotIndexed)
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  // getReporter retrieves the current index reporter instance from the atomic pointer.
   154  // Expected errors:
   155  //   - indexer.ErrIndexNotInitialized if the reporter is not initialized
   156  func (e *EventsIndex) getReporter() (state_synchronization.IndexReporter, error) {
   157  	reporter := e.reporter.Load()
   158  	if reporter == nil {
   159  		return nil, indexer.ErrIndexNotInitialized
   160  	}
   161  	return *reporter, nil
   162  }