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 }