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  }