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  }