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