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