github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/core/ledger/confighistory/mgr.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package confighistory 8 9 import ( 10 "fmt" 11 "path/filepath" 12 13 "github.com/golang/protobuf/proto" 14 "github.com/hyperledger/fabric-protos-go/common" 15 "github.com/hyperledger/fabric-protos-go/ledger/rwset/kvrwset" 16 "github.com/hyperledger/fabric-protos-go/peer" 17 "github.com/osdi23p228/fabric/common/flogging" 18 "github.com/osdi23p228/fabric/common/ledger/snapshot" 19 "github.com/osdi23p228/fabric/core/ledger" 20 "github.com/pkg/errors" 21 ) 22 23 var ( 24 logger = flogging.MustGetLogger("confighistory") 25 importConfigsBatchSize = 1024 * 1024 26 ) 27 28 const ( 29 collectionConfigNamespace = "lscc" // lscc namespace was introduced in version 1.2 and we continue to use this in order to be compatible with existing data 30 snapshotFileFormat = byte(1) 31 snapshotDataFileName = "confighistory.data" 32 snapshotMetadataFileName = "confighistory.metadata" 33 ) 34 35 // Mgr manages the history of configurations such as chaincode's collection configurations. 36 // It should be registered as a state listener. The state listener builds the history. 37 type Mgr struct { 38 ccInfoProvider ledger.DeployedChaincodeInfoProvider 39 dbProvider *dbProvider 40 } 41 42 // NewMgr constructs an instance that implements interface `Mgr` 43 func NewMgr(dbPath string, ccInfoProvider ledger.DeployedChaincodeInfoProvider) (*Mgr, error) { 44 p, err := newDBProvider(dbPath) 45 if err != nil { 46 return nil, err 47 } 48 return &Mgr{ccInfoProvider, p}, nil 49 } 50 51 // Name returns the name of the listener 52 func (m *Mgr) Name() string { 53 return "collection configuration history listener" 54 } 55 56 func (m *Mgr) Initialize(ledgerID string, qe ledger.SimpleQueryExecutor) error { 57 // Noop 58 return nil 59 } 60 61 // InterestedInNamespaces implements function from the interface ledger.StateListener 62 func (m *Mgr) InterestedInNamespaces() []string { 63 return m.ccInfoProvider.Namespaces() 64 } 65 66 // StateCommitDone implements function from the interface ledger.StateListener 67 func (m *Mgr) StateCommitDone(ledgerID string) { 68 // Noop 69 } 70 71 // HandleStateUpdates implements function from the interface ledger.StateListener 72 // In this implementation, the latest collection config package is retrieved via 73 // ledger.DeployedChaincodeInfoProvider and is persisted as a separate entry in a separate db. 74 // The composite key for the entry is a tuple of <blockNum, namespace, key> 75 func (m *Mgr) HandleStateUpdates(trigger *ledger.StateUpdateTrigger) error { 76 updatedCCs, err := m.ccInfoProvider.UpdatedChaincodes(extractPublicUpdates(trigger.StateUpdates)) 77 if err != nil { 78 return err 79 } 80 // updated chaincodes can be empty if the invocation to this function is triggered 81 // because of state updates that contains only chaincode approval transaction output 82 if len(updatedCCs) == 0 { 83 return nil 84 } 85 updatedCollConfigs := map[string]*peer.CollectionConfigPackage{} 86 for _, cc := range updatedCCs { 87 ccInfo, err := m.ccInfoProvider.ChaincodeInfo(trigger.LedgerID, cc.Name, trigger.PostCommitQueryExecutor) 88 if err != nil { 89 return err 90 } 91 92 // DeployedChaincodeInfoProvider implementation in new lifecycle return an empty 'CollectionConfigPackage' 93 // (instead of a nil) to indicate the absence of collection config, so check for both conditions 94 if ccInfo.ExplicitCollectionConfigPkg == nil || len(ccInfo.ExplicitCollectionConfigPkg.Config) == 0 { 95 continue 96 } 97 updatedCollConfigs[ccInfo.Name] = ccInfo.ExplicitCollectionConfigPkg 98 } 99 if len(updatedCollConfigs) == 0 { 100 return nil 101 } 102 dbHandle := m.dbProvider.getDB(trigger.LedgerID) 103 batch := dbHandle.newBatch() 104 err = prepareDBBatch(batch, updatedCollConfigs, trigger.CommittingBlockNum) 105 if err != nil { 106 return err 107 } 108 return dbHandle.writeBatch(batch, true) 109 } 110 111 func (m *Mgr) ImportConfigHistory(ledgerID string, dir string) error { 112 db := m.dbProvider.getDB(ledgerID) 113 empty, err := db.isEmpty() 114 if err != nil { 115 return err 116 } 117 if !empty { 118 return errors.New(fmt.Sprintf( 119 "config history for ledger [%s] exists. Incremental import is not supported. "+ 120 "Remove the existing ledger data before retry", 121 ledgerID, 122 )) 123 } 124 125 configMetadata, err := snapshot.OpenFile(filepath.Join(dir, snapshotMetadataFileName), snapshotFileFormat) 126 if err != nil { 127 return err 128 } 129 numCollectionConfigs, err := configMetadata.DecodeUVarInt() 130 if err != nil { 131 return err 132 } 133 collectionConfigData, err := snapshot.OpenFile(filepath.Join(dir, snapshotDataFileName), snapshotFileFormat) 134 if err != nil { 135 return err 136 } 137 138 batch := db.NewUpdateBatch() 139 currentBatchSize := 0 140 for i := uint64(0); i < numCollectionConfigs; i++ { 141 key, err := collectionConfigData.DecodeBytes() 142 if err != nil { 143 return err 144 } 145 val, err := collectionConfigData.DecodeBytes() 146 if err != nil { 147 return err 148 } 149 batch.Put(key, val) 150 currentBatchSize += len(key) + len(val) 151 if currentBatchSize >= importConfigsBatchSize { 152 if err := db.WriteBatch(batch, true); err != nil { 153 return err 154 } 155 batch = db.NewUpdateBatch() 156 } 157 } 158 return db.WriteBatch(batch, true) 159 } 160 161 // GetRetriever returns an implementation of `ledger.ConfigHistoryRetriever` for the given ledger id. 162 func (m *Mgr) GetRetriever(ledgerID string, ledgerInfoRetriever LedgerInfoRetriever) *Retriever { 163 return &Retriever{ 164 ledgerInfoRetriever: ledgerInfoRetriever, 165 ledgerID: ledgerID, 166 deployedCCInfoProvider: m.ccInfoProvider, 167 dbHandle: m.dbProvider.getDB(ledgerID), 168 } 169 } 170 171 // Close implements the function in the interface 'Mgr' 172 func (m *Mgr) Close() { 173 m.dbProvider.Close() 174 } 175 176 type Retriever struct { 177 ledgerInfoRetriever LedgerInfoRetriever 178 ledgerID string 179 deployedCCInfoProvider ledger.DeployedChaincodeInfoProvider 180 dbHandle *db 181 } 182 183 // MostRecentCollectionConfigBelow implements function from the interface ledger.ConfigHistoryRetriever 184 func (r *Retriever) MostRecentCollectionConfigBelow(blockNum uint64, chaincodeName string) (*ledger.CollectionConfigInfo, error) { 185 compositeKV, err := r.dbHandle.mostRecentEntryBelow(blockNum, collectionConfigNamespace, constructCollectionConfigKey(chaincodeName)) 186 if err != nil { 187 return nil, err 188 } 189 implicitColls, err := r.getImplicitCollection(chaincodeName) 190 if err != nil { 191 return nil, err 192 } 193 194 return constructCollectionConfigInfo(compositeKV, implicitColls) 195 } 196 197 // CollectionConfigAt implements function from the interface ledger.ConfigHistoryRetriever 198 func (r *Retriever) CollectionConfigAt(blockNum uint64, chaincodeName string) (*ledger.CollectionConfigInfo, error) { 199 info, err := r.ledgerInfoRetriever.GetBlockchainInfo() 200 if err != nil { 201 return nil, err 202 } 203 maxCommittedBlockNum := info.Height - 1 204 if maxCommittedBlockNum < blockNum { 205 return nil, &ledger.ErrCollectionConfigNotYetAvailable{MaxBlockNumCommitted: maxCommittedBlockNum, 206 Msg: fmt.Sprintf("The maximum block number committed [%d] is less than the requested block number [%d]", maxCommittedBlockNum, blockNum)} 207 } 208 209 compositeKV, err := r.dbHandle.entryAt(blockNum, collectionConfigNamespace, constructCollectionConfigKey(chaincodeName)) 210 if err != nil { 211 return nil, err 212 } 213 implicitColls, err := r.getImplicitCollection(chaincodeName) 214 if err != nil { 215 return nil, err 216 } 217 return constructCollectionConfigInfo(compositeKV, implicitColls) 218 } 219 220 // ExportConfigHistory exports configuration history from the confighistoryDB to 221 // a file. Currently, we store only one type of configuration in the db, i.e., 222 // private data collection configuration. 223 // We write the full key and value stored in the database as is to the file. 224 // Though we could decode the key and write a proto message with exact ns, key, 225 // block number, and collection config, we store the full key and value to avoid 226 // unnecessary encoding and decoding of proto messages. 227 // The key format stored in db is "s" + ns + byte(0) + key + "~collection" + byte(0) 228 // + blockNum. As we store the key as is, we store 13 extra bytes. For a million 229 // records, it would add only 12 MB overhead. Note that the protobuf also adds some 230 // extra bytes. Further, the collection config namespace is not expected to have 231 // millions of entries. 232 func (r *Retriever) ExportConfigHistory(dir string, newHashFunc snapshot.NewHashFunc) (map[string][]byte, error) { 233 nsItr, err := r.dbHandle.getNamespaceIterator(collectionConfigNamespace) 234 if err != nil { 235 return nil, err 236 } 237 defer nsItr.Release() 238 239 var numCollectionConfigs uint64 = 0 240 var dataFileWriter *snapshot.FileWriter 241 for nsItr.Next() { 242 if err := nsItr.Error(); err != nil { 243 return nil, errors.Wrap(err, "internal leveldb error while iterating for collection config history") 244 } 245 if numCollectionConfigs == 0 { // first iteration, create the data file 246 dataFileWriter, err = snapshot.CreateFile(filepath.Join(dir, snapshotDataFileName), snapshotFileFormat, newHashFunc) 247 if err != nil { 248 return nil, err 249 } 250 defer dataFileWriter.Close() 251 } 252 if err := dataFileWriter.EncodeBytes(nsItr.Key()); err != nil { 253 return nil, err 254 } 255 if err := dataFileWriter.EncodeBytes(nsItr.Value()); err != nil { 256 return nil, err 257 } 258 numCollectionConfigs++ 259 } 260 261 if dataFileWriter == nil { 262 return nil, nil 263 } 264 265 dataHash, err := dataFileWriter.Done() 266 if err != nil { 267 return nil, err 268 } 269 metadataFileWriter, err := snapshot.CreateFile(filepath.Join(dir, snapshotMetadataFileName), snapshotFileFormat, newHashFunc) 270 if err != nil { 271 return nil, err 272 } 273 defer metadataFileWriter.Close() 274 if err = metadataFileWriter.EncodeUVarint(numCollectionConfigs); err != nil { 275 return nil, err 276 } 277 metadataHash, err := metadataFileWriter.Done() 278 if err != nil { 279 return nil, err 280 } 281 282 return map[string][]byte{ 283 snapshotDataFileName: dataHash, 284 snapshotMetadataFileName: metadataHash, 285 }, nil 286 } 287 288 func (r *Retriever) getImplicitCollection(chaincodeName string) ([]*peer.StaticCollectionConfig, error) { 289 qe, err := r.ledgerInfoRetriever.NewQueryExecutor() 290 if err != nil { 291 return nil, err 292 } 293 defer qe.Done() 294 return r.deployedCCInfoProvider.ImplicitCollections(r.ledgerID, chaincodeName, qe) 295 } 296 297 func prepareDBBatch(batch *batch, chaincodeCollConfigs map[string]*peer.CollectionConfigPackage, committingBlockNum uint64) error { 298 for ccName, collConfig := range chaincodeCollConfigs { 299 key := constructCollectionConfigKey(ccName) 300 var configBytes []byte 301 var err error 302 if configBytes, err = proto.Marshal(collConfig); err != nil { 303 return errors.WithStack(err) 304 } 305 batch.add(collectionConfigNamespace, key, committingBlockNum, configBytes) 306 } 307 return nil 308 } 309 310 func compositeKVToCollectionConfig(compositeKV *compositeKV) (*ledger.CollectionConfigInfo, error) { 311 conf := &peer.CollectionConfigPackage{} 312 if err := proto.Unmarshal(compositeKV.value, conf); err != nil { 313 return nil, errors.Wrap(err, "error unmarshalling compositeKV to collection config") 314 } 315 return &ledger.CollectionConfigInfo{ 316 CollectionConfig: conf, 317 CommittingBlockNum: compositeKV.blockNum, 318 }, nil 319 } 320 321 func constructCollectionConfigKey(chaincodeName string) string { 322 return chaincodeName + "~collection" // collection config key as in version 1.2 and we continue to use this in order to be compatible with existing data 323 } 324 325 func extractPublicUpdates(stateUpdates ledger.StateUpdates) map[string][]*kvrwset.KVWrite { 326 m := map[string][]*kvrwset.KVWrite{} 327 for ns, updates := range stateUpdates { 328 m[ns] = updates.PublicUpdates 329 } 330 return m 331 } 332 333 func constructCollectionConfigInfo( 334 compositeKV *compositeKV, 335 implicitColls []*peer.StaticCollectionConfig, 336 ) (*ledger.CollectionConfigInfo, error) { 337 var collConf *ledger.CollectionConfigInfo 338 var err error 339 340 if compositeKV == nil && len(implicitColls) == 0 { 341 return nil, nil 342 } 343 344 collConf = &ledger.CollectionConfigInfo{ 345 CollectionConfig: &peer.CollectionConfigPackage{}, 346 } 347 if compositeKV != nil { 348 if collConf, err = compositeKVToCollectionConfig(compositeKV); err != nil { 349 return nil, err 350 } 351 } 352 353 for _, implicitColl := range implicitColls { 354 cc := &peer.CollectionConfig{} 355 cc.Payload = &peer.CollectionConfig_StaticCollectionConfig{StaticCollectionConfig: implicitColl} 356 collConf.CollectionConfig.Config = append( 357 collConf.CollectionConfig.Config, 358 cc, 359 ) 360 } 361 return collConf, nil 362 } 363 364 // LedgerInfoRetriever retrieves the relevant info from ledger 365 type LedgerInfoRetriever interface { 366 GetBlockchainInfo() (*common.BlockchainInfo, error) 367 NewQueryExecutor() (ledger.QueryExecutor, error) 368 }