github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/rpc/backend/backend_stream_blocks.go (about) 1 package backend 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 8 "github.com/rs/zerolog" 9 10 "github.com/onflow/flow-go/engine/access/subscription" 11 "github.com/onflow/flow-go/model/flow" 12 "github.com/onflow/flow-go/state/protocol" 13 "github.com/onflow/flow-go/storage" 14 "github.com/onflow/flow-go/utils/logging" 15 ) 16 17 // backendSubscribeBlocks is a struct representing a backend implementation for subscribing to blocks. 18 type backendSubscribeBlocks struct { 19 log zerolog.Logger 20 state protocol.State 21 blocks storage.Blocks 22 headers storage.Headers 23 24 subscriptionHandler *subscription.SubscriptionHandler 25 blockTracker subscription.BlockTracker 26 } 27 28 // SubscribeBlocksFromStartBlockID subscribes to the finalized or sealed blocks starting at the requested 29 // start block id, up until the latest available block. Once the latest is 30 // reached, the stream will remain open and responses are sent for each new 31 // block as it becomes available. 32 // 33 // Each block is filtered by the provided block status, and only 34 // those blocks that match the status are returned. 35 // 36 // Parameters: 37 // - ctx: Context for the operation. 38 // - startBlockID: The identifier of the starting block. 39 // - blockStatus: The status of the block, which could be only BlockStatusSealed or BlockStatusFinalized. 40 // 41 // If invalid parameters will be supplied SubscribeBlocksFromStartBlockID will return a failed subscription. 42 func (b *backendSubscribeBlocks) SubscribeBlocksFromStartBlockID(ctx context.Context, startBlockID flow.Identifier, blockStatus flow.BlockStatus) subscription.Subscription { 43 return b.subscribeFromStartBlockID(ctx, startBlockID, b.getBlockResponse(blockStatus)) 44 } 45 46 // SubscribeBlocksFromStartHeight subscribes to the finalized or sealed blocks starting at the requested 47 // start block height, up until the latest available block. Once the latest is 48 // reached, the stream will remain open and responses are sent for each new 49 // block as it becomes available. 50 // 51 // Each block is filtered by the provided block status, and only 52 // those blocks that match the status are returned. 53 // 54 // Parameters: 55 // - ctx: Context for the operation. 56 // - startHeight: The height of the starting block. 57 // - blockStatus: The status of the block, which could be only BlockStatusSealed or BlockStatusFinalized. 58 // 59 // If invalid parameters will be supplied SubscribeBlocksFromStartHeight will return a failed subscription. 60 func (b *backendSubscribeBlocks) SubscribeBlocksFromStartHeight(ctx context.Context, startHeight uint64, blockStatus flow.BlockStatus) subscription.Subscription { 61 return b.subscribeFromStartHeight(ctx, startHeight, b.getBlockResponse(blockStatus)) 62 } 63 64 // SubscribeBlocksFromLatest subscribes to the finalized or sealed blocks starting at the latest sealed block, 65 // up until the latest available block. Once the latest is 66 // reached, the stream will remain open and responses are sent for each new 67 // block as it becomes available. 68 // 69 // Each block is filtered by the provided block status, and only 70 // those blocks that match the status are returned. 71 // 72 // Parameters: 73 // - ctx: Context for the operation. 74 // - blockStatus: The status of the block, which could be only BlockStatusSealed or BlockStatusFinalized. 75 // 76 // If invalid parameters will be supplied SubscribeBlocksFromLatest will return a failed subscription. 77 func (b *backendSubscribeBlocks) SubscribeBlocksFromLatest(ctx context.Context, blockStatus flow.BlockStatus) subscription.Subscription { 78 return b.subscribeFromLatest(ctx, b.getBlockResponse(blockStatus)) 79 } 80 81 // SubscribeBlockHeadersFromStartBlockID streams finalized or sealed block headers starting at the requested 82 // start block id, up until the latest available block header. Once the latest is 83 // reached, the stream will remain open and responses are sent for each new 84 // block header as it becomes available. 85 // 86 // Each block header are filtered by the provided block status, and only 87 // those block headers that match the status are returned. 88 // 89 // Parameters: 90 // - ctx: Context for the operation. 91 // - startBlockID: The identifier of the starting block. 92 // - blockStatus: The status of the block, which could be only BlockStatusSealed or BlockStatusFinalized. 93 // 94 // If invalid parameters will be supplied SubscribeBlockHeadersFromStartBlockID will return a failed subscription. 95 func (b *backendSubscribeBlocks) SubscribeBlockHeadersFromStartBlockID(ctx context.Context, startBlockID flow.Identifier, blockStatus flow.BlockStatus) subscription.Subscription { 96 return b.subscribeFromStartBlockID(ctx, startBlockID, b.getBlockHeaderResponse(blockStatus)) 97 } 98 99 // SubscribeBlockHeadersFromStartHeight streams finalized or sealed block headers starting at the requested 100 // start block height, up until the latest available block header. Once the latest is 101 // reached, the stream will remain open and responses are sent for each new 102 // block header as it becomes available. 103 // 104 // Each block header are filtered by the provided block status, and only 105 // those block headers that match the status are returned. 106 // 107 // Parameters: 108 // - ctx: Context for the operation. 109 // - startHeight: The height of the starting block. 110 // - blockStatus: The status of the block, which could be only BlockStatusSealed or BlockStatusFinalized. 111 // 112 // If invalid parameters will be supplied SubscribeBlockHeadersFromStartHeight will return a failed subscription. 113 func (b *backendSubscribeBlocks) SubscribeBlockHeadersFromStartHeight(ctx context.Context, startHeight uint64, blockStatus flow.BlockStatus) subscription.Subscription { 114 return b.subscribeFromStartHeight(ctx, startHeight, b.getBlockHeaderResponse(blockStatus)) 115 } 116 117 // SubscribeBlockHeadersFromLatest streams finalized or sealed block headers starting at the latest sealed block, 118 // up until the latest available block header. Once the latest is 119 // reached, the stream will remain open and responses are sent for each new 120 // block header as it becomes available. 121 // 122 // Each block header are filtered by the provided block status, and only 123 // those block headers that match the status are returned. 124 // 125 // Parameters: 126 // - ctx: Context for the operation. 127 // - blockStatus: The status of the block, which could be only BlockStatusSealed or BlockStatusFinalized. 128 // 129 // If invalid parameters will be supplied SubscribeBlockHeadersFromLatest will return a failed subscription. 130 func (b *backendSubscribeBlocks) SubscribeBlockHeadersFromLatest(ctx context.Context, blockStatus flow.BlockStatus) subscription.Subscription { 131 return b.subscribeFromLatest(ctx, b.getBlockHeaderResponse(blockStatus)) 132 } 133 134 // SubscribeBlockDigestsFromStartBlockID streams finalized or sealed lightweight block starting at the requested 135 // start block id, up until the latest available block. Once the latest is 136 // reached, the stream will remain open and responses are sent for each new 137 // block as it becomes available. 138 // 139 // Each lightweight block are filtered by the provided block status, and only 140 // those blocks that match the status are returned. 141 // 142 // Parameters: 143 // - ctx: Context for the operation. 144 // - startBlockID: The identifier of the starting block. 145 // - blockStatus: The status of the block, which could be only BlockStatusSealed or BlockStatusFinalized. 146 // 147 // If invalid parameters will be supplied SubscribeBlockDigestsFromStartBlockID will return a failed subscription. 148 func (b *backendSubscribeBlocks) SubscribeBlockDigestsFromStartBlockID(ctx context.Context, startBlockID flow.Identifier, blockStatus flow.BlockStatus) subscription.Subscription { 149 return b.subscribeFromStartBlockID(ctx, startBlockID, b.getBlockDigestResponse(blockStatus)) 150 } 151 152 // SubscribeBlockDigestsFromStartHeight streams finalized or sealed lightweight block starting at the requested 153 // start block height, up until the latest available block. Once the latest is 154 // reached, the stream will remain open and responses are sent for each new 155 // block as it becomes available. 156 // 157 // Each lightweight block are filtered by the provided block status, and only 158 // those blocks that match the status are returned. 159 // 160 // Parameters: 161 // - ctx: Context for the operation. 162 // - startHeight: The height of the starting block. 163 // - blockStatus: The status of the block, which could be only BlockStatusSealed or BlockStatusFinalized. 164 // 165 // If invalid parameters will be supplied SubscribeBlockDigestsFromStartHeight will return a failed subscription. 166 func (b *backendSubscribeBlocks) SubscribeBlockDigestsFromStartHeight(ctx context.Context, startHeight uint64, blockStatus flow.BlockStatus) subscription.Subscription { 167 return b.subscribeFromStartHeight(ctx, startHeight, b.getBlockDigestResponse(blockStatus)) 168 } 169 170 // SubscribeBlockDigestsFromLatest streams finalized or sealed lightweight block starting at the latest sealed block, 171 // up until the latest available block. Once the latest is 172 // reached, the stream will remain open and responses are sent for each new 173 // block as it becomes available. 174 // 175 // Each lightweight block are filtered by the provided block status, and only 176 // those blocks that match the status are returned. 177 // 178 // Parameters: 179 // - ctx: Context for the operation. 180 // - blockStatus: The status of the block, which could be only BlockStatusSealed or BlockStatusFinalized. 181 // 182 // If invalid parameters will be supplied SubscribeBlockDigestsFromLatest will return a failed subscription. 183 func (b *backendSubscribeBlocks) SubscribeBlockDigestsFromLatest(ctx context.Context, blockStatus flow.BlockStatus) subscription.Subscription { 184 return b.subscribeFromLatest(ctx, b.getBlockDigestResponse(blockStatus)) 185 } 186 187 // subscribeFromStartBlockID is common method that allows clients to subscribe starting at the requested start block id. 188 // 189 // Parameters: 190 // - ctx: Context for the operation. 191 // - startBlockID: The identifier of the starting block. 192 // - getData: The callback used by subscriptions to retrieve data information for the specified height and block status. 193 // 194 // If invalid parameters are supplied, subscribeFromStartBlockID will return a failed subscription. 195 func (b *backendSubscribeBlocks) subscribeFromStartBlockID(ctx context.Context, startBlockID flow.Identifier, getData subscription.GetDataByHeightFunc) subscription.Subscription { 196 nextHeight, err := b.blockTracker.GetStartHeightFromBlockID(startBlockID) 197 if err != nil { 198 return subscription.NewFailedSubscription(err, "could not get start height from block id") 199 } 200 return b.subscriptionHandler.Subscribe(ctx, nextHeight, getData) 201 } 202 203 // subscribeFromStartHeight is common method that allows clients to subscribe starting at the requested start block height. 204 // 205 // Parameters: 206 // - ctx: Context for the operation. 207 // - startHeight: The height of the starting block. 208 // - getData: The callback used by subscriptions to retrieve data information for the specified height and block status. 209 // 210 // If invalid parameters are supplied, subscribeFromStartHeight will return a failed subscription. 211 func (b *backendSubscribeBlocks) subscribeFromStartHeight(ctx context.Context, startHeight uint64, getData subscription.GetDataByHeightFunc) subscription.Subscription { 212 nextHeight, err := b.blockTracker.GetStartHeightFromHeight(startHeight) 213 if err != nil { 214 return subscription.NewFailedSubscription(err, "could not get start height from block height") 215 } 216 return b.subscriptionHandler.Subscribe(ctx, nextHeight, getData) 217 } 218 219 // subscribeFromLatest is common method that allows clients to subscribe starting at the latest sealed block. 220 // 221 // Parameters: 222 // - ctx: Context for the operation. 223 // - getData: The callback used by subscriptions to retrieve data information for the specified height and block status. 224 // 225 // No errors are expected during normal operation. 226 func (b *backendSubscribeBlocks) subscribeFromLatest(ctx context.Context, getData subscription.GetDataByHeightFunc) subscription.Subscription { 227 nextHeight, err := b.blockTracker.GetStartHeightFromLatest(ctx) 228 if err != nil { 229 return subscription.NewFailedSubscription(err, "could not get start height from latest") 230 } 231 return b.subscriptionHandler.Subscribe(ctx, nextHeight, getData) 232 } 233 234 // getBlockResponse returns a GetDataByHeightFunc that retrieves block information for the specified height. 235 func (b *backendSubscribeBlocks) getBlockResponse(blockStatus flow.BlockStatus) subscription.GetDataByHeightFunc { 236 return func(_ context.Context, height uint64) (interface{}, error) { 237 block, err := b.getBlock(height, blockStatus) 238 if err != nil { 239 return nil, err 240 } 241 242 b.log.Trace(). 243 Hex("block_id", logging.ID(block.ID())). 244 Uint64("height", height). 245 Msgf("sending block info") 246 247 return block, nil 248 } 249 } 250 251 // getBlockHeaderResponse returns a GetDataByHeightFunc that retrieves block header information for the specified height. 252 func (b *backendSubscribeBlocks) getBlockHeaderResponse(blockStatus flow.BlockStatus) subscription.GetDataByHeightFunc { 253 return func(_ context.Context, height uint64) (interface{}, error) { 254 header, err := b.getBlockHeader(height, blockStatus) 255 if err != nil { 256 return nil, err 257 } 258 259 b.log.Trace(). 260 Hex("block_id", logging.ID(header.ID())). 261 Uint64("height", height). 262 Msgf("sending block header info") 263 264 return header, nil 265 } 266 } 267 268 // getBlockDigestResponse returns a GetDataByHeightFunc that retrieves lightweight block information for the specified height. 269 func (b *backendSubscribeBlocks) getBlockDigestResponse(blockStatus flow.BlockStatus) subscription.GetDataByHeightFunc { 270 return func(_ context.Context, height uint64) (interface{}, error) { 271 header, err := b.getBlockHeader(height, blockStatus) 272 if err != nil { 273 return nil, err 274 } 275 276 b.log.Trace(). 277 Hex("block_id", logging.ID(header.ID())). 278 Uint64("height", height). 279 Msgf("sending lightweight block info") 280 281 return flow.NewBlockDigest(header.ID(), header.Height, header.Timestamp), nil 282 } 283 } 284 285 // getBlockHeader returns the block header for the given block height. 286 // Expected errors during normal operation: 287 // - subscription.ErrBlockNotReady: block for the given block height is not available. 288 func (b *backendSubscribeBlocks) getBlockHeader(height uint64, expectedBlockStatus flow.BlockStatus) (*flow.Header, error) { 289 err := b.validateHeight(height, expectedBlockStatus) 290 if err != nil { 291 return nil, err 292 } 293 294 // since we are querying a finalized or sealed block header, we can use the height index and save an ID computation 295 header, err := b.headers.ByHeight(height) 296 if err != nil { 297 if errors.Is(err, storage.ErrNotFound) { 298 return nil, fmt.Errorf("failed to retrieve block header for height %d: %w", height, subscription.ErrBlockNotReady) 299 } 300 return nil, err 301 } 302 303 return header, nil 304 } 305 306 // getBlock returns the block for the given block height. 307 // Expected errors during normal operation: 308 // - subscription.ErrBlockNotReady: block for the given block height is not available. 309 func (b *backendSubscribeBlocks) getBlock(height uint64, expectedBlockStatus flow.BlockStatus) (*flow.Block, error) { 310 err := b.validateHeight(height, expectedBlockStatus) 311 if err != nil { 312 return nil, err 313 } 314 315 // since we are querying a finalized or sealed block, we can use the height index and save an ID computation 316 block, err := b.blocks.ByHeight(height) 317 if err != nil { 318 if errors.Is(err, storage.ErrNotFound) { 319 return nil, fmt.Errorf("failed to retrieve block for height %d: %w", height, subscription.ErrBlockNotReady) 320 } 321 return nil, err 322 } 323 324 return block, nil 325 } 326 327 // validateHeight checks if the given block height is valid and available based on the expected block status. 328 // Expected errors during normal operation: 329 // - subscription.ErrBlockNotReady when unable to retrieve the block by height. 330 func (b *backendSubscribeBlocks) validateHeight(height uint64, expectedBlockStatus flow.BlockStatus) error { 331 highestHeight, err := b.blockTracker.GetHighestHeight(expectedBlockStatus) 332 if err != nil { 333 return fmt.Errorf("could not get highest available height: %w", err) 334 } 335 336 // fail early if no notification has been received for the given block height. 337 // note: it's possible for the data to exist in the data store before the notification is 338 // received. this ensures a consistent view is available to all streams. 339 if height > highestHeight { 340 return fmt.Errorf("block %d is not available yet: %w", height, subscription.ErrBlockNotReady) 341 } 342 343 return nil 344 }