github.com/leonlxy/hyperledger@v1.0.0-alpha.0.20170427033203-34922035d248/core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go (about)

     1  /*
     2  Copyright IBM Corp. 2016, 2017 All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  		 http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package statecouchdb
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"strconv"
    25  	"strings"
    26  	"sync"
    27  
    28  	"github.com/hyperledger/fabric/common/flogging"
    29  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb"
    30  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version"
    31  	"github.com/hyperledger/fabric/core/ledger/ledgerconfig"
    32  	"github.com/hyperledger/fabric/core/ledger/util/couchdb"
    33  )
    34  
    35  var logger = flogging.MustGetLogger("statecouchdb")
    36  
    37  var compositeKeySep = []byte{0x00}
    38  var lastKeyIndicator = byte(0x01)
    39  
    40  var binaryWrapper = "valueBytes"
    41  
    42  //querySkip is implemented for future use by query paging
    43  //currently defaulted to 0 and is not used
    44  var querySkip = 0
    45  
    46  // VersionedDBProvider implements interface VersionedDBProvider
    47  type VersionedDBProvider struct {
    48  	couchInstance *couchdb.CouchInstance
    49  	databases     map[string]*VersionedDB
    50  	mux           sync.Mutex
    51  	openCounts    uint64
    52  }
    53  
    54  // NewVersionedDBProvider instantiates VersionedDBProvider
    55  func NewVersionedDBProvider() (*VersionedDBProvider, error) {
    56  	logger.Debugf("constructing CouchDB VersionedDBProvider")
    57  	couchDBDef := couchdb.GetCouchDBDefinition()
    58  	couchInstance, err := couchdb.CreateCouchInstance(couchDBDef.URL, couchDBDef.Username, couchDBDef.Password,
    59  		couchDBDef.MaxRetries, couchDBDef.MaxRetriesOnStartup, couchDBDef.RequestTimeout)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	return &VersionedDBProvider{couchInstance, make(map[string]*VersionedDB), sync.Mutex{}, 0}, nil
    65  }
    66  
    67  // GetDBHandle gets the handle to a named database
    68  func (provider *VersionedDBProvider) GetDBHandle(dbName string) (statedb.VersionedDB, error) {
    69  	provider.mux.Lock()
    70  	defer provider.mux.Unlock()
    71  
    72  	vdb := provider.databases[dbName]
    73  	if vdb == nil {
    74  		var err error
    75  		vdb, err = newVersionedDB(provider.couchInstance, dbName)
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  		provider.databases[dbName] = vdb
    80  	}
    81  	return vdb, nil
    82  }
    83  
    84  // Close closes the underlying db instance
    85  func (provider *VersionedDBProvider) Close() {
    86  	// No close needed on Couch
    87  }
    88  
    89  // VersionedDB implements VersionedDB interface
    90  type VersionedDB struct {
    91  	db     *couchdb.CouchDatabase
    92  	dbName string
    93  }
    94  
    95  // newVersionedDB constructs an instance of VersionedDB
    96  func newVersionedDB(couchInstance *couchdb.CouchInstance, dbName string) (*VersionedDB, error) {
    97  	// CreateCouchDatabase creates a CouchDB database object, as well as the underlying database if it does not exist
    98  	db, err := couchdb.CreateCouchDatabase(*couchInstance, dbName)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	return &VersionedDB{db, dbName}, nil
   103  }
   104  
   105  // Open implements method in VersionedDB interface
   106  func (vdb *VersionedDB) Open() error {
   107  	// no need to open db since a shared couch instance is used
   108  	return nil
   109  }
   110  
   111  // Close implements method in VersionedDB interface
   112  func (vdb *VersionedDB) Close() {
   113  	// no need to close db since a shared couch instance is used
   114  }
   115  
   116  // GetState implements method in VersionedDB interface
   117  func (vdb *VersionedDB) GetState(namespace string, key string) (*statedb.VersionedValue, error) {
   118  	logger.Debugf("GetState(). ns=%s, key=%s", namespace, key)
   119  
   120  	compositeKey := constructCompositeKey(namespace, key)
   121  
   122  	couchDoc, _, err := vdb.db.ReadDoc(string(compositeKey))
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	if couchDoc == nil {
   127  		return nil, nil
   128  	}
   129  
   130  	//remove the data wrapper and return the value and version
   131  	returnValue, returnVersion := removeDataWrapper(couchDoc.JSONValue, couchDoc.Attachments)
   132  
   133  	return &statedb.VersionedValue{Value: returnValue, Version: &returnVersion}, nil
   134  }
   135  
   136  func removeDataWrapper(wrappedValue []byte, attachments []*couchdb.Attachment) ([]byte, version.Height) {
   137  
   138  	//initialize the return value
   139  	returnValue := []byte{}
   140  
   141  	//initialize a default return version
   142  	returnVersion := version.NewHeight(0, 0)
   143  
   144  	//create a generic map for the json
   145  	jsonResult := make(map[string]interface{})
   146  
   147  	//unmarshal the selected json into the generic map
   148  	decoder := json.NewDecoder(bytes.NewBuffer(wrappedValue))
   149  	decoder.UseNumber()
   150  	_ = decoder.Decode(&jsonResult)
   151  
   152  	// handle binary or json data
   153  	if jsonResult[dataWrapper] == nil && attachments != nil { // binary attachment
   154  		// get binary data from attachment
   155  		for _, attachment := range attachments {
   156  			if attachment.Name == binaryWrapper {
   157  				returnValue = attachment.AttachmentBytes
   158  			}
   159  		}
   160  	} else {
   161  		//place the result json in the data key
   162  		returnMap := jsonResult[dataWrapper]
   163  
   164  		//marshal the mapped data.   this wrappers the result in a key named "data"
   165  		returnValue, _ = json.Marshal(returnMap)
   166  
   167  	}
   168  
   169  	//create an array containing the blockNum and txNum
   170  	versionArray := strings.Split(fmt.Sprintf("%s", jsonResult["version"]), ":")
   171  
   172  	//convert the blockNum from String to unsigned int
   173  	blockNum, _ := strconv.ParseUint(versionArray[0], 10, 64)
   174  
   175  	//convert the txNum from String to unsigned int
   176  	txNum, _ := strconv.ParseUint(versionArray[1], 10, 64)
   177  
   178  	//create the version based on the blockNum and txNum
   179  	returnVersion = version.NewHeight(blockNum, txNum)
   180  
   181  	return returnValue, *returnVersion
   182  
   183  }
   184  
   185  // GetStateMultipleKeys implements method in VersionedDB interface
   186  func (vdb *VersionedDB) GetStateMultipleKeys(namespace string, keys []string) ([]*statedb.VersionedValue, error) {
   187  
   188  	vals := make([]*statedb.VersionedValue, len(keys))
   189  	for i, key := range keys {
   190  		val, err := vdb.GetState(namespace, key)
   191  		if err != nil {
   192  			return nil, err
   193  		}
   194  		vals[i] = val
   195  	}
   196  	return vals, nil
   197  
   198  }
   199  
   200  // GetStateRangeScanIterator implements method in VersionedDB interface
   201  // startKey is inclusive
   202  // endKey is exclusive
   203  func (vdb *VersionedDB) GetStateRangeScanIterator(namespace string, startKey string, endKey string) (statedb.ResultsIterator, error) {
   204  
   205  	//Get the querylimit from core.yaml
   206  	queryLimit := ledgerconfig.GetQueryLimit()
   207  
   208  	compositeStartKey := constructCompositeKey(namespace, startKey)
   209  	compositeEndKey := constructCompositeKey(namespace, endKey)
   210  	if endKey == "" {
   211  		compositeEndKey[len(compositeEndKey)-1] = lastKeyIndicator
   212  	}
   213  	queryResult, err := vdb.db.ReadDocRange(string(compositeStartKey), string(compositeEndKey), queryLimit, querySkip)
   214  	if err != nil {
   215  		logger.Debugf("Error calling ReadDocRange(): %s\n", err.Error())
   216  		return nil, err
   217  	}
   218  	logger.Debugf("Exiting GetStateRangeScanIterator")
   219  	return newKVScanner(namespace, *queryResult), nil
   220  
   221  }
   222  
   223  // ExecuteQuery implements method in VersionedDB interface
   224  func (vdb *VersionedDB) ExecuteQuery(namespace, query string) (statedb.ResultsIterator, error) {
   225  
   226  	//Get the querylimit from core.yaml
   227  	queryLimit := ledgerconfig.GetQueryLimit()
   228  
   229  	queryString, err := ApplyQueryWrapper(namespace, query, queryLimit, 0)
   230  	if err != nil {
   231  		logger.Debugf("Error calling ApplyQueryWrapper(): %s\n", err.Error())
   232  		return nil, err
   233  	}
   234  
   235  	queryResult, err := vdb.db.QueryDocuments(queryString)
   236  	if err != nil {
   237  		logger.Debugf("Error calling QueryDocuments(): %s\n", err.Error())
   238  		return nil, err
   239  	}
   240  	logger.Debugf("Exiting ExecuteQuery")
   241  	return newQueryScanner(*queryResult), nil
   242  }
   243  
   244  // ApplyUpdates implements method in VersionedDB interface
   245  func (vdb *VersionedDB) ApplyUpdates(batch *statedb.UpdateBatch, height *version.Height) error {
   246  
   247  	namespaces := batch.GetUpdatedNamespaces()
   248  	for _, ns := range namespaces {
   249  		updates := batch.GetUpdates(ns)
   250  		for k, vv := range updates {
   251  			compositeKey := constructCompositeKey(ns, k)
   252  			logger.Debugf("Channel [%s]: Applying key=[%#v]", vdb.dbName, compositeKey)
   253  
   254  			//convert nils to deletes
   255  			if vv.Value == nil {
   256  
   257  				vdb.db.DeleteDoc(string(compositeKey), "")
   258  
   259  			} else {
   260  				couchDoc := &couchdb.CouchDoc{}
   261  
   262  				//Check to see if the value is a valid JSON
   263  				//If this is not a valid JSON, then store as an attachment
   264  				if couchdb.IsJSON(string(vv.Value)) {
   265  					// Handle it as json
   266  					couchDoc.JSONValue = addVersionAndChainCodeID(vv.Value, ns, vv.Version)
   267  				} else { // if the data is not JSON, save as binary attachment in Couch
   268  
   269  					attachment := &couchdb.Attachment{}
   270  					attachment.AttachmentBytes = vv.Value
   271  					attachment.ContentType = "application/octet-stream"
   272  					attachment.Name = binaryWrapper
   273  					attachments := append([]*couchdb.Attachment{}, attachment)
   274  
   275  					couchDoc.Attachments = attachments
   276  					couchDoc.JSONValue = addVersionAndChainCodeID(nil, ns, vv.Version)
   277  				}
   278  
   279  				// SaveDoc using couchdb client and use attachment to persist the binary data
   280  				rev, err := vdb.db.SaveDoc(string(compositeKey), "", couchDoc)
   281  				if err != nil {
   282  					logger.Errorf("Error during Commit(): %s\n", err.Error())
   283  					return err
   284  				}
   285  				if rev != "" {
   286  					logger.Debugf("Saved document revision number: %s\n", rev)
   287  				}
   288  			}
   289  		}
   290  	}
   291  
   292  	// Record a savepoint at a given height
   293  	err := vdb.recordSavepoint(height)
   294  	if err != nil {
   295  		logger.Errorf("Error during recordSavepoint: %s\n", err.Error())
   296  		return err
   297  	}
   298  
   299  	return nil
   300  }
   301  
   302  //addVersionAndChainCodeID adds keys for version and chaincodeID to the JSON value
   303  func addVersionAndChainCodeID(value []byte, chaincodeID string, version *version.Height) []byte {
   304  
   305  	//create a version mapping
   306  	jsonMap := map[string]interface{}{"version": fmt.Sprintf("%v:%v", version.BlockNum, version.TxNum)}
   307  
   308  	//add the chaincodeID
   309  	jsonMap["chaincodeid"] = chaincodeID
   310  
   311  	//Add the wrapped data if the value is not null
   312  	if value != nil {
   313  
   314  		//create a new genericMap
   315  		rawJSON := (*json.RawMessage)(&value)
   316  
   317  		//add the rawJSON to the map
   318  		jsonMap[dataWrapper] = rawJSON
   319  
   320  	}
   321  
   322  	//marshal the data to a byte array
   323  	returnJSON, _ := json.Marshal(jsonMap)
   324  
   325  	return returnJSON
   326  
   327  }
   328  
   329  // Savepoint docid (key) for couchdb
   330  const savepointDocID = "statedb_savepoint"
   331  
   332  // Savepoint data for couchdb
   333  type couchSavepointData struct {
   334  	BlockNum  uint64 `json:"BlockNum"`
   335  	TxNum     uint64 `json:"TxNum"`
   336  	UpdateSeq string `json:"UpdateSeq"`
   337  }
   338  
   339  // recordSavepoint Record a savepoint in statedb.
   340  // Couch parallelizes writes in cluster or sharded setup and ordering is not guaranteed.
   341  // Hence we need to fence the savepoint with sync. So ensure_full_commit is called before AND after writing savepoint document
   342  // TODO: Optimization - merge 2nd ensure_full_commit with savepoint by using X-Couch-Full-Commit header
   343  func (vdb *VersionedDB) recordSavepoint(height *version.Height) error {
   344  	var err error
   345  	var savepointDoc couchSavepointData
   346  	// ensure full commit to flush all changes until now to disk
   347  	dbResponse, err := vdb.db.EnsureFullCommit()
   348  	if err != nil || dbResponse.Ok != true {
   349  		logger.Errorf("Failed to perform full commit\n")
   350  		return errors.New("Failed to perform full commit")
   351  	}
   352  
   353  	// construct savepoint document
   354  	// UpdateSeq would be useful if we want to get all db changes since a logical savepoint
   355  	dbInfo, _, err := vdb.db.GetDatabaseInfo()
   356  	if err != nil {
   357  		logger.Errorf("Failed to get DB info %s\n", err.Error())
   358  		return err
   359  	}
   360  	savepointDoc.BlockNum = height.BlockNum
   361  	savepointDoc.TxNum = height.TxNum
   362  	savepointDoc.UpdateSeq = dbInfo.UpdateSeq
   363  
   364  	savepointDocJSON, err := json.Marshal(savepointDoc)
   365  	if err != nil {
   366  		logger.Errorf("Failed to create savepoint data %s\n", err.Error())
   367  		return err
   368  	}
   369  
   370  	// SaveDoc using couchdb client and use JSON format
   371  	_, err = vdb.db.SaveDoc(savepointDocID, "", &couchdb.CouchDoc{JSONValue: savepointDocJSON, Attachments: nil})
   372  	if err != nil {
   373  		logger.Errorf("Failed to save the savepoint to DB %s\n", err.Error())
   374  		return err
   375  	}
   376  
   377  	// ensure full commit to flush savepoint to disk
   378  	dbResponse, err = vdb.db.EnsureFullCommit()
   379  	if err != nil || dbResponse.Ok != true {
   380  		logger.Errorf("Failed to perform full commit\n")
   381  		return errors.New("Failed to perform full commit")
   382  	}
   383  	return nil
   384  }
   385  
   386  // GetLatestSavePoint implements method in VersionedDB interface
   387  func (vdb *VersionedDB) GetLatestSavePoint() (*version.Height, error) {
   388  
   389  	var err error
   390  	couchDoc, _, err := vdb.db.ReadDoc(savepointDocID)
   391  	if err != nil {
   392  		logger.Errorf("Failed to read savepoint data %s\n", err.Error())
   393  		return nil, err
   394  	}
   395  
   396  	// ReadDoc() not found (404) will result in nil response, in these cases return height nil
   397  	if couchDoc == nil || couchDoc.JSONValue == nil {
   398  		return nil, nil
   399  	}
   400  
   401  	savepointDoc := &couchSavepointData{}
   402  	err = json.Unmarshal(couchDoc.JSONValue, &savepointDoc)
   403  	if err != nil {
   404  		logger.Errorf("Failed to unmarshal savepoint data %s\n", err.Error())
   405  		return nil, err
   406  	}
   407  
   408  	return &version.Height{BlockNum: savepointDoc.BlockNum, TxNum: savepointDoc.TxNum}, nil
   409  }
   410  
   411  func constructCompositeKey(ns string, key string) []byte {
   412  	compositeKey := []byte(ns)
   413  	compositeKey = append(compositeKey, compositeKeySep...)
   414  	compositeKey = append(compositeKey, []byte(key)...)
   415  	return compositeKey
   416  }
   417  
   418  func splitCompositeKey(compositeKey []byte) (string, string) {
   419  	split := bytes.SplitN(compositeKey, compositeKeySep, 2)
   420  	return string(split[0]), string(split[1])
   421  }
   422  
   423  type kvScanner struct {
   424  	cursor    int
   425  	namespace string
   426  	results   []couchdb.QueryResult
   427  }
   428  
   429  func newKVScanner(namespace string, queryResults []couchdb.QueryResult) *kvScanner {
   430  	return &kvScanner{-1, namespace, queryResults}
   431  }
   432  
   433  func (scanner *kvScanner) Next() (statedb.QueryResult, error) {
   434  
   435  	scanner.cursor++
   436  
   437  	if scanner.cursor >= len(scanner.results) {
   438  		return nil, nil
   439  	}
   440  
   441  	selectedKV := scanner.results[scanner.cursor]
   442  
   443  	_, key := splitCompositeKey([]byte(selectedKV.ID))
   444  
   445  	//remove the data wrapper and return the value and version
   446  	returnValue, returnVersion := removeDataWrapper(selectedKV.Value, selectedKV.Attachments)
   447  
   448  	return &statedb.VersionedKV{
   449  		CompositeKey:   statedb.CompositeKey{Namespace: scanner.namespace, Key: key},
   450  		VersionedValue: statedb.VersionedValue{Value: returnValue, Version: &returnVersion}}, nil
   451  }
   452  
   453  func (scanner *kvScanner) Close() {
   454  	scanner = nil
   455  }
   456  
   457  type queryScanner struct {
   458  	cursor  int
   459  	results []couchdb.QueryResult
   460  }
   461  
   462  func newQueryScanner(queryResults []couchdb.QueryResult) *queryScanner {
   463  	return &queryScanner{-1, queryResults}
   464  }
   465  
   466  func (scanner *queryScanner) Next() (statedb.QueryResult, error) {
   467  
   468  	scanner.cursor++
   469  
   470  	if scanner.cursor >= len(scanner.results) {
   471  		return nil, nil
   472  	}
   473  
   474  	selectedResultRecord := scanner.results[scanner.cursor]
   475  
   476  	namespace, key := splitCompositeKey([]byte(selectedResultRecord.ID))
   477  
   478  	//remove the data wrapper and return the value and version
   479  	returnValue, returnVersion := removeDataWrapper(selectedResultRecord.Value, selectedResultRecord.Attachments)
   480  
   481  	return &statedb.VersionedKV{
   482  		CompositeKey:   statedb.CompositeKey{Namespace: namespace, Key: key},
   483  		VersionedValue: statedb.VersionedValue{Value: returnValue, Version: &returnVersion}}, nil
   484  }
   485  
   486  func (scanner *queryScanner) Close() {
   487  	scanner = nil
   488  }