github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/finalizer/collection/finalizer.go (about)

     1  package collection
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/dgraph-io/badger/v2"
     7  
     8  	"github.com/onflow/flow-go/model/cluster"
     9  	"github.com/onflow/flow-go/model/flow"
    10  	"github.com/onflow/flow-go/model/messages"
    11  	"github.com/onflow/flow-go/module"
    12  	"github.com/onflow/flow-go/module/mempool"
    13  	"github.com/onflow/flow-go/network"
    14  	"github.com/onflow/flow-go/storage/badger/operation"
    15  	"github.com/onflow/flow-go/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  // No errors are expected during normal operation.
    56  func (f *Finalizer) MakeFinal(blockID flow.Identifier) error {
    57  	return operation.RetryOnConflict(f.db.Update, func(tx *badger.Txn) error {
    58  
    59  		// retrieve the header of the block we want to finalize
    60  		var header flow.Header
    61  		err := operation.RetrieveHeader(blockID, &header)(tx)
    62  		if err != nil {
    63  			return fmt.Errorf("could not retrieve header: %w", err)
    64  		}
    65  
    66  		// retrieve the current finalized cluster state boundary
    67  		var boundary uint64
    68  		err = operation.RetrieveClusterFinalizedHeight(header.ChainID, &boundary)(tx)
    69  		if err != nil {
    70  			return fmt.Errorf("could not retrieve boundary: %w", err)
    71  		}
    72  
    73  		// retrieve the ID of the last finalized block as marker for stopping
    74  		var headID flow.Identifier
    75  		err = operation.LookupClusterBlockHeight(header.ChainID, boundary, &headID)(tx)
    76  		if err != nil {
    77  			return fmt.Errorf("could not retrieve head: %w", err)
    78  		}
    79  
    80  		// there are no blocks to finalize, we may have already finalized
    81  		// this block - exit early
    82  		if boundary >= header.Height {
    83  			return nil
    84  		}
    85  
    86  		// To finalize all blocks from the currently finalized one up to and
    87  		// including the current, we first enumerate each of these blocks.
    88  		// We start at the youngest block and remember all visited blocks,
    89  		// while tracing back until we reach the finalized state
    90  		steps := []*flow.Header{&header}
    91  		parentID := header.ParentID
    92  		for parentID != headID {
    93  			var parent flow.Header
    94  			err = operation.RetrieveHeader(parentID, &parent)(tx)
    95  			if err != nil {
    96  				return fmt.Errorf("could not retrieve parent (%x): %w", parentID, err)
    97  			}
    98  			steps = append(steps, &parent)
    99  			parentID = parent.ParentID
   100  		}
   101  
   102  		// now we can step backwards in order to go from oldest to youngest; for
   103  		// each header, we reconstruct the block and then apply the related
   104  		// changes to the protocol state
   105  		for i := len(steps) - 1; i >= 0; i-- {
   106  			clusterBlockID := steps[i].ID()
   107  
   108  			// look up the transactions included in the payload
   109  			step := steps[i]
   110  			var payload cluster.Payload
   111  			err = procedure.RetrieveClusterPayload(clusterBlockID, &payload)(tx)
   112  			if err != nil {
   113  				return fmt.Errorf("could not retrieve payload for cluster block (id=%x): %w", clusterBlockID, err)
   114  			}
   115  
   116  			// remove the transactions from the memory pool
   117  			for _, colTx := range payload.Collection.Transactions {
   118  				txID := colTx.ID()
   119  				// ignore result -- we don't care whether the transaction was in the pool
   120  				_ = f.transactions.Remove(txID)
   121  			}
   122  
   123  			// finalize the block in cluster state
   124  			err = procedure.FinalizeClusterBlock(clusterBlockID)(tx)
   125  			if err != nil {
   126  				return fmt.Errorf("could not finalize cluster block (id=%x): %w", clusterBlockID, err)
   127  			}
   128  
   129  			block := &cluster.Block{
   130  				Header:  step,
   131  				Payload: &payload,
   132  			}
   133  			f.metrics.ClusterBlockFinalized(block)
   134  
   135  			// if the finalized collection is empty, we don't need to include it
   136  			// in the reference height index or submit it to consensus nodes
   137  			if len(payload.Collection.Transactions) == 0 {
   138  				continue
   139  			}
   140  
   141  			// look up the reference block height to populate index
   142  			var refBlock flow.Header
   143  			err = operation.RetrieveHeader(payload.ReferenceBlockID, &refBlock)(tx)
   144  			if err != nil {
   145  				return fmt.Errorf("could not retrieve reference block (id=%x): %w", payload.ReferenceBlockID, err)
   146  			}
   147  			// index the finalized cluster block by reference block height
   148  			err = operation.IndexClusterBlockByReferenceHeight(refBlock.Height, clusterBlockID)(tx)
   149  			if err != nil {
   150  				return fmt.Errorf("could not index cluster block (id=%x) by reference height (%d): %w", clusterBlockID, refBlock.Height, err)
   151  			}
   152  
   153  			//TODO when we incorporate HotStuff AND require BFT, the consensus
   154  			// node will need to be able ensure finalization by checking a
   155  			// 3-chain of children for this block. Probably it will be simplest
   156  			// to have a follower engine configured for the cluster chain
   157  			// running on consensus nodes, rather than pushing finalized blocks
   158  			// explicitly.
   159  			// For now, we just use the parent signers as the guarantors of this
   160  			// collection.
   161  
   162  			// TODO add real signatures here (2711)
   163  			f.prov.SubmitLocal(&messages.SubmitCollectionGuarantee{
   164  				Guarantee: flow.CollectionGuarantee{
   165  					CollectionID:     payload.Collection.ID(),
   166  					ReferenceBlockID: payload.ReferenceBlockID,
   167  					ChainID:          header.ChainID,
   168  					SignerIndices:    step.ParentVoterIndices,
   169  					Signature:        nil, // TODO: to remove because it's not easily verifiable by consensus nodes
   170  				},
   171  			})
   172  		}
   173  
   174  		return nil
   175  	})
   176  }