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 }