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

     1  package consensus
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/dgraph-io/badger/v2"
     8  
     9  	"github.com/onflow/flow-go/model/flow"
    10  	"github.com/onflow/flow-go/module"
    11  	"github.com/onflow/flow-go/module/trace"
    12  	"github.com/onflow/flow-go/state/protocol"
    13  	"github.com/onflow/flow-go/storage"
    14  	"github.com/onflow/flow-go/storage/badger/operation"
    15  )
    16  
    17  // Finalizer is a simple wrapper around our temporary state to clean up after a
    18  // block has been fully finalized to the persistent protocol state.
    19  type Finalizer struct {
    20  	db      *badger.DB
    21  	headers storage.Headers
    22  	state   protocol.FollowerState
    23  	cleanup CleanupFunc
    24  	tracer  module.Tracer
    25  }
    26  
    27  // NewFinalizer creates a new finalizer for the temporary state.
    28  func NewFinalizer(db *badger.DB,
    29  	headers storage.Headers,
    30  	state protocol.FollowerState,
    31  	tracer module.Tracer,
    32  	options ...func(*Finalizer)) *Finalizer {
    33  	f := &Finalizer{
    34  		db:      db,
    35  		state:   state,
    36  		headers: headers,
    37  		cleanup: CleanupNothing(),
    38  		tracer:  tracer,
    39  	}
    40  	for _, option := range options {
    41  		option(f)
    42  	}
    43  	return f
    44  }
    45  
    46  // MakeFinal will finalize the block with the given ID and clean up the memory
    47  // pools after it.
    48  //
    49  // This assumes that guarantees and seals are already in persistent state when
    50  // included in a block proposal. Between entering the non-finalized chain state
    51  // and being finalized, entities should be present in both the volatile memory
    52  // pools and persistent storage.
    53  // No errors are expected during normal operation.
    54  func (f *Finalizer) MakeFinal(blockID flow.Identifier) error {
    55  
    56  	span, ctx := f.tracer.StartBlockSpan(context.Background(), blockID, trace.CONFinalizerFinalizeBlock)
    57  	defer span.End()
    58  
    59  	// STEP ONE: This is an idempotent operation. In case we are trying to
    60  	// finalize a block that is already below finalized height, we want to do
    61  	// one of two things: if it conflicts with the block already finalized at
    62  	// that height, it's an invalid operation. Otherwise, it is a no-op.
    63  
    64  	var finalized uint64
    65  	err := f.db.View(operation.RetrieveFinalizedHeight(&finalized))
    66  	if err != nil {
    67  		return fmt.Errorf("could not retrieve finalized height: %w", err)
    68  	}
    69  
    70  	pending, err := f.headers.ByBlockID(blockID)
    71  	if err != nil {
    72  		return fmt.Errorf("could not retrieve pending header: %w", err)
    73  	}
    74  
    75  	if pending.Height <= finalized {
    76  		dupID, err := f.headers.BlockIDByHeight(pending.Height)
    77  		if err != nil {
    78  			return fmt.Errorf("could not retrieve finalized equivalent: %w", err)
    79  		}
    80  		if dupID != blockID {
    81  			return fmt.Errorf("cannot finalize pending block conflicting with finalized state (height: %d, pending: %x, finalized: %x)", pending.Height, blockID, dupID)
    82  		}
    83  		return nil
    84  	}
    85  
    86  	// STEP TWO: At least one block in the chain back to the finalized state is
    87  	// a valid candidate for finalization. Figure out all blocks between the
    88  	// to-be-finalized block and the last finalized block. If we can't trace
    89  	// back to the last finalized block, this is also an invalid call.
    90  
    91  	var finalID flow.Identifier
    92  	err = f.db.View(operation.LookupBlockHeight(finalized, &finalID))
    93  	if err != nil {
    94  		return fmt.Errorf("could not retrieve finalized header: %w", err)
    95  	}
    96  	pendingIDs := []flow.Identifier{blockID}
    97  	ancestorID := pending.ParentID
    98  	for ancestorID != finalID {
    99  		ancestor, err := f.headers.ByBlockID(ancestorID)
   100  		if err != nil {
   101  			return fmt.Errorf("could not retrieve parent (%x): %w", ancestorID, err)
   102  		}
   103  		if ancestor.Height < finalized {
   104  			return fmt.Errorf("cannot finalize pending block unconnected to last finalized block (height: %d, finalized: %d)", ancestor.Height, finalized)
   105  		}
   106  		pendingIDs = append(pendingIDs, ancestorID)
   107  		ancestorID = ancestor.ParentID
   108  	}
   109  
   110  	// STEP THREE: We walk backwards through the collected ancestors, starting
   111  	// with the first block after finalizing state, and finalize them one by
   112  	// one in the protocol state.
   113  
   114  	for i := len(pendingIDs) - 1; i >= 0; i-- {
   115  		pendingID := pendingIDs[i]
   116  		err = f.state.Finalize(ctx, pendingID)
   117  		if err != nil {
   118  			return fmt.Errorf("could not finalize block (%x): %w", pendingID, err)
   119  		}
   120  		err := f.cleanup(pendingID)
   121  		if err != nil {
   122  			return fmt.Errorf("could not execute cleanup (%x): %w", pendingID, err)
   123  		}
   124  	}
   125  
   126  	return nil
   127  }