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 }