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 }