github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/core/ledger/kvledger/txmgmt/privacyenabledstate/db.go (about)

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