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 }