github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/forks/finalizer/finalizer.go (about) 1 package finalizer 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/koko1123/flow-go-1/consensus/hotstuff" 8 "github.com/koko1123/flow-go-1/consensus/hotstuff/forks" 9 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 10 "github.com/koko1123/flow-go-1/model/flow" 11 "github.com/koko1123/flow-go-1/module" 12 "github.com/koko1123/flow-go-1/module/forest" 13 ) 14 15 // Finalizer implements HotStuff finalization logic 16 type Finalizer struct { 17 notifier hotstuff.FinalizationConsumer 18 forest forest.LevelledForest 19 20 finalizationCallback module.Finalizer 21 lastLocked *forks.BlockQC // lastLockedBlockQC is the QC that POINTS TO the the most recently locked block 22 lastFinalized *forks.BlockQC // lastFinalizedBlockQC is the QC that POINTS TO the most recently finalized locked block 23 } 24 25 type ancestryChain struct { 26 block *BlockContainer 27 oneChain *forks.BlockQC 28 twoChain *forks.BlockQC 29 threeChain *forks.BlockQC 30 } 31 32 // ErrPrunedAncestry is a sentinel error: cannot resolve ancestry of block due to pruning 33 var ErrPrunedAncestry = errors.New("cannot resolve pruned ancestor") 34 35 func New(trustedRoot *forks.BlockQC, finalizationCallback module.Finalizer, notifier hotstuff.FinalizationConsumer) (*Finalizer, error) { 36 if (trustedRoot.Block.BlockID != trustedRoot.QC.BlockID) || (trustedRoot.Block.View != trustedRoot.QC.View) { 37 return nil, model.NewConfigurationErrorf("invalid root: root qc is not pointing to root block") 38 } 39 40 fnlzr := Finalizer{ 41 notifier: notifier, 42 finalizationCallback: finalizationCallback, 43 forest: *forest.NewLevelledForest(trustedRoot.Block.View), 44 lastLocked: trustedRoot, 45 lastFinalized: trustedRoot, 46 } 47 // verify and add root block to levelled forest 48 err := fnlzr.VerifyBlock(trustedRoot.Block) 49 if err != nil { 50 return nil, fmt.Errorf("invalid root block: %w", err) 51 } 52 fnlzr.forest.AddVertex(&BlockContainer{Block: trustedRoot.Block}) 53 fnlzr.notifier.OnBlockIncorporated(trustedRoot.Block) 54 return &fnlzr, nil 55 } 56 57 func (r *Finalizer) LockedBlock() *model.Block { return r.lastLocked.Block } 58 func (r *Finalizer) LockedBlockQC() *flow.QuorumCertificate { return r.lastLocked.QC } 59 func (r *Finalizer) FinalizedBlock() *model.Block { return r.lastFinalized.Block } 60 func (r *Finalizer) FinalizedView() uint64 { return r.lastFinalized.Block.View } 61 func (r *Finalizer) FinalizedBlockQC() *flow.QuorumCertificate { return r.lastFinalized.QC } 62 63 // GetBlock returns block for given ID 64 func (r *Finalizer) GetBlock(blockID flow.Identifier) (*model.Block, bool) { 65 blockContainer, hasBlock := r.forest.GetVertex(blockID) 66 if !hasBlock { 67 return nil, false 68 } 69 return blockContainer.(*BlockContainer).Block, true 70 } 71 72 // GetBlock returns all known blocks for the given 73 func (r *Finalizer) GetBlocksForView(view uint64) []*model.Block { 74 vertexIterator := r.forest.GetVerticesAtLevel(view) 75 l := make([]*model.Block, 0, 1) // in the vast majority of cases, there will only be one proposal for a particular view 76 for vertexIterator.HasNext() { 77 v := vertexIterator.NextVertex().(*BlockContainer) 78 l = append(l, v.Block) 79 } 80 return l 81 } 82 83 // IsKnownBlock checks whether block is known. 84 // UNVALIDATED: expects block to pass Finalizer.VerifyBlock(block) 85 func (r *Finalizer) IsKnownBlock(block *model.Block) bool { 86 _, hasBlock := r.forest.GetVertex(block.BlockID) 87 return hasBlock 88 } 89 90 // IsProcessingNeeded performs basic checks whether or not block needs processing 91 // only considering the block's height and hash 92 // Returns false if any of the following conditions applies 93 // - block view is _below_ the most recently finalized block 94 // - known block 95 // 96 // UNVALIDATED: expects block to pass Finalizer.VerifyBlock(block) 97 func (r *Finalizer) IsProcessingNeeded(block *model.Block) bool { 98 if block.View < r.lastFinalized.Block.View || r.IsKnownBlock(block) { 99 return false 100 } 101 return true 102 } 103 104 // IsSafeBlock returns true if block is safe to vote for 105 // (according to the definition in https://arxiv.org/abs/1803.05069v6). 106 // NO MODIFICATION of consensus state (read only) 107 // UNVALIDATED: expects block to pass Finalizer.VerifyBlock(block) 108 func (r *Finalizer) IsSafeBlock(block *model.Block) bool { 109 // According to the paper, a block is considered a safe block if 110 // * it extends from locked block (safety rule), 111 // * or the view of the parent block is higher than the view number of locked block (liveness rule). 112 // The two rules can be boiled down to the following: 113 // 1. If block.QC.View is higher than locked view, it definitely is a safe block. 114 // 2. If block.QC.View is lower than locked view, it definitely is not a safe block. 115 // 3. If block.QC.View equals to locked view: parent must be the locked block. 116 qc := block.QC 117 if qc.View > r.lastLocked.Block.View { 118 return true 119 } 120 if (qc.View == r.lastLocked.Block.View) && (qc.BlockID == r.lastLocked.Block.BlockID) { 121 return true 122 } 123 return false 124 } 125 126 // ProcessBlock adds `block` to the consensus state. 127 // Calling this method with previously-processed blocks leaves the consensus state invariant 128 // (though, it will potentially cause some duplicate processing). 129 // UNVALIDATED: expects block to pass Finalizer.VerifyBlock(block) 130 func (r *Finalizer) AddBlock(block *model.Block) error { 131 if !r.IsProcessingNeeded(block) { 132 return nil 133 } 134 blockContainer := &BlockContainer{Block: block} 135 if err := r.checkForConflictingQCs(blockContainer.Block.QC); err != nil { 136 return err 137 } 138 r.checkForDoubleProposal(blockContainer) 139 r.forest.AddVertex(blockContainer) 140 err := r.updateConsensusState(blockContainer) 141 if err != nil { 142 return fmt.Errorf("updating consensus state failed: %w", err) 143 } 144 err = r.finalizationCallback.MakeValid(blockContainer.Block.BlockID) 145 if err != nil { 146 return fmt.Errorf("MakeValid fails in other component: %w", err) 147 } 148 r.notifier.OnBlockIncorporated(blockContainer.Block) 149 return nil 150 } 151 152 // checkForConflictingQCs checks if qc conflicts with a stored Quorum Certificate. 153 // In case a conflicting QC is found, an ByzantineThresholdExceededError is returned. 154 // 155 // Two Quorum Certificates q1 and q2 are defined as conflicting iff: 156 // - q1.View == q2.View 157 // - q1.BlockID != q2.BlockID 158 // 159 // This means there are two Quorums for conflicting blocks at the same view. 160 // Per Lemma 1 from the HotStuff paper https://arxiv.org/abs/1803.05069v6, two 161 // conflicting QCs can exists if and onluy of the Byzantine threshold is exceeded. 162 func (r *Finalizer) checkForConflictingQCs(qc *flow.QuorumCertificate) error { 163 it := r.forest.GetVerticesAtLevel(qc.View) 164 for it.HasNext() { 165 otherBlock := it.NextVertex() // by construction, must have same view as qc.View 166 if qc.BlockID != otherBlock.VertexID() { 167 // * we have just found another block at the same view number as qc.View but with different hash 168 // * if this block has a child c, this child will have 169 // c.qc.view = parentView 170 // c.qc.ID != parentBlockID 171 // => conflicting qc 172 otherChildren := r.forest.GetChildren(otherBlock.VertexID()) 173 if otherChildren.HasNext() { 174 otherChild := otherChildren.NextVertex() 175 conflictingQC := otherChild.(*BlockContainer).Block.QC 176 return model.ByzantineThresholdExceededError{Evidence: fmt.Sprintf( 177 "conflicting QCs at view %d: %v and %v", 178 qc.View, qc.BlockID, conflictingQC.BlockID, 179 )} 180 } 181 } 182 } 183 return nil 184 } 185 186 // checkForDoubleProposal checks if Block is a double proposal. In case it is, 187 // notifier.OnDoubleProposeDetected is triggered 188 func (r *Finalizer) checkForDoubleProposal(container *BlockContainer) { 189 it := r.forest.GetVerticesAtLevel(container.Block.View) 190 for it.HasNext() { 191 otherVertex := it.NextVertex() // by construction, must have same view as parentView 192 if container.VertexID() != otherVertex.VertexID() { 193 r.notifier.OnDoubleProposeDetected(container.Block, otherVertex.(*BlockContainer).Block) 194 } 195 } 196 } 197 198 // updateConsensusState updates consensus state. 199 // Calling this method with previously-processed blocks leaves the consensus state invariant. 200 // UNVALIDATED: assumes that relevant block properties are consistent with previous blocks 201 func (r *Finalizer) updateConsensusState(blockContainer *BlockContainer) error { 202 ancestryChain, err := r.getThreeChain(blockContainer) 203 // We expect that getThreeChain might error with a ErrorPrunedAncestry. This error indicates that the 204 // 3-chain of this block reaches _beyond_ the last finalized block. It is straight forward to show: 205 // Lemma: Let B be a block whose 3-chain reaches beyond the last finalized block 206 // => B will not update the locked or finalized block 207 if errors.Is(err, ErrPrunedAncestry) { // blockContainer's 3-chain reaches beyond the last finalized block 208 // based on Lemma from above, we can skip attempting to update locked or finalized block 209 return nil 210 } 211 if err != nil { // otherwise, there is an unknown error that we need to escalate to the higher-level application logic 212 return fmt.Errorf("retrieving 3-chain ancestry failed: %w", err) 213 } 214 215 r.updateLockedQc(ancestryChain) 216 err = r.updateFinalizedBlockQc(ancestryChain) 217 if err != nil { 218 return fmt.Errorf("updating finalized block failed: %w", err) 219 } 220 return nil 221 } 222 223 // getThreeChain returns the three chain or a ErrorPrunedAncestry sentinel error 224 // to indicate that the 3-chain from blockContainer is (partially) pruned 225 func (r *Finalizer) getThreeChain(blockContainer *BlockContainer) (*ancestryChain, error) { 226 ancestryChain := ancestryChain{block: blockContainer} 227 228 var err error 229 ancestryChain.oneChain, err = r.getNextAncestryLevel(blockContainer.Block) 230 if err != nil { 231 return nil, err 232 } 233 ancestryChain.twoChain, err = r.getNextAncestryLevel(ancestryChain.oneChain.Block) 234 if err != nil { 235 return nil, err 236 } 237 ancestryChain.threeChain, err = r.getNextAncestryLevel(ancestryChain.twoChain.Block) 238 if err != nil { 239 return nil, err 240 } 241 return &ancestryChain, nil 242 } 243 244 // getNextAncestryLevel parent from forest. Returns QCBlock for the parent, 245 // i.e. the parent block itself and the qc pointing to the parent, i.e. block.QC(). 246 // If the block's parent is below the pruned view, it will error with an ErrorPrunedAncestry. 247 // UNVALIDATED: expects block to pass Finalizer.VerifyBlock(block) 248 func (r *Finalizer) getNextAncestryLevel(block *model.Block) (*forks.BlockQC, error) { 249 // The finalizer prunes all blocks in forest which are below the most recently finalized block. 250 // Hence, we have a pruned ancestry if and only if either of the following conditions applies: 251 // (a) if a block's parent view (i.e. block.QC.View) is below the most recently finalized block. 252 // (b) if a block's view is equal to the most recently finalized block. 253 // Caution: 254 // * Under normal operation, case (b) is covered by the logic for case (a) 255 // * However, the existence of a genesis block requires handling case (b) explicitly: 256 // The root block is specified and trusted by the node operator. If the root block is the 257 // genesis block, it might not contain a qc pointing to a parent (as there is no parent). 258 // In this case, condition (a) cannot be evaluated. 259 if (block.View <= r.lastFinalized.Block.View) || (block.QC.View < r.lastFinalized.Block.View) { 260 return nil, ErrPrunedAncestry 261 } 262 263 parentVertex, parentBlockKnown := r.forest.GetVertex(block.QC.BlockID) 264 if !parentBlockKnown { 265 return nil, model.MissingBlockError{View: block.QC.View, BlockID: block.QC.BlockID} 266 } 267 newBlock := parentVertex.(*BlockContainer).Block 268 if newBlock.BlockID != block.QC.BlockID || newBlock.View != block.QC.View { 269 return nil, fmt.Errorf("mismatch between finalized block and QC") 270 } 271 272 blockQC := forks.BlockQC{Block: newBlock, QC: block.QC} 273 274 return &blockQC, nil 275 } 276 277 // updateLockedBlock updates `lastLockedBlockQC` 278 // We use the locking rule from 'Event-driven HotStuff Protocol' where the condition is: 279 // - Consider the set S of all blocks that have a INDIRECT 2-chain on top of it 280 // - The 'Locked Block' is the block in S with the _highest view number_ (newest); 281 // 282 // Calling this method with previously-processed blocks leaves consensus state invariant. 283 func (r *Finalizer) updateLockedQc(ancestryChain *ancestryChain) { 284 if ancestryChain.twoChain.Block.View <= r.lastLocked.Block.View { 285 return 286 } 287 // update qc to newer block with any 2-chain on top of it: 288 r.lastLocked = ancestryChain.twoChain 289 } 290 291 // updateFinalizedBlockQc updates `lastFinalizedBlockQC` 292 // We use the finalization rule from 'Event-driven HotStuff Protocol' where the condition is: 293 // - Consider the set S of all blocks that have a DIRECT 2-chain on top of it PLUS any 1-chain 294 // - The 'Last finalized Block' is the block in S with the _highest view number_ (newest); 295 // 296 // Calling this method with previously-processed blocks leaves consensus state invariant. 297 func (r *Finalizer) updateFinalizedBlockQc(ancestryChain *ancestryChain) error { 298 // Note: we assume that all stored blocks pass Finalizer.VerifyBlock(block); 299 // specifically, that Block's ViewNumber is strictly monotonously 300 // increasing which is enforced by LevelledForest.VerifyVertex(...) 301 // We denote: 302 // * a DIRECT 1-chain as '<-' 303 // * a general 1-chain as '<~' (direct or indirect) 304 // The rule from 'Event-driven HotStuff' for finalizing block b is 305 // b <- b' <- b'' <~ b* (aka a DIRECT 2-chain PLUS any 1-chain) 306 // where b* is the input block to this method. 307 // Hence, we can finalize b, if and only the viewNumber of b'' is exactly 2 higher than the view of b 308 b := ancestryChain.threeChain // note that b is actually not the block itself here but rather the QC pointing to it 309 if ancestryChain.oneChain.Block.View != b.Block.View+2 { 310 return nil 311 } 312 return r.finalizeUpToBlock(b.QC) 313 } 314 315 // finalizeUpToBlock finalizes all blocks up to (and including) the block pointed to by `blockQC`. 316 // Finalization starts with the child of `lastFinalizedBlockQC` (explicitly checked); 317 // and calls OnFinalizedBlock on the newly finalized blocks in the respective order 318 func (r *Finalizer) finalizeUpToBlock(qc *flow.QuorumCertificate) error { 319 if qc.View < r.lastFinalized.Block.View { 320 return model.ByzantineThresholdExceededError{Evidence: fmt.Sprintf( 321 "finalizing blocks with view %d which is lower than previously finalized block at view %d", 322 qc.View, r.lastFinalized.Block.View, 323 )} 324 } 325 if qc.View == r.lastFinalized.Block.View { 326 // Sanity check: the previously last Finalized Block must be an ancestor of `block` 327 if r.lastFinalized.Block.BlockID != qc.BlockID { 328 return model.ByzantineThresholdExceededError{Evidence: fmt.Sprintf( 329 "finalizing blocks at conflicting forks: %v and %v", 330 qc.BlockID, r.lastFinalized.Block.BlockID, 331 )} 332 } 333 return nil 334 } 335 // Have: qc.View > r.lastFinalizedBlockQC.View => finalizing new block 336 337 // get Block and finalize everything up to the block's parent 338 blockVertex, _ := r.forest.GetVertex(qc.BlockID) // require block to resolve parent 339 blockContainer := blockVertex.(*BlockContainer) 340 err := r.finalizeUpToBlock(blockContainer.Block.QC) // finalize Parent, i.e. the block pointed to by the block's QC 341 if err != nil { 342 return err 343 } 344 345 block := blockContainer.Block 346 if block.BlockID != qc.BlockID || block.View != qc.View { 347 return fmt.Errorf("mismatch between finalized block and QC") 348 } 349 350 // finalize block itself: 351 r.lastFinalized = &forks.BlockQC{Block: block, QC: qc} 352 err = r.forest.PruneUpToLevel(blockContainer.Block.View) 353 if err != nil { 354 return fmt.Errorf("pruning levelled forest failed: %w", err) 355 } 356 357 // notify other critical components about finalized block 358 err = r.finalizationCallback.MakeFinal(blockContainer.VertexID()) 359 if err != nil { 360 return fmt.Errorf("finalization error in other component: %w", err) 361 } 362 363 // notify less important components about finalized block 364 r.notifier.OnFinalizedBlock(blockContainer.Block) 365 return nil 366 } 367 368 // VerifyBlock checks block for validity 369 func (r *Finalizer) VerifyBlock(block *model.Block) error { 370 if block.View < r.forest.LowestLevel { 371 return nil 372 } 373 blockContainer := &BlockContainer{Block: block} 374 err := r.forest.VerifyVertex(blockContainer) 375 if err != nil { 376 return fmt.Errorf("invalid block: %w", err) 377 } 378 379 // omit checking existence of parent if block at lowest non-pruned view number 380 if (block.View == r.forest.LowestLevel) || (block.QC.View < r.forest.LowestLevel) { 381 return nil 382 } 383 // for block whose parents are _not_ below the pruning height, we expect the parent to be known. 384 if _, isParentKnown := r.forest.GetVertex(block.QC.BlockID); !isParentKnown { // we are missing the parent 385 return model.MissingBlockError{ 386 View: block.QC.View, 387 BlockID: block.QC.BlockID, 388 } 389 } 390 return nil 391 }