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

     1  package matching
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/rs/zerolog"
     7  
     8  	"github.com/onflow/flow-go/consensus/hotstuff/model"
     9  	"github.com/onflow/flow-go/engine"
    10  	"github.com/onflow/flow-go/engine/common/fifoqueue"
    11  	sealing "github.com/onflow/flow-go/engine/consensus"
    12  	"github.com/onflow/flow-go/model/flow"
    13  	"github.com/onflow/flow-go/module"
    14  	"github.com/onflow/flow-go/module/metrics"
    15  	"github.com/onflow/flow-go/network"
    16  	"github.com/onflow/flow-go/network/channels"
    17  	"github.com/onflow/flow-go/state/protocol"
    18  	"github.com/onflow/flow-go/storage"
    19  )
    20  
    21  // defaultReceiptQueueCapacity maximum capacity of receipts queue
    22  const defaultReceiptQueueCapacity = 10000
    23  
    24  // defaultIncorporatedBlockQueueCapacity maximum capacity of block incorporated events queue
    25  const defaultIncorporatedBlockQueueCapacity = 10
    26  
    27  // Engine is a wrapper struct for `Core` which implements consensus algorithm.
    28  // Engine is responsible for handling incoming messages, queueing for processing, broadcasting proposals.
    29  type Engine struct {
    30  	unit                       *engine.Unit
    31  	log                        zerolog.Logger
    32  	me                         module.Local
    33  	core                       sealing.MatchingCore
    34  	state                      protocol.State
    35  	receipts                   storage.ExecutionReceipts
    36  	index                      storage.Index
    37  	metrics                    module.EngineMetrics
    38  	inboundEventsNotifier      engine.Notifier
    39  	finalizationEventsNotifier engine.Notifier
    40  	blockIncorporatedNotifier  engine.Notifier
    41  	pendingReceipts            *fifoqueue.FifoQueue
    42  	pendingIncorporatedBlocks  *fifoqueue.FifoQueue
    43  }
    44  
    45  func NewEngine(
    46  	log zerolog.Logger,
    47  	net network.EngineRegistry,
    48  	me module.Local,
    49  	engineMetrics module.EngineMetrics,
    50  	mempool module.MempoolMetrics,
    51  	state protocol.State,
    52  	receipts storage.ExecutionReceipts,
    53  	index storage.Index,
    54  	core sealing.MatchingCore) (*Engine, error) {
    55  
    56  	// FIFO queue for execution receipts
    57  	receiptsQueue, err := fifoqueue.NewFifoQueue(
    58  		defaultReceiptQueueCapacity,
    59  		fifoqueue.WithLengthObserver(func(len int) { mempool.MempoolEntries(metrics.ResourceBlockProposalQueue, uint(len)) }),
    60  	)
    61  	if err != nil {
    62  		return nil, fmt.Errorf("failed to create queue for inbound receipts: %w", err)
    63  	}
    64  
    65  	pendingIncorporatedBlocks, err := fifoqueue.NewFifoQueue(defaultIncorporatedBlockQueueCapacity)
    66  	if err != nil {
    67  		return nil, fmt.Errorf("failed to create queue for incorporated block events: %w", err)
    68  	}
    69  
    70  	e := &Engine{
    71  		log:                        log.With().Str("engine", "matching.Engine").Logger(),
    72  		unit:                       engine.NewUnit(),
    73  		me:                         me,
    74  		core:                       core,
    75  		state:                      state,
    76  		receipts:                   receipts,
    77  		index:                      index,
    78  		metrics:                    engineMetrics,
    79  		inboundEventsNotifier:      engine.NewNotifier(),
    80  		finalizationEventsNotifier: engine.NewNotifier(),
    81  		blockIncorporatedNotifier:  engine.NewNotifier(),
    82  		pendingReceipts:            receiptsQueue,
    83  		pendingIncorporatedBlocks:  pendingIncorporatedBlocks,
    84  	}
    85  
    86  	// register engine with the receipt provider
    87  	_, err = net.Register(channels.ReceiveReceipts, e)
    88  	if err != nil {
    89  		return nil, fmt.Errorf("could not register for results: %w", err)
    90  	}
    91  
    92  	return e, nil
    93  }
    94  
    95  // Ready returns a ready channel that is closed once the engine has fully
    96  // started. For consensus engine, this is true once the underlying consensus
    97  // algorithm has started.
    98  func (e *Engine) Ready() <-chan struct{} {
    99  	e.unit.Launch(e.inboundEventsProcessingLoop)
   100  	e.unit.Launch(e.finalizationProcessingLoop)
   101  	e.unit.Launch(e.blockIncorporatedEventsProcessingLoop)
   102  	return e.unit.Ready()
   103  }
   104  
   105  // Done returns a done channel that is closed once the engine has fully stopped.
   106  // For the consensus engine, we wait for hotstuff to finish.
   107  func (e *Engine) Done() <-chan struct{} {
   108  	return e.unit.Done()
   109  }
   110  
   111  // SubmitLocal submits an event originating on the local node.
   112  func (e *Engine) SubmitLocal(event interface{}) {
   113  	err := e.ProcessLocal(event)
   114  	if err != nil {
   115  		e.log.Fatal().Err(err).Msg("internal error processing event")
   116  	}
   117  }
   118  
   119  // Submit submits the given event from the node with the given origin ID
   120  // for processing in a non-blocking manner. It returns instantly and logs
   121  // a potential processing error internally when done.
   122  func (e *Engine) Submit(channel channels.Channel, originID flow.Identifier, event interface{}) {
   123  	err := e.Process(channel, originID, event)
   124  	if err != nil {
   125  		e.log.Fatal().Err(err).Msg("internal error processing event")
   126  	}
   127  }
   128  
   129  // ProcessLocal processes an event originating on the local node.
   130  func (e *Engine) ProcessLocal(event interface{}) error {
   131  	return e.process(e.me.NodeID(), event)
   132  }
   133  
   134  // Process processes the given event from the node with the given origin ID in
   135  // a blocking manner. It returns the potential processing error when done.
   136  func (e *Engine) Process(channel channels.Channel, originID flow.Identifier, event interface{}) error {
   137  	err := e.process(originID, event)
   138  	if err != nil {
   139  		if engine.IsIncompatibleInputTypeError(err) {
   140  			e.log.Warn().Msgf("%v delivered unsupported message %T through %v", originID, event, channel)
   141  			return nil
   142  		}
   143  		return fmt.Errorf("unexpected error while processing engine message: %w", err)
   144  	}
   145  	return nil
   146  }
   147  
   148  // process events for the matching engine on the consensus node.
   149  func (e *Engine) process(originID flow.Identifier, event interface{}) error {
   150  	receipt, ok := event.(*flow.ExecutionReceipt)
   151  	if !ok {
   152  		return fmt.Errorf("no matching processor for message of type %T from origin %x: %w", event, originID[:],
   153  			engine.IncompatibleInputTypeError)
   154  	}
   155  	e.metrics.MessageReceived(metrics.EngineSealing, metrics.MessageExecutionReceipt)
   156  	e.pendingReceipts.Push(receipt)
   157  	e.inboundEventsNotifier.Notify()
   158  	return nil
   159  }
   160  
   161  // HandleReceipt ingests receipts from the Requester module.
   162  func (e *Engine) HandleReceipt(originID flow.Identifier, receipt flow.Entity) {
   163  	e.log.Debug().Msg("received receipt from requester engine")
   164  	err := e.process(originID, receipt)
   165  	if err != nil {
   166  		e.log.Fatal().Err(err).Msg("internal error processing event from requester module")
   167  	}
   168  }
   169  
   170  // OnFinalizedBlock implements the `OnFinalizedBlock` callback from the `hotstuff.FinalizationConsumer`
   171  // CAUTION: the input to this callback is treated as trusted; precautions should be taken that messages
   172  // from external nodes cannot be considered as inputs to this function
   173  func (e *Engine) OnFinalizedBlock(*model.Block) {
   174  	e.finalizationEventsNotifier.Notify()
   175  }
   176  
   177  // OnBlockIncorporated implements the `OnBlockIncorporated` callback from the `hotstuff.FinalizationConsumer`
   178  // CAUTION: the input to this callback is treated as trusted; precautions should be taken that messages
   179  // from external nodes cannot be considered as inputs to this function
   180  func (e *Engine) OnBlockIncorporated(incorporatedBlock *model.Block) {
   181  	e.pendingIncorporatedBlocks.Push(incorporatedBlock.BlockID)
   182  	e.blockIncorporatedNotifier.Notify()
   183  }
   184  
   185  // processIncorporatedBlock selects receipts that were included into incorporated block and submits them
   186  // for further processing by matching core.
   187  // Without the logic below, the sealing engine would produce IncorporatedResults
   188  // only from receipts received directly from ENs. sealing Core would not know about
   189  // Receipts that are incorporated by other nodes in their blocks blocks (but never
   190  // received directly from the EN).
   191  // No errors expected during normal operations.
   192  func (e *Engine) processIncorporatedBlock(blockID flow.Identifier) error {
   193  	index, err := e.index.ByBlockID(blockID)
   194  	if err != nil {
   195  		return fmt.Errorf("could not retrieve payload index for incorporated block %v", blockID)
   196  	}
   197  	for _, receiptID := range index.ReceiptIDs {
   198  		receipt, err := e.receipts.ByID(receiptID)
   199  		if err != nil {
   200  			return fmt.Errorf("could not retrieve receipt incorporated in block %v: %w", blockID, err)
   201  		}
   202  		e.pendingReceipts.Push(receipt)
   203  	}
   204  	e.inboundEventsNotifier.Notify()
   205  	return nil
   206  }
   207  
   208  // finalizationProcessingLoop is a separate goroutine that performs processing of finalization events
   209  func (e *Engine) finalizationProcessingLoop() {
   210  	finalizationNotifier := e.finalizationEventsNotifier.Channel()
   211  	for {
   212  		select {
   213  		case <-e.unit.Quit():
   214  			return
   215  		case <-finalizationNotifier:
   216  			err := e.core.OnBlockFinalization()
   217  			if err != nil {
   218  				e.log.Fatal().Err(err).Msg("could not process last finalized event")
   219  			}
   220  		}
   221  	}
   222  }
   223  
   224  // blockIncorporatedEventsProcessingLoop is a separate goroutine for processing block incorporated events.
   225  func (e *Engine) blockIncorporatedEventsProcessingLoop() {
   226  	c := e.blockIncorporatedNotifier.Channel()
   227  
   228  	for {
   229  		select {
   230  		case <-e.unit.Quit():
   231  			return
   232  		case <-c:
   233  			err := e.processBlockIncorporatedEvents()
   234  			if err != nil {
   235  				e.log.Fatal().Err(err).Msg("internal error processing block incorporated queued message")
   236  			}
   237  		}
   238  	}
   239  }
   240  
   241  func (e *Engine) inboundEventsProcessingLoop() {
   242  	c := e.inboundEventsNotifier.Channel()
   243  
   244  	for {
   245  		select {
   246  		case <-e.unit.Quit():
   247  			return
   248  		case <-c:
   249  			err := e.processAvailableEvents()
   250  			if err != nil {
   251  				e.log.Fatal().Err(err).Msg("internal error processing queued message")
   252  			}
   253  		}
   254  	}
   255  }
   256  
   257  // processBlockIncorporatedEvents performs processing of block incorporated hot stuff events.
   258  // No errors expected during normal operations.
   259  func (e *Engine) processBlockIncorporatedEvents() error {
   260  	for {
   261  		select {
   262  		case <-e.unit.Quit():
   263  			return nil
   264  		default:
   265  		}
   266  
   267  		msg, ok := e.pendingIncorporatedBlocks.Pop()
   268  		if ok {
   269  			err := e.processIncorporatedBlock(msg.(flow.Identifier))
   270  			if err != nil {
   271  				return fmt.Errorf("could not process incorporated block: %w", err)
   272  			}
   273  			continue
   274  		}
   275  
   276  		// when there is no more messages in the queue, back to the loop to wait
   277  		// for the next incoming message to arrive.
   278  		return nil
   279  	}
   280  }
   281  
   282  // processAvailableEvents processes _all_ available events (untrusted messages
   283  // from other nodes as well as internally trusted.
   284  // No errors expected during normal operations.
   285  func (e *Engine) processAvailableEvents() error {
   286  	for {
   287  		select {
   288  		case <-e.unit.Quit():
   289  			return nil
   290  		default:
   291  		}
   292  
   293  		msg, ok := e.pendingIncorporatedBlocks.Pop()
   294  		if ok {
   295  			err := e.processIncorporatedBlock(msg.(flow.Identifier))
   296  			if err != nil {
   297  				return fmt.Errorf("could not process incorporated block: %w", err)
   298  			}
   299  			continue
   300  		}
   301  
   302  		msg, ok = e.pendingReceipts.Pop()
   303  		if ok {
   304  			err := e.core.ProcessReceipt(msg.(*flow.ExecutionReceipt))
   305  			if err != nil {
   306  				return fmt.Errorf("could not handle execution receipt: %w", err)
   307  			}
   308  			continue
   309  		}
   310  
   311  		// when there is no more messages in the queue, back to the inboundEventsProcessingLoop to wait
   312  		// for the next incoming message to arrive.
   313  		return nil
   314  	}
   315  }