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 }