github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/common/synchronization/engine.go (about)

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