github.com/defanghe/fabric@v2.1.1+incompatible/core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package statecouchdb 8 9 import ( 10 "bytes" 11 "context" 12 "encoding/json" 13 "fmt" 14 "sync" 15 16 "github.com/hyperledger/fabric/common/flogging" 17 "github.com/hyperledger/fabric/common/ledger/dataformat" 18 "github.com/hyperledger/fabric/common/metrics" 19 "github.com/hyperledger/fabric/core/common/ccprovider" 20 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb" 21 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version" 22 "github.com/hyperledger/fabric/core/ledger/util/couchdb" 23 "github.com/pkg/errors" 24 ) 25 26 var logger = flogging.MustGetLogger("statecouchdb") 27 28 const ( 29 // savepointDocID is used as a key for maintaining savepoint (maintained in metadatadb for a channel) 30 savepointDocID = "statedb_savepoint" 31 // fabricInternalDBName is used to create a db in couch that would be used for internal data such as the version of the data format 32 // a double underscore ensures that the dbname does not clash with the dbnames created for the chaincodes 33 fabricInternalDBName = "fabric__internal" 34 // dataformatVersionDocID is used as a key for maintaining version of the data format (maintained in fabric internal db) 35 dataformatVersionDocID = "dataformatVersion" 36 ) 37 38 // VersionedDBProvider implements interface VersionedDBProvider 39 type VersionedDBProvider struct { 40 couchInstance *couchdb.CouchInstance 41 databases map[string]*VersionedDB 42 mux sync.Mutex 43 openCounts uint64 44 redoLoggerProvider *redoLoggerProvider 45 cache *statedb.Cache 46 } 47 48 // NewVersionedDBProvider instantiates VersionedDBProvider 49 func NewVersionedDBProvider(config *couchdb.Config, metricsProvider metrics.Provider, cache *statedb.Cache) (*VersionedDBProvider, error) { 50 logger.Debugf("constructing CouchDB VersionedDBProvider") 51 couchInstance, err := couchdb.CreateCouchInstance(config, metricsProvider) 52 if err != nil { 53 return nil, err 54 } 55 if err := checkExpectedDataformatVersion(couchInstance); err != nil { 56 return nil, err 57 } 58 p, err := newRedoLoggerProvider(config.RedoLogPath) 59 if err != nil { 60 return nil, err 61 } 62 return &VersionedDBProvider{ 63 couchInstance: couchInstance, 64 databases: make(map[string]*VersionedDB), 65 mux: sync.Mutex{}, 66 openCounts: 0, 67 redoLoggerProvider: p, 68 cache: cache, 69 }, 70 nil 71 } 72 73 func checkExpectedDataformatVersion(couchInstance *couchdb.CouchInstance) error { 74 databasesToIgnore := []string{fabricInternalDBName} 75 isEmpty, err := couchInstance.IsEmpty(databasesToIgnore) 76 if err != nil { 77 return err 78 } 79 if isEmpty { 80 logger.Debugf("couch instance is empty. Setting dataformat version to %s", dataformat.Version20) 81 return writeDataFormatVersion(couchInstance, dataformat.Version20) 82 } 83 dataformatVersion, err := readDataformatVersion(couchInstance) 84 if err != nil { 85 return err 86 } 87 if dataformatVersion != dataformat.Version20 { 88 return &dataformat.ErrVersionMismatch{ 89 DBInfo: "CouchDB for state database", 90 ExpectedVersion: dataformat.Version20, 91 Version: dataformatVersion, 92 } 93 } 94 return nil 95 } 96 97 func readDataformatVersion(couchInstance *couchdb.CouchInstance) (string, error) { 98 db, err := couchdb.CreateCouchDatabase(couchInstance, fabricInternalDBName) 99 if err != nil { 100 return "", err 101 } 102 doc, _, err := db.ReadDoc(dataformatVersionDocID) 103 logger.Debugf("dataformatVersionDoc = %s", doc) 104 if err != nil || doc == nil { 105 return "", err 106 } 107 return decodeDataformatInfo(doc) 108 } 109 110 func writeDataFormatVersion(couchInstance *couchdb.CouchInstance, dataformatVersion string) error { 111 db, err := couchdb.CreateCouchDatabase(couchInstance, fabricInternalDBName) 112 if err != nil { 113 return err 114 } 115 doc, err := encodeDataformatInfo(dataformatVersion) 116 if err != nil { 117 return err 118 } 119 if _, err := db.SaveDoc(dataformatVersionDocID, "", doc); err != nil { 120 return err 121 } 122 dbResponse, err := db.EnsureFullCommit() 123 124 if err != nil { 125 return err 126 } 127 if !dbResponse.Ok { 128 logger.Errorf("failed to perform full commit while writing dataformat version") 129 return errors.New("failed to perform full commit while writing dataformat version") 130 } 131 return nil 132 } 133 134 // GetDBHandle gets the handle to a named database 135 func (provider *VersionedDBProvider) GetDBHandle(dbName string) (statedb.VersionedDB, error) { 136 provider.mux.Lock() 137 defer provider.mux.Unlock() 138 vdb := provider.databases[dbName] 139 if vdb == nil { 140 var err error 141 vdb, err = newVersionedDB( 142 provider.couchInstance, 143 provider.redoLoggerProvider.newRedoLogger(dbName), 144 dbName, 145 provider.cache, 146 ) 147 if err != nil { 148 return nil, err 149 } 150 provider.databases[dbName] = vdb 151 } 152 return vdb, nil 153 } 154 155 // Close closes the underlying db instance 156 func (provider *VersionedDBProvider) Close() { 157 // No close needed on Couch 158 provider.redoLoggerProvider.close() 159 } 160 161 // HealthCheck checks to see if the couch instance of the peer is healthy 162 func (provider *VersionedDBProvider) HealthCheck(ctx context.Context) error { 163 return provider.couchInstance.HealthCheck(ctx) 164 } 165 166 // VersionedDB implements VersionedDB interface 167 type VersionedDB struct { 168 couchInstance *couchdb.CouchInstance 169 metadataDB *couchdb.CouchDatabase // A database per channel to store metadata such as savepoint. 170 chainName string // The name of the chain/channel. 171 namespaceDBs map[string]*couchdb.CouchDatabase // One database per deployed chaincode. 172 committedDataCache *versionsCache // Used as a local cache during bulk processing of a block. 173 verCacheLock sync.RWMutex 174 mux sync.RWMutex 175 redoLogger *redoLogger 176 cache *statedb.Cache 177 } 178 179 // newVersionedDB constructs an instance of VersionedDB 180 func newVersionedDB(couchInstance *couchdb.CouchInstance, redoLogger *redoLogger, dbName string, cache *statedb.Cache) (*VersionedDB, error) { 181 // CreateCouchDatabase creates a CouchDB database object, as well as the underlying database if it does not exist 182 chainName := dbName 183 dbName = couchdb.ConstructMetadataDBName(dbName) 184 185 metadataDB, err := couchdb.CreateCouchDatabase(couchInstance, dbName) 186 if err != nil { 187 return nil, err 188 } 189 namespaceDBMap := make(map[string]*couchdb.CouchDatabase) 190 vdb := &VersionedDB{ 191 couchInstance: couchInstance, 192 metadataDB: metadataDB, 193 chainName: chainName, 194 namespaceDBs: namespaceDBMap, 195 committedDataCache: newVersionCache(), 196 redoLogger: redoLogger, 197 cache: cache, 198 } 199 logger.Debugf("chain [%s]: checking for redolog record", chainName) 200 redologRecord, err := redoLogger.load() 201 if err != nil { 202 return nil, err 203 } 204 savepoint, err := vdb.GetLatestSavePoint() 205 if err != nil { 206 return nil, err 207 } 208 209 // in normal circumstances, redolog is expected to be either equal to the last block 210 // committed to the statedb or one ahead (in the event of a crash). However, either of 211 // these or both could be nil on first time start (fresh start/rebuild) 212 if redologRecord == nil || savepoint == nil { 213 logger.Debugf("chain [%s]: No redo-record or save point present", chainName) 214 return vdb, nil 215 } 216 217 logger.Debugf("chain [%s]: save point = %#v, version of redolog record = %#v", 218 chainName, savepoint, redologRecord.Version) 219 220 if redologRecord.Version.BlockNum-savepoint.BlockNum == 1 { 221 logger.Debugf("chain [%s]: Re-applying last batch", chainName) 222 if err := vdb.applyUpdates(redologRecord.UpdateBatch, redologRecord.Version); err != nil { 223 return nil, err 224 } 225 } 226 return vdb, nil 227 } 228 229 // getNamespaceDBHandle gets the handle to a named chaincode database 230 func (vdb *VersionedDB) getNamespaceDBHandle(namespace string) (*couchdb.CouchDatabase, error) { 231 vdb.mux.RLock() 232 db := vdb.namespaceDBs[namespace] 233 vdb.mux.RUnlock() 234 if db != nil { 235 return db, nil 236 } 237 namespaceDBName := couchdb.ConstructNamespaceDBName(vdb.chainName, namespace) 238 vdb.mux.Lock() 239 defer vdb.mux.Unlock() 240 db = vdb.namespaceDBs[namespace] 241 if db == nil { 242 var err error 243 db, err = couchdb.CreateCouchDatabase(vdb.couchInstance, namespaceDBName) 244 if err != nil { 245 return nil, err 246 } 247 vdb.namespaceDBs[namespace] = db 248 } 249 return db, nil 250 } 251 252 // ProcessIndexesForChaincodeDeploy creates indexes for a specified namespace 253 func (vdb *VersionedDB) ProcessIndexesForChaincodeDeploy(namespace string, fileEntries []*ccprovider.TarFileEntry) error { 254 db, err := vdb.getNamespaceDBHandle(namespace) 255 if err != nil { 256 return err 257 } 258 for _, fileEntry := range fileEntries { 259 indexData := fileEntry.FileContent 260 filename := fileEntry.FileHeader.Name 261 _, err = db.CreateIndex(string(indexData)) 262 if err != nil { 263 return errors.WithMessagef(err, "error creating index from file [%s] for channel [%s]", filename, namespace) 264 } 265 } 266 return nil 267 } 268 269 // GetDBType returns the hosted stateDB 270 func (vdb *VersionedDB) GetDBType() string { 271 return "couchdb" 272 } 273 274 // LoadCommittedVersions populates committedVersions and revisionNumbers into cache. 275 // A bulk retrieve from couchdb is used to populate the cache. 276 // committedVersions cache will be used for state validation of readsets 277 // revisionNumbers cache will be used during commit phase for couchdb bulk updates 278 func (vdb *VersionedDB) LoadCommittedVersions(keys []*statedb.CompositeKey) error { 279 missingKeys := map[string][]string{} 280 committedDataCache := newVersionCache() 281 for _, compositeKey := range keys { 282 ns, key := compositeKey.Namespace, compositeKey.Key 283 committedDataCache.setVerAndRev(ns, key, nil, "") 284 logger.Debugf("Load into version cache: %s~%s", ns, key) 285 286 if !vdb.cache.Enabled(ns) { 287 missingKeys[ns] = append(missingKeys[ns], key) 288 continue 289 } 290 cv, err := vdb.cache.GetState(vdb.chainName, ns, key) 291 if err != nil { 292 return err 293 } 294 if cv == nil { 295 missingKeys[ns] = append(missingKeys[ns], key) 296 continue 297 } 298 vv, err := constructVersionedValue(cv) 299 if err != nil { 300 return err 301 } 302 rev := string(cv.AdditionalInfo) 303 committedDataCache.setVerAndRev(ns, key, vv.Version, rev) 304 } 305 306 nsMetadataMap, err := vdb.retrieveMetadata(missingKeys) 307 logger.Debugf("missingKeys=%s", missingKeys) 308 logger.Debugf("nsMetadataMap=%s", nsMetadataMap) 309 if err != nil { 310 return err 311 } 312 for ns, nsMetadata := range nsMetadataMap { 313 for _, keyMetadata := range nsMetadata { 314 // TODO - why would version be ever zero if loaded from db? 315 if len(keyMetadata.Version) != 0 { 316 version, _, err := decodeVersionAndMetadata(keyMetadata.Version) 317 if err != nil { 318 return err 319 } 320 committedDataCache.setVerAndRev(ns, keyMetadata.ID, version, keyMetadata.Rev) 321 } 322 } 323 } 324 vdb.verCacheLock.Lock() 325 defer vdb.verCacheLock.Unlock() 326 vdb.committedDataCache = committedDataCache 327 return nil 328 } 329 330 // GetVersion implements method in VersionedDB interface 331 func (vdb *VersionedDB) GetVersion(namespace string, key string) (*version.Height, error) { 332 version, keyFound := vdb.GetCachedVersion(namespace, key) 333 if !keyFound { 334 // This if block get executed only during simulation because during commit 335 // we always call `LoadCommittedVersions` before calling `GetVersion` 336 vv, err := vdb.GetState(namespace, key) 337 if err != nil || vv == nil { 338 return nil, err 339 } 340 version = vv.Version 341 } 342 return version, nil 343 } 344 345 // GetCachedVersion returns version from cache. `LoadCommittedVersions` function populates the cache 346 func (vdb *VersionedDB) GetCachedVersion(namespace string, key string) (*version.Height, bool) { 347 logger.Debugf("Retrieving cached version: %s~%s", key, namespace) 348 vdb.verCacheLock.RLock() 349 defer vdb.verCacheLock.RUnlock() 350 return vdb.committedDataCache.getVersion(namespace, key) 351 } 352 353 // ValidateKeyValue implements method in VersionedDB interface 354 func (vdb *VersionedDB) ValidateKeyValue(key string, value []byte) error { 355 err := validateKey(key) 356 if err != nil { 357 return err 358 } 359 return validateValue(value) 360 } 361 362 // BytesKeySupported implements method in VersionvdbedDB interface 363 func (vdb *VersionedDB) BytesKeySupported() bool { 364 return false 365 } 366 367 // GetState implements method in VersionedDB interface 368 func (vdb *VersionedDB) GetState(namespace string, key string) (*statedb.VersionedValue, error) { 369 logger.Debugf("GetState(). ns=%s, key=%s", namespace, key) 370 371 // (1) read the KV from the cache if available 372 cacheEnabled := vdb.cache.Enabled(namespace) 373 if cacheEnabled { 374 cv, err := vdb.cache.GetState(vdb.chainName, namespace, key) 375 if err != nil { 376 return nil, err 377 } 378 if cv != nil { 379 vv, err := constructVersionedValue(cv) 380 if err != nil { 381 return nil, err 382 } 383 return vv, nil 384 } 385 } 386 387 // (2) read from the database if cache miss occurs 388 kv, err := vdb.readFromDB(namespace, key) 389 if err != nil { 390 return nil, err 391 } 392 if kv == nil { 393 return nil, nil 394 } 395 396 // (3) if the value is not nil, store in the cache 397 if cacheEnabled { 398 cacheValue := constructCacheValue(kv.VersionedValue, kv.revision) 399 if err := vdb.cache.PutState(vdb.chainName, namespace, key, cacheValue); err != nil { 400 return nil, err 401 } 402 } 403 404 return kv.VersionedValue, nil 405 } 406 407 func (vdb *VersionedDB) readFromDB(namespace, key string) (*keyValue, error) { 408 db, err := vdb.getNamespaceDBHandle(namespace) 409 if err != nil { 410 return nil, err 411 } 412 couchDoc, _, err := db.ReadDoc(key) 413 if err != nil { 414 return nil, err 415 } 416 if couchDoc == nil { 417 return nil, nil 418 } 419 kv, err := couchDocToKeyValue(couchDoc) 420 if err != nil { 421 return nil, err 422 } 423 return kv, nil 424 } 425 426 // GetStateMultipleKeys implements method in VersionedDB interface 427 func (vdb *VersionedDB) GetStateMultipleKeys(namespace string, keys []string) ([]*statedb.VersionedValue, error) { 428 vals := make([]*statedb.VersionedValue, len(keys)) 429 for i, key := range keys { 430 val, err := vdb.GetState(namespace, key) 431 if err != nil { 432 return nil, err 433 } 434 vals[i] = val 435 } 436 return vals, nil 437 } 438 439 // GetStateRangeScanIterator implements method in VersionedDB interface 440 // startKey is inclusive 441 // endKey is exclusive 442 func (vdb *VersionedDB) GetStateRangeScanIterator(namespace string, startKey string, endKey string) (statedb.ResultsIterator, error) { 443 return vdb.GetStateRangeScanIteratorWithMetadata(namespace, startKey, endKey, nil) 444 } 445 446 const optionBookmark = "bookmark" 447 const optionLimit = "limit" 448 449 // GetStateRangeScanIteratorWithMetadata implements method in VersionedDB interface 450 // startKey is inclusive 451 // endKey is exclusive 452 // metadata contains a map of additional query options 453 func (vdb *VersionedDB) GetStateRangeScanIteratorWithMetadata(namespace string, startKey string, endKey string, metadata map[string]interface{}) (statedb.QueryResultsIterator, error) { 454 logger.Debugf("Entering GetStateRangeScanIteratorWithMetadata namespace: %s startKey: %s endKey: %s metadata: %v", namespace, startKey, endKey, metadata) 455 // Get the internalQueryLimit from core.yaml 456 internalQueryLimit := vdb.couchInstance.InternalQueryLimit() 457 requestedLimit := int32(0) 458 // if metadata is provided, validate and apply options 459 if metadata != nil { 460 //validate the metadata 461 err := statedb.ValidateRangeMetadata(metadata) 462 if err != nil { 463 return nil, err 464 } 465 if limitOption, ok := metadata[optionLimit]; ok { 466 requestedLimit = limitOption.(int32) 467 } 468 } 469 db, err := vdb.getNamespaceDBHandle(namespace) 470 if err != nil { 471 return nil, err 472 } 473 return newQueryScanner(namespace, db, "", internalQueryLimit, requestedLimit, "", startKey, endKey) 474 } 475 476 func (scanner *queryScanner) getNextStateRangeScanResults() error { 477 queryLimit := scanner.queryDefinition.internalQueryLimit 478 if scanner.paginationInfo.requestedLimit > 0 { 479 moreResultsNeeded := scanner.paginationInfo.requestedLimit - scanner.resultsInfo.totalRecordsReturned 480 if moreResultsNeeded < scanner.queryDefinition.internalQueryLimit { 481 queryLimit = moreResultsNeeded 482 } 483 } 484 queryResult, nextStartKey, err := rangeScanFilterCouchInternalDocs(scanner.db, 485 scanner.queryDefinition.startKey, scanner.queryDefinition.endKey, queryLimit) 486 if err != nil { 487 return err 488 } 489 scanner.resultsInfo.results = queryResult 490 scanner.queryDefinition.startKey = nextStartKey 491 scanner.paginationInfo.cursor = 0 492 return nil 493 } 494 495 func rangeScanFilterCouchInternalDocs(db *couchdb.CouchDatabase, 496 startKey, endKey string, queryLimit int32, 497 ) ([]*couchdb.QueryResult, string, error) { 498 var finalResults []*couchdb.QueryResult 499 var finalNextStartKey string 500 for { 501 results, nextStartKey, err := db.ReadDocRange(startKey, endKey, queryLimit) 502 if err != nil { 503 logger.Debugf("Error calling ReadDocRange(): %s\n", err.Error()) 504 return nil, "", err 505 } 506 var filteredResults []*couchdb.QueryResult 507 for _, doc := range results { 508 if !isCouchInternalKey(doc.ID) { 509 filteredResults = append(filteredResults, doc) 510 } 511 } 512 513 finalResults = append(finalResults, filteredResults...) 514 finalNextStartKey = nextStartKey 515 queryLimit = int32(len(results) - len(filteredResults)) 516 if queryLimit == 0 || finalNextStartKey == "" { 517 break 518 } 519 startKey = finalNextStartKey 520 } 521 var err error 522 for i := 0; isCouchInternalKey(finalNextStartKey); i++ { 523 _, finalNextStartKey, err = db.ReadDocRange(finalNextStartKey, endKey, 1) 524 logger.Debugf("i=%d, finalNextStartKey=%s", i, finalNextStartKey) 525 if err != nil { 526 return nil, "", err 527 } 528 } 529 return finalResults, finalNextStartKey, nil 530 } 531 532 func isCouchInternalKey(key string) bool { 533 return len(key) != 0 && key[0] == '_' 534 } 535 536 // ExecuteQuery implements method in VersionedDB interface 537 func (vdb *VersionedDB) ExecuteQuery(namespace, query string) (statedb.ResultsIterator, error) { 538 queryResult, err := vdb.ExecuteQueryWithMetadata(namespace, query, nil) 539 if err != nil { 540 return nil, err 541 } 542 return queryResult, nil 543 } 544 545 // ExecuteQueryWithMetadata implements method in VersionedDB interface 546 func (vdb *VersionedDB) ExecuteQueryWithMetadata(namespace, query string, metadata map[string]interface{}) (statedb.QueryResultsIterator, error) { 547 logger.Debugf("Entering ExecuteQueryWithMetadata namespace: %s, query: %s, metadata: %v", namespace, query, metadata) 548 // Get the querylimit from core.yaml 549 internalQueryLimit := vdb.couchInstance.InternalQueryLimit() 550 bookmark := "" 551 requestedLimit := int32(0) 552 // if metadata is provided, then validate and set provided options 553 if metadata != nil { 554 err := validateQueryMetadata(metadata) 555 if err != nil { 556 return nil, err 557 } 558 if limitOption, ok := metadata[optionLimit]; ok { 559 requestedLimit = limitOption.(int32) 560 } 561 if bookmarkOption, ok := metadata[optionBookmark]; ok { 562 bookmark = bookmarkOption.(string) 563 } 564 } 565 queryString, err := applyAdditionalQueryOptions(query, internalQueryLimit, bookmark) 566 if err != nil { 567 logger.Errorf("Error calling applyAdditionalQueryOptions(): %s", err.Error()) 568 return nil, err 569 } 570 db, err := vdb.getNamespaceDBHandle(namespace) 571 if err != nil { 572 return nil, err 573 } 574 return newQueryScanner(namespace, db, queryString, internalQueryLimit, requestedLimit, bookmark, "", "") 575 } 576 577 // executeQueryWithBookmark executes a "paging" query with a bookmark, this method allows a 578 // paged query without returning a new query iterator 579 func (scanner *queryScanner) executeQueryWithBookmark() error { 580 queryLimit := scanner.queryDefinition.internalQueryLimit 581 if scanner.paginationInfo.requestedLimit > 0 { 582 if scanner.paginationInfo.requestedLimit-scanner.resultsInfo.totalRecordsReturned < scanner.queryDefinition.internalQueryLimit { 583 queryLimit = scanner.paginationInfo.requestedLimit - scanner.resultsInfo.totalRecordsReturned 584 } 585 } 586 queryString, err := applyAdditionalQueryOptions(scanner.queryDefinition.query, 587 queryLimit, scanner.paginationInfo.bookmark) 588 if err != nil { 589 logger.Debugf("Error calling applyAdditionalQueryOptions(): %s\n", err.Error()) 590 return err 591 } 592 queryResult, bookmark, err := scanner.db.QueryDocuments(queryString) 593 if err != nil { 594 logger.Debugf("Error calling QueryDocuments(): %s\n", err.Error()) 595 return err 596 } 597 scanner.resultsInfo.results = queryResult 598 scanner.paginationInfo.bookmark = bookmark 599 scanner.paginationInfo.cursor = 0 600 return nil 601 } 602 603 func validateQueryMetadata(metadata map[string]interface{}) error { 604 for key, keyVal := range metadata { 605 switch key { 606 case optionBookmark: 607 //Verify the bookmark is a string 608 if _, ok := keyVal.(string); ok { 609 continue 610 } 611 return fmt.Errorf("Invalid entry, \"bookmark\" must be a string") 612 613 case optionLimit: 614 //Verify the limit is an integer 615 if _, ok := keyVal.(int32); ok { 616 continue 617 } 618 return fmt.Errorf("Invalid entry, \"limit\" must be an int32") 619 620 default: 621 return fmt.Errorf("Invalid entry, option %s not recognized", key) 622 } 623 } 624 return nil 625 } 626 627 // ApplyUpdates implements method in VersionedDB interface 628 func (vdb *VersionedDB) ApplyUpdates(updates *statedb.UpdateBatch, height *version.Height) error { 629 if height != nil && updates.ContainsPostOrderWrites { 630 // height is passed nil when committing missing private data for previously committed blocks 631 r := &redoRecord{ 632 UpdateBatch: updates, 633 Version: height, 634 } 635 if err := vdb.redoLogger.persist(r); err != nil { 636 return err 637 } 638 } 639 return vdb.applyUpdates(updates, height) 640 } 641 642 func (vdb *VersionedDB) applyUpdates(updates *statedb.UpdateBatch, height *version.Height) error { 643 // TODO a note about https://jira.hyperledger.org/browse/FAB-8622 644 // The write lock is needed only for the stage 2. 645 646 // stage 1 - buildCommitters builds committers per namespace (per DB). Each committer transforms the 647 // given batch in the form of underlying db and keep it in memory. 648 committers, err := vdb.buildCommitters(updates) 649 if err != nil { 650 return err 651 } 652 653 // stage 2 -- executeCommitter executes each committer to push the changes to the DB 654 if err = vdb.executeCommitter(committers); err != nil { 655 return err 656 } 657 658 // Stgae 3 - postCommitProcessing - flush and record savepoint. 659 namespaces := updates.GetUpdatedNamespaces() 660 if err := vdb.postCommitProcessing(committers, namespaces, height); err != nil { 661 return err 662 } 663 664 return nil 665 } 666 667 func (vdb *VersionedDB) postCommitProcessing(committers []*committer, namespaces []string, height *version.Height) error { 668 var wg sync.WaitGroup 669 670 wg.Add(1) 671 errChan := make(chan error, 1) 672 defer close(errChan) 673 go func() { 674 defer wg.Done() 675 676 cacheUpdates := make(statedb.CacheUpdates) 677 for _, c := range committers { 678 if !c.cacheEnabled { 679 continue 680 } 681 cacheUpdates.Add(c.namespace, c.cacheKVs) 682 } 683 684 if len(cacheUpdates) == 0 { 685 return 686 } 687 688 // update the cache 689 if err := vdb.cache.UpdateStates(vdb.chainName, cacheUpdates); err != nil { 690 vdb.cache.Reset() 691 errChan <- err 692 } 693 694 }() 695 696 // Record a savepoint at a given height 697 if err := vdb.ensureFullCommitAndRecordSavepoint(height, namespaces); err != nil { 698 logger.Errorf("Error during recordSavepoint: %s", err.Error()) 699 return err 700 } 701 702 wg.Wait() 703 select { 704 case err := <-errChan: 705 return errors.WithStack(err) 706 default: 707 return nil 708 } 709 } 710 711 // ClearCachedVersions clears committedVersions and revisionNumbers 712 func (vdb *VersionedDB) ClearCachedVersions() { 713 logger.Debugf("Clear Cache") 714 vdb.verCacheLock.Lock() 715 defer vdb.verCacheLock.Unlock() 716 vdb.committedDataCache = newVersionCache() 717 } 718 719 // Open implements method in VersionedDB interface 720 func (vdb *VersionedDB) Open() error { 721 // no need to open db since a shared couch instance is used 722 return nil 723 } 724 725 // Close implements method in VersionedDB interface 726 func (vdb *VersionedDB) Close() { 727 // no need to close db since a shared couch instance is used 728 } 729 730 // ensureFullCommitAndRecordSavepoint flushes all the dbs (corresponding to `namespaces`) to disk 731 // and Record a savepoint in the metadata db. 732 // Couch parallelizes writes in cluster or sharded setup and ordering is not guaranteed. 733 // Hence we need to fence the savepoint with sync. So ensure_full_commit on all updated 734 // namespace DBs is called before savepoint to ensure all block writes are flushed. Savepoint 735 // itself is flushed to the metadataDB. 736 func (vdb *VersionedDB) ensureFullCommitAndRecordSavepoint(height *version.Height, namespaces []string) error { 737 // ensure full commit to flush all changes on updated namespaces until now to disk 738 // namespace also includes empty namespace which is nothing but metadataDB 739 errsChan := make(chan error, len(namespaces)) 740 defer close(errsChan) 741 var commitWg sync.WaitGroup 742 commitWg.Add(len(namespaces)) 743 744 for _, ns := range namespaces { 745 go func(ns string) { 746 defer commitWg.Done() 747 db, err := vdb.getNamespaceDBHandle(ns) 748 if err != nil { 749 errsChan <- err 750 return 751 } 752 _, err = db.EnsureFullCommit() 753 if err != nil { 754 errsChan <- err 755 return 756 } 757 }(ns) 758 } 759 760 commitWg.Wait() 761 762 select { 763 case err := <-errsChan: 764 logger.Errorf("Failed to perform full commit") 765 return errors.Wrap(err, "failed to perform full commit") 766 default: 767 logger.Debugf("All changes have been flushed to the disk") 768 } 769 770 // If a given height is nil, it denotes that we are committing pvt data of old blocks. 771 // In this case, we should not store a savepoint for recovery. The lastUpdatedOldBlockList 772 // in the pvtstore acts as a savepoint for pvt data. 773 if height == nil { 774 return nil 775 } 776 777 // construct savepoint document and save 778 savepointCouchDoc, err := encodeSavepoint(height) 779 if err != nil { 780 return err 781 } 782 _, err = vdb.metadataDB.SaveDoc(savepointDocID, "", savepointCouchDoc) 783 if err != nil { 784 logger.Errorf("Failed to save the savepoint to DB %s", err.Error()) 785 return err 786 } 787 // Note: Ensure full commit on metadataDB after storing the savepoint is not necessary 788 // as CouchDB syncs states to disk periodically (every 1 second). If peer fails before 789 // syncing the savepoint to disk, ledger recovery process kicks in to ensure consistency 790 // between CouchDB and block store on peer restart 791 return nil 792 } 793 794 // GetLatestSavePoint implements method in VersionedDB interface 795 func (vdb *VersionedDB) GetLatestSavePoint() (*version.Height, error) { 796 var err error 797 couchDoc, _, err := vdb.metadataDB.ReadDoc(savepointDocID) 798 if err != nil { 799 logger.Errorf("Failed to read savepoint data %s", err.Error()) 800 return nil, err 801 } 802 // ReadDoc() not found (404) will result in nil response, in these cases return height nil 803 if couchDoc == nil || couchDoc.JSONValue == nil { 804 return nil, nil 805 } 806 return decodeSavepoint(couchDoc) 807 } 808 809 // applyAdditionalQueryOptions will add additional fields to the query required for query processing 810 func applyAdditionalQueryOptions(queryString string, queryLimit int32, queryBookmark string) (string, error) { 811 const jsonQueryFields = "fields" 812 const jsonQueryLimit = "limit" 813 const jsonQueryBookmark = "bookmark" 814 //create a generic map for the query json 815 jsonQueryMap := make(map[string]interface{}) 816 //unmarshal the selector json into the generic map 817 decoder := json.NewDecoder(bytes.NewBuffer([]byte(queryString))) 818 decoder.UseNumber() 819 err := decoder.Decode(&jsonQueryMap) 820 if err != nil { 821 return "", err 822 } 823 if fieldsJSONArray, ok := jsonQueryMap[jsonQueryFields]; ok { 824 switch fieldsJSONArray.(type) { 825 case []interface{}: 826 //Add the "_id", and "version" fields, these are needed by default 827 jsonQueryMap[jsonQueryFields] = append(fieldsJSONArray.([]interface{}), 828 idField, versionField) 829 default: 830 return "", errors.New("fields definition must be an array") 831 } 832 } 833 // Add limit 834 // This will override any limit passed in the query. 835 // Explicit paging not yet supported. 836 jsonQueryMap[jsonQueryLimit] = queryLimit 837 // Add the bookmark if provided 838 if queryBookmark != "" { 839 jsonQueryMap[jsonQueryBookmark] = queryBookmark 840 } 841 //Marshal the updated json query 842 editedQuery, err := json.Marshal(jsonQueryMap) 843 if err != nil { 844 return "", err 845 } 846 logger.Debugf("Rewritten query: %s", editedQuery) 847 return string(editedQuery), nil 848 } 849 850 type queryScanner struct { 851 namespace string 852 db *couchdb.CouchDatabase 853 queryDefinition *queryDefinition 854 paginationInfo *paginationInfo 855 resultsInfo *resultsInfo 856 } 857 858 type queryDefinition struct { 859 startKey string 860 endKey string 861 query string 862 internalQueryLimit int32 863 } 864 865 type paginationInfo struct { 866 cursor int32 867 requestedLimit int32 868 bookmark string 869 } 870 871 type resultsInfo struct { 872 totalRecordsReturned int32 873 results []*couchdb.QueryResult 874 } 875 876 func newQueryScanner(namespace string, db *couchdb.CouchDatabase, query string, internalQueryLimit, 877 limit int32, bookmark, startKey, endKey string) (*queryScanner, error) { 878 scanner := &queryScanner{namespace, db, &queryDefinition{startKey, endKey, query, internalQueryLimit}, &paginationInfo{-1, limit, bookmark}, &resultsInfo{0, nil}} 879 var err error 880 // query is defined, then execute the query and return the records and bookmark 881 if scanner.queryDefinition.query != "" { 882 err = scanner.executeQueryWithBookmark() 883 } else { 884 err = scanner.getNextStateRangeScanResults() 885 } 886 if err != nil { 887 return nil, err 888 } 889 scanner.paginationInfo.cursor = -1 890 return scanner, nil 891 } 892 893 func (scanner *queryScanner) Next() (statedb.QueryResult, error) { 894 //test for no results case 895 if len(scanner.resultsInfo.results) == 0 { 896 return nil, nil 897 } 898 // increment the cursor 899 scanner.paginationInfo.cursor++ 900 // check to see if additional records are needed 901 // requery if the cursor exceeds the internalQueryLimit 902 if scanner.paginationInfo.cursor >= scanner.queryDefinition.internalQueryLimit { 903 var err error 904 // query is defined, then execute the query and return the records and bookmark 905 if scanner.queryDefinition.query != "" { 906 err = scanner.executeQueryWithBookmark() 907 } else { 908 err = scanner.getNextStateRangeScanResults() 909 } 910 if err != nil { 911 return nil, err 912 } 913 //if no more results, then return 914 if len(scanner.resultsInfo.results) == 0 { 915 return nil, nil 916 } 917 } 918 //If the cursor is greater than or equal to the number of result records, return 919 if scanner.paginationInfo.cursor >= int32(len(scanner.resultsInfo.results)) { 920 return nil, nil 921 } 922 selectedResultRecord := scanner.resultsInfo.results[scanner.paginationInfo.cursor] 923 key := selectedResultRecord.ID 924 // remove the reserved fields from CouchDB JSON and return the value and version 925 kv, err := couchDocToKeyValue(&couchdb.CouchDoc{JSONValue: selectedResultRecord.Value, Attachments: selectedResultRecord.Attachments}) 926 if err != nil { 927 return nil, err 928 } 929 scanner.resultsInfo.totalRecordsReturned++ 930 return &statedb.VersionedKV{ 931 CompositeKey: statedb.CompositeKey{Namespace: scanner.namespace, Key: key}, 932 VersionedValue: *kv.VersionedValue}, nil 933 } 934 935 func (scanner *queryScanner) Close() { 936 scanner = nil 937 } 938 939 func (scanner *queryScanner) GetBookmarkAndClose() string { 940 retval := "" 941 if scanner.queryDefinition.query != "" { 942 retval = scanner.paginationInfo.bookmark 943 } else { 944 retval = scanner.queryDefinition.startKey 945 } 946 scanner.Close() 947 return retval 948 } 949 950 func constructCacheValue(v *statedb.VersionedValue, rev string) *statedb.CacheValue { 951 return &statedb.CacheValue{ 952 VersionBytes: v.Version.ToBytes(), 953 Value: v.Value, 954 Metadata: v.Metadata, 955 AdditionalInfo: []byte(rev), 956 } 957 } 958 959 func constructVersionedValue(cv *statedb.CacheValue) (*statedb.VersionedValue, error) { 960 height, _, err := version.NewHeightFromBytes(cv.VersionBytes) 961 if err != nil { 962 return nil, err 963 } 964 965 return &statedb.VersionedValue{ 966 Value: cv.Value, 967 Version: height, 968 Metadata: cv.Metadata, 969 }, nil 970 }