Hyperledger Fabric enables COUCHDB as a state database
I. Overview
- Data Request Flow
The super ledger uses an endorsement/consensus model, where simulation execution and block validation are performed separately in the nodes of different roles. Simulation execution is concurrent, which can improve scalability and throughput:
- Endorsement node: Analog execution chain code
- Peer node: Validate transactions and submit
2. Super Ledger storage elements
The super ledger contains the following elements:
- Ledger number: Quickly query which ledger exists
- Ledger data: Actual chunk data storage
- Chunk index: Quickly query chunks/trades
- State data: The latest World State data
- Historical data: History of tracking Keys
Each peer node maintains four db, respectively:
- Ledger index Library (idstore): Storage Chainid
- State database (STATEDB): Storing world States
- History Database (HISTORYDB): Store the version change of key
- Block Index Library (Blockindex): Store block index
3. State database
The state database optional types include LEVELDB and couchdb. LEVELDB is the default key/value state database embedded in the peer process, and COUCHDB is an optional external state database. As with the LEVELDB key/value store, COUCHDB can store any binary data modeled as Chaincode (the COUCHDB attachment function is used internally for non-JSON binary data). However, when Chaincode values (for example, assets) are modeled as JSON data, stored as JSON documents, COUCHDB supports rich querying of chaincode data.
Both LEVELDB and COUCHDB support core chaincode operations, such as getting and setting a key (asset) and querying based on the key. A key can be queried by a range, and a composite key can be modeled to support an equivalent query against multiple parameters. For example, the asset ID can be used to query all assets owned by an entity as a key combination of the owner. These key-based queries can be used for read-only queries against the ledger, and for transactions that update the general ledger.
If you model the asset as JSON and use COUCHDB, you can use the Couchdb JSON query language in Chaincode to perform complex rich queries on the Chaincode data values, which are useful for understanding the contents of the ledger. For these types of queries, transactional protocol responses are typically useful to client applications, but are typically not submitted as transactions to the sort service. In fact, there is no guarantee that the result set will be stable between chaincode execution and rich query commit time, so it is not appropriate to use the results of rich queries to perform the final transaction update operation, unless you can guarantee the stability of the result set between the Chaincode execution time and the commit time. Or you can deal with potential changes in subsequent transactions. For example, if you perform a rich query on all of Alice's assets and transfer it to Bob, a new asset may be assigned to Alice by another transaction, which is another transaction between the Chaincode execution time and the commit time, which may miss this "virtual value" during this process.
COUCHDB is run as a standalone database process with peer, so there are additional considerations for setup, management, and operation. We can consider starting with the default embedded Leveldb, which can be transferred to COUCHDB if additional complex rich queries are required. Modeling Chaincode Asset data as JSON is a good practice so that we can execute the complex rich queries we need in the future.
Two. Enable Couchdb
This article uses the relevant components and resources in the Fabric-samples in the Hyperledger Fabric1.2, in the test environment (Fabric-samples/chaincode-docker-devmode) Launching the COUCHDB service via Docker
1. Configuring COUCHDB Boot Information
Reference: Fabric-samples/first-network/docker-compose-couch.yaml
couchdb0: container_name: couchdb0 image: hyperledger/fabric-couchdb # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. environment: - COUCHDB_USER= - COUCHDB_PASSWORD= # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service, # for example map it to utilize Fauxton User Interface in dev environments. ports: - "5984:5984" networks: - byfn
Modified: Add and modify Fabric-samples/chaincode-docker-devmode/docker-compose-simple.yaml end
couchdb: container_name: couchdb image: hyperledger/fabric-couchdb # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. environment: - COUCHDB_USER= - COUCHDB_PASSWORD= # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service, # for example map it to utilize Fauxton User Interface in dev environments. ports: - "5984:5984"
2. Configuring COUCHDB Connection Information
Reference Fabric-samples/first-network/docker-compose-couch.yaml
peer0.org1.example.com: environment: - CORE_LEDGER_STATE_STATEDATABASE=CouchDB - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb0:5984 # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD # provide the credentials for ledger to connect to CouchDB. The username and password must # match the username and password set for the associated CouchDB. - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME= - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD= depends_on: - couchdb0
Modified: Fabric-samples/chaincode-docker-devmode/docker-compose-simple.yaml in the peer module
Before modification
peer: container_name: peer image: hyperledger/fabric-peer environment: - CORE_PEER_ID=peer - CORE_PEER_ADDRESS=peer:7051 - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer:7051 - CORE_PEER_LOCALMSPID=DEFAULT - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock - CORE_LOGGING_LEVEL=DEBUG - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp volumes: - /var/run/:/host/var/run/ - ./msp:/etc/hyperledger/msp working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer command: peer node start --peer-chaincodedev=true -o orderer:7050 ports: - 7051:7051 - 7053:7053 depends_on: - orderer
After modification
peer: container_name: peer image: hyperledger/fabric-peer environment: - CORE_PEER_ID=peer - CORE_PEER_ADDRESS=peer:7051 - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer:7051 - CORE_PEER_LOCALMSPID=DEFAULT - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock - CORE_LOGGING_LEVEL=DEBUG - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp - CORE_LEDGER_STATE_STATEDATABASE=CouchDB - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb:5984 - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME= - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD= volumes: - /var/run/:/host/var/run/ - ./msp:/etc/hyperledger/msp working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer command: peer node start --peer-chaincodedev=true -o orderer:7050 ports: - 7051:7051 - 7053:7053 depends_on: - orderer - couchdb
Note The format of the JSON file and the consistency of the configuration information, such as the couchdb name, etc.
3. Start the test environment
# docker-compose -f docker-compose-simple.yaml up -d# docker container ls
Three. Writing a chain code
1. Code structure
Code Package: TestDB
Code files
- DOMAIN.GO//Data structure code
- MAIN.GO//Business test code
2. Data structure
package maintype BillStruct struct { ObjectType string `json:"DocType"` //对象类型定义 BillInfoID string `json:"BillInfoID"` //票据ID BillInfoAmt string `json:"BillInfoAmt"` //票据金额 BillInfoType string `json:"BillInfoType"` //票据类型 BillIsseData string `json:"BillIsseData"` //出票日期 BillDueDate string `json:"BillDueDate"` //到期日期 HoldrAcct string `json:"HoldrAcct"` //持票人名称 HoldrCmID string `json:"HoldrCmID"` //持票人ID WaitEndroseAcct string `json:"WaitEndroseAcct"` //待背书人名称 WaitEndorseCmID string `json:"WaitEndorseCmID"` //待背书人ID}
3. Test code
Please read the comment information carefully and do not do a code splitting description here
Package Mainimport ("Github.com/hyperledger/fabric/core/chaincode/shim" "FMT" "Github.com/hyperledger/fabric/pro Tos/peer "" Encoding/json "" bytes ")//define structural body couchdbchaincode as shim. Chaincodestubinterface implements the Class object type Couchdbchaincode struct {}//overrides shim. The Init method of the Chaincodestubinterface interface is the func (T *couchdbchaincode) init (stub shim. Chaincodestubinterface) Peer. Response {return shim. Success (nil)}//rewrite shim. The Invoke method of the Chaincodestubinterface interface is func (T *couchdbchaincode) invoke (stub shim. Chaincodestubinterface) Peer. Response {//Get user intent with parameter fun, args: = Stub. Getfunctionandparameters ()//based on user intent to determine what implementation function to use if fun = "Billinit" {return billinit} else if fun = = "Querybills" {return querybills (stub, args)} else if fun = = "Querywaitbills" {return Querywaitbills ( Stub, args)}//If the user's intent does not match, make the error prompt return shim. Error ("Illegal operation, invalid function name specified")}//billinit function: Initialize ticket data func billinit (stub shim. Chaincodestubinterface) Peer. Response {/* Defines the first ticket: Bearer name: AAA bearer Id:aiD to endorser name: No to endorser ID: none */billA: = billstruct{ObjectType: "Billobj", Billinfoid: "POC001", Billinfoamt: "£", Billinfotype: "111", Billissedata: "20180501", Billduedate: "20 180508 ", Holdracct:" AAA ", Holdrcmid:" AID ", Waitendroseacct:", Waitendorsecmid: "",}//through JSON. The Marshal method serializes the ticket to Billabyte, _: = json. Marshal (BillA)//through stubs. The Putstate method stores the serialized byte array as err: = Stub. Putstate (billa.billinfoid, billabyte) if err! = Nil {return shim. Error ("Initialize first ticket failed:" + err.) Error ())} BILLB: = billstruct{ObjectType: "Billobj", Billinfoid: "POC002", Billinfo AMT: "$", Billinfotype: "111", Billissedata: "20180501", Billduedate: "20180508", HOLDRACCT: "AAA", Holdrcmid: "AID", Waitendroseacct: "BBB", Waitendorsecmid: "BID", } Billbbyte, _: = json. Marshal(billb) Err = stub. Putstate (billb.billinfoid, billbbyte) if err! = Nil {return shim. Error ("Initialize second ticket failed:" + err.) Error ())} BILLC: = billstruct{ObjectType: "Billobj", Billinfoid: "POC003", Billinfo AMT: "$", Billinfotype: "111", Billissedata: "20180501", Billduedate: "20180508", HOLDRACCT: "BBB", Holdrcmid: "BID", Waitendroseacct: "CCC", Waitendorsecmid: "CID", } Billcbyte, _: = json. Marshal (BILLC) Err = stub. Putstate (billc.billinfoid, billcbyte) if err! = Nil {return shim. Error ("Initialize third ticket failed:" + err.) Error ())} BILLD: = billstruct{ObjectType: "Billobj", Billinfoid: "POC004", Billinfo AMT: "$", Billinfotype: "111", Billissedata: "20180501", Billduedate: "20180508", HOLDRACCT: "CCC", Holdrcmid: "CID", Waitendroseacct: "BBB", WaitendoRsecmid: "BID",} billdbyte, _: = json. Marshal (billd) Err = stub. Putstate (billd.billinfoid, billdbyte) if err! = Nil {return shim. Error ("Initialize fourth ticket failed:" + err.) Error ())} return shim. Success ([]byte ("All tickets initialized successfully"))}//querybills function: Bulk Query the list of bearer votes for the specified user func querybills (stub shim. Chaincodestubinterface, args []string) peer. Response {//Determines if there are parameters passed in if Len (args)! = 1 {return shim. Error ("must specify the bearer's ID number")}//Use the first parameter as User ID Holdrcmid: = args[0]/* To stitch the COUCHDB query string into a JSON string in the following format: {"s Elector ": {" DocType ":" Billobj "," Holdrcmid ":"%s "}} */queryString: = FMT. Sprintf ("{\" selector\ ": {\" doctype\ ": \" billobj\ ", \" holdrcmid\ ": \"%s\ "}}", Holdrcmid)// Data query operation with custom getbillbyquerystring function result, err: = getbillbyquerystring (stub, queryString) if err! = Nil {R Eturn Shim. Error ("Errors occurred" when checking the bearer's list of tickets in bulk according to the bearer's document number + Err.) Error ())} return shim. Success (Result)}//querywaitbills function: Bulk Query the list of pending notes for the specified user func QuerywaitbilLS (stub shim. Chaincodestubinterface, args []string) peer. Response {if Len (args)! = 1 {return shim. Error ("must specify the ID number of the person to be endorsed")} Waitendorsecmid: = Args[0] queryString: = FMT. Sprintf ("{\" selector\ ": {\" doctype\ ": \" billobj\ ", \" waitendorsecmid\ ": \"%s\ "}}", Waitendorsecmid) result, err: = Getbillbyquerystring (stub, queryString) if err! = Nil {return shim. Error ("Errors occurred" in bulk querying the list of notes to be endorsed according to the ID number of the person to be endorsed + ERR. Error ())} return shim. Success (Result)}//Custom function: getbillbyquerystring: Queries data based on the specified query string (COUCHDB query statement) Func getbillbyquerystring (stub shim. Chaincodestubinterface, queryString string) ([]byte, error) {//through stub. The Getqueryresult method gets the iterator iterator iterator, err: = Stub. Getqueryresult (queryString) if err! = Nil {return nil, err}//delay close iterator iterator defer iterator. Close ()//define byte buffer variable var buffer bytes. Buffer//define delimiter var issplit bool//iterate over iterators for iterator. Hasnext () {///through the next () method of the iterator gets the key and value of the next object (*queryresult. KV) Result, err: = iterator. Next () if err! = Nil {return nil, err} if issplit {buffer. WriteString (";") }//define format//Key:result.key result. Value buffer. WriteString ("key:") buffer. WriteString (result. Key) buffer. WriteString (", Value:") buffer. WriteString (String (Result). Value)////Get to the first value, set Issplit to True to split with the second value Issplit = true}//returns the byte type of the buffer object, return buffer. Bytes (), Nil}func Main () {//Start the chain code couchdbchaincode ERR: = shim. Start (New (Couchdbchaincode))//If there is an error, prompt for an error message if err! = Nil {fmt. Errorf (Err. Error ())}}
Four. Install the chain code
1. Upload the link code
Upload chain code package TestDB to: Fabric-samples/chaincode
# ls /home/bruce/hyfa/fabric-samples/chaincode/testdb/ domain.go main.go
2. Compile the chain code
# cd /home/bruce/hyfa/fabric-samples/chaincode/testdb/# go build # ls domain.go main.go testdb
3. Start the chain code
Enter the Chaincode container for operation
# docker container exec -it chaincode bash #进入chaincode容器进行操作# cd testdb/# CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=testCouchDB:1.0 ./testdb2018-08-05 10:33:37.063 UTC [shim] SetupChaincodeLogging -> INFO 001 Chaincode log level not provided; defaulting to: INFO2018-08-05 10:33:37.063 UTC [shim] SetupChaincodeLogging -> INFO 002 Chaincode (build level: ) starting up ...
4. Installation and instantiation of the chain code
Entering the CLI container for operation
# docker container exec -it cli bash# peer chaincode install -n testCouchDB -v 1.0 -p chaincodedev/chaincode/testdb# peer chaincode instantiate -n testCouchDB -v 1.0 -C myc -c ‘{"Args":["init"]}‘如有更新请用如下命令进行操作# peer chaincode install -n testCouchDB -v 1.1 -p chaincodedev/chaincode/testdb# peer chaincode upgrade -n testCouchDB -v 1.1 -C myc -c ‘{"Args":["init"]}‘
Five. Test chain code
1. Initializing notes
# peer chaincode invoke -n testCouchDB -C myc -c ‘{"Args":["billInit"]}‘
2. Query the notes held by the specified user
# peer chaincode query -n testCouchDB -C myc -c ‘{"Args":["queryBills","AID"]}‘
key: POC001, value: { "BillDueDate": "20180508", "BillInfoAmt": "1000", "BillInfoID": "POC001", "BillInfoType": "111", "BillIsseData": "20180501", "HoldrAcct": "AAA", "HoldrCmID": "AID", "WaitEndorseCmID": "", "WaitEndroseAcct": "", "docType": "billObj"};key: POC002, value: { "BillDueDate": "20180508", "BillInfoAmt": "1000", "BillInfoID": "POC002", "BillInfoType": "111", "BillIsseData": "20180501", "HoldrAcct": "AAA", "HoldrCmID": "AID", "WaitEndorseCmID": "BID", "WaitEndroseAcct": "BBB", "docType": "billObj"}
The query results can see the delimiter we define;
3. Query the specified user to endorse the ticket
# peer chaincode query -n testCouchDB -C myc -c ‘{"Args":["queryWaitBills","BID"]}‘
key: POC002, value: { "BillDueDate": "20180508", "BillInfoAmt": "1000", "BillInfoID": "POC002", "BillInfoType": "111", "BillIsseData": "20180501", "HoldrAcct": "AAA", "HoldrCmID": "AID", "WaitEndorseCmID": "BID", "WaitEndroseAcct": "BBB", "docType": "billObj"};key: POC004, value: { "BillDueDate": "20180508", "BillInfoAmt": "1000", "BillInfoID": "POC004", "BillInfoType": "111", "BillIsseData": "20180501", "HoldrAcct": "CCC", "HoldrCmID": "CID", "WaitEndorseCmID": "BID", "WaitEndroseAcct": "BBB", "docType": "billObj"}
Also about LEVELDB,COUCHDB or MongoDB, in the future with the Hyperledger fabric version of the changes to take a different database type, we will wait and see, now the only thing to do is to use the existing resources under the Hyperledger Fabric creates the greatest business value for business scenarios.
Hyperledger fabric Enable COUCHDB for the state database