github.com/onflow/flow-go@v0.33.17/engine/common/synchronization/engine.go (about) 1 // (c) 2019 Dapper Labs - ALL RIGHTS RESERVED 2 3 package synchronization 4 5 import ( 6 "context" 7 "fmt" 8 "time" 9 10 "github.com/hashicorp/go-multierror" 11 "github.com/rs/zerolog" 12 13 "github.com/onflow/flow-go/consensus/hotstuff" 14 "github.com/onflow/flow-go/engine" 15 "github.com/onflow/flow-go/engine/common/fifoqueue" 16 "github.com/onflow/flow-go/engine/consensus" 17 "github.com/onflow/flow-go/model/chainsync" 18 "github.com/onflow/flow-go/model/flow" 19 "github.com/onflow/flow-go/model/messages" 20 "github.com/onflow/flow-go/module" 21 synccore "github.com/onflow/flow-go/module/chainsync" 22 "github.com/onflow/flow-go/module/component" 23 "github.com/onflow/flow-go/module/events" 24 "github.com/onflow/flow-go/module/irrecoverable" 25 "github.com/onflow/flow-go/module/metrics" 26 "github.com/onflow/flow-go/network" 27 "github.com/onflow/flow-go/network/alsp" 28 "github.com/onflow/flow-go/network/channels" 29 "github.com/onflow/flow-go/state/protocol" 30 "github.com/onflow/flow-go/storage" 31 "github.com/onflow/flow-go/utils/logging" 32 "github.com/onflow/flow-go/utils/rand" 33 ) 34 35 // defaultSyncResponseQueueCapacity maximum capacity of sync responses queue 36 const defaultSyncResponseQueueCapacity = 500 37 38 // defaultBlockResponseQueueCapacity maximum capacity of block responses queue 39 const defaultBlockResponseQueueCapacity = 500 40 41 // Engine is the synchronization engine, responsible for synchronizing chain state. 42 type Engine struct { 43 component.Component 44 hotstuff.FinalizationConsumer 45 46 log zerolog.Logger 47 metrics module.EngineMetrics 48 me module.Local 49 finalizedHeaderCache module.FinalizedHeaderCache 50 con network.Conduit 51 blocks storage.Blocks 52 comp consensus.Compliance 53 54 pollInterval time.Duration 55 scanInterval time.Duration 56 core module.SyncCore 57 participantsProvider module.IdentifierProvider 58 59 requestHandler *RequestHandler // component responsible for handling requests 60 spamDetectionConfig *SpamDetectionConfig 61 62 pendingSyncResponses engine.MessageStore // message store for *message.SyncResponse 63 pendingBlockResponses engine.MessageStore // message store for *message.BlockResponse 64 responseMessageHandler *engine.MessageHandler // message handler responsible for response processing 65 } 66 67 var _ network.MessageProcessor = (*Engine)(nil) 68 var _ component.Component = (*Engine)(nil) 69 70 // New creates a new main chain synchronization engine. 71 func New( 72 log zerolog.Logger, 73 metrics module.EngineMetrics, 74 net network.EngineRegistry, 75 me module.Local, 76 state protocol.State, 77 blocks storage.Blocks, 78 comp consensus.Compliance, 79 core module.SyncCore, 80 participantsProvider module.IdentifierProvider, 81 spamDetectionConfig *SpamDetectionConfig, 82 opts ...OptionFunc, 83 ) (*Engine, error) { 84 85 opt := DefaultConfig() 86 for _, f := range opts { 87 f(opt) 88 } 89 90 if comp == nil { 91 panic("must initialize synchronization engine with comp engine") 92 } 93 94 finalizedHeaderCache, finalizedCacheWorker, err := events.NewFinalizedHeaderCache(state) 95 if err != nil { 96 return nil, fmt.Errorf("could not create finalized header cache: %w", err) 97 } 98 99 // initialize the propagation engine with its dependencies 100 e := &Engine{ 101 FinalizationConsumer: finalizedHeaderCache, 102 log: log.With().Str("engine", "synchronization").Logger(), 103 metrics: metrics, 104 me: me, 105 finalizedHeaderCache: finalizedHeaderCache, 106 blocks: blocks, 107 comp: comp, 108 core: core, 109 pollInterval: opt.PollInterval, 110 scanInterval: opt.ScanInterval, 111 participantsProvider: participantsProvider, 112 spamDetectionConfig: spamDetectionConfig, 113 } 114 115 // register the engine with the network layer and store the conduit 116 con, err := net.Register(channels.SyncCommittee, e) 117 if err != nil { 118 return nil, fmt.Errorf("could not register engine: %w", err) 119 } 120 e.con = con 121 e.requestHandler = NewRequestHandler(log, metrics, NewResponseSender(con), me, finalizedHeaderCache, blocks, core, true) 122 123 // set up worker routines 124 builder := component.NewComponentManagerBuilder(). 125 AddWorker(finalizedCacheWorker). 126 AddWorker(e.checkLoop). 127 AddWorker(e.responseProcessingLoop) 128 for i := 0; i < defaultEngineRequestsWorkers; i++ { 129 builder.AddWorker(e.requestHandler.requestProcessingWorker) 130 } 131 e.Component = builder.Build() 132 133 err = e.setupResponseMessageHandler() 134 if err != nil { 135 return nil, fmt.Errorf("could not setup message handler") 136 } 137 138 return e, nil 139 } 140 141 // setupResponseMessageHandler initializes the inbound queues and the MessageHandler for UNTRUSTED responses. 142 func (e *Engine) setupResponseMessageHandler() error { 143 syncResponseQueue, err := fifoqueue.NewFifoQueue(defaultSyncResponseQueueCapacity) 144 if err != nil { 145 return fmt.Errorf("failed to create queue for sync responses: %w", err) 146 } 147 148 e.pendingSyncResponses = &engine.FifoMessageStore{ 149 FifoQueue: syncResponseQueue, 150 } 151 152 blockResponseQueue, err := fifoqueue.NewFifoQueue(defaultBlockResponseQueueCapacity) 153 if err != nil { 154 return fmt.Errorf("failed to create queue for block responses: %w", err) 155 } 156 157 e.pendingBlockResponses = &engine.FifoMessageStore{ 158 FifoQueue: blockResponseQueue, 159 } 160 161 // define message queueing behaviour 162 e.responseMessageHandler = engine.NewMessageHandler( 163 e.log, 164 engine.NewNotifier(), 165 engine.Pattern{ 166 Match: func(msg *engine.Message) bool { 167 _, ok := msg.Payload.(*messages.SyncResponse) 168 if ok { 169 e.metrics.MessageReceived(metrics.EngineSynchronization, metrics.MessageSyncResponse) 170 } 171 return ok 172 }, 173 Store: e.pendingSyncResponses, 174 }, 175 engine.Pattern{ 176 Match: func(msg *engine.Message) bool { 177 _, ok := msg.Payload.(*messages.BlockResponse) 178 if ok { 179 e.metrics.MessageReceived(metrics.EngineSynchronization, metrics.MessageBlockResponse) 180 } 181 return ok 182 }, 183 Store: e.pendingBlockResponses, 184 }, 185 ) 186 187 return nil 188 } 189 190 // Process processes the given event from the node with the given origin ID in 191 // a blocking manner. It returns the potential processing error when done. 192 func (e *Engine) Process(channel channels.Channel, originID flow.Identifier, event interface{}) error { 193 err := e.process(channel, originID, event) 194 if err != nil { 195 if engine.IsIncompatibleInputTypeError(err) { 196 e.log.Warn().Msgf("%v delivered unsupported message %T through %v", originID, event, channel) 197 return nil 198 } 199 return fmt.Errorf("unexpected error while processing engine message: %w", err) 200 } 201 return nil 202 } 203 204 // process processes events for the synchronization engine. 205 // Error returns: 206 // - IncompatibleInputTypeError if input has unexpected type 207 // - All other errors are potential symptoms of internal state corruption or bugs (fatal). 208 func (e *Engine) process(channel channels.Channel, originID flow.Identifier, event interface{}) error { 209 switch message := event.(type) { 210 case *messages.BatchRequest: 211 err := e.validateBatchRequestForALSP(originID, message) 212 if err != nil { 213 irrecoverable.Throw(context.TODO(), fmt.Errorf("failed to validate batch request from %x: %w", originID[:], err)) 214 } 215 return e.requestHandler.Process(channel, originID, event) 216 case *messages.RangeRequest: 217 err := e.validateRangeRequestForALSP(originID, message) 218 if err != nil { 219 irrecoverable.Throw(context.TODO(), fmt.Errorf("failed to validate range request from %x: %w", originID[:], err)) 220 } 221 return e.requestHandler.Process(channel, originID, event) 222 223 case *messages.SyncRequest: 224 err := e.validateSyncRequestForALSP(originID) 225 if err != nil { 226 irrecoverable.Throw(context.TODO(), fmt.Errorf("failed to validate sync request from %x: %w", originID[:], err)) 227 } 228 return e.requestHandler.Process(channel, originID, event) 229 230 case *messages.BlockResponse: 231 err := e.validateBlockResponseForALSP(channel, originID, message) 232 if err != nil { 233 irrecoverable.Throw(context.TODO(), fmt.Errorf("failed to validate block response from %x: %w", originID[:], err)) 234 } 235 return e.responseMessageHandler.Process(originID, event) 236 237 case *messages.SyncResponse: 238 err := e.validateSyncResponseForALSP(channel, originID, message) 239 if err != nil { 240 irrecoverable.Throw(context.TODO(), fmt.Errorf("failed to validate sync response from %x: %w", originID[:], err)) 241 } 242 return e.responseMessageHandler.Process(originID, event) 243 default: 244 return fmt.Errorf("received input with type %T from %x: %w", event, originID[:], engine.IncompatibleInputTypeError) 245 } 246 } 247 248 // responseProcessingLoop is a separate goroutine that performs processing of queued responses 249 func (e *Engine) responseProcessingLoop(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 250 ready() 251 252 notifier := e.responseMessageHandler.GetNotifier() 253 done := ctx.Done() 254 for { 255 select { 256 case <-done: 257 return 258 case <-notifier: 259 e.processAvailableResponses(ctx) 260 } 261 } 262 } 263 264 // processAvailableResponses is processor of pending events which drives events from networking layer to business logic. 265 func (e *Engine) processAvailableResponses(ctx context.Context) { 266 for { 267 select { 268 case <-ctx.Done(): 269 return 270 default: 271 } 272 273 msg, ok := e.pendingSyncResponses.Get() 274 if ok { 275 e.onSyncResponse(msg.OriginID, msg.Payload.(*messages.SyncResponse)) 276 e.metrics.MessageHandled(metrics.EngineSynchronization, metrics.MessageSyncResponse) 277 continue 278 } 279 280 msg, ok = e.pendingBlockResponses.Get() 281 if ok { 282 e.onBlockResponse(msg.OriginID, msg.Payload.(*messages.BlockResponse)) 283 e.metrics.MessageHandled(metrics.EngineSynchronization, metrics.MessageBlockResponse) 284 continue 285 } 286 287 // when there is no more messages in the queue, back to the loop to wait 288 // for the next incoming message to arrive. 289 return 290 } 291 } 292 293 // onSyncResponse processes a synchronization response. 294 func (e *Engine) onSyncResponse(originID flow.Identifier, res *messages.SyncResponse) { 295 e.log.Debug().Str("origin_id", originID.String()).Msg("received sync response") 296 final := e.finalizedHeaderCache.Get() 297 e.core.HandleHeight(final, res.Height) 298 } 299 300 // onBlockResponse processes a response containing a specifically requested block. 301 func (e *Engine) onBlockResponse(originID flow.Identifier, res *messages.BlockResponse) { 302 // process the blocks one by one 303 if len(res.Blocks) == 0 { 304 e.log.Debug().Msg("received empty block response") 305 return 306 } 307 308 first := res.Blocks[0].Header.Height 309 last := res.Blocks[len(res.Blocks)-1].Header.Height 310 e.log.Debug().Uint64("first", first).Uint64("last", last).Msg("received block response") 311 312 filteredBlocks := make([]*messages.BlockProposal, 0, len(res.Blocks)) 313 for _, block := range res.Blocks { 314 header := block.Header 315 if !e.core.HandleBlock(&header) { 316 e.log.Debug().Uint64("height", header.Height).Msg("block handler rejected") 317 continue 318 } 319 filteredBlocks = append(filteredBlocks, &messages.BlockProposal{Block: block}) 320 } 321 322 // forward the block to the compliance engine for validation and processing 323 e.comp.OnSyncedBlocks(flow.Slashable[[]*messages.BlockProposal]{ 324 OriginID: originID, 325 Message: filteredBlocks, 326 }) 327 } 328 329 // checkLoop will regularly scan for items that need requesting. 330 func (e *Engine) checkLoop(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 331 ready() 332 333 pollChan := make(<-chan time.Time) 334 if e.pollInterval > 0 { 335 poll := time.NewTicker(e.pollInterval) 336 pollChan = poll.C 337 defer poll.Stop() 338 } 339 scan := time.NewTicker(e.scanInterval) 340 defer scan.Stop() 341 342 done := ctx.Done() 343 for { 344 // give the quit channel a priority to be selected 345 select { 346 case <-done: 347 return 348 default: 349 } 350 351 select { 352 case <-done: 353 return 354 case <-pollChan: 355 e.pollHeight() 356 case <-scan.C: 357 final := e.finalizedHeaderCache.Get() 358 participants := e.participantsProvider.Identifiers() 359 ranges, batches := e.core.ScanPending(final) 360 e.sendRequests(participants, ranges, batches) 361 } 362 } 363 } 364 365 // pollHeight will send a synchronization request to three random nodes. 366 func (e *Engine) pollHeight() { 367 final := e.finalizedHeaderCache.Get() 368 participants := e.participantsProvider.Identifiers() 369 370 nonce, err := rand.Uint64() 371 if err != nil { 372 // TODO: this error should be returned by pollHeight() 373 // it is logged for now since the only error possible is related to a failure 374 // of the system entropy generation. Such error is going to cause failures in other 375 // components where it's handled properly and will lead to crashing the module. 376 e.log.Warn().Err(err).Msg("nonce generation failed during pollHeight") 377 return 378 } 379 380 // send the request for synchronization 381 req := &messages.SyncRequest{ 382 Nonce: nonce, 383 Height: final.Height, 384 } 385 e.log.Debug(). 386 Uint64("height", req.Height). 387 Uint64("range_nonce", req.Nonce). 388 Msg("sending sync request") 389 err = e.con.Multicast(req, synccore.DefaultPollNodes, participants...) 390 if err != nil { 391 e.log.Warn().Err(err).Msg("sending sync request to poll heights failed") 392 return 393 } 394 e.metrics.MessageSent(metrics.EngineSynchronization, metrics.MessageSyncRequest) 395 } 396 397 // sendRequests sends a request for each range and batch using consensus participants from last finalized snapshot. 398 func (e *Engine) sendRequests(participants flow.IdentifierList, ranges []chainsync.Range, batches []chainsync.Batch) { 399 var errs *multierror.Error 400 401 for _, ran := range ranges { 402 nonce, err := rand.Uint64() 403 if err != nil { 404 // TODO: this error should be returned by sendRequests 405 // it is logged for now since the only error possible is related to a failure 406 // of the system entropy generation. Such error is going to cause failures in other 407 // components where it's handled properly and will lead to crashing the module. 408 e.log.Error().Err(err).Msg("nonce generation failed during range request") 409 return 410 } 411 req := &messages.RangeRequest{ 412 Nonce: nonce, 413 FromHeight: ran.From, 414 ToHeight: ran.To, 415 } 416 err = e.con.Multicast(req, synccore.DefaultBlockRequestNodes, participants...) 417 if err != nil { 418 errs = multierror.Append(errs, fmt.Errorf("could not submit range request: %w", err)) 419 continue 420 } 421 e.log.Info(). 422 Uint64("range_from", req.FromHeight). 423 Uint64("range_to", req.ToHeight). 424 Uint64("range_nonce", req.Nonce). 425 Msg("range requested") 426 e.core.RangeRequested(ran) 427 e.metrics.MessageSent(metrics.EngineSynchronization, metrics.MessageRangeRequest) 428 } 429 430 for _, batch := range batches { 431 nonce, err := rand.Uint64() 432 if err != nil { 433 // TODO: this error should be returned by sendRequests 434 // it is logged for now since the only error possible is related to a failure 435 // of the system entropy generation. Such error is going to cause failures in other 436 // components where it's handled properly and will lead to crashing the module. 437 e.log.Error().Err(err).Msg("nonce generation failed during batch request") 438 return 439 } 440 req := &messages.BatchRequest{ 441 Nonce: nonce, 442 BlockIDs: batch.BlockIDs, 443 } 444 err = e.con.Multicast(req, synccore.DefaultBlockRequestNodes, participants...) 445 if err != nil { 446 errs = multierror.Append(errs, fmt.Errorf("could not submit batch request: %w", err)) 447 continue 448 } 449 e.log.Debug(). 450 Strs("block_ids", flow.IdentifierList(batch.BlockIDs).Strings()). 451 Uint64("range_nonce", req.Nonce). 452 Msg("batch requested") 453 e.core.BatchRequested(batch) 454 e.metrics.MessageSent(metrics.EngineSynchronization, metrics.MessageBatchRequest) 455 } 456 457 if err := errs.ErrorOrNil(); err != nil { 458 e.log.Warn().Err(err).Msg("sending range and batch requests failed") 459 } 460 } 461 462 // validateBatchRequestForALSP checks if a batch request should be reported as a misbehavior and sends misbehavior report to ALSP. 463 // The misbehavior is due to either: 464 // 1. unambiguous malicious or incorrect behavior (0 block IDs) OR 465 // 2. large number of block IDs in batch request. This is more ambiguous to detect as malicious behavior because there is no way to know for sure 466 // if the sender is sending a large batch request maliciously or not, so we use a probabilistic approach to report the misbehavior. 467 // 468 // Args: 469 // - originID: the sender of the batch request 470 // - batchRequest: the batch request to validate 471 // Returns: 472 // - error: If an error is encountered while validating the batch request. Error is assumed to be irrecoverable because of internal processes that didn't allow validation to complete. 473 func (e *Engine) validateBatchRequestForALSP(originID flow.Identifier, batchRequest *messages.BatchRequest) error { 474 // Generate a random integer between 0 and spamProbabilityMultiplier (exclusive) 475 n, err := rand.Uint32n(spamProbabilityMultiplier) 476 if err != nil { 477 return fmt.Errorf("failed to generate random number from %x: %w", originID[:], err) 478 } 479 480 // validity check: if no block IDs, always report as misbehavior 481 if len(batchRequest.BlockIDs) == 0 { 482 e.log.Warn(). 483 Hex("origin_id", logging.ID(originID)). 484 Str(logging.KeySuspicious, "true"). 485 Str("reason", alsp.InvalidMessage.String()). 486 Msg("received invalid batch request with 0 block IDs, creating ALSP report") 487 report, err := alsp.NewMisbehaviorReport(originID, alsp.InvalidMessage) 488 if err != nil { 489 // failing to create the misbehavior report is unlikely. If an error is encountered while 490 // creating the misbehavior report it indicates a bug and processing can not proceed. 491 return fmt.Errorf("failed to create misbehavior report (invalid batch request, no block IDs) from %x: %w", originID[:], err) 492 } 493 // failed unambiguous validation check and should be reported as misbehavior 494 e.con.ReportMisbehavior(report) 495 return nil 496 } 497 498 // to avoid creating a misbehavior report for every batch request received, use a probabilistic approach. 499 // The larger the batch request and base probability, the higher the probability of creating a misbehavior report. 500 501 // batchRequestProb is calculated as follows: 502 // batchRequestBaseProb * (len(batchRequest.BlockIDs) + 1) / synccore.DefaultConfig().MaxSize 503 // Example 1 (small batch of block IDs) if the batch request is for 10 blocks IDs and batchRequestBaseProb is 0.01, then the probability of 504 // creating a misbehavior report is: 505 // batchRequestBaseProb * (10+1) / synccore.DefaultConfig().MaxSize 506 // = 0.01 * 11 / 64 = 0.00171875 = 0.171875% 507 // Example 2 (large batch of block IDs) if the batch request is for 1000 block IDs and batchRequestBaseProb is 0.01, then the probability of 508 // creating a misbehavior report is: 509 // batchRequestBaseProb * (1000+1) / synccore.DefaultConfig().MaxSize 510 // = 0.01 * 1001 / 64 = 0.15640625 = 15.640625% 511 batchRequestProb := e.spamDetectionConfig.batchRequestBaseProb * (float32(len(batchRequest.BlockIDs)) + 1) / float32(synccore.DefaultConfig().MaxSize) 512 if float32(n) < batchRequestProb*spamProbabilityMultiplier { 513 // create a misbehavior report 514 e.log.Debug(). 515 Hex("origin_id", logging.ID(originID)). 516 Str(logging.KeyLoad, "true"). 517 Str("reason", alsp.ResourceIntensiveRequest.String()). 518 Msgf("for %d block IDs, creating probabilistic ALSP report", len(batchRequest.BlockIDs)) 519 report, err := alsp.NewMisbehaviorReport(originID, alsp.ResourceIntensiveRequest) 520 if err != nil { 521 // failing to create the misbehavior report is unlikely. If an error is encountered while 522 // creating the misbehavior report it indicates a bug and processing can not proceed. 523 return fmt.Errorf("failed to create misbehavior report from %x: %w", originID[:], err) 524 } 525 // failed probabilistic (load) validation check and should be reported as misbehavior 526 e.con.ReportMisbehavior(report) 527 return nil 528 } 529 return nil 530 } 531 532 // TODO: implement spam reporting similar to validateSyncRequestForALSP 533 func (e *Engine) validateBlockResponseForALSP(channel channels.Channel, id flow.Identifier, blockResponse *messages.BlockResponse) error { 534 return nil 535 } 536 537 // validateRangeRequestForALSP checks if a range request should be reported as a misbehavior and sends misbehavior report to ALSP. 538 // The misbehavior is due to either: 539 // 1. unambiguous malicious or incorrect behavior (toHeight < fromHeight) OR 540 // 2. large height in range request. This is more ambiguous to detect as malicious behavior because there is no way to know for sure 541 // if the sender is sending a large range request height maliciously or not, so we use a probabilistic approach to report the misbehavior. 542 // 543 // Args: 544 // - originID: the sender of the range request 545 // - rangeRequest: the range request to validate 546 // Returns: 547 // - error: If an error is encountered while validating the range request. Error is assumed to be irrecoverable because of internal processes that didn't allow validation to complete. 548 func (e *Engine) validateRangeRequestForALSP(originID flow.Identifier, rangeRequest *messages.RangeRequest) error { 549 // Generate a random integer between 0 and spamProbabilityMultiplier (exclusive) 550 n, err := rand.Uint32n(spamProbabilityMultiplier) 551 if err != nil { 552 return fmt.Errorf("failed to generate random number from %x: %w", originID[:], err) 553 } 554 555 // check if range request is valid 556 if rangeRequest.ToHeight < rangeRequest.FromHeight { 557 e.log.Warn(). 558 Hex("origin_id", logging.ID(originID)). 559 Str(logging.KeySuspicious, "true"). 560 Str("reason", alsp.InvalidMessage.String()). 561 Msgf("received invalid range request from height %d is not less than the to height %d, creating ALSP report", rangeRequest.FromHeight, rangeRequest.ToHeight) 562 report, err := alsp.NewMisbehaviorReport(originID, alsp.InvalidMessage) 563 if err != nil { 564 // failing to create the misbehavior report is unlikely. If an error is encountered while 565 // creating the misbehavior report it indicates a bug and processing can not proceed. 566 return fmt.Errorf("failed to create misbehavior report (invalid range request) from %x: %w", originID[:], err) 567 } 568 // failed unambiguous validation check and should be reported as misbehavior 569 e.con.ReportMisbehavior(report) 570 return nil 571 } 572 573 // to avoid creating a misbehavior report for every range request received, use a probabilistic approach. 574 // The higher the range request and base probability, the higher the probability of creating a misbehavior report. 575 576 // rangeRequestProb is calculated as follows: 577 // rangeRequestBaseProb * ((rangeRequest.ToHeight-rangeRequest.FromHeight) + 1) / synccore.DefaultConfig().MaxSize 578 // Example 1 (small range) if the range request is for 10 blocks and rangeRequestBaseProb is 0.01, then the probability of 579 // creating a misbehavior report is: 580 // rangeRequestBaseProb * (10+1) / synccore.DefaultConfig().MaxSize 581 // = 0.01 * 11 / 64 = 0.00171875 = 0.171875% 582 // Example 2 (large range) if the range request is for 1000 blocks and rangeRequestBaseProb is 0.01, then the probability of 583 // creating a misbehavior report is: 584 // rangeRequestBaseProb * (1000+1) / synccore.DefaultConfig().MaxSize 585 // = 0.01 * 1001 / 64 = 0.15640625 = 15.640625% 586 rangeRequestProb := e.spamDetectionConfig.rangeRequestBaseProb * (float32(rangeRequest.ToHeight-rangeRequest.FromHeight) + 1) / float32(synccore.DefaultConfig().MaxSize) 587 if float32(n) < rangeRequestProb*spamProbabilityMultiplier { 588 // create a misbehavior report 589 e.log.Debug(). 590 Hex("origin_id", logging.ID(originID)). 591 Str(logging.KeyLoad, "true"). 592 Str("reason", alsp.ResourceIntensiveRequest.String()). 593 Msgf("from height %d to height %d, creating probabilistic ALSP report", rangeRequest.FromHeight, rangeRequest.ToHeight) 594 report, err := alsp.NewMisbehaviorReport(originID, alsp.ResourceIntensiveRequest) 595 if err != nil { 596 // failing to create the misbehavior report is unlikely. If an error is encountered while 597 // creating the misbehavior report it indicates a bug and processing can not proceed. 598 return fmt.Errorf("failed to create misbehavior report from %x: %w", originID[:], err) 599 } 600 // failed validation check and should be reported as misbehavior 601 602 // failed probabilistic (load) validation check and should be reported as misbehavior 603 e.con.ReportMisbehavior(report) 604 return nil 605 } 606 607 // passed all validation checks with no misbehavior detected 608 return nil 609 } 610 611 // validateSyncRequestForALSP checks if a sync request should be reported as a misbehavior and sends misbehavior report to ALSP. 612 // The misbehavior is ambiguous to detect as malicious behavior because there is no way to know for sure if the sender is sending 613 // a sync request maliciously or not, so we use a probabilistic approach to report the misbehavior. 614 // 615 // Args: 616 // - originID: the sender of the sync request 617 // Returns: 618 // - error: If an error is encountered while validating the sync request. Error is assumed to be irrecoverable because of internal processes that didn't allow validation to complete. 619 func (e *Engine) validateSyncRequestForALSP(originID flow.Identifier) error { 620 // Generate a random integer between 0 and spamProbabilityMultiplier (exclusive) 621 n, err := rand.Uint32n(spamProbabilityMultiplier) 622 if err != nil { 623 return fmt.Errorf("failed to generate random number from %x: %w", originID[:], err) 624 } 625 626 // to avoid creating a misbehavior report for every sync request received, use a probabilistic approach. 627 // Create a report with a probability of spamDetectionConfig.syncRequestProb 628 if float32(n) < e.spamDetectionConfig.syncRequestProb*spamProbabilityMultiplier { 629 630 // create misbehavior report 631 e.log.Debug(). 632 Hex("origin_id", logging.ID(originID)). 633 Str(logging.KeyLoad, "true"). 634 Str("reason", alsp.ResourceIntensiveRequest.String()). 635 Msg("creating probabilistic ALSP report") 636 637 report, err := alsp.NewMisbehaviorReport(originID, alsp.ResourceIntensiveRequest) 638 if err != nil { 639 // failing to create the misbehavior report is unlikely. If an error is encountered while 640 // creating the misbehavior report it indicates a bug and processing can not proceed. 641 return fmt.Errorf("failed to create misbehavior report from %x: %w", originID[:], err) 642 } 643 e.con.ReportMisbehavior(report) 644 return nil 645 } 646 647 // passed all validation checks with no misbehavior detected 648 return nil 649 } 650 651 // TODO: implement spam reporting similar to validateSyncRequestForALSP 652 func (e *Engine) validateSyncResponseForALSP(channel channels.Channel, id flow.Identifier, syncResponse *messages.SyncResponse) error { 653 return nil 654 }