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 }