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  }