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  }