github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/consensus/approvals/tracker/tracker.go (about)

     1  package tracker
     2  
     3  import (
     4  	"encoding/hex"
     5  	"encoding/json"
     6  	"time"
     7  
     8  	"github.com/rs/zerolog"
     9  
    10  	"github.com/onflow/flow-go/engine/consensus"
    11  	"github.com/onflow/flow-go/model/flow"
    12  	"github.com/onflow/flow-go/model/flow/filter/id"
    13  	"github.com/onflow/flow-go/module/mempool"
    14  	"github.com/onflow/flow-go/storage"
    15  )
    16  
    17  // SealingTracker is an auxiliary component for tracking progress of the sealing
    18  // logic (specifically sealing.Core). It has access to the storage, to collect data
    19  // that is not be available directly from sealing.Core. The SealingTracker is immutable
    20  // and therefore intrinsically thread safe.
    21  //
    22  // The SealingTracker essentially acts as a factory for individual SealingObservations,
    23  // which capture information about the progress of a _single_ go routine. Consequently,
    24  // SealingObservations don't need to be concurrency safe, as they are supposed to
    25  // be thread-local structure.
    26  type SealingTracker struct {
    27  	log        zerolog.Logger
    28  	headersDB  storage.Headers
    29  	receiptsDB storage.ExecutionReceipts
    30  	sealsPl    mempool.IncorporatedResultSeals
    31  }
    32  
    33  func NewSealingTracker(log zerolog.Logger, headersDB storage.Headers, receiptsDB storage.ExecutionReceipts, sealsPl mempool.IncorporatedResultSeals) *SealingTracker {
    34  	return &SealingTracker{
    35  		log:        log.With().Str("engine", "sealing.SealingTracker").Logger(),
    36  		headersDB:  headersDB,
    37  		receiptsDB: receiptsDB,
    38  		sealsPl:    sealsPl,
    39  	}
    40  }
    41  
    42  // nextUnsealedFinalizedBlock determines the ID of the finalized but unsealed
    43  // block with smallest height. It returns an Identity filter that only accepts
    44  // the respective ID.
    45  // In case the next unsealed block has not been finalized, we return the
    46  // False-filter (or if we encounter any problems).
    47  func (st *SealingTracker) nextUnsealedFinalizedBlock(sealedBlock *flow.Header) flow.IdentifierFilter {
    48  	nextUnsealedHeight := sealedBlock.Height + 1
    49  	nextUnsealed, err := st.headersDB.ByHeight(nextUnsealedHeight)
    50  	if err != nil {
    51  		return id.False
    52  	}
    53  	return id.Is(nextUnsealed.ID())
    54  }
    55  
    56  // NewSealingObservation constructs a SealingObservation, which capture information
    57  // about the progress of a _single_ go routine. Consequently, SealingObservations
    58  // don't need to be concurrency safe, as they are supposed to be thread-local structure.
    59  func (st *SealingTracker) NewSealingObservation(finalizedBlock *flow.Header, seal *flow.Seal, sealedBlock *flow.Header) consensus.SealingObservation {
    60  	return &SealingObservation{
    61  		SealingTracker:      st,
    62  		startTime:           time.Now(),
    63  		finalizedBlock:      finalizedBlock,
    64  		latestFinalizedSeal: seal,
    65  		latestSealedBlock:   sealedBlock,
    66  		isRelevant:          st.nextUnsealedFinalizedBlock(sealedBlock),
    67  		records:             make(map[flow.Identifier]*SealingRecord),
    68  	}
    69  }
    70  
    71  // SealingObservation captures information about the progress of a _single_ go routine.
    72  // Consequently, it is _not concurrency safe_, as SealingObservation is intended to be
    73  // a thread-local structure.
    74  // SealingObservation is supposed to track the status of various (unsealed) incorporated
    75  // results, which sealing.Core processes (driven by that single goroutine).
    76  type SealingObservation struct {
    77  	*SealingTracker
    78  
    79  	finalizedBlock      *flow.Header
    80  	latestFinalizedSeal *flow.Seal
    81  	latestSealedBlock   *flow.Header
    82  
    83  	startTime  time.Time                          // time when this instance was created
    84  	isRelevant flow.IdentifierFilter              // policy to determine for which blocks we want to track
    85  	records    map[flow.Identifier]*SealingRecord // each record is for one (unsealed) incorporated result
    86  }
    87  
    88  // QualifiesForEmergencySealing captures whether sealing.Core has
    89  // determined that the incorporated result qualifies for emergency sealing.
    90  func (st *SealingObservation) QualifiesForEmergencySealing(ir *flow.IncorporatedResult, emergencySealable bool) {
    91  	if !st.isRelevant(ir.Result.BlockID) {
    92  		return
    93  	}
    94  	st.getOrCreateRecord(ir).QualifiesForEmergencySealing(emergencySealable)
    95  }
    96  
    97  // ApprovalsMissing captures whether sealing.Core has determined that
    98  // some approvals are still missing for the incorporated result. Calling this
    99  // method with empty `chunksWithMissingApprovals` indicates that all chunks
   100  // have sufficient approvals.
   101  func (st *SealingObservation) ApprovalsMissing(ir *flow.IncorporatedResult, chunksWithMissingApprovals map[uint64]flow.IdentifierList) {
   102  	if !st.isRelevant(ir.Result.BlockID) {
   103  		return
   104  	}
   105  	st.getOrCreateRecord(ir).ApprovalsMissing(chunksWithMissingApprovals)
   106  }
   107  
   108  // ApprovalsRequested captures the number of approvals that the business
   109  // logic has re-requested for the incorporated result.
   110  func (st *SealingObservation) ApprovalsRequested(ir *flow.IncorporatedResult, requestCount uint) {
   111  	if !st.isRelevant(ir.Result.BlockID) {
   112  		return
   113  	}
   114  	st.getOrCreateRecord(ir).ApprovalsRequested(requestCount)
   115  }
   116  
   117  // getOrCreateRecord returns the sealing record for the given incorporated result.
   118  // If no such record is found, a new record is created and stored in `records`.
   119  func (st *SealingObservation) getOrCreateRecord(ir *flow.IncorporatedResult) *SealingRecord {
   120  	irID := ir.ID()
   121  	record, found := st.records[irID]
   122  	if !found {
   123  		record = &SealingRecord{
   124  			SealingObservation: st,
   125  			IncorporatedResult: ir,
   126  			entries:            make(Rec),
   127  		}
   128  		st.records[irID] = record
   129  	}
   130  	return record
   131  }
   132  
   133  // Complete is supposed to be called when a single execution of the sealing logic
   134  // has been completed. It compiles the information about the incorporated results.
   135  func (st *SealingObservation) Complete() {
   136  	observation := st.log.Info()
   137  
   138  	// basic information
   139  	observation.Str("finalized_block", st.finalizedBlock.ID().String()).
   140  		Uint64("finalized_block_height", st.finalizedBlock.Height).
   141  		Uint("seals_mempool_size", st.sealsPl.Size())
   142  
   143  	// details about the latest finalized seal
   144  	sealDetails, err := st.latestFinalizedSealInfo()
   145  	if err != nil {
   146  		st.log.Error().Err(err).Msg("failed to marshal latestFinalizedSeal details")
   147  	} else {
   148  		observation = observation.Str("finalized_seal", sealDetails)
   149  	}
   150  
   151  	// details about the unsealed results that are next
   152  	recList := make([]Rec, 0, len(st.records))
   153  	for irID, rec := range st.records {
   154  		r, err := rec.Generate()
   155  		if err != nil {
   156  			st.log.Error().Err(err).
   157  				Str("incorporated_result", irID.String()).
   158  				Msg("failed to generate sealing record")
   159  			continue
   160  		}
   161  		recList = append(recList, r)
   162  	}
   163  	if len(recList) > 0 {
   164  		bytes, err := json.Marshal(recList)
   165  		if err != nil {
   166  			st.log.Error().Err(err).Msg("failed to marshal records")
   167  		}
   168  		observation = observation.Str("next_unsealed_results", string(bytes))
   169  	}
   170  
   171  	// dump observation to Logger
   172  	observation = observation.Int64("duration_ms", time.Since(st.startTime).Milliseconds())
   173  	observation.Msg("sealing observation")
   174  }
   175  
   176  // latestFinalizedSealInfo returns a json string representation with the most
   177  // relevant data about the latest finalized seal
   178  func (st *SealingObservation) latestFinalizedSealInfo() (string, error) {
   179  	r := make(map[string]interface{})
   180  	r["executed_block_id"] = st.latestFinalizedSeal.BlockID.String()
   181  	r["executed_block_height"] = st.latestSealedBlock.Height
   182  	r["result_id"] = st.latestFinalizedSeal.ResultID.String()
   183  	r["result_final_state"] = hex.EncodeToString(st.latestFinalizedSeal.FinalState[:])
   184  
   185  	bytes, err := json.Marshal(r)
   186  	if err != nil {
   187  		return "", err
   188  	}
   189  	return string(bytes), nil
   190  }