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

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