github.com/koko1123/flow-go-1@v0.29.6/module/finalizer/collection/finalizer.go (about)

     1  package collection
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/dgraph-io/badger/v3"
     7  
     8  	"github.com/koko1123/flow-go-1/model/cluster"
     9  	"github.com/koko1123/flow-go-1/model/flow"
    10  	"github.com/koko1123/flow-go-1/model/messages"
    11  	"github.com/koko1123/flow-go-1/module"
    12  	"github.com/koko1123/flow-go-1/module/mempool"
    13  	"github.com/koko1123/flow-go-1/network"
    14  	"github.com/koko1123/flow-go-1/storage/badger/operation"
    15  	"github.com/koko1123/flow-go-1/storage/badger/procedure"
    16  )
    17  
    18  // Finalizer is a simple wrapper around our temporary state to clean up after a
    19  // block has been finalized. This involves removing the transactions within the
    20  // finalized collection from the mempool and updating the finalized boundary in
    21  // the cluster state.
    22  type Finalizer struct {
    23  	db           *badger.DB
    24  	transactions mempool.Transactions
    25  	prov         network.Engine
    26  	metrics      module.CollectionMetrics
    27  }
    28  
    29  // NewFinalizer creates a new finalizer for collection nodes.
    30  func NewFinalizer(
    31  	db *badger.DB,
    32  	transactions mempool.Transactions,
    33  	prov network.Engine,
    34  	metrics module.CollectionMetrics,
    35  ) *Finalizer {
    36  	f := &Finalizer{
    37  		db:           db,
    38  		transactions: transactions,
    39  		prov:         prov,
    40  		metrics:      metrics,
    41  	}
    42  	return f
    43  }
    44  
    45  // MakeFinal handles finalization logic for a block.
    46  //
    47  // The newly finalized block, and all un-finalized ancestors, are marked as
    48  // finalized in the cluster state. All transactions included in the collections
    49  // within the finalized blocks are removed from the mempool.
    50  //
    51  // This assumes that transactions are added to persistent state when they are
    52  // included in a block proposal. Between entering the non-finalized chain state
    53  // and being finalized, entities should be present in both the volatile memory
    54  // pools and persistent storage.
    55  func (f *Finalizer) MakeFinal(blockID flow.Identifier) error {
    56  	return operation.RetryOnConflict(f.db.Update, func(tx *badger.Txn) error {
    57  
    58  		// retrieve the header of the block we want to finalize
    59  		var header flow.Header
    60  		err := operation.RetrieveHeader(blockID, &header)(tx)
    61  		if err != nil {
    62  			return fmt.Errorf("could not retrieve header: %w", err)
    63  		}
    64  
    65  		// retrieve the current finalized cluster state boundary
    66  		var boundary uint64
    67  		err = operation.RetrieveClusterFinalizedHeight(header.ChainID, &boundary)(tx)
    68  		if err != nil {
    69  			return fmt.Errorf("could not retrieve boundary: %w", err)
    70  		}
    71  
    72  		// retrieve the ID of the last finalized block as marker for stopping
    73  		var headID flow.Identifier
    74  		err = operation.LookupClusterBlockHeight(header.ChainID, boundary, &headID)(tx)
    75  		if err != nil {
    76  			return fmt.Errorf("could not retrieve head: %w", err)
    77  		}
    78  
    79  		// there are no blocks to finalize, we may have already finalized
    80  		// this block - exit early
    81  		if boundary >= header.Height {
    82  			return nil
    83  		}
    84  
    85  		// To finalize all blocks from the currently finalized one up to and
    86  		// including the current, we first enumerate each of these blocks.
    87  		// We start at the youngest block and remember all visited blocks,
    88  		// while tracing back until we reach the finalized state
    89  		steps := []*flow.Header{&header}
    90  		parentID := header.ParentID
    91  		for parentID != headID {
    92  			var parent flow.Header
    93  			err = operation.RetrieveHeader(parentID, &parent)(tx)
    94  			if err != nil {
    95  				return fmt.Errorf("could not retrieve parent (%x): %w", parentID, err)
    96  			}
    97  			steps = append(steps, &parent)
    98  			parentID = parent.ParentID
    99  		}
   100  
   101  		// now we can step backwards in order to go from oldest to youngest; for
   102  		// each header, we reconstruct the block and then apply the related
   103  		// changes to the protocol state
   104  		for i := len(steps) - 1; i >= 0; i-- {
   105  			clusterBlockID := steps[i].ID()
   106  
   107  			// look up the transactions included in the payload
   108  			step := steps[i]
   109  			var payload cluster.Payload
   110  			err = procedure.RetrieveClusterPayload(clusterBlockID, &payload)(tx)
   111  			if err != nil {
   112  				return fmt.Errorf("could not retrieve payload for cluster block (id=%x): %w", clusterBlockID, err)
   113  			}
   114  
   115  			// remove the transactions from the memory pool
   116  			for _, colTx := range payload.Collection.Transactions {
   117  				txID := colTx.ID()
   118  				// ignore result -- we don't care whether the transaction was in the pool
   119  				_ = f.transactions.Remove(txID)
   120  			}
   121  
   122  			// finalize the block in cluster state
   123  			err = procedure.FinalizeClusterBlock(clusterBlockID)(tx)
   124  			if err != nil {
   125  				return fmt.Errorf("could not finalize cluster block (id=%x): %w", clusterBlockID, err)
   126  			}
   127  
   128  			block := &cluster.Block{
   129  				Header:  step,
   130  				Payload: &payload,
   131  			}
   132  			f.metrics.ClusterBlockFinalized(block)
   133  
   134  			// if the finalized collection is empty, we don't need to include it
   135  			// in the reference height index or submit it to consensus nodes
   136  			if len(payload.Collection.Transactions) == 0 {
   137  				continue
   138  			}
   139  
   140  			// look up the reference block height to populate index
   141  			var refBlock flow.Header
   142  			err = operation.RetrieveHeader(payload.ReferenceBlockID, &refBlock)(tx)
   143  			if err != nil {
   144  				return fmt.Errorf("could not retrieve reference block (id=%x): %w", payload.ReferenceBlockID, err)
   145  			}
   146  			// index the finalized cluster block by reference block height
   147  			err = operation.IndexClusterBlockByReferenceHeight(refBlock.Height, clusterBlockID)(tx)
   148  			if err != nil {
   149  				return fmt.Errorf("could not index cluster block (id=%x) by reference height (%d): %w", clusterBlockID, refBlock.Height, err)
   150  			}
   151  
   152  			//TODO when we incorporate HotStuff AND require BFT, the consensus
   153  			// node will need to be able ensure finalization by checking a
   154  			// 3-chain of children for this block. Probably it will be simplest
   155  			// to have a follower engine configured for the cluster chain
   156  			// running on consensus nodes, rather than pushing finalized blocks
   157  			// explicitly.
   158  			// For now, we just use the parent signers as the guarantors of this
   159  			// collection.
   160  
   161  			// TODO add real signatures here (2711)
   162  			f.prov.SubmitLocal(&messages.SubmitCollectionGuarantee{
   163  				Guarantee: flow.CollectionGuarantee{
   164  					CollectionID:     payload.Collection.ID(),
   165  					ReferenceBlockID: payload.ReferenceBlockID,
   166  					ChainID:          header.ChainID,
   167  					SignerIndices:    step.ParentVoterIndices,
   168  					Signature:        nil, // TODO: to remove because it's not easily verifiable by consensus nodes
   169  				},
   170  			})
   171  		}
   172  
   173  		return nil
   174  	})
   175  }
   176  
   177  // MakeValid marks a block as having passed HotStuff validation.
   178  func (f *Finalizer) MakeValid(blockID flow.Identifier) error {
   179  	return operation.RetryOnConflict(
   180  		f.db.Update,
   181  		operation.SkipDuplicates(
   182  			operation.InsertBlockValidity(blockID, true),
   183  		),
   184  	)
   185  }