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  }