github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/ledger/kvledger/txmgmt/privacyenabledstate/db.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package privacyenabledstate
     8  
     9  import (
    10  	"encoding/base64"
    11  	"strings"
    12  
    13  	"github.com/hechain20/hechain/common/flogging"
    14  	"github.com/hechain20/hechain/common/metrics"
    15  	"github.com/hechain20/hechain/core/common/ccprovider"
    16  	"github.com/hechain20/hechain/core/ledger"
    17  	"github.com/hechain20/hechain/core/ledger/cceventmgmt"
    18  	"github.com/hechain20/hechain/core/ledger/internal/version"
    19  	"github.com/hechain20/hechain/core/ledger/kvledger/bookkeeping"
    20  	"github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/statedb"
    21  	"github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/statedb/statecouchdb"
    22  	"github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/statedb/stateleveldb"
    23  	"github.com/hechain20/hechain/core/ledger/util"
    24  	"github.com/hyperledger/fabric-lib-go/healthz"
    25  	"github.com/pkg/errors"
    26  )
    27  
    28  var logger = flogging.MustGetLogger("privacyenabledstate")
    29  
    30  const (
    31  	nsJoiner       = "$$"
    32  	pvtDataPrefix  = "p"
    33  	hashDataPrefix = "h"
    34  )
    35  
    36  // StateDBConfig encapsulates the configuration for stateDB on the ledger.
    37  type StateDBConfig struct {
    38  	// ledger.StateDBConfig is used to configure the stateDB for the ledger.
    39  	*ledger.StateDBConfig
    40  	// LevelDBPath is the filesystem path when statedb type is "goleveldb".
    41  	// It is internally computed by the ledger component,
    42  	// so it is not in ledger.StateDBConfig and not exposed to other components.
    43  	LevelDBPath string
    44  }
    45  
    46  // DBProvider encapsulates other providers such as VersionedDBProvider and
    47  // BookeepingProvider which are required to create DB for a channel
    48  type DBProvider struct {
    49  	VersionedDBProvider statedb.VersionedDBProvider
    50  	HealthCheckRegistry ledger.HealthCheckRegistry
    51  	bookkeepingProvider *bookkeeping.Provider
    52  }
    53  
    54  // NewDBProvider constructs an instance of DBProvider
    55  func NewDBProvider(
    56  	bookkeeperProvider *bookkeeping.Provider,
    57  	metricsProvider metrics.Provider,
    58  	healthCheckRegistry ledger.HealthCheckRegistry,
    59  	stateDBConf *StateDBConfig,
    60  	sysNamespaces []string,
    61  ) (*DBProvider, error) {
    62  	var vdbProvider statedb.VersionedDBProvider
    63  	var err error
    64  
    65  	if stateDBConf != nil && stateDBConf.StateDatabase == ledger.CouchDB {
    66  		if vdbProvider, err = statecouchdb.NewVersionedDBProvider(stateDBConf.CouchDB, metricsProvider, sysNamespaces); err != nil {
    67  			return nil, err
    68  		}
    69  	} else {
    70  		if vdbProvider, err = stateleveldb.NewVersionedDBProvider(stateDBConf.LevelDBPath); err != nil {
    71  			return nil, err
    72  		}
    73  	}
    74  
    75  	dbProvider := &DBProvider{
    76  		VersionedDBProvider: vdbProvider,
    77  		HealthCheckRegistry: healthCheckRegistry,
    78  		bookkeepingProvider: bookkeeperProvider,
    79  	}
    80  
    81  	err = dbProvider.RegisterHealthChecker()
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	return dbProvider, nil
    87  }
    88  
    89  // RegisterHealthChecker registers the underlying stateDB with the healthChecker.
    90  // For now, we register only the CouchDB as it runs as a separate process but not
    91  // for the GoLevelDB as it is an embedded database.
    92  func (p *DBProvider) RegisterHealthChecker() error {
    93  	if healthChecker, ok := p.VersionedDBProvider.(healthz.HealthChecker); ok {
    94  		return p.HealthCheckRegistry.RegisterChecker("couchdb", healthChecker)
    95  	}
    96  	return nil
    97  }
    98  
    99  // GetDBHandle gets a handle to DB for a given id, i.e., a channel
   100  func (p *DBProvider) GetDBHandle(id string, chInfoProvider channelInfoProvider) (*DB, error) {
   101  	vdb, err := p.VersionedDBProvider.GetDBHandle(id, &namespaceProvider{chInfoProvider})
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	bookkeeper := p.bookkeepingProvider.GetDBHandle(id, bookkeeping.MetadataPresenceIndicator)
   106  	metadataHint, err := newMetadataHint(bookkeeper)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	return NewDB(vdb, id, metadataHint)
   111  }
   112  
   113  // Close closes all the VersionedDB instances and releases any resources held by VersionedDBProvider
   114  func (p *DBProvider) Close() {
   115  	p.VersionedDBProvider.Close()
   116  }
   117  
   118  // Drop drops channel-specific data from the statedb
   119  func (p *DBProvider) Drop(ledgerid string) error {
   120  	return p.VersionedDBProvider.Drop(ledgerid)
   121  }
   122  
   123  // DB uses a single database to maintain both the public and private data
   124  type DB struct {
   125  	statedb.VersionedDB
   126  	metadataHint *metadataHint
   127  }
   128  
   129  // NewDB wraps a VersionedDB instance. The public data is managed directly by the wrapped versionedDB.
   130  // For managing the hashed data and private data, this implementation creates separate namespaces in the wrapped db
   131  func NewDB(vdb statedb.VersionedDB, ledgerid string, metadataHint *metadataHint) (*DB, error) {
   132  	return &DB{vdb, metadataHint}, nil
   133  }
   134  
   135  // IsBulkOptimizable checks whether the underlying statedb implements statedb.BulkOptimizable
   136  func (s *DB) IsBulkOptimizable() bool {
   137  	_, ok := s.VersionedDB.(statedb.BulkOptimizable)
   138  	return ok
   139  }
   140  
   141  // LoadCommittedVersionsOfPubAndHashedKeys loads committed version of given public and hashed states
   142  func (s *DB) LoadCommittedVersionsOfPubAndHashedKeys(pubKeys []*statedb.CompositeKey,
   143  	hashedKeys []*HashedCompositeKey) error {
   144  	bulkOptimizable, ok := s.VersionedDB.(statedb.BulkOptimizable)
   145  	if !ok {
   146  		return nil
   147  	}
   148  	// Here, hashedKeys are merged into pubKeys to get a combined set of keys for combined loading
   149  	for _, key := range hashedKeys {
   150  		ns := deriveHashedDataNs(key.Namespace, key.CollectionName)
   151  		// No need to check for duplicates as hashedKeys are in separate namespace
   152  		var keyHashStr string
   153  		if !s.BytesKeySupported() {
   154  			keyHashStr = base64.StdEncoding.EncodeToString([]byte(key.KeyHash))
   155  		} else {
   156  			keyHashStr = key.KeyHash
   157  		}
   158  		pubKeys = append(pubKeys, &statedb.CompositeKey{
   159  			Namespace: ns,
   160  			Key:       keyHashStr,
   161  		})
   162  	}
   163  
   164  	err := bulkOptimizable.LoadCommittedVersions(pubKeys)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  // ClearCachedVersions clears the version cache
   173  func (s *DB) ClearCachedVersions() {
   174  	bulkOptimizable, ok := s.VersionedDB.(statedb.BulkOptimizable)
   175  	if ok {
   176  		bulkOptimizable.ClearCachedVersions()
   177  	}
   178  }
   179  
   180  // GetChaincodeEventListener returns a struct that implements cceventmgmt.ChaincodeLifecycleEventListener
   181  // if the underlying statedb implements statedb.IndexCapable.
   182  func (s *DB) GetChaincodeEventListener() cceventmgmt.ChaincodeLifecycleEventListener {
   183  	_, ok := s.VersionedDB.(statedb.IndexCapable)
   184  	if ok {
   185  		return s
   186  	}
   187  	return nil
   188  }
   189  
   190  // GetPrivateData gets the value of a private data item identified by a tuple <namespace, collection, key>
   191  func (s *DB) GetPrivateData(namespace, collection, key string) (*statedb.VersionedValue, error) {
   192  	return s.GetState(derivePvtDataNs(namespace, collection), key)
   193  }
   194  
   195  // GetPrivateDataHash gets the hash of the value of a private data item identified by a tuple <namespace, collection, key>
   196  func (s *DB) GetPrivateDataHash(namespace, collection, key string) (*statedb.VersionedValue, error) {
   197  	return s.GetValueHash(namespace, collection, util.ComputeStringHash(key))
   198  }
   199  
   200  // GetValueHash gets the value hash of a private data item identified by a tuple <namespace, collection, keyHash>
   201  func (s *DB) GetValueHash(namespace, collection string, keyHash []byte) (*statedb.VersionedValue, error) {
   202  	keyHashStr := string(keyHash)
   203  	if !s.BytesKeySupported() {
   204  		keyHashStr = base64.StdEncoding.EncodeToString(keyHash)
   205  	}
   206  	return s.GetState(deriveHashedDataNs(namespace, collection), keyHashStr)
   207  }
   208  
   209  // GetKeyHashVersion gets the version of a private data item identified by a tuple <namespace, collection, keyHash>
   210  func (s *DB) GetKeyHashVersion(namespace, collection string, keyHash []byte) (*version.Height, error) {
   211  	keyHashStr := string(keyHash)
   212  	if !s.BytesKeySupported() {
   213  		keyHashStr = base64.StdEncoding.EncodeToString(keyHash)
   214  	}
   215  	return s.GetVersion(deriveHashedDataNs(namespace, collection), keyHashStr)
   216  }
   217  
   218  // GetCachedKeyHashVersion retrieves the keyhash version from cache
   219  func (s *DB) GetCachedKeyHashVersion(namespace, collection string, keyHash []byte) (*version.Height, bool) {
   220  	bulkOptimizable, ok := s.VersionedDB.(statedb.BulkOptimizable)
   221  	if !ok {
   222  		return nil, false
   223  	}
   224  
   225  	keyHashStr := string(keyHash)
   226  	if !s.BytesKeySupported() {
   227  		keyHashStr = base64.StdEncoding.EncodeToString(keyHash)
   228  	}
   229  	return bulkOptimizable.GetCachedVersion(deriveHashedDataNs(namespace, collection), keyHashStr)
   230  }
   231  
   232  // GetPrivateDataMultipleKeys gets the values for the multiple private data items in a single call
   233  func (s *DB) GetPrivateDataMultipleKeys(namespace, collection string, keys []string) ([]*statedb.VersionedValue, error) {
   234  	return s.GetStateMultipleKeys(derivePvtDataNs(namespace, collection), keys)
   235  }
   236  
   237  // GetPrivateDataRangeScanIterator returns an iterator that contains all the key-values between given key ranges.
   238  // startKey is included in the results and endKey is excluded.
   239  func (s *DB) GetPrivateDataRangeScanIterator(namespace, collection, startKey, endKey string) (statedb.ResultsIterator, error) {
   240  	return s.GetStateRangeScanIterator(derivePvtDataNs(namespace, collection), startKey, endKey)
   241  }
   242  
   243  // ExecuteQueryOnPrivateData executes the given query and returns an iterator that contains results of type specific to the underlying data store.
   244  func (s DB) ExecuteQueryOnPrivateData(namespace, collection, query string) (statedb.ResultsIterator, error) {
   245  	return s.ExecuteQuery(derivePvtDataNs(namespace, collection), query)
   246  }
   247  
   248  // ApplyUpdates overrides the function in statedb.VersionedDB and throws appropriate error message
   249  // Otherwise, somewhere in the code, usage of this function could lead to updating only public data.
   250  func (s *DB) ApplyUpdates(batch *statedb.UpdateBatch, height *version.Height) error {
   251  	return errors.New("this function should not be invoked on this type. Please invoke function ApplyPrivacyAwareUpdates")
   252  }
   253  
   254  // ApplyPrivacyAwareUpdates applies the batch to the underlying db
   255  func (s *DB) ApplyPrivacyAwareUpdates(updates *UpdateBatch, height *version.Height) error {
   256  	// combinedUpdates includes both updates to public db and private db, which are partitioned by a separate namespace
   257  	combinedUpdates := updates.PubUpdates
   258  	addPvtUpdates(combinedUpdates, updates.PvtUpdates)
   259  	addHashedUpdates(combinedUpdates, updates.HashUpdates, !s.BytesKeySupported())
   260  	if err := s.metadataHint.setMetadataUsedFlag(updates); err != nil {
   261  		return err
   262  	}
   263  	return s.VersionedDB.ApplyUpdates(combinedUpdates.UpdateBatch, height)
   264  }
   265  
   266  // GetStateMetadata implements corresponding function in interface DB. This implementation provides
   267  // an optimization such that it keeps track if a namespaces has never stored metadata for any of
   268  // its items, the value 'nil' is returned without going to the db. This is intended to be invoked
   269  // in the validation and commit path. This saves the chaincodes from paying unnecessary performance
   270  // penalty if they do not use features that leverage metadata (such as key-level endorsement),
   271  func (s *DB) GetStateMetadata(namespace, key string) ([]byte, error) {
   272  	if !s.metadataHint.metadataEverUsedFor(namespace) {
   273  		return nil, nil
   274  	}
   275  	vv, err := s.GetState(namespace, key)
   276  	if err != nil || vv == nil {
   277  		return nil, err
   278  	}
   279  	return vv.Metadata, nil
   280  }
   281  
   282  // GetPrivateDataMetadataByHash implements corresponding function in interface DB. For additional details, see
   283  // description of the similar function 'GetStateMetadata'
   284  func (s *DB) GetPrivateDataMetadataByHash(namespace, collection string, keyHash []byte) ([]byte, error) {
   285  	if !s.metadataHint.metadataEverUsedFor(namespace) {
   286  		return nil, nil
   287  	}
   288  	vv, err := s.GetValueHash(namespace, collection, keyHash)
   289  	if err != nil || vv == nil {
   290  		return nil, err
   291  	}
   292  	return vv.Metadata, nil
   293  }
   294  
   295  // HandleChaincodeDeploy initializes database artifacts for the database associated with the namespace
   296  // This function deliberately suppresses the errors that occur during the creation of the indexes on couchdb.
   297  // This is because, in the present code, we do not differentiate between the errors because of couchdb interaction
   298  // and the errors because of bad index files - the later being unfixable by the admin. Note that the error suppression
   299  // is acceptable since peer can continue in the committing role without the indexes. However, executing chaincode queries
   300  // may be affected, until a new chaincode with fixed indexes is installed and instantiated
   301  func (s *DB) HandleChaincodeDeploy(chaincodeDefinition *cceventmgmt.ChaincodeDefinition, dbArtifactsTar []byte) error {
   302  	// Check to see if the interface for IndexCapable is implemented
   303  	indexCapable, ok := s.VersionedDB.(statedb.IndexCapable)
   304  	if !ok {
   305  		return nil
   306  	}
   307  	if chaincodeDefinition == nil {
   308  		return errors.New("chaincode definition not found while creating couchdb index")
   309  	}
   310  	dbArtifacts, err := ccprovider.ExtractFileEntries(dbArtifactsTar, indexCapable.GetDBType())
   311  	if err != nil {
   312  		logger.Errorf("Index creation: error extracting db artifacts from tar for chaincode [%s]: %s", chaincodeDefinition.Name, err)
   313  		return nil
   314  	}
   315  
   316  	collectionConfigMap := extractCollectionNames(chaincodeDefinition)
   317  	for directoryPath, indexFiles := range dbArtifacts {
   318  		indexFilesData := make(map[string][]byte)
   319  		for _, f := range indexFiles {
   320  			indexFilesData[f.FileHeader.Name] = f.FileContent
   321  		}
   322  
   323  		indexInfo := getIndexInfo(directoryPath)
   324  		switch {
   325  		case indexInfo.hasIndexForChaincode:
   326  			err := indexCapable.ProcessIndexesForChaincodeDeploy(chaincodeDefinition.Name, indexFilesData)
   327  			if err != nil {
   328  				logger.Errorf("Error processing index for chaincode [%s]: %s", chaincodeDefinition.Name, err)
   329  			}
   330  		case indexInfo.hasIndexForCollection:
   331  			_, ok := collectionConfigMap[indexInfo.collectionName]
   332  			if !ok {
   333  				logger.Errorf("Error processing index for chaincode [%s]: cannot create an index for an undefined collection=[%s]",
   334  					chaincodeDefinition.Name, indexInfo.collectionName)
   335  				continue
   336  			}
   337  			err := indexCapable.ProcessIndexesForChaincodeDeploy(derivePvtDataNs(chaincodeDefinition.Name, indexInfo.collectionName), indexFilesData)
   338  			if err != nil {
   339  				logger.Errorf("Error processing collection index for chaincode [%s]: %s", chaincodeDefinition.Name, err)
   340  			}
   341  		}
   342  	}
   343  	return nil
   344  }
   345  
   346  // ChaincodeDeployDone is a noop for couchdb state impl
   347  func (s *DB) ChaincodeDeployDone(succeeded bool) {
   348  	// NOOP
   349  }
   350  
   351  func derivePvtDataNs(namespace, collection string) string {
   352  	return namespace + nsJoiner + pvtDataPrefix + collection
   353  }
   354  
   355  func deriveHashedDataNs(namespace, collection string) string {
   356  	return namespace + nsJoiner + hashDataPrefix + collection
   357  }
   358  
   359  func decodeHashedDataNsColl(hashedDataNs string) (string, string, error) {
   360  	strs := strings.Split(hashedDataNs, nsJoiner+hashDataPrefix)
   361  	if len(strs) != 2 {
   362  		return "", "", errors.Errorf("not a valid hashedDataNs [%s]", hashedDataNs)
   363  	}
   364  	return strs[0], strs[1], nil
   365  }
   366  
   367  func isPvtdataNs(namespace string) bool {
   368  	return strings.Contains(namespace, nsJoiner+pvtDataPrefix)
   369  }
   370  
   371  func isHashedDataNs(namespace string) bool {
   372  	return strings.Contains(namespace, nsJoiner+hashDataPrefix)
   373  }
   374  
   375  func addPvtUpdates(pubUpdateBatch *PubUpdateBatch, pvtUpdateBatch *PvtUpdateBatch) {
   376  	for ns, nsBatch := range pvtUpdateBatch.UpdateMap {
   377  		for _, coll := range nsBatch.GetCollectionNames() {
   378  			for key, vv := range nsBatch.GetUpdates(coll) {
   379  				pubUpdateBatch.Update(derivePvtDataNs(ns, coll), key, vv)
   380  			}
   381  		}
   382  	}
   383  }
   384  
   385  func addHashedUpdates(pubUpdateBatch *PubUpdateBatch, hashedUpdateBatch *HashedUpdateBatch, base64Key bool) {
   386  	for ns, nsBatch := range hashedUpdateBatch.UpdateMap {
   387  		for _, coll := range nsBatch.GetCollectionNames() {
   388  			for key, vv := range nsBatch.GetUpdates(coll) {
   389  				if base64Key {
   390  					key = base64.StdEncoding.EncodeToString([]byte(key))
   391  				}
   392  				pubUpdateBatch.Update(deriveHashedDataNs(ns, coll), key, vv)
   393  			}
   394  		}
   395  	}
   396  }
   397  
   398  func extractCollectionNames(chaincodeDefinition *cceventmgmt.ChaincodeDefinition) map[string]bool {
   399  	collectionConfigs := chaincodeDefinition.CollectionConfigs
   400  	collectionConfigsMap := make(map[string]bool)
   401  	if collectionConfigs != nil {
   402  		for _, config := range collectionConfigs.Config {
   403  			sConfig := config.GetStaticCollectionConfig()
   404  			if sConfig == nil {
   405  				continue
   406  			}
   407  			collectionConfigsMap[sConfig.Name] = true
   408  		}
   409  	}
   410  	return collectionConfigsMap
   411  }
   412  
   413  type indexInfo struct {
   414  	hasIndexForChaincode  bool
   415  	hasIndexForCollection bool
   416  	collectionName        string
   417  }
   418  
   419  const (
   420  	// Example for chaincode indexes:
   421  	// "META-INF/statedb/couchdb/indexes"
   422  	chaincodeIndexDirDepth = 3
   423  
   424  	// Example for collection scoped indexes:
   425  	// "META-INF/statedb/couchdb/collections/collectionMarbles/indexes"
   426  	collectionDirDepth      = 3
   427  	collectionNameDepth     = 4
   428  	collectionIndexDirDepth = 5
   429  )
   430  
   431  // Note previous functions will have ensured that the path starts
   432  // with 'META-INF/statedb' and does not have leading or trailing
   433  // path deliminators.
   434  func getIndexInfo(indexPath string) *indexInfo {
   435  	indexInfo := &indexInfo{}
   436  	pathParts := strings.Split(indexPath, "/")
   437  	pathDepth := len(pathParts)
   438  
   439  	switch {
   440  	case pathDepth > chaincodeIndexDirDepth && pathParts[chaincodeIndexDirDepth] == "indexes":
   441  		indexInfo.hasIndexForChaincode = true
   442  	case pathDepth > collectionIndexDirDepth && pathParts[collectionDirDepth] == "collections" && pathParts[collectionIndexDirDepth] == "indexes":
   443  		indexInfo.hasIndexForCollection = true
   444  		indexInfo.collectionName = pathParts[collectionNameDepth]
   445  	}
   446  	return indexInfo
   447  }
   448  
   449  // channelInfoProvider interface enables the privateenabledstate package to retrieve all the config blocks
   450  // and  namespaces and collections.
   451  type channelInfoProvider interface {
   452  	// NamespacesAndCollections returns namespaces and collections for the channel.
   453  	NamespacesAndCollections(vdb statedb.VersionedDB) (map[string][]string, error)
   454  }
   455  
   456  // namespaceProvider implements statedb.NamespaceProvider interface
   457  type namespaceProvider struct {
   458  	channelInfoProvider
   459  }
   460  
   461  // PossibleNamespaces returns all possible namespaces for a channel. In ledger, a private data namespace is
   462  // created only if the peer is a member of the collection or owns the implicit collection. However, this function
   463  // adopts a simple implementation that always adds private data namespace for a collection without checking
   464  // peer membership/ownership. As a result, it returns a superset of namespaces that may be created.
   465  // However, it will not cause any inconsistent issue because the caller in statecouchdb will check if any
   466  // existing database matches the namespace and filter out all extra namespaces if no databases match them.
   467  // Checking peer membership is complicated because it requires retrieving all the collection configs from
   468  // the collection config store. Because this is a temporary function needed to retroactively build namespaces
   469  // when upgrading v2.0/2.1 peers to a newer v2.x version and because returning extra private data namespaces
   470  // does not cause inconsistence, it makes sense to use the simple implementation.
   471  func (p *namespaceProvider) PossibleNamespaces(vdb statedb.VersionedDB) ([]string, error) {
   472  	retNamespaces := []string{}
   473  	nsCollMap, err := p.NamespacesAndCollections(vdb)
   474  	if err != nil {
   475  		return nil, err
   476  	}
   477  	for ns, collections := range nsCollMap {
   478  		retNamespaces = append(retNamespaces, ns)
   479  		for _, collection := range collections {
   480  			retNamespaces = append(retNamespaces, deriveHashedDataNs(ns, collection))
   481  			retNamespaces = append(retNamespaces, derivePvtDataNs(ns, collection))
   482  		}
   483  	}
   484  	return retNamespaces, nil
   485  }