github.com/renegr87/renegr87@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  }