github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/pacemaker/view_tracker.go (about) 1 package pacemaker 2 3 import ( 4 "fmt" 5 6 "github.com/onflow/flow-go/consensus/hotstuff" 7 "github.com/onflow/flow-go/consensus/hotstuff/model" 8 "github.com/onflow/flow-go/model/flow" 9 ) 10 11 // viewTracker is a sub-component of the PaceMaker, which encapsulates the logic for tracking 12 // and updating the current view. For crash resilience, the viewTracker persists its latest 13 // internal state. 14 // 15 // In addition, viewTracker maintains and persists a proof to show that it entered the current 16 // view according to protocol rules. To enter a new view `v`, the Pacemaker must observe a 17 // valid QC or TC for view `v-1`. Per convention, the proof has the following structure: 18 // - If the current view was entered by observing a QC, this QC is returned by `NewestQC()`. 19 // Furthermore, `LastViewTC()` returns nil. 20 // - If the current view was entered by observing a TC, `NewestQC()` returns the newest QC 21 // known. `LastViewTC()` returns the TC that triggered the view change 22 type viewTracker struct { 23 livenessData hotstuff.LivenessData 24 persist hotstuff.Persister 25 } 26 27 // newViewTracker instantiates a viewTracker. 28 func newViewTracker(persist hotstuff.Persister) (viewTracker, error) { 29 livenessData, err := persist.GetLivenessData() 30 if err != nil { 31 return viewTracker{}, fmt.Errorf("could not load liveness data: %w", err) 32 } 33 34 if livenessData.CurrentView < 1 { 35 return viewTracker{}, model.NewConfigurationErrorf("PaceMaker cannot start in view 0 (view zero is reserved for genesis block, which has no proposer)") 36 } 37 38 return viewTracker{ 39 livenessData: *livenessData, 40 persist: persist, 41 }, nil 42 } 43 44 // CurView returns the current view. 45 func (vt *viewTracker) CurView() uint64 { 46 return vt.livenessData.CurrentView 47 } 48 49 // NewestQC returns the QC with the highest view known. 50 func (vt *viewTracker) NewestQC() *flow.QuorumCertificate { 51 return vt.livenessData.NewestQC 52 } 53 54 // LastViewTC returns TC for last view, this is nil if ond only of the current view 55 // was entered with a QC. 56 func (vt *viewTracker) LastViewTC() *flow.TimeoutCertificate { 57 return vt.livenessData.LastViewTC 58 } 59 60 // ProcessQC ingests a QC, which might advance the current view. Panics for nil input! 61 // QCs with views smaller or equal to the newest QC known are a no-op. ProcessQC returns 62 // the resulting view after processing the QC. 63 // No errors are expected, any error should be treated as exception. 64 func (vt *viewTracker) ProcessQC(qc *flow.QuorumCertificate) (uint64, error) { 65 view := vt.livenessData.CurrentView 66 if qc.View < view { 67 // If the QC is for a past view, our view does not change. Nevertheless, the QC might be 68 // newer than the newest QC we know, since view changes can happen through TCs as well. 69 // While not very likely, is is possible that individual replicas know newer QCs than the 70 // ones previously included in TCs. E.g. a primary that crashed before it could construct 71 // its block is has rebooted and is now sharing its newest QC as part of a TimeoutObject. 72 err := vt.updateNewestQC(qc) 73 if err != nil { 74 return view, fmt.Errorf("could not update tracked newest QC: %w", err) 75 } 76 return view, nil 77 } 78 79 // supermajority of replicas have already voted during round `qc.view`, hence it is safe to proceed to subsequent view 80 newView := qc.View + 1 81 err := vt.updateLivenessData(newView, qc, nil) 82 if err != nil { 83 return 0, fmt.Errorf("failed to update liveness data: %w", err) 84 } 85 return newView, nil 86 } 87 88 // ProcessTC ingests a TC, which might advance the current view. A nil TC is accepted as 89 // input, so that callers may pass in e.g. `Proposal.LastViewTC`, which may or may not have 90 // a value. It returns the resulting view after processing the TC and embedded QC. 91 // No errors are expected, any error should be treated as exception 92 func (vt *viewTracker) ProcessTC(tc *flow.TimeoutCertificate) (uint64, error) { 93 view := vt.livenessData.CurrentView 94 if tc == nil { 95 return view, nil 96 } 97 98 if tc.View < view { 99 // TC and the embedded QC are for a past view, hence our view does not change. Nevertheless, 100 // the QC might be newer than the newest QC we know. While not very likely, is is possible 101 // that individual replicas know newer QCs than the ones previously included in any TCs. 102 // E.g. a primary that crashed before it could construct its block is has rebooted and 103 // now contributed its newest QC to this TC. 104 err := vt.updateNewestQC(tc.NewestQC) 105 if err != nil { 106 return 0, fmt.Errorf("could not update tracked newest QC: %w", err) 107 } 108 return view, nil 109 } 110 111 // supermajority of replicas have already reached their timeout for view `tc.View`, hence it is safe to proceed to subsequent view 112 newView := tc.View + 1 113 err := vt.updateLivenessData(newView, tc.NewestQC, tc) 114 if err != nil { 115 return 0, fmt.Errorf("failed to update liveness data: %w", err) 116 } 117 return newView, nil 118 } 119 120 // updateLivenessData updates the current view, qc, tc. We want to avoid unnecessary data-base 121 // writes, which we enforce by requiring that the view number is STRICTLY monotonously increasing. 122 // Otherwise, an exception is returned. No errors are expected, any error should be treated as exception. 123 func (vt *viewTracker) updateLivenessData(newView uint64, qc *flow.QuorumCertificate, tc *flow.TimeoutCertificate) error { 124 if newView <= vt.livenessData.CurrentView { 125 // This should never happen: in the current implementation, it is trivially apparent that 126 // newView is _always_ larger than currentView. This check is to protect the code from 127 // future modifications that violate the necessary condition for 128 // STRICTLY monotonously increasing view numbers. 129 return fmt.Errorf("cannot move from view %d to %d: currentView must be strictly monotonously increasing", 130 vt.livenessData.CurrentView, newView) 131 } 132 133 vt.livenessData.CurrentView = newView 134 if vt.livenessData.NewestQC.View < qc.View { 135 vt.livenessData.NewestQC = qc 136 } 137 vt.livenessData.LastViewTC = tc 138 err := vt.persist.PutLivenessData(&vt.livenessData) 139 if err != nil { 140 return fmt.Errorf("could not persist liveness data: %w", err) 141 } 142 return nil 143 } 144 145 // updateNewestQC updates the highest QC tracked by view, iff `qc` has a larger 146 // view than the newest stored QC. Otherwise, this method is a no-op. 147 // No errors are expected, any error should be treated as exception. 148 func (vt *viewTracker) updateNewestQC(qc *flow.QuorumCertificate) error { 149 if vt.livenessData.NewestQC.View >= qc.View { 150 return nil 151 } 152 153 vt.livenessData.NewestQC = qc 154 err := vt.persist.PutLivenessData(&vt.livenessData) 155 if err != nil { 156 return fmt.Errorf("could not persist liveness data: %w", err) 157 } 158 159 return nil 160 }