github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/badger/procedure/cluster.go (about) 1 package procedure 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/storage/badger/operation" 11 ) 12 13 // This file implements storage functions for blocks in cluster consensus. 14 15 // InsertClusterBlock inserts a cluster consensus block, updating all 16 // associated indexes. 17 func InsertClusterBlock(block *cluster.Block) func(*badger.Txn) error { 18 return func(tx *badger.Txn) error { 19 20 // check payload integrity 21 if block.Header.PayloadHash != block.Payload.Hash() { 22 return fmt.Errorf("computed payload hash does not match header") 23 } 24 25 // store the block header 26 blockID := block.ID() 27 err := operation.InsertHeader(blockID, block.Header)(tx) 28 if err != nil { 29 return fmt.Errorf("could not insert header: %w", err) 30 } 31 32 // insert the block payload 33 err = InsertClusterPayload(blockID, block.Payload)(tx) 34 if err != nil { 35 return fmt.Errorf("could not insert payload: %w", err) 36 } 37 38 // index the child block for recovery 39 err = IndexNewBlock(blockID, block.Header.ParentID)(tx) 40 if err != nil { 41 return fmt.Errorf("could not index new block: %w", err) 42 } 43 return nil 44 } 45 } 46 47 // RetrieveClusterBlock retrieves a cluster consensus block by block ID. 48 func RetrieveClusterBlock(blockID flow.Identifier, block *cluster.Block) func(*badger.Txn) error { 49 return func(tx *badger.Txn) error { 50 51 // retrieve the block header 52 var header flow.Header 53 err := operation.RetrieveHeader(blockID, &header)(tx) 54 if err != nil { 55 return fmt.Errorf("could not retrieve header: %w", err) 56 } 57 58 // retrieve payload 59 var payload cluster.Payload 60 err = RetrieveClusterPayload(blockID, &payload)(tx) 61 if err != nil { 62 return fmt.Errorf("could not retrieve payload: %w", err) 63 } 64 65 // overwrite block 66 *block = cluster.Block{ 67 Header: &header, 68 Payload: &payload, 69 } 70 71 return nil 72 } 73 } 74 75 // RetrieveLatestFinalizedClusterHeader retrieves the latest finalized for the 76 // given cluster chain ID. 77 func RetrieveLatestFinalizedClusterHeader(chainID flow.ChainID, final *flow.Header) func(tx *badger.Txn) error { 78 return func(tx *badger.Txn) error { 79 var boundary uint64 80 err := operation.RetrieveClusterFinalizedHeight(chainID, &boundary)(tx) 81 if err != nil { 82 return fmt.Errorf("could not retrieve boundary: %w", err) 83 } 84 85 var finalID flow.Identifier 86 err = operation.LookupClusterBlockHeight(chainID, boundary, &finalID)(tx) 87 if err != nil { 88 return fmt.Errorf("could not retrieve final ID: %w", err) 89 } 90 91 err = operation.RetrieveHeader(finalID, final)(tx) 92 if err != nil { 93 return fmt.Errorf("could not retrieve finalized header: %w", err) 94 } 95 96 return nil 97 } 98 } 99 100 // FinalizeClusterBlock finalizes a block in cluster consensus. 101 func FinalizeClusterBlock(blockID flow.Identifier) func(*badger.Txn) error { 102 return func(tx *badger.Txn) error { 103 104 // retrieve the header to check the parent 105 var header flow.Header 106 err := operation.RetrieveHeader(blockID, &header)(tx) 107 if err != nil { 108 return fmt.Errorf("could not retrieve header: %w", err) 109 } 110 111 // get the chain ID, which determines which cluster state to query 112 chainID := header.ChainID 113 114 // retrieve the current finalized state boundary 115 var boundary uint64 116 err = operation.RetrieveClusterFinalizedHeight(chainID, &boundary)(tx) 117 if err != nil { 118 return fmt.Errorf("could not retrieve boundary: %w", err) 119 } 120 121 // retrieve the ID of the boundary head 122 var headID flow.Identifier 123 err = operation.LookupClusterBlockHeight(chainID, boundary, &headID)(tx) 124 if err != nil { 125 return fmt.Errorf("could not retrieve head: %w", err) 126 } 127 128 // check that the head ID is the parent of the block we finalize 129 if header.ParentID != headID { 130 return fmt.Errorf("can't finalize non-child of chain head") 131 } 132 133 // insert block view -> ID mapping 134 err = operation.IndexClusterBlockHeight(chainID, header.Height, header.ID())(tx) 135 if err != nil { 136 return fmt.Errorf("could not insert view->ID mapping: %w", err) 137 } 138 139 // update the finalized boundary 140 err = operation.UpdateClusterFinalizedHeight(chainID, header.Height)(tx) 141 if err != nil { 142 return fmt.Errorf("could not update finalized boundary: %w", err) 143 } 144 145 // NOTE: we don't want to prune forks that have become invalid here, so 146 // that we can keep validating entities and generating slashing 147 // challenges for some time - the pruning should happen some place else 148 // after a certain delay of blocks 149 150 return nil 151 } 152 } 153 154 // InsertClusterPayload inserts the payload for a cluster block. It inserts 155 // both the collection and all constituent transactions, allowing duplicates. 156 func InsertClusterPayload(blockID flow.Identifier, payload *cluster.Payload) func(*badger.Txn) error { 157 return func(tx *badger.Txn) error { 158 159 // cluster payloads only contain a single collection, allow duplicates, 160 // because it is valid for two competing forks to have the same payload. 161 light := payload.Collection.Light() 162 err := operation.SkipDuplicates(operation.InsertCollection(&light))(tx) 163 if err != nil { 164 return fmt.Errorf("could not insert payload collection: %w", err) 165 } 166 167 // insert constituent transactions 168 for _, colTx := range payload.Collection.Transactions { 169 err = operation.SkipDuplicates(operation.InsertTransaction(colTx.ID(), colTx))(tx) 170 if err != nil { 171 return fmt.Errorf("could not insert payload transaction: %w", err) 172 } 173 } 174 175 // index the transaction IDs within the collection 176 txIDs := payload.Collection.Light().Transactions 177 err = operation.SkipDuplicates(operation.IndexCollectionPayload(blockID, txIDs))(tx) 178 if err != nil { 179 return fmt.Errorf("could not index collection: %w", err) 180 } 181 182 // insert the reference block ID 183 err = operation.IndexReferenceBlockByClusterBlock(blockID, payload.ReferenceBlockID)(tx) 184 if err != nil { 185 return fmt.Errorf("could not insert reference block ID: %w", err) 186 } 187 188 return nil 189 } 190 } 191 192 // RetrieveClusterPayload retrieves a cluster consensus block payload by block ID. 193 func RetrieveClusterPayload(blockID flow.Identifier, payload *cluster.Payload) func(*badger.Txn) error { 194 return func(tx *badger.Txn) error { 195 196 // lookup the reference block ID 197 var refID flow.Identifier 198 err := operation.LookupReferenceBlockByClusterBlock(blockID, &refID)(tx) 199 if err != nil { 200 return fmt.Errorf("could not retrieve reference block ID: %w", err) 201 } 202 203 // lookup collection transaction IDs 204 var txIDs []flow.Identifier 205 err = operation.LookupCollectionPayload(blockID, &txIDs)(tx) 206 if err != nil { 207 return fmt.Errorf("could not look up collection payload: %w", err) 208 } 209 210 colTransactions := make([]*flow.TransactionBody, 0, len(txIDs)) 211 // retrieve individual transactions 212 for _, txID := range txIDs { 213 var nextTx flow.TransactionBody 214 err = operation.RetrieveTransaction(txID, &nextTx)(tx) 215 if err != nil { 216 return fmt.Errorf("could not retrieve transaction: %w", err) 217 } 218 colTransactions = append(colTransactions, &nextTx) 219 } 220 221 *payload = cluster.PayloadFromTransactions(refID, colTransactions...) 222 223 return nil 224 } 225 }