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 }