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 }