github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/verification/assigner/engine.go (about) 1 package assigner 2 3 import ( 4 "context" 5 "fmt" 6 "sync/atomic" 7 8 "github.com/rs/zerolog" 9 "github.com/rs/zerolog/log" 10 11 "github.com/onflow/flow-go/engine" 12 "github.com/onflow/flow-go/model/chunks" 13 "github.com/onflow/flow-go/model/flow" 14 "github.com/onflow/flow-go/model/flow/filter" 15 "github.com/onflow/flow-go/module" 16 "github.com/onflow/flow-go/module/trace" 17 "github.com/onflow/flow-go/state/protocol" 18 "github.com/onflow/flow-go/storage" 19 "github.com/onflow/flow-go/utils/logging" 20 ) 21 22 // The Assigner engine reads the receipts from each finalized block. 23 // For each receipt, it reads its result and find the chunks the assigned 24 // to me to verify, and then save it to the chunks job queue for the 25 // fetcher engine to process. 26 type Engine struct { 27 unit *engine.Unit 28 log zerolog.Logger 29 metrics module.VerificationMetrics 30 tracer module.Tracer 31 me module.Local 32 state protocol.State 33 assigner module.ChunkAssigner // to determine chunks this node should verify. 34 chunksQueue storage.ChunksQueue // to store chunks to be verified. 35 newChunkListener module.NewJobListener // to notify chunk queue consumer about a new chunk. 36 blockConsumerNotifier module.ProcessingNotifier // to report a block has been processed. 37 stopAtHeight uint64 38 stopAtBlockID atomic.Value 39 } 40 41 func New( 42 log zerolog.Logger, 43 metrics module.VerificationMetrics, 44 tracer module.Tracer, 45 me module.Local, 46 state protocol.State, 47 assigner module.ChunkAssigner, 48 chunksQueue storage.ChunksQueue, 49 newChunkListener module.NewJobListener, 50 stopAtHeight uint64, 51 ) *Engine { 52 e := &Engine{ 53 unit: engine.NewUnit(), 54 log: log.With().Str("engine", "assigner").Logger(), 55 metrics: metrics, 56 tracer: tracer, 57 me: me, 58 state: state, 59 assigner: assigner, 60 chunksQueue: chunksQueue, 61 newChunkListener: newChunkListener, 62 stopAtHeight: stopAtHeight, 63 } 64 e.stopAtBlockID.Store(flow.ZeroID) 65 return e 66 } 67 68 func (e *Engine) WithBlockConsumerNotifier(notifier module.ProcessingNotifier) { 69 e.blockConsumerNotifier = notifier 70 } 71 72 func (e *Engine) Ready() <-chan struct{} { 73 return e.unit.Ready() 74 } 75 76 func (e *Engine) Done() <-chan struct{} { 77 return e.unit.Done() 78 } 79 80 // resultChunkAssignment receives an execution result that appears in a finalized incorporating block. 81 // In case this verification node is authorized at the reference block of this execution receipt's result, 82 // chunk assignment is computed for the result, and the list of assigned chunks returned. 83 func (e *Engine) resultChunkAssignment(ctx context.Context, 84 result *flow.ExecutionResult, 85 incorporatingBlock flow.Identifier, 86 ) (flow.ChunkList, error) { 87 resultID := result.ID() 88 log := log.With(). 89 Hex("result_id", logging.ID(resultID)). 90 Hex("executed_block_id", logging.ID(result.BlockID)). 91 Hex("incorporating_block_id", logging.ID(incorporatingBlock)). 92 Logger() 93 e.metrics.OnExecutionResultReceivedAtAssignerEngine() 94 95 // verification node should be authorized at the reference block id. 96 ok, err := authorizedAsVerification(e.state, result.BlockID, e.me.NodeID()) 97 if err != nil { 98 return nil, fmt.Errorf("could not verify weight of verification node for result at reference block id: %w", err) 99 } 100 if !ok { 101 log.Warn().Msg("node is not authorized at reference block id, receipt is discarded") 102 return nil, nil 103 } 104 105 // chunk assignment 106 chunkList, err := e.chunkAssignments(ctx, result, incorporatingBlock) 107 if err != nil { 108 return nil, fmt.Errorf("could not determine chunk assignment: %w", err) 109 } 110 e.metrics.OnChunksAssignmentDoneAtAssigner(len(chunkList)) 111 112 // TODO: de-escalate to debug level on stable version. 113 log.Info(). 114 Int("total_chunks", len(result.Chunks)). 115 Int("total_assigned_chunks", len(chunkList)). 116 Msg("chunk assignment done") 117 118 return chunkList, nil 119 } 120 121 // processChunk receives a chunk that belongs to execution result id. It creates a chunk locator 122 // for the chunk and stores the chunk locator in the chunks queue. 123 // 124 // Note that the chunk is assume to be legitimately assigned to this verification node 125 // (through the chunk assigner), and belong to the execution result. 126 // 127 // Deduplication of chunk locators is delegated to the chunks queue. 128 func (e *Engine) processChunk(chunk *flow.Chunk, resultID flow.Identifier, blockHeight uint64) (bool, error) { 129 lg := e.log.With(). 130 Hex("result_id", logging.ID(resultID)). 131 Hex("chunk_id", logging.ID(chunk.ID())). 132 Uint64("chunk_index", chunk.Index). 133 Uint64("block_height", blockHeight). 134 Logger() 135 136 locator := &chunks.Locator{ 137 ResultID: resultID, 138 Index: chunk.Index, 139 } 140 141 // pushes chunk locator to the chunks queue 142 ok, err := e.chunksQueue.StoreChunkLocator(locator) 143 if err != nil { 144 return false, fmt.Errorf("could not push chunk locator to chunks queue: %w", err) 145 } 146 if !ok { 147 lg.Debug().Msg("could not push duplicate chunk locator to chunks queue") 148 return false, nil 149 } 150 151 e.metrics.OnAssignedChunkProcessedAtAssigner() 152 153 // notifies chunk queue consumer of a new chunk 154 e.newChunkListener.Check() 155 lg.Info().Msg("chunk locator successfully pushed to chunks queue") 156 157 return true, nil 158 } 159 160 // ProcessFinalizedBlock is the entry point of assigner engine. It pushes the block down the pipeline with tracing on it enabled. 161 // Through the pipeline the execution receipts included in the block are indexed, and their chunk assignments are done, and 162 // the assigned chunks are pushed to the chunks queue, which is the output stream of this engine. 163 // Once the assigner engine is done handling all the receipts in the block, it notifies the block consumer. 164 func (e *Engine) ProcessFinalizedBlock(block *flow.Block) { 165 blockID := block.ID() 166 167 span, ctx := e.tracer.StartBlockSpan(e.unit.Ctx(), blockID, trace.VERProcessFinalizedBlock) 168 defer span.End() 169 170 e.processFinalizedBlock(ctx, block) 171 } 172 173 // processFinalizedBlock indexes the execution receipts included in the block, performs chunk assignment on its result, and 174 // processes the chunks assigned to this verification node by pushing them to the chunks consumer. 175 func (e *Engine) processFinalizedBlock(ctx context.Context, block *flow.Block) { 176 177 if e.stopAtHeight > 0 && block.Header.Height == e.stopAtHeight { 178 e.stopAtBlockID.Store(block.ID()) 179 } 180 181 blockID := block.ID() 182 // we should always notify block consumer before returning. 183 defer e.blockConsumerNotifier.Notify(blockID) 184 185 // keeps track of total assigned and processed chunks in 186 // this block for logging. 187 assignedChunksCount := uint64(0) 188 processedChunksCount := uint64(0) 189 190 lg := e.log.With(). 191 Hex("block_id", logging.ID(blockID)). 192 Uint64("block_height", block.Header.Height). 193 Int("result_num", len(block.Payload.Results)).Logger() 194 lg.Debug().Msg("new finalized block arrived") 195 196 // determine chunk assigment on each result and pushes the assigned chunks to the chunks queue. 197 receiptsGroupedByResultID := block.Payload.Receipts.GroupByResultID() // for logging purposes 198 for _, result := range block.Payload.Results { 199 resultID := result.ID() 200 201 // log receipts committing to result 202 resultLog := lg.With().Hex("result_id", logging.ID(resultID)).Logger() 203 for _, receipt := range receiptsGroupedByResultID.GetGroup(resultID) { 204 resultLog = resultLog.With().Hex("receipts_for_result", logging.ID(receipt.ID())).Logger() 205 } 206 resultLog.Debug().Msg("determining chunk assignment for incorporated result") 207 208 // compute chunk assignment 209 chunkList, err := e.resultChunkAssignmentWithTracing(ctx, result, blockID) 210 if err != nil { 211 resultLog.Fatal().Err(err).Msg("could not determine assigned chunks for result") 212 } 213 214 assignedChunksCount += uint64(len(chunkList)) 215 for _, chunk := range chunkList { 216 217 if e.stopAtHeight > 0 && e.stopAtBlockID.Load() == chunk.BlockID { 218 resultLog.Fatal(). 219 Hex("chunk_id", logging.ID(chunk.ID())). 220 Msgf("Chunk for block at finalized height %d received - stopping node", e.stopAtHeight) 221 } 222 223 processed, err := e.processChunkWithTracing(ctx, chunk, resultID, block.Header.Height) 224 if err != nil { 225 resultLog.Fatal(). 226 Err(err). 227 Hex("chunk_id", logging.ID(chunk.ID())). 228 Uint64("chunk_index", chunk.Index). 229 Msg("could not process chunk") 230 } 231 232 if processed { 233 processedChunksCount++ 234 } 235 } 236 } 237 238 e.metrics.OnFinalizedBlockArrivedAtAssigner(block.Header.Height) 239 lg.Info(). 240 Uint64("total_assigned_chunks", assignedChunksCount). 241 Uint64("total_processed_chunks", processedChunksCount). 242 Msg("finished processing finalized block") 243 } 244 245 // chunkAssignments returns the list of chunks in the chunk list assigned to this verification node. 246 func (e *Engine) chunkAssignments(ctx context.Context, result *flow.ExecutionResult, incorporatingBlock flow.Identifier) (flow.ChunkList, error) { 247 span, _ := e.tracer.StartSpanFromContext(ctx, trace.VERMatchMyChunkAssignments) 248 defer span.End() 249 250 assignment, err := e.assigner.Assign(result, incorporatingBlock) 251 if err != nil { 252 return nil, err 253 } 254 255 mine, err := assignedChunks(e.me.NodeID(), assignment, result.Chunks) 256 if err != nil { 257 return nil, fmt.Errorf("could not determine my assignments: %w", err) 258 } 259 260 return mine, nil 261 } 262 263 // authorizedAsVerification checks whether this instance of verification node is authorized at specified block ID. 264 // It returns true and nil if verification node has positive weight at referenced block ID, and returns false and nil otherwise. 265 // It returns false and error if it could not extract the weight of node as a verification node at the specified block. 266 func authorizedAsVerification(state protocol.State, blockID flow.Identifier, identifier flow.Identifier) (bool, error) { 267 // TODO define specific error for handling cases 268 identity, err := state.AtBlockID(blockID).Identity(identifier) 269 if err != nil { 270 return false, nil 271 } 272 273 // checks role of node is verification 274 if identity.Role != flow.RoleVerification { 275 return false, fmt.Errorf("node has an invalid role. expected: %s, got: %s", flow.RoleVerification, identity.Role) 276 } 277 278 // checks identity is an active epoch participant with positive weight 279 if !filter.IsValidCurrentEpochParticipant(identity) || identity.InitialWeight == 0 { 280 return false, nil 281 } 282 283 return true, nil 284 } 285 286 // resultChunkAssignmentWithTracing computes the chunk assignment for the provided receipt with tracing enabled. 287 func (e *Engine) resultChunkAssignmentWithTracing( 288 ctx context.Context, 289 result *flow.ExecutionResult, 290 incorporatingBlock flow.Identifier, 291 ) (flow.ChunkList, error) { 292 var err error 293 var chunkList flow.ChunkList 294 e.tracer.WithSpanFromContext(ctx, trace.VERAssignerHandleExecutionReceipt, func() { 295 chunkList, err = e.resultChunkAssignment(ctx, result, incorporatingBlock) 296 }) 297 return chunkList, err 298 } 299 300 // processChunkWithTracing receives a chunks belong to the same execution result and processes it with tracing enabled. 301 // 302 // Note that the chunk in the input should be legitimately assigned to this verification node 303 // (through the chunk assigner), and belong to the same execution result. 304 func (e *Engine) processChunkWithTracing(ctx context.Context, chunk *flow.Chunk, resultID flow.Identifier, blockHeight uint64) (bool, error) { 305 var err error 306 var processed bool 307 e.tracer.WithSpanFromContext(ctx, trace.VERAssignerProcessChunk, func() { 308 processed, err = e.processChunk(chunk, resultID, blockHeight) 309 }) 310 return processed, err 311 } 312 313 // assignedChunks returns the chunks assigned to a specific assignee based on the input chunk assignment. 314 func assignedChunks(assignee flow.Identifier, assignment *chunks.Assignment, chunks flow.ChunkList) (flow.ChunkList, error) { 315 // indices of chunks assigned to verifier 316 chunkIndices := assignment.ByNodeID(assignee) 317 318 // chunks keeps the list of chunks assigned to the verifier 319 myChunks := make(flow.ChunkList, 0, len(chunkIndices)) 320 for _, index := range chunkIndices { 321 chunk, ok := chunks.ByIndex(index) 322 if !ok { 323 return nil, fmt.Errorf("chunk out of range requested: %v", index) 324 } 325 326 myChunks = append(myChunks, chunk) 327 } 328 329 return myChunks, nil 330 }