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  }