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  }