github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/collection/compliance/engine.go (about) 1 package compliance 2 3 import ( 4 "fmt" 5 6 "github.com/rs/zerolog" 7 8 "github.com/onflow/flow-go/consensus/hotstuff" 9 "github.com/onflow/flow-go/consensus/hotstuff/model" 10 "github.com/onflow/flow-go/engine" 11 "github.com/onflow/flow-go/engine/collection" 12 "github.com/onflow/flow-go/engine/common/fifoqueue" 13 "github.com/onflow/flow-go/model/flow" 14 "github.com/onflow/flow-go/model/messages" 15 "github.com/onflow/flow-go/module" 16 "github.com/onflow/flow-go/module/component" 17 "github.com/onflow/flow-go/module/events" 18 "github.com/onflow/flow-go/module/irrecoverable" 19 "github.com/onflow/flow-go/module/metrics" 20 "github.com/onflow/flow-go/state/protocol" 21 "github.com/onflow/flow-go/storage" 22 ) 23 24 // defaultBlockQueueCapacity maximum capacity of inbound queue for `messages.ClusterBlockProposal`s 25 const defaultBlockQueueCapacity = 10_000 26 27 // Engine is a wrapper struct for `Core` which implements cluster consensus algorithm. 28 // Engine is responsible for handling incoming messages, queueing for processing, broadcasting proposals. 29 // Implements collection.Compliance interface. 30 type Engine struct { 31 component.Component 32 hotstuff.FinalizationConsumer 33 34 log zerolog.Logger 35 metrics module.EngineMetrics 36 me module.Local 37 headers storage.Headers 38 payloads storage.ClusterPayloads 39 state protocol.State 40 core *Core 41 pendingBlocks *fifoqueue.FifoQueue // queue for processing inbound blocks 42 pendingBlocksNotifier engine.Notifier 43 } 44 45 var _ collection.Compliance = (*Engine)(nil) 46 47 func NewEngine( 48 log zerolog.Logger, 49 me module.Local, 50 state protocol.State, 51 payloads storage.ClusterPayloads, 52 core *Core, 53 ) (*Engine, error) { 54 engineLog := log.With().Str("cluster_compliance", "engine").Logger() 55 56 // FIFO queue for block proposals 57 blocksQueue, err := fifoqueue.NewFifoQueue( 58 defaultBlockQueueCapacity, 59 fifoqueue.WithLengthObserver(func(len int) { 60 core.mempoolMetrics.MempoolEntries(metrics.ResourceClusterBlockProposalQueue, uint(len)) 61 }), 62 ) 63 if err != nil { 64 return nil, fmt.Errorf("failed to create queue for inbound block proposals: %w", err) 65 } 66 67 eng := &Engine{ 68 log: engineLog, 69 metrics: core.engineMetrics, 70 me: me, 71 headers: core.headers, 72 payloads: payloads, 73 state: state, 74 core: core, 75 pendingBlocks: blocksQueue, 76 pendingBlocksNotifier: engine.NewNotifier(), 77 } 78 finalizationActor, finalizationWorker := events.NewFinalizationActor(eng.processOnFinalizedBlock) 79 eng.FinalizationConsumer = finalizationActor 80 81 // create the component manager and worker threads 82 eng.Component = component.NewComponentManagerBuilder(). 83 AddWorker(eng.processBlocksLoop). 84 AddWorker(finalizationWorker). 85 Build() 86 87 return eng, nil 88 } 89 90 // processBlocksLoop processes available blocks as they are queued. 91 func (e *Engine) processBlocksLoop(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 92 ready() 93 94 doneSignal := ctx.Done() 95 newMessageSignal := e.pendingBlocksNotifier.Channel() 96 for { 97 select { 98 case <-doneSignal: 99 return 100 case <-newMessageSignal: 101 err := e.processQueuedBlocks(doneSignal) 102 if err != nil { 103 ctx.Throw(err) 104 } 105 } 106 } 107 } 108 109 // processQueuedBlocks processes any available messages from the inbound queues. 110 // Only returns when all inbound queues are empty (or the engine is terminated). 111 // No errors expected during normal operations. 112 func (e *Engine) processQueuedBlocks(doneSignal <-chan struct{}) error { 113 for { 114 select { 115 case <-doneSignal: 116 return nil 117 default: 118 } 119 120 msg, ok := e.pendingBlocks.Pop() 121 if ok { 122 inBlock := msg.(flow.Slashable[*messages.ClusterBlockProposal]) 123 err := e.core.OnBlockProposal(inBlock) 124 e.core.engineMetrics.MessageHandled(metrics.EngineClusterCompliance, metrics.MessageBlockProposal) 125 if err != nil { 126 return fmt.Errorf("could not handle block proposal: %w", err) 127 } 128 continue 129 } 130 131 // when there is no more messages in the queue, back to the loop to wait 132 // for the next incoming message to arrive. 133 return nil 134 } 135 } 136 137 // OnClusterBlockProposal feeds a new block proposal into the processing pipeline. 138 // Incoming proposals are queued and eventually dispatched by worker. 139 func (e *Engine) OnClusterBlockProposal(proposal flow.Slashable[*messages.ClusterBlockProposal]) { 140 e.core.engineMetrics.MessageReceived(metrics.EngineClusterCompliance, metrics.MessageBlockProposal) 141 if e.pendingBlocks.Push(proposal) { 142 e.pendingBlocksNotifier.Notify() 143 } else { 144 e.core.engineMetrics.InboundMessageDropped(metrics.EngineClusterCompliance, metrics.MessageBlockProposal) 145 } 146 } 147 148 // OnSyncedClusterBlock feeds a block obtained from sync proposal into the processing pipeline. 149 // Incoming proposals are queued and eventually dispatched by worker. 150 func (e *Engine) OnSyncedClusterBlock(syncedBlock flow.Slashable[*messages.ClusterBlockProposal]) { 151 e.core.engineMetrics.MessageReceived(metrics.EngineClusterCompliance, metrics.MessageSyncedClusterBlock) 152 if e.pendingBlocks.Push(syncedBlock) { 153 e.pendingBlocksNotifier.Notify() 154 } else { 155 e.core.engineMetrics.InboundMessageDropped(metrics.EngineClusterCompliance, metrics.MessageSyncedClusterBlock) 156 } 157 } 158 159 // processOnFinalizedBlock informs compliance.Core about finalization of the respective block. 160 // The input to this callback is treated as trusted. This method should be executed on 161 // `OnFinalizedBlock` notifications from the node-internal consensus instance. 162 // No errors expected during normal operations. 163 func (e *Engine) processOnFinalizedBlock(block *model.Block) error { 164 // retrieve the latest finalized header, so we know the height 165 finalHeader, err := e.headers.ByBlockID(block.BlockID) 166 if err != nil { // no expected errors 167 return fmt.Errorf("could not get finalized header: %w", err) 168 } 169 e.core.ProcessFinalizedBlock(finalHeader) 170 return nil 171 }