github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/ledger/confighistory/mgr.go (about) 1 /* 2 Copyright hechain. 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/hechain20/hechain/common/flogging" 15 "github.com/hechain20/hechain/common/ledger/snapshot" 16 "github.com/hechain20/hechain/core/ledger" 17 "github.com/hechain20/hechain/internal/fileutil" 18 "github.com/hyperledger/fabric-protos-go/ledger/rwset/kvrwset" 19 "github.com/hyperledger/fabric-protos-go/peer" 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 // Initialize implements function from the interface ledger.StateListener 57 func (m *Mgr) Initialize(ledgerID string, qe ledger.SimpleQueryExecutor) error { 58 // Noop 59 return nil 60 } 61 62 // InterestedInNamespaces implements function from the interface ledger.StateListener 63 func (m *Mgr) InterestedInNamespaces() []string { 64 return m.ccInfoProvider.Namespaces() 65 } 66 67 // StateCommitDone implements function from the interface ledger.StateListener 68 func (m *Mgr) StateCommitDone(ledgerID string) { 69 // Noop 70 } 71 72 // HandleStateUpdates implements function from the interface ledger.StateListener 73 // In this implementation, the latest collection config package is retrieved via 74 // ledger.DeployedChaincodeInfoProvider and is persisted as a separate entry in a separate db. 75 // The composite key for the entry is a tuple of <blockNum, namespace, key> 76 func (m *Mgr) HandleStateUpdates(trigger *ledger.StateUpdateTrigger) error { 77 updatedCCs, err := m.ccInfoProvider.UpdatedChaincodes(extractPublicUpdates(trigger.StateUpdates)) 78 if err != nil { 79 return err 80 } 81 // updated chaincodes can be empty if the invocation to this function is triggered 82 // because of state updates that contains only chaincode approval transaction output 83 if len(updatedCCs) == 0 { 84 return nil 85 } 86 updatedCollConfigs := map[string]*peer.CollectionConfigPackage{} 87 for _, cc := range updatedCCs { 88 ccInfo, err := m.ccInfoProvider.ChaincodeInfo(trigger.LedgerID, cc.Name, trigger.PostCommitQueryExecutor) 89 if err != nil { 90 return err 91 } 92 93 // DeployedChaincodeInfoProvider implementation in new lifecycle return an empty 'CollectionConfigPackage' 94 // (instead of a nil) to indicate the absence of collection config, so check for both conditions 95 if ccInfo.ExplicitCollectionConfigPkg == nil || len(ccInfo.ExplicitCollectionConfigPkg.Config) == 0 { 96 continue 97 } 98 updatedCollConfigs[ccInfo.Name] = ccInfo.ExplicitCollectionConfigPkg 99 } 100 if len(updatedCollConfigs) == 0 { 101 return nil 102 } 103 dbHandle := m.dbProvider.getDB(trigger.LedgerID) 104 batch := dbHandle.newBatch() 105 err = prepareDBBatch(batch, updatedCollConfigs, trigger.CommittingBlockNum) 106 if err != nil { 107 return err 108 } 109 return dbHandle.writeBatch(batch, true) 110 } 111 112 // ImportConfigHistory imports the collection config history associated with a given 113 // ledgerID from the snapshot files present in the dir 114 func (m *Mgr) ImportFromSnapshot(ledgerID string, dir string) error { 115 exist, _, err := fileutil.FileExists(filepath.Join(dir, snapshotDataFileName)) 116 if err != nil { 117 return err 118 } 119 if !exist { 120 // when the ledger being bootstapped never had a private data collection for 121 // any chaincode, the snapshot files associated with the confighistory store 122 // will not be present in the snapshot directory. Hence, we can return early 123 return nil 124 } 125 db := m.dbProvider.getDB(ledgerID) 126 empty, err := db.IsEmpty() 127 if err != nil { 128 return err 129 } 130 if !empty { 131 return errors.New(fmt.Sprintf( 132 "config history for ledger [%s] exists. Incremental import is not supported. "+ 133 "Remove the existing ledger data before retry", 134 ledgerID, 135 )) 136 } 137 138 configMetadata, err := snapshot.OpenFile(filepath.Join(dir, snapshotMetadataFileName), snapshotFileFormat) 139 if err != nil { 140 return err 141 } 142 numCollectionConfigs, err := configMetadata.DecodeUVarInt() 143 if err != nil { 144 return err 145 } 146 collectionConfigData, err := snapshot.OpenFile(filepath.Join(dir, snapshotDataFileName), snapshotFileFormat) 147 if err != nil { 148 return err 149 } 150 151 batch := db.NewUpdateBatch() 152 currentBatchSize := 0 153 for i := uint64(0); i < numCollectionConfigs; i++ { 154 key, err := collectionConfigData.DecodeBytes() 155 if err != nil { 156 return err 157 } 158 val, err := collectionConfigData.DecodeBytes() 159 if err != nil { 160 return err 161 } 162 batch.Put(key, val) 163 currentBatchSize += len(key) + len(val) 164 if currentBatchSize >= importConfigsBatchSize { 165 if err := db.WriteBatch(batch, true); err != nil { 166 return err 167 } 168 currentBatchSize = 0 169 batch.Reset() 170 } 171 } 172 return db.WriteBatch(batch, true) 173 } 174 175 // GetRetriever returns an implementation of `ledger.ConfigHistoryRetriever` for the given ledger id. 176 func (m *Mgr) GetRetriever(ledgerID string) *Retriever { 177 return &Retriever{ 178 ledgerID: ledgerID, 179 dbHandle: m.dbProvider.getDB(ledgerID), 180 } 181 } 182 183 // Close implements the function in the interface 'Mgr' 184 func (m *Mgr) Close() { 185 m.dbProvider.Close() 186 } 187 188 // Drop drops channel-specific data from the config history db 189 func (m *Mgr) Drop(ledgerid string) error { 190 return m.dbProvider.Drop(ledgerid) 191 } 192 193 // Retriever helps consumer retrieve collection config history 194 type Retriever struct { 195 ledgerID string 196 dbHandle *db 197 } 198 199 // MostRecentCollectionConfigBelow implements function from the interface ledger.ConfigHistoryRetriever 200 func (r *Retriever) MostRecentCollectionConfigBelow(blockNum uint64, chaincodeName string) (*ledger.CollectionConfigInfo, error) { 201 compositeKV, err := r.dbHandle.mostRecentEntryBelow(blockNum, collectionConfigNamespace, constructCollectionConfigKey(chaincodeName)) 202 if err != nil || compositeKV == nil { 203 return nil, err 204 } 205 return compositeKVToCollectionConfig(compositeKV) 206 } 207 208 // ExportConfigHistory exports configuration history from the confighistoryDB to 209 // a file. Currently, we store only one type of configuration in the db, i.e., 210 // private data collection configuration. 211 // We write the full key and value stored in the database as is to the file. 212 // Though we could decode the key and write a proto message with exact ns, key, 213 // block number, and collection config, we store the full key and value to avoid 214 // unnecessary encoding and decoding of proto messages. 215 // The key format stored in db is "s" + ns + byte(0) + key + "~collection" + byte(0) 216 // + blockNum. As we store the key as is, we store 13 extra bytes. For a million 217 // records, it would add only 12 MB overhead. Note that the protobuf also adds some 218 // extra bytes. Further, the collection config namespace is not expected to have 219 // millions of entries. 220 func (r *Retriever) ExportConfigHistory(dir string, newHashFunc snapshot.NewHashFunc) (map[string][]byte, error) { 221 nsItr, err := r.dbHandle.getNamespaceIterator(collectionConfigNamespace) 222 if err != nil { 223 return nil, err 224 } 225 defer nsItr.Release() 226 227 var numCollectionConfigs uint64 = 0 228 var dataFileWriter *snapshot.FileWriter 229 for nsItr.Next() { 230 if err := nsItr.Error(); err != nil { 231 return nil, errors.Wrap(err, "internal leveldb error while iterating for collection config history") 232 } 233 if numCollectionConfigs == 0 { // first iteration, create the data file 234 dataFileWriter, err = snapshot.CreateFile(filepath.Join(dir, snapshotDataFileName), snapshotFileFormat, newHashFunc) 235 if err != nil { 236 return nil, err 237 } 238 defer dataFileWriter.Close() 239 } 240 if err := dataFileWriter.EncodeBytes(nsItr.Key()); err != nil { 241 return nil, err 242 } 243 if err := dataFileWriter.EncodeBytes(nsItr.Value()); err != nil { 244 return nil, err 245 } 246 numCollectionConfigs++ 247 } 248 249 if dataFileWriter == nil { 250 return nil, nil 251 } 252 253 dataHash, err := dataFileWriter.Done() 254 if err != nil { 255 return nil, err 256 } 257 metadataFileWriter, err := snapshot.CreateFile(filepath.Join(dir, snapshotMetadataFileName), snapshotFileFormat, newHashFunc) 258 if err != nil { 259 return nil, err 260 } 261 defer metadataFileWriter.Close() 262 if err = metadataFileWriter.EncodeUVarint(numCollectionConfigs); err != nil { 263 return nil, err 264 } 265 metadataHash, err := metadataFileWriter.Done() 266 if err != nil { 267 return nil, err 268 } 269 270 return map[string][]byte{ 271 snapshotDataFileName: dataHash, 272 snapshotMetadataFileName: metadataHash, 273 }, nil 274 } 275 276 func prepareDBBatch(batch *batch, chaincodeCollConfigs map[string]*peer.CollectionConfigPackage, committingBlockNum uint64) error { 277 for ccName, collConfig := range chaincodeCollConfigs { 278 key := constructCollectionConfigKey(ccName) 279 var configBytes []byte 280 var err error 281 if configBytes, err = proto.Marshal(collConfig); err != nil { 282 return errors.WithStack(err) 283 } 284 batch.add(collectionConfigNamespace, key, committingBlockNum, configBytes) 285 } 286 return nil 287 } 288 289 func compositeKVToCollectionConfig(compositeKV *compositeKV) (*ledger.CollectionConfigInfo, error) { 290 conf := &peer.CollectionConfigPackage{} 291 if err := proto.Unmarshal(compositeKV.value, conf); err != nil { 292 return nil, errors.Wrap(err, "error unmarshalling compositeKV to collection config") 293 } 294 return &ledger.CollectionConfigInfo{ 295 CollectionConfig: conf, 296 CommittingBlockNum: compositeKV.blockNum, 297 }, nil 298 } 299 300 func constructCollectionConfigKey(chaincodeName string) string { 301 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 302 } 303 304 func extractPublicUpdates(stateUpdates ledger.StateUpdates) map[string][]*kvrwset.KVWrite { 305 m := map[string][]*kvrwset.KVWrite{} 306 for ns, updates := range stateUpdates { 307 m[ns] = updates.PublicUpdates 308 } 309 return m 310 }