github.com/onflow/flow-go@v0.33.17/engine/access/state_stream/backend/backend.go (about) 1 package backend 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/rs/zerolog" 9 "google.golang.org/grpc/codes" 10 "google.golang.org/grpc/status" 11 12 "github.com/onflow/flow-go/engine" 13 "github.com/onflow/flow-go/engine/access/rpc/backend" 14 "github.com/onflow/flow-go/engine/access/state_stream" 15 "github.com/onflow/flow-go/engine/common/rpc" 16 "github.com/onflow/flow-go/fvm/errors" 17 "github.com/onflow/flow-go/model/flow" 18 "github.com/onflow/flow-go/module/counters" 19 "github.com/onflow/flow-go/module/execution" 20 "github.com/onflow/flow-go/module/executiondatasync/execution_data" 21 "github.com/onflow/flow-go/module/executiondatasync/execution_data/cache" 22 "github.com/onflow/flow-go/module/state_synchronization" 23 "github.com/onflow/flow-go/module/state_synchronization/indexer" 24 "github.com/onflow/flow-go/state/protocol" 25 "github.com/onflow/flow-go/storage" 26 ) 27 28 // Config defines the configurable options for the ingress server. 29 type Config struct { 30 state_stream.EventFilterConfig 31 32 // ListenAddr is the address the GRPC server will listen on as host:port 33 ListenAddr string 34 35 // MaxExecutionDataMsgSize is the max message size for block execution data API 36 MaxExecutionDataMsgSize uint 37 38 // RpcMetricsEnabled specifies whether to enable the GRPC metrics 39 RpcMetricsEnabled bool 40 41 // MaxGlobalStreams defines the global max number of streams that can be open at the same time. 42 MaxGlobalStreams uint32 43 44 // RegisterIDsRequestLimit defines the max number of register IDs that can be received in a single request. 45 RegisterIDsRequestLimit uint32 46 47 // ExecutionDataCacheSize is the max number of objects for the execution data cache. 48 ExecutionDataCacheSize uint32 49 50 // ClientSendTimeout is the timeout for sending a message to the client. After the timeout, 51 // the stream is closed with an error. 52 ClientSendTimeout time.Duration 53 54 // ClientSendBufferSize is the size of the response buffer for sending messages to the client. 55 ClientSendBufferSize uint 56 57 // ResponseLimit is the max responses per second allowed on a stream. After exceeding the limit, 58 // the stream is paused until more capacity is available. Searches of past data can be CPU 59 // intensive, so this helps manage the impact. 60 ResponseLimit float64 61 62 // HeartbeatInterval specifies the block interval at which heartbeat messages should be sent. 63 HeartbeatInterval uint64 64 } 65 66 type GetExecutionDataFunc func(context.Context, uint64) (*execution_data.BlockExecutionDataEntity, error) 67 type GetStartHeightFunc func(flow.Identifier, uint64) (uint64, error) 68 69 type StateStreamBackend struct { 70 ExecutionDataBackend 71 EventsBackend 72 73 log zerolog.Logger 74 state protocol.State 75 headers storage.Headers 76 seals storage.Seals 77 results storage.ExecutionResults 78 execDataStore execution_data.ExecutionDataStore 79 execDataCache *cache.ExecutionDataCache 80 broadcaster *engine.Broadcaster 81 rootBlockHeight uint64 82 rootBlockID flow.Identifier 83 registers *execution.RegistersAsyncStore 84 indexReporter state_synchronization.IndexReporter 85 registerRequestLimit int 86 87 // highestHeight contains the highest consecutive block height for which we have received a 88 // new Execution Data notification. 89 highestHeight counters.StrictMonotonousCounter 90 } 91 92 func New( 93 log zerolog.Logger, 94 config Config, 95 state protocol.State, 96 headers storage.Headers, 97 seals storage.Seals, 98 results storage.ExecutionResults, 99 execDataStore execution_data.ExecutionDataStore, 100 execDataCache *cache.ExecutionDataCache, 101 broadcaster *engine.Broadcaster, 102 rootHeight uint64, 103 highestAvailableHeight uint64, 104 registers *execution.RegistersAsyncStore, 105 eventsIndex *backend.EventsIndex, 106 useEventsIndex bool, 107 ) (*StateStreamBackend, error) { 108 logger := log.With().Str("module", "state_stream_api").Logger() 109 110 // cache the root block height and ID for runtime lookups. 111 rootBlockID, err := headers.BlockIDByHeight(rootHeight) 112 if err != nil { 113 return nil, fmt.Errorf("could not get root block ID: %w", err) 114 } 115 116 b := &StateStreamBackend{ 117 log: logger, 118 state: state, 119 headers: headers, 120 seals: seals, 121 results: results, 122 execDataStore: execDataStore, 123 execDataCache: execDataCache, 124 broadcaster: broadcaster, 125 rootBlockHeight: rootHeight, 126 rootBlockID: rootBlockID, 127 registers: registers, 128 indexReporter: eventsIndex, 129 registerRequestLimit: int(config.RegisterIDsRequestLimit), 130 highestHeight: counters.NewMonotonousCounter(highestAvailableHeight), 131 } 132 133 b.ExecutionDataBackend = ExecutionDataBackend{ 134 log: logger, 135 headers: headers, 136 broadcaster: broadcaster, 137 sendTimeout: config.ClientSendTimeout, 138 responseLimit: config.ResponseLimit, 139 sendBufferSize: int(config.ClientSendBufferSize), 140 getExecutionData: b.getExecutionData, 141 getStartHeight: b.getStartHeight, 142 } 143 144 b.EventsBackend = EventsBackend{ 145 log: logger, 146 headers: headers, 147 broadcaster: broadcaster, 148 sendTimeout: config.ClientSendTimeout, 149 responseLimit: config.ResponseLimit, 150 sendBufferSize: int(config.ClientSendBufferSize), 151 getExecutionData: b.getExecutionData, 152 getStartHeight: b.getStartHeight, 153 useIndex: useEventsIndex, 154 eventsIndex: eventsIndex, 155 } 156 157 return b, nil 158 } 159 160 // getExecutionData returns the execution data for the given block height. 161 // Expected errors during normal operation: 162 // - storage.ErrNotFound or execution_data.BlobNotFoundError: execution data for the given block height is not available. 163 func (b *StateStreamBackend) getExecutionData(ctx context.Context, height uint64) (*execution_data.BlockExecutionDataEntity, error) { 164 // fail early if no notification has been received for the given block height. 165 // note: it's possible for the data to exist in the data store before the notification is 166 // received. this ensures a consistent view is available to all streams. 167 if height > b.highestHeight.Value() { 168 return nil, fmt.Errorf("execution data for block %d is not available yet: %w", height, storage.ErrNotFound) 169 } 170 171 execData, err := b.execDataCache.ByHeight(ctx, height) 172 if err != nil { 173 return nil, fmt.Errorf("could not get execution data for block %d: %w", height, err) 174 } 175 176 return execData, nil 177 } 178 179 // getStartHeight returns the start height to use when searching. 180 // Only one of startBlockID and startHeight may be set. Otherwise, an InvalidArgument error is returned. 181 // If a block is provided and does not exist, a NotFound error is returned. 182 // If neither startBlockID nor startHeight is provided, the latest sealed block is used. 183 func (b *StateStreamBackend) getStartHeight(startBlockID flow.Identifier, startHeight uint64) (height uint64, err error) { 184 // make sure only one of start block ID and start height is provided 185 if startBlockID != flow.ZeroID && startHeight > 0 { 186 return 0, status.Errorf(codes.InvalidArgument, "only one of start block ID and start height may be provided") 187 } 188 189 // ensure that the resolved start height is available 190 defer func() { 191 if err == nil { 192 height, err = b.checkStartHeight(height) 193 } 194 }() 195 196 if startBlockID != flow.ZeroID { 197 return b.startHeightFromBlockID(startBlockID) 198 } 199 200 if startHeight > 0 { 201 return b.startHeightFromHeight(startHeight) 202 } 203 204 // if no start block was provided, use the latest sealed block 205 header, err := b.state.Sealed().Head() 206 if err != nil { 207 return 0, status.Errorf(codes.Internal, "could not get latest sealed block: %v", err) 208 } 209 return header.Height, nil 210 } 211 212 func (b *StateStreamBackend) startHeightFromBlockID(startBlockID flow.Identifier) (uint64, error) { 213 header, err := b.headers.ByBlockID(startBlockID) 214 if err != nil { 215 return 0, rpc.ConvertStorageError(fmt.Errorf("could not get header for block %v: %w", startBlockID, err)) 216 } 217 return header.Height, nil 218 } 219 220 func (b *StateStreamBackend) startHeightFromHeight(startHeight uint64) (uint64, error) { 221 if startHeight < b.rootBlockHeight { 222 return 0, status.Errorf(codes.InvalidArgument, "start height must be greater than or equal to the root height %d", b.rootBlockHeight) 223 } 224 225 header, err := b.headers.ByHeight(startHeight) 226 if err != nil { 227 return 0, rpc.ConvertStorageError(fmt.Errorf("could not get header for height %d: %w", startHeight, err)) 228 } 229 return header.Height, nil 230 } 231 232 func (b *StateStreamBackend) checkStartHeight(height uint64) (uint64, error) { 233 // if the start block is the root block, there will not be an execution data. skip it and 234 // begin from the next block. 235 if height == b.rootBlockHeight { 236 height = b.rootBlockHeight + 1 237 } 238 239 if !b.useIndex { 240 return height, nil 241 } 242 243 lowestHeight, highestHeight, err := b.getIndexerHeights() 244 if err != nil { 245 return 0, err 246 } 247 248 if height < lowestHeight { 249 return 0, status.Errorf(codes.InvalidArgument, "start height %d is lower than lowest indexed height %d", height, lowestHeight) 250 } 251 252 if height > highestHeight { 253 return 0, status.Errorf(codes.InvalidArgument, "start height %d is higher than highest indexed height %d", height, highestHeight) 254 } 255 256 return height, nil 257 } 258 259 // getIndexerHeights returns the lowest and highest indexed block heights 260 // Expected errors during normal operation: 261 // - codes.FailedPrecondition: if the index reporter is not ready yet. 262 // - codes.Internal: if there was any other error getting the heights. 263 func (b *StateStreamBackend) getIndexerHeights() (uint64, uint64, error) { 264 lowestHeight, err := b.indexReporter.LowestIndexedHeight() 265 if err != nil { 266 if errors.Is(err, storage.ErrHeightNotIndexed) || errors.Is(err, indexer.ErrIndexNotInitialized) { 267 // the index is not ready yet, but likely will be eventually 268 return 0, 0, status.Errorf(codes.FailedPrecondition, "failed to get lowest indexed height: %v", err) 269 } 270 return 0, 0, rpc.ConvertError(err, "failed to get lowest indexed height", codes.Internal) 271 } 272 273 highestHeight, err := b.indexReporter.HighestIndexedHeight() 274 if err != nil { 275 if errors.Is(err, storage.ErrHeightNotIndexed) || errors.Is(err, indexer.ErrIndexNotInitialized) { 276 // the index is not ready yet, but likely will be eventually 277 return 0, 0, status.Errorf(codes.FailedPrecondition, "failed to get highest indexed height: %v", err) 278 } 279 return 0, 0, rpc.ConvertError(err, "failed to get highest indexed height", codes.Internal) 280 } 281 282 return lowestHeight, highestHeight, nil 283 } 284 285 // setHighestHeight sets the highest height for which execution data is available. 286 func (b *StateStreamBackend) setHighestHeight(height uint64) bool { 287 return b.highestHeight.Set(height) 288 } 289 290 // GetRegisterValues returns the register values for the given register IDs at the given block height. 291 func (b *StateStreamBackend) GetRegisterValues(ids flow.RegisterIDs, height uint64) ([]flow.RegisterValue, error) { 292 if len(ids) > b.registerRequestLimit { 293 return nil, status.Errorf(codes.InvalidArgument, "number of register IDs exceeds limit of %d", b.registerRequestLimit) 294 } 295 296 values, err := b.registers.RegisterValues(ids, height) 297 if err != nil { 298 if errors.Is(err, storage.ErrHeightNotIndexed) { 299 return nil, status.Errorf(codes.OutOfRange, "register values for block %d is not available", height) 300 } 301 if errors.Is(err, storage.ErrNotFound) { 302 return nil, status.Errorf(codes.NotFound, "register values for block %d not found", height) 303 } 304 return nil, err 305 } 306 307 return values, nil 308 }