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 }