github.com/onflow/flow-go@v0.33.17/module/finalizer/consensus/finalizer.go (about)

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