github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/ledger/kvledger/snapshot.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package kvledger 8 9 import ( 10 "bufio" 11 "encoding/hex" 12 "encoding/json" 13 "fmt" 14 "hash" 15 "io" 16 "io/ioutil" 17 "math" 18 "os" 19 "path/filepath" 20 21 "github.com/hechain20/hechain/common/ledger/blkstorage" 22 "github.com/hechain20/hechain/core/chaincode/implicitcollection" 23 "github.com/hechain20/hechain/core/ledger" 24 "github.com/hechain20/hechain/core/ledger/confighistory" 25 "github.com/hechain20/hechain/core/ledger/internal/version" 26 "github.com/hechain20/hechain/core/ledger/kvledger/msgs" 27 "github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/pvtstatepurgemgmt" 28 "github.com/hechain20/hechain/core/ledger/pvtdatapolicy" 29 "github.com/hechain20/hechain/internal/fileutil" 30 "github.com/hyperledger/fabric-protos-go/peer" 31 "github.com/pkg/errors" 32 ) 33 34 const ( 35 SnapshotSignableMetadataFileName = "_snapshot_signable_metadata.json" 36 snapshotAdditionalMetadataFileName = "_snapshot_additional_metadata.json" 37 jsonFileIndent = " " 38 simpleKeyValueDB = "SimpleKeyValueDB" 39 ) 40 41 // SnapshotSignableMetadata is used to build a JSON that represents a unique snapshot and 42 // can be signed by the peer. Hashsum of the resultant JSON is intended to be used as a single 43 // hash of the snapshot, if need be. 44 type SnapshotSignableMetadata struct { 45 ChannelName string `json:"channel_name"` 46 LastBlockNumber uint64 `json:"last_block_number"` 47 LastBlockHashInHex string `json:"last_block_hash"` 48 PreviousBlockHashInHex string `json:"previous_block_hash"` 49 FilesAndHashes map[string]string `json:"snapshot_files_raw_hashes"` 50 StateDBType string `json:"state_db_type"` 51 } 52 53 func (m *SnapshotSignableMetadata) ToJSON() ([]byte, error) { 54 return json.MarshalIndent(m, "", jsonFileIndent) 55 } 56 57 type snapshotAdditionalMetadata struct { 58 SnapshotHashInHex string `json:"snapshot_hash"` 59 LastBlockCommitHashInHex string `json:"last_block_commit_hash"` 60 } 61 62 func (m *snapshotAdditionalMetadata) ToJSON() ([]byte, error) { 63 return json.MarshalIndent(m, "", jsonFileIndent) 64 } 65 66 type SnapshotMetadata struct { 67 *SnapshotSignableMetadata 68 *snapshotAdditionalMetadata 69 } 70 71 type SnapshotMetadataJSONs struct { 72 signableMetadata string 73 additionalMetadata string 74 } 75 76 func (j *SnapshotMetadataJSONs) ToMetadata() (*SnapshotMetadata, error) { 77 metadata := &SnapshotSignableMetadata{} 78 if err := json.Unmarshal([]byte(j.signableMetadata), metadata); err != nil { 79 return nil, errors.Wrap(err, "error while unmarshalling signable metadata") 80 } 81 82 additionalMetadata := &snapshotAdditionalMetadata{} 83 if err := json.Unmarshal([]byte(j.additionalMetadata), additionalMetadata); err != nil { 84 return nil, errors.Wrap(err, "error while unmarshalling additional metadata") 85 } 86 return &SnapshotMetadata{ 87 SnapshotSignableMetadata: metadata, 88 snapshotAdditionalMetadata: additionalMetadata, 89 }, nil 90 } 91 92 // generateSnapshot generates a snapshot. This function should be invoked when commit on the kvledger are paused 93 // after committing the last block fully and further the commits should not be resumed till this function finishes 94 func (l *kvLedger) generateSnapshot() error { 95 snapshotsRootDir := l.config.SnapshotsConfig.RootDir 96 bcInfo, err := l.GetBlockchainInfo() 97 if err != nil { 98 return err 99 } 100 lastBlockNum := bcInfo.Height - 1 101 snapshotTempDir, err := ioutil.TempDir( 102 SnapshotsTempDirPath(snapshotsRootDir), 103 fmt.Sprintf("%s-%d-", l.ledgerID, lastBlockNum), 104 ) 105 if err != nil { 106 return errors.Wrapf(err, "error while creating temp dir [%s]", snapshotTempDir) 107 } 108 defer os.RemoveAll(snapshotTempDir) 109 110 newHashFunc := func() (hash.Hash, error) { 111 return l.hashProvider.GetHash(snapshotHashOpts) 112 } 113 114 txIDsExportSummary, err := l.blockStore.ExportTxIds(snapshotTempDir, newHashFunc) 115 if err != nil { 116 return err 117 } 118 logger.Debugw("Exported TxIDs from blockstore", "channelID", l.ledgerID) 119 120 configsHistoryExportSummary, err := l.configHistoryRetriever.ExportConfigHistory(snapshotTempDir, newHashFunc) 121 if err != nil { 122 return err 123 } 124 logger.Debugw("Exported collection config history", "channelID", l.ledgerID) 125 126 stateDBExportSummary, err := l.txmgr.ExportPubStateAndPvtStateHashes(snapshotTempDir, newHashFunc) 127 if err != nil { 128 return err 129 } 130 logger.Debugw("Exported public state and private state hashes", "channelID", l.ledgerID) 131 132 if err := l.generateSnapshotMetadataFiles( 133 snapshotTempDir, txIDsExportSummary, 134 configsHistoryExportSummary, stateDBExportSummary, 135 ); err != nil { 136 return err 137 } 138 logger.Debugw("Generated metadata files", "channelID", l.ledgerID) 139 140 if err := fileutil.SyncDir(snapshotTempDir); err != nil { 141 return err 142 } 143 slgr := SnapshotsDirForLedger(snapshotsRootDir, l.ledgerID) 144 if err := os.MkdirAll(slgr, 0o755); err != nil { 145 return errors.Wrapf(err, "error while creating final dir for snapshot:%s", slgr) 146 } 147 if err := fileutil.SyncParentDir(slgr); err != nil { 148 return err 149 } 150 slgrht := SnapshotDirForLedgerBlockNum(snapshotsRootDir, l.ledgerID, lastBlockNum) 151 if err := os.Rename(snapshotTempDir, slgrht); err != nil { 152 return errors.Wrapf(err, "error while renaming dir [%s] to [%s]:", snapshotTempDir, slgrht) 153 } 154 return fileutil.SyncParentDir(slgrht) 155 } 156 157 func (l *kvLedger) generateSnapshotMetadataFiles( 158 dir string, 159 txIDsExportSummary, 160 configsHistoryExportSummary, 161 stateDBExportSummary map[string][]byte) error { 162 // generate metadata file 163 filesAndHashes := map[string]string{} 164 for fileName, hashsum := range txIDsExportSummary { 165 filesAndHashes[fileName] = hex.EncodeToString(hashsum) 166 } 167 for fileName, hashsum := range configsHistoryExportSummary { 168 filesAndHashes[fileName] = hex.EncodeToString(hashsum) 169 } 170 for fileName, hashsum := range stateDBExportSummary { 171 filesAndHashes[fileName] = hex.EncodeToString(hashsum) 172 } 173 bcInfo, err := l.GetBlockchainInfo() 174 if err != nil { 175 return err 176 } 177 178 stateDBType := l.config.StateDBConfig.StateDatabase 179 if stateDBType != ledger.CouchDB { 180 stateDBType = simpleKeyValueDB 181 } 182 signableMetadata := &SnapshotSignableMetadata{ 183 ChannelName: l.ledgerID, 184 LastBlockNumber: bcInfo.Height - 1, 185 LastBlockHashInHex: hex.EncodeToString(bcInfo.CurrentBlockHash), 186 PreviousBlockHashInHex: hex.EncodeToString(bcInfo.PreviousBlockHash), 187 FilesAndHashes: filesAndHashes, 188 StateDBType: stateDBType, 189 } 190 191 signableMetadataBytes, err := signableMetadata.ToJSON() 192 if err != nil { 193 return errors.Wrap(err, "error while marshelling snapshot metadata to JSON") 194 } 195 if err := fileutil.CreateAndSyncFile(filepath.Join(dir, SnapshotSignableMetadataFileName), signableMetadataBytes, 0o444); err != nil { 196 return err 197 } 198 199 // generate metadata hash file 200 hash, err := l.hashProvider.GetHash(snapshotHashOpts) 201 if err != nil { 202 return err 203 } 204 if _, err := hash.Write(signableMetadataBytes); err != nil { 205 return err 206 } 207 208 additionalMetadata := &snapshotAdditionalMetadata{ 209 SnapshotHashInHex: hex.EncodeToString(hash.Sum(nil)), 210 LastBlockCommitHashInHex: hex.EncodeToString(l.commitHash), 211 } 212 213 additionalMetadataBytes, err := additionalMetadata.ToJSON() 214 if err != nil { 215 return errors.Wrap(err, "error while marshalling snapshot additional metadata to JSON") 216 } 217 return fileutil.CreateAndSyncFile(filepath.Join(dir, snapshotAdditionalMetadataFileName), additionalMetadataBytes, 0o444) 218 } 219 220 // CreateFromSnapshot implements the corresponding method from interface ledger.PeerLedgerProvider 221 // This function creates a new ledger from the supplied snapshot. If a failure happens during this 222 // process, the partially created ledger is deleted 223 func (p *Provider) CreateFromSnapshot(snapshotDir string) (ledger.PeerLedger, string, error) { 224 metadataJSONs, err := loadSnapshotMetadataJSONs(snapshotDir) 225 if err != nil { 226 return nil, "", errors.WithMessagef(err, "error while loading metadata") 227 } 228 229 metadata, err := metadataJSONs.ToMetadata() 230 if err != nil { 231 return nil, "", errors.WithMessagef(err, "error while unmarshalling metadata") 232 } 233 234 if err := verifySnapshot(snapshotDir, metadata, p.initializer.HashProvider); err != nil { 235 return nil, "", errors.WithMessagef(err, "error while verifying snapshot") 236 } 237 238 ledgerID := metadata.ChannelName 239 lastBlockNum := metadata.LastBlockNumber 240 logger.Debugw("Verified hashes", "snapshotDir", snapshotDir, "ledgerID", ledgerID) 241 242 lastBlkHash, err := hex.DecodeString(metadata.LastBlockHashInHex) 243 if err != nil { 244 return nil, "", errors.Wrapf(err, "error while decoding last block hash") 245 } 246 previousBlkHash, err := hex.DecodeString(metadata.PreviousBlockHashInHex) 247 if err != nil { 248 return nil, "", errors.Wrapf(err, "error while decoding previous block hash") 249 } 250 251 snapshotInfo := &blkstorage.SnapshotInfo{ 252 LastBlockNum: lastBlockNum, 253 LastBlockHash: lastBlkHash, 254 PreviousBlockHash: previousBlkHash, 255 } 256 257 if err = p.idStore.createLedgerID( 258 ledgerID, 259 &msgs.LedgerMetadata{ 260 Status: msgs.Status_UNDER_CONSTRUCTION, 261 BootSnapshotMetadata: &msgs.BootSnapshotMetadata{ 262 SingableMetadata: metadataJSONs.signableMetadata, 263 AdditionalMetadata: metadataJSONs.additionalMetadata, 264 }, 265 }, 266 ); err != nil { 267 return nil, "", errors.WithMessagef(err, "error while creating ledger id") 268 } 269 270 savepoint := version.NewHeight(lastBlockNum, math.MaxUint64) 271 272 if err = p.blkStoreProvider.ImportFromSnapshot(ledgerID, snapshotDir, snapshotInfo); err != nil { 273 return nil, "", p.deleteUnderConstructionLedger( 274 nil, 275 ledgerID, 276 errors.WithMessage(err, "error while importing data into block store"), 277 ) 278 } 279 logger.Debugw("Imported data into blockstore", "ledgerID", ledgerID) 280 281 if err = p.configHistoryMgr.ImportFromSnapshot(metadata.ChannelName, snapshotDir); err != nil { 282 return nil, "", p.deleteUnderConstructionLedger( 283 nil, 284 ledgerID, 285 errors.WithMessage(err, "error while importing data into config history Mgr"), 286 ) 287 } 288 logger.Debugw("Imported data into collection config history", "ledgerID", ledgerID) 289 290 configHistoryRetiever := p.configHistoryMgr.GetRetriever(ledgerID) 291 btlPolicy := pvtdatapolicy.ConstructBTLPolicy( 292 &mostRecentCollectionConfigFetcher{ 293 DeployedChaincodeInfoProvider: p.initializer.DeployedChaincodeInfoProvider, 294 Retriever: configHistoryRetiever, 295 }, 296 ) 297 purgeMgrBuilder := pvtstatepurgemgmt.NewPurgeMgrBuilder(ledgerID, btlPolicy, p.bookkeepingProvider) 298 logger.Debugw("Constructed pvtdata hashes consumer for purge Mgr", "ledgerID", ledgerID) 299 300 pvtdataStoreBuilder, err := p.pvtdataStoreProvider.SnapshotDataImporterFor( 301 ledgerID, lastBlockNum, p.initializer.MembershipInfoProvider, configHistoryRetiever, 302 SnapshotsTempDirPath(p.initializer.Config.SnapshotsConfig.RootDir), 303 ) 304 if err != nil { 305 return nil, "", p.deleteUnderConstructionLedger( 306 nil, 307 ledgerID, 308 errors.WithMessage(err, "error while getting pvtdata hashes consumer for pvtdata store"), 309 ) 310 } 311 logger.Debugw("Constructed pvtdata hashes consumer for pvt data store", "ledgerID", ledgerID) 312 313 if err = p.dbProvider.ImportFromSnapshot(ledgerID, savepoint, snapshotDir, purgeMgrBuilder, pvtdataStoreBuilder); err != nil { 314 return nil, "", p.deleteUnderConstructionLedger( 315 nil, 316 ledgerID, 317 errors.WithMessage(err, "error while importing data into state db"), 318 ) 319 } 320 logger.Debugw("Imported data into statedb, purgeMgr, and pvtdata store", "ledgerID", ledgerID) 321 322 if p.historydbProvider != nil { 323 if err := p.historydbProvider.MarkStartingSavepoint(ledgerID, savepoint); err != nil { 324 return nil, "", p.deleteUnderConstructionLedger( 325 nil, 326 ledgerID, 327 errors.WithMessage(err, "error while preparing history db"), 328 ) 329 } 330 logger.Debugw("Preparing history db", "ledgerID", ledgerID) 331 } 332 333 lgr, err := p.open(ledgerID, metadata, true) 334 if err != nil { 335 return nil, "", p.deleteUnderConstructionLedger( 336 lgr, 337 ledgerID, 338 errors.WithMessage(err, "error while opening ledger"), 339 ) 340 } 341 342 if err = p.idStore.updateLedgerStatus(ledgerID, msgs.Status_ACTIVE); err != nil { 343 return nil, "", p.deleteUnderConstructionLedger( 344 lgr, 345 ledgerID, 346 errors.WithMessage(err, "error while updating the ledger status to Status_ACTIVE"), 347 ) 348 } 349 return lgr, ledgerID, nil 350 } 351 352 func loadSnapshotMetadataJSONs(snapshotDir string) (*SnapshotMetadataJSONs, error) { 353 signableMetadataFilePath := filepath.Join(snapshotDir, SnapshotSignableMetadataFileName) 354 signableMetadataBytes, err := ioutil.ReadFile(signableMetadataFilePath) 355 if err != nil { 356 return nil, err 357 } 358 additionalMetadataFilePath := filepath.Join(snapshotDir, snapshotAdditionalMetadataFileName) 359 additionalMetadataBytes, err := ioutil.ReadFile(additionalMetadataFilePath) 360 if err != nil { 361 return nil, err 362 } 363 return &SnapshotMetadataJSONs{ 364 signableMetadata: string(signableMetadataBytes), 365 additionalMetadata: string(additionalMetadataBytes), 366 }, nil 367 } 368 369 func verifySnapshot(snapshotDir string, snapshotMetadata *SnapshotMetadata, hashProvider ledger.HashProvider) error { 370 if err := verifyFileHash( 371 snapshotDir, 372 SnapshotSignableMetadataFileName, 373 snapshotMetadata.SnapshotHashInHex, 374 hashProvider, 375 ); err != nil { 376 return err 377 } 378 379 filesAndHashes := snapshotMetadata.FilesAndHashes 380 for f, h := range filesAndHashes { 381 if err := verifyFileHash(snapshotDir, f, h, hashProvider); err != nil { 382 return err 383 } 384 } 385 return nil 386 } 387 388 func verifyFileHash(dir, file string, expectedHashInHex string, hashProvider ledger.HashProvider) error { 389 hashImpl, err := hashProvider.GetHash(snapshotHashOpts) 390 if err != nil { 391 return err 392 } 393 394 filePath := filepath.Join(dir, file) 395 f, err := os.Open(filePath) 396 if err != nil { 397 return err 398 } 399 defer f.Close() 400 _, err = io.Copy(hashImpl, bufio.NewReader(f)) 401 if err != nil { 402 return err 403 } 404 hashInHex := hex.EncodeToString(hashImpl.Sum(nil)) 405 if hashInHex != expectedHashInHex { 406 return errors.Errorf("hash mismatch for file [%s]. Expected hash = [%s], Actual hash = [%s]", 407 file, expectedHashInHex, hashInHex, 408 ) 409 } 410 return nil 411 } 412 413 type mostRecentCollectionConfigFetcher struct { 414 *confighistory.Retriever 415 ledger.DeployedChaincodeInfoProvider 416 } 417 418 func (c *mostRecentCollectionConfigFetcher) CollectionInfo(chaincodeName, collectionName string) (*peer.StaticCollectionConfig, error) { 419 isImplicitCollection, mspID := implicitcollection.MspIDIfImplicitCollection(collectionName) 420 if isImplicitCollection { 421 return c.GenerateImplicitCollectionForOrg(mspID), nil 422 } 423 424 explicitCollections, err := c.MostRecentCollectionConfigBelow(math.MaxUint64, chaincodeName) 425 if err != nil || explicitCollections == nil || explicitCollections.CollectionConfig == nil { 426 return nil, errors.WithMessage(err, "error while fetching most recent collection config") 427 } 428 429 for _, c := range explicitCollections.CollectionConfig.Config { 430 stateCollectionConfig := c.GetStaticCollectionConfig() 431 if stateCollectionConfig.Name == collectionName { 432 return stateCollectionConfig, nil 433 } 434 } 435 return nil, nil 436 }