github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/forks/forkchoice/newest.go (about)

     1  package forkchoice
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/koko1123/flow-go-1/consensus/hotstuff"
     7  	"github.com/koko1123/flow-go-1/consensus/hotstuff/forks"
     8  	"github.com/koko1123/flow-go-1/consensus/hotstuff/forks/finalizer"
     9  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
    10  	"github.com/koko1123/flow-go-1/model/flow"
    11  )
    12  
    13  // NewestForkChoice implements HotStuff's original fork choice rule:
    14  // always use the newest QC (i.e. the QC with highest view number)
    15  type NewestForkChoice struct {
    16  	// preferredParent stores the preferred parent to build a block on top of. It contains the
    17  	// parent block as well as the QC POINTING to the parent, which can be used to build the block.
    18  	// preferredParent.QC called 'GenericQC' in 'Chained HotStuff Protocol' https://arxiv.org/abs/1803.05069v6
    19  	preferredParent *forks.BlockQC
    20  	finalizer       *finalizer.Finalizer
    21  	notifier        hotstuff.Consumer
    22  }
    23  
    24  // NewNewestForkChoice creates a new NewNewestForkChoice instance
    25  func NewNewestForkChoice(finalizer *finalizer.Finalizer, notifier hotstuff.Consumer) (*NewestForkChoice, error) {
    26  
    27  	// build the initial block-QC pair
    28  	// NOTE: I don't like this structure because it stores view and block ID in two separate places; this means
    29  	// we don't have a single field that is the source of truth, and opens the door for bugs that would otherwise
    30  	// be impossible
    31  	block := finalizer.FinalizedBlock()
    32  	qc := finalizer.FinalizedBlockQC()
    33  	if block.BlockID != qc.BlockID || block.View != qc.View {
    34  		return nil, fmt.Errorf("mismatch between finalized block and QC")
    35  	}
    36  
    37  	blockQC := forks.BlockQC{Block: block, QC: qc}
    38  
    39  	fc := &NewestForkChoice{
    40  		preferredParent: &blockQC,
    41  		finalizer:       finalizer,
    42  		notifier:        notifier,
    43  	}
    44  
    45  	notifier.OnQcIncorporated(qc)
    46  
    47  	return fc, nil
    48  }
    49  
    50  // MakeForkChoice prompts the ForkChoice to generate a fork choice for the
    51  // current view `curView`. NewestForkChoice always returns the qc with the largest
    52  // view number seen.
    53  // It returns a qc and the block that the qc is pointing to.
    54  //
    55  // PREREQUISITE:
    56  // ForkChoice cannot generate ForkChoices retroactively for past views.
    57  // If used correctly, MakeForkChoice should only ever have processed QCs
    58  // whose view is smaller than curView, for the following reason:
    59  // Processing a QC with view v should result in the PaceMaker being in
    60  // view v+1 or larger. Hence, given that the current View is curView,
    61  // all QCs should have view < curView.
    62  // To prevent accidental miss-usage, ForkChoices will error if `curView`
    63  // is smaller than the view of any qc ForkChoice has seen.
    64  // Note that tracking the view of the newest qc is for safety purposes
    65  // and _independent_ of the fork-choice rule.
    66  func (fc *NewestForkChoice) MakeForkChoice(curView uint64) (*flow.QuorumCertificate, *model.Block, error) {
    67  	choice := fc.preferredParent
    68  	if choice.Block.View >= curView {
    69  		// sanity check;
    70  		// processing a QC with view v should result in the PaceMaker being in view v+1 or larger
    71  		// Hence, given that the current View is curView, all QCs should have view < curView
    72  		return nil, nil, fmt.Errorf(
    73  			"ForkChoice selected qc with view %d which is larger than requested view %d",
    74  			choice.Block.View, curView,
    75  		)
    76  	}
    77  	fc.notifier.OnForkChoiceGenerated(curView, choice.QC)
    78  	return choice.QC, choice.Block, nil
    79  }
    80  
    81  // AddQC updates `preferredParent` according to the fork-choice rule.
    82  // Currently, we implement 'Chained HotStuff Protocol' where the fork-choice
    83  // rule is: "build on newest QC"
    84  // It assumes the QC has been validated
    85  func (fc *NewestForkChoice) AddQC(qc *flow.QuorumCertificate) error {
    86  	if qc.View <= fc.preferredParent.Block.View {
    87  		// Per construction, preferredParent.View() is always larger than the last finalized block's view.
    88  		// Hence, this check suffices to drop all QCs with qc.View <= last_finalized_block.View().
    89  		return nil
    90  	}
    91  
    92  	// Have qc.View > last_finalized_block.View(). Hence, block referenced by qc should be stored:
    93  	block, err := fc.ensureBlockStored(qc)
    94  	if err != nil {
    95  		return fmt.Errorf("cannot add QC: %w", err)
    96  	}
    97  
    98  	if block.BlockID != qc.BlockID || block.View != qc.View {
    99  		return fmt.Errorf("mismatch between finalized block and QC")
   100  	}
   101  
   102  	blockQC := forks.BlockQC{Block: block, QC: qc}
   103  	fc.preferredParent = &blockQC
   104  	fc.notifier.OnQcIncorporated(qc)
   105  
   106  	return nil
   107  }
   108  
   109  func (fc *NewestForkChoice) ensureBlockStored(qc *flow.QuorumCertificate) (*model.Block, error) {
   110  	block, haveBlock := fc.finalizer.GetBlock(qc.BlockID)
   111  	if !haveBlock {
   112  		// This should never happen and indicates an implementation bug.
   113  		// Finding the block to which the qc points to should always be possible for the folling reason:
   114  		// * Check in function AddQC guarantees: qc.View > fc.preferredParent.Block.View
   115  		// * Forks guarantees that every block's qc is also processed by ForkChoice
   116  		//   => fc.preferredParent.Block.View > fc.finalizer.FinalizedBlock().View()
   117  		//   (as NewestForkChoice tracks the qc with the largest view)
   118  		// => qc.View > fc.finalizer.FinalizedBlock().View()
   119  		//    any block whose view is larger than the latest finalized block should be stored in finalizer
   120  		return nil, model.MissingBlockError{View: qc.View, BlockID: qc.BlockID}
   121  	}
   122  	if block.View != qc.View {
   123  		return nil, fmt.Errorf("invalid qc with mismatching view")
   124  	}
   125  	return block, nil
   126  }