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