github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/common/synchronization/request_handler.go (about) 1 package synchronization 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 8 "github.com/rs/zerolog" 9 10 "github.com/onflow/flow-go/engine" 11 "github.com/onflow/flow-go/model/flow" 12 "github.com/onflow/flow-go/model/messages" 13 "github.com/onflow/flow-go/module" 14 "github.com/onflow/flow-go/module/chainsync" 15 "github.com/onflow/flow-go/module/component" 16 "github.com/onflow/flow-go/module/events" 17 "github.com/onflow/flow-go/module/irrecoverable" 18 "github.com/onflow/flow-go/module/metrics" 19 "github.com/onflow/flow-go/network/channels" 20 "github.com/onflow/flow-go/storage" 21 "github.com/onflow/flow-go/utils/logging" 22 ) 23 24 // defaultSyncRequestQueueCapacity maximum capacity of sync requests queue 25 const defaultSyncRequestQueueCapacity = 500 26 27 // defaultSyncRequestQueueCapacity maximum capacity of range requests queue 28 const defaultRangeRequestQueueCapacity = 500 29 30 // defaultSyncRequestQueueCapacity maximum capacity of batch requests queue 31 const defaultBatchRequestQueueCapacity = 500 32 33 // defaultEngineRequestsWorkers number of workers to dispatch events for requests 34 const defaultEngineRequestsWorkers = 8 35 36 // RequestHandler encapsulates message queues and processing logic for the sync engine. 37 // It logically separates request processing from active participation (sending requests), 38 // primarily to simplify nodes which bridge the public and private networks. 39 // 40 // The RequestHandlerEngine embeds RequestHandler to create an engine which only responds 41 // to requests on the public network (does not send requests over this network). 42 // The Engine embeds RequestHandler and additionally includes logic for sending sync requests. 43 // 44 // Although the RequestHandler defines a notifier, message queue, and processing worker logic, 45 // it is not itself a component.Component and does not manage any worker threads. The containing 46 // engine is responsible for starting the worker threads for processing requests. 47 type RequestHandler struct { 48 me module.Local 49 log zerolog.Logger 50 metrics module.EngineMetrics 51 52 blocks storage.Blocks 53 finalizedHeaderCache module.FinalizedHeaderCache 54 core module.SyncCore 55 responseSender ResponseSender 56 57 pendingSyncRequests engine.MessageStore // message store for *message.SyncRequest 58 pendingBatchRequests engine.MessageStore // message store for *message.BatchRequest 59 pendingRangeRequests engine.MessageStore // message store for *message.RangeRequest 60 requestMessageHandler *engine.MessageHandler // message handler responsible for request processing 61 62 queueMissingHeights bool // true if missing heights should be added to download queue 63 } 64 65 func NewRequestHandler( 66 log zerolog.Logger, 67 metrics module.EngineMetrics, 68 responseSender ResponseSender, 69 me module.Local, 70 finalizedHeaderCache *events.FinalizedHeaderCache, 71 blocks storage.Blocks, 72 core module.SyncCore, 73 queueMissingHeights bool, 74 ) *RequestHandler { 75 r := &RequestHandler{ 76 me: me, 77 log: log.With().Str("engine", "synchronization").Logger(), 78 metrics: metrics, 79 finalizedHeaderCache: finalizedHeaderCache, 80 blocks: blocks, 81 core: core, 82 responseSender: responseSender, 83 queueMissingHeights: queueMissingHeights, 84 } 85 86 r.setupRequestMessageHandler() 87 88 return r 89 } 90 91 // Process processes the given event from the node with the given origin ID in a blocking manner. 92 // No errors are expected during normal operation. 93 func (r *RequestHandler) Process(channel channels.Channel, originID flow.Identifier, event interface{}) error { 94 err := r.requestMessageHandler.Process(originID, event) 95 if err != nil { 96 if engine.IsIncompatibleInputTypeError(err) { 97 r.log.Warn().Msgf("%v delivered unsupported message %T through %v", originID, event, channel) 98 return nil 99 } 100 return fmt.Errorf("unexpected error while processing engine message: %w", err) 101 } 102 return nil 103 } 104 105 // setupRequestMessageHandler initializes the inbound queues and the MessageHandler for UNTRUSTED requests. 106 func (r *RequestHandler) setupRequestMessageHandler() { 107 // RequestHeap deduplicates requests by keeping only one sync request for each requester. 108 r.pendingSyncRequests = NewRequestHeap(defaultSyncRequestQueueCapacity) 109 r.pendingRangeRequests = NewRequestHeap(defaultRangeRequestQueueCapacity) 110 r.pendingBatchRequests = NewRequestHeap(defaultBatchRequestQueueCapacity) 111 112 // define message queueing behaviour 113 r.requestMessageHandler = engine.NewMessageHandler( 114 r.log, 115 engine.NewNotifier(), 116 engine.Pattern{ 117 Match: func(msg *engine.Message) bool { 118 _, ok := msg.Payload.(*messages.SyncRequest) 119 if ok { 120 r.metrics.MessageReceived(metrics.EngineSynchronization, metrics.MessageSyncRequest) 121 } 122 return ok 123 }, 124 Store: r.pendingSyncRequests, 125 }, 126 engine.Pattern{ 127 Match: func(msg *engine.Message) bool { 128 _, ok := msg.Payload.(*messages.RangeRequest) 129 if ok { 130 r.metrics.MessageReceived(metrics.EngineSynchronization, metrics.MessageRangeRequest) 131 } 132 return ok 133 }, 134 Store: r.pendingRangeRequests, 135 }, 136 engine.Pattern{ 137 Match: func(msg *engine.Message) bool { 138 _, ok := msg.Payload.(*messages.BatchRequest) 139 if ok { 140 r.metrics.MessageReceived(metrics.EngineSynchronization, metrics.MessageBatchRequest) 141 } 142 return ok 143 }, 144 Store: r.pendingBatchRequests, 145 }, 146 ) 147 } 148 149 // onSyncRequest processes an outgoing handshake; if we have a higher height, we 150 // inform the other node of it, so they can organize their block downloads. If 151 // we have a lower height, we add the difference to our own download queue. 152 // No errors are expected during normal operation. 153 func (r *RequestHandler) onSyncRequest(originID flow.Identifier, req *messages.SyncRequest) error { 154 finalizedHeader := r.finalizedHeaderCache.Get() 155 156 logger := r.log.With().Str("origin_id", originID.String()).Logger() 157 logger.Debug(). 158 Uint64("origin_height", req.Height). 159 Uint64("local_height", finalizedHeader.Height). 160 Msg("received new sync request") 161 162 if r.queueMissingHeights { 163 // queue any missing heights as needed 164 r.core.HandleHeight(finalizedHeader, req.Height) 165 } 166 167 // don't bother sending a response if we're within tolerance or if we're 168 // behind the requester 169 if r.core.WithinTolerance(finalizedHeader, req.Height) || req.Height > finalizedHeader.Height { 170 return nil 171 } 172 173 // if we're sufficiently ahead of the requester, send a response 174 res := &messages.SyncResponse{ 175 Height: finalizedHeader.Height, 176 Nonce: req.Nonce, 177 } 178 err := r.responseSender.SendResponse(res, originID) 179 if err != nil { 180 logger.Warn().Err(err).Msg("sending sync response failed") 181 return nil 182 } 183 r.metrics.MessageSent(metrics.EngineSynchronization, metrics.MessageSyncResponse) 184 185 return nil 186 } 187 188 // onRangeRequest processes a request for a range of blocks by height. 189 // No errors are expected during normal operation. 190 func (r *RequestHandler) onRangeRequest(originID flow.Identifier, req *messages.RangeRequest) error { 191 logger := r.log.With().Str("origin_id", originID.String()).Logger() 192 logger.Debug().Msg("received new range request") 193 194 // get the latest final state to know if we can fulfill the request 195 finalizedHeader := r.finalizedHeaderCache.Get() 196 197 // if we don't have anything to send, we can bail right away 198 if finalizedHeader.Height < req.FromHeight || req.FromHeight > req.ToHeight { 199 return nil 200 } 201 202 // enforce client-side max request size 203 var maxSize uint 204 // TODO: clean up this logic 205 if core, ok := r.core.(*chainsync.Core); ok { 206 maxSize = core.Config.MaxSize 207 } else { 208 maxSize = chainsync.DefaultConfig().MaxSize 209 } 210 maxHeight := req.FromHeight + uint64(maxSize) 211 if maxHeight < req.ToHeight { 212 logger.Warn(). 213 Uint64("from", req.FromHeight). 214 Uint64("to", req.ToHeight). 215 Uint64("size", (req.ToHeight-req.FromHeight)+1). 216 Uint("max_size", maxSize). 217 Bool(logging.KeySuspicious, true). 218 Msg("range request is too large") 219 220 req.ToHeight = maxHeight 221 } 222 223 // get all the blocks, one by one 224 blocks := make([]messages.UntrustedBlock, 0, req.ToHeight-req.FromHeight+1) 225 for height := req.FromHeight; height <= req.ToHeight; height++ { 226 block, err := r.blocks.ByHeight(height) 227 if errors.Is(err, storage.ErrNotFound) { 228 logger.Error().Uint64("height", height).Msg("skipping unknown heights") 229 break 230 } 231 if err != nil { 232 return fmt.Errorf("could not get block for height (%d): %w", height, err) 233 } 234 blocks = append(blocks, messages.UntrustedBlockFromInternal(block)) 235 } 236 237 // if there are no blocks to send, skip network message 238 if len(blocks) == 0 { 239 logger.Debug().Msg("skipping empty range response") 240 return nil 241 } 242 243 // send the response 244 res := &messages.BlockResponse{ 245 Nonce: req.Nonce, 246 Blocks: blocks, 247 } 248 err := r.responseSender.SendResponse(res, originID) 249 if err != nil { 250 logger.Warn().Err(err).Msg("sending range response failed") 251 return nil 252 } 253 r.metrics.MessageSent(metrics.EngineSynchronization, metrics.MessageBlockResponse) 254 255 return nil 256 } 257 258 // onBatchRequest processes a request for a specific block by block ID. 259 func (r *RequestHandler) onBatchRequest(originID flow.Identifier, req *messages.BatchRequest) error { 260 logger := r.log.With().Str("origin_id", originID.String()).Logger() 261 logger.Debug().Msg("received new batch request") 262 263 // we should bail and send nothing on empty request 264 if len(req.BlockIDs) == 0 { 265 return nil 266 } 267 268 // TODO: clean up this logic 269 var maxSize uint 270 if core, ok := r.core.(*chainsync.Core); ok { 271 maxSize = core.Config.MaxSize 272 } else { 273 maxSize = chainsync.DefaultConfig().MaxSize 274 } 275 276 if len(req.BlockIDs) > int(maxSize) { 277 logger.Warn(). 278 Int("size", len(req.BlockIDs)). 279 Uint("max_size", maxSize). 280 Bool(logging.KeySuspicious, true). 281 Msg("batch request is too large") 282 } 283 284 // deduplicate the block IDs in the batch request 285 blockIDs := make(map[flow.Identifier]struct{}) 286 for _, blockID := range req.BlockIDs { 287 blockIDs[blockID] = struct{}{} 288 289 // enforce client-side max request size 290 if len(blockIDs) == int(maxSize) { 291 break 292 } 293 } 294 295 // try to get all the blocks by ID 296 blocks := make([]messages.UntrustedBlock, 0, len(blockIDs)) 297 for blockID := range blockIDs { 298 block, err := r.blocks.ByID(blockID) 299 if errors.Is(err, storage.ErrNotFound) { 300 logger.Debug().Hex("block_id", blockID[:]).Msg("skipping unknown block") 301 continue 302 } 303 if err != nil { 304 return fmt.Errorf("could not get block by ID (%s): %w", blockID, err) 305 } 306 blocks = append(blocks, messages.UntrustedBlockFromInternal(block)) 307 } 308 309 // if there are no blocks to send, skip network message 310 if len(blocks) == 0 { 311 logger.Debug().Msg("skipping empty batch response") 312 return nil 313 } 314 315 // send the response 316 res := &messages.BlockResponse{ 317 Nonce: req.Nonce, 318 Blocks: blocks, 319 } 320 err := r.responseSender.SendResponse(res, originID) 321 if err != nil { 322 logger.Warn().Err(err).Msg("sending batch response failed") 323 return nil 324 } 325 r.metrics.MessageSent(metrics.EngineSynchronization, metrics.MessageBlockResponse) 326 327 return nil 328 } 329 330 // processAvailableRequests is processor of pending events which drives events from networking layer to business logic. 331 func (r *RequestHandler) processAvailableRequests(ctx context.Context) error { 332 for { 333 select { 334 case <-ctx.Done(): 335 return nil 336 default: 337 } 338 339 msg, ok := r.pendingSyncRequests.Get() 340 if ok { 341 err := r.onSyncRequest(msg.OriginID, msg.Payload.(*messages.SyncRequest)) 342 if err != nil { 343 return fmt.Errorf("processing sync request failed: %w", err) 344 } 345 continue 346 } 347 348 msg, ok = r.pendingRangeRequests.Get() 349 if ok { 350 err := r.onRangeRequest(msg.OriginID, msg.Payload.(*messages.RangeRequest)) 351 if err != nil { 352 return fmt.Errorf("processing range request failed: %w", err) 353 } 354 continue 355 } 356 357 msg, ok = r.pendingBatchRequests.Get() 358 if ok { 359 err := r.onBatchRequest(msg.OriginID, msg.Payload.(*messages.BatchRequest)) 360 if err != nil { 361 return fmt.Errorf("processing batch request failed: %w", err) 362 } 363 continue 364 } 365 366 // when there is no more messages in the queue, back to the loop to wait 367 // for the next incoming message to arrive. 368 return nil 369 } 370 } 371 372 // requestProcessingWorker is a separate goroutine that performs processing of queued requests. 373 // Multiple instances may be invoked. It is invoked and managed by the Engine or RequestHandlerEngine 374 // which embeds this RequestHandler. 375 func (r *RequestHandler) requestProcessingWorker(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 376 ready() 377 378 notifier := r.requestMessageHandler.GetNotifier() 379 done := ctx.Done() 380 for { 381 select { 382 case <-done: 383 return 384 case <-notifier: 385 err := r.processAvailableRequests(ctx) 386 if err != nil { 387 r.log.Err(err).Msg("internal error processing queued requests") 388 ctx.Throw(err) 389 } 390 } 391 } 392 }