Hyperledger Fabric 啓用CouchDB做爲狀態數據庫node
超級帳本採用背書/共識模型,模擬執行和區塊驗證是在不一樣角色的節點中分開執行的。模擬執行是併發的,這樣能夠提升擴展性和吞吐量:git
超級帳本包含如下元素:github
每一個Peer節點會維護四個DB,分別爲:docker
狀態數據庫可選類型包括LevelDB和CouchDB。LevelDB是嵌入在peer進程中的默認鍵/值狀態數據庫,CouchDB是一個可選的外部狀態數據庫。與LevelDB鍵/值存儲同樣,CouchDB能夠存儲任何以chaincode建模的二進制數據(CouchDB附件函數在內部用於非json二進制數據)。可是,當chaincode值(例如,資產)被建模爲JSON數據時,做爲JSON文檔存儲,CouchDB支持對chaincode數據進行豐富的查詢。數據庫
LevelDB和CouchDB都支持核心chaincode操做,例如獲取和設置一個鍵(資產),並根據鍵進行查詢。鍵能夠經過範圍查詢,能夠對組合鍵進行建模,以支持針對多個參數的等價查詢。例如,做爲全部者的組合鍵,資產id能夠用於查詢某個實體擁有的全部資產。這些基於key的查詢能夠用於針對帳本的只讀查詢,以及更新總帳的事務。json
若是將資產建模爲JSON並使用CouchDB,那麼就可使用chaincode中的CouchDB JSON查詢語言對chaincode數據值執行復雜的富查詢,這些類型的查詢對於理解帳本上的內容頗有幫助。對於這些類型的查詢,事務協議響應一般對客戶端應用程序有用,但一般不會做爲事務提交到排序服務。事實上,也沒法保證結果集在chaincode執行與富查詢提交時間之間的穩定性,所以使用富查詢的結果去執行最終的事務更新操做是不合適的,除非能夠保證結果集在chaincode執行時間與提交時間之間的穩定性,或者能夠處理在後續交易中的潛在變化。例如,若是對Alice所擁有的全部資產執行一個富查詢並將其傳輸給Bob,那麼一個新的資產可能會被另外一個事務分配給Alice,這是在chaincode執行時間和提交時間之間的另外一個事務,可能此過程當中會錯過這個「虛值」。數組
CouchDB做爲一個獨立的數據庫進程與peer一塊兒運行,所以在設置、管理和操做方面有額外的考慮。咱們能夠考慮從默認的嵌入式LevelDB開始,若是須要額外的複雜的富查詢,能夠轉移到CouchDB。將chaincode資產數據建模爲JSON是一種很好的作法,這樣咱們就能夠在未來執行須要的複雜的富查詢。bash
本文均採用Hyperledger Fabric1.2中fabric-samples中相關組件與資源,在測試環境(fabric-samples/chaincode-docker-devmode)經過Docker啓動CouchDB服務數據結構
參考: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
修改:fabric-samples/chaincode-docker-devmode/docker-compose-simple.yaml 末尾添加並修改
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"
參考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
修改:fabric-samples/chaincode-docker-devmode/docker-compose-simple.yaml 中peer模塊
修改前
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
修改後
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
注意JSON文件的格式以及配置信息的一致性,如couchdb名稱等
# docker-compose -f docker-compose-simple.yaml up -d # docker container ls
代碼包:testdb
代碼文件
package main type 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 }
請仔細閱讀註釋信息,此處不作代碼分割描述
package main import ( "github.com/hyperledger/fabric/core/chaincode/shim" "fmt" "github.com/hyperledger/fabric/protos/peer" "encoding/json" "bytes" ) //定義結構體CouchDBChaincode,做爲shim.ChaincodeStubInterface實現類對象 type CouchDBChaincode struct { } //重寫shim.ChaincodeStubInterface接口的Init方法 func (t *CouchDBChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response { return shim.Success(nil) } //重寫shim.ChaincodeStubInterface接口的Invoke方法 func (t *CouchDBChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response { //獲取用戶意圖與參數 fun, args := stub.GetFunctionAndParameters() //根據用戶意圖判斷使用何種實現函數 if fun == "billInit" { return billInit(stub) } else if fun == "queryBills" { return queryBills(stub, args) } else if fun == "queryWaitBills" { return queryWaitBills(stub, args) } //若是用戶意圖不符合如上,進行錯誤提示 return shim.Error("非法操做,指定的函數名無效") } //billInit函數:初始化票據數據 func billInit(stub shim.ChaincodeStubInterface) peer.Response { /* 定義第一個票據: 持票人名稱:AAA 持票人ID:AID 待背書人名稱:無 待背書人ID:無 */ billA := BillStruct{ ObjectType: "billObj", BillInfoID: "POC001", BillInfoAmt: "1000", BillInfoType: "111", BillIsseData: "20180501", BillDueDate: "20180508", HoldrAcct: "AAA", HoldrCmID: "AID", WaitEndroseAcct: "", WaitEndorseCmID: "", } //經過json.Marshal方法對票據進行序列化操做 billAByte, _ := json.Marshal(billA) //經過stub.PutState方法存儲序列化後的字節數組 err := stub.PutState(billA.BillInfoID, billAByte) if err != nil { return shim.Error("初始化第一個票據失敗:" + err.Error()) } billB := BillStruct{ ObjectType: "billObj", BillInfoID: "POC002", BillInfoAmt: "1000", 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("初始化第二個票據失敗:" + err.Error()) } billC := BillStruct{ ObjectType: "billObj", BillInfoID: "POC003", BillInfoAmt: "1000", 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("初始化第三個票據失敗:" + err.Error()) } billD := BillStruct{ ObjectType: "billObj", BillInfoID: "POC004", BillInfoAmt: "1000", 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("初始化第四個票據失敗:" + err.Error()) } return shim.Success([]byte("全部票據初始化成功")) } //queryBills函數:批量查詢指定用戶的持票列表 func queryBills(stub shim.ChaincodeStubInterface, args []string) peer.Response { //判斷是否有參數傳入 if len(args) != 1 { return shim.Error("必須指定持票人的證件號碼") } //將第一個參數做爲用戶ID holdrCmID := args[0] /*將CouchDB查詢字符串拼接成一個JSON串,格式以下: { "selector": { "docType": "billObj", "HoldrCmID": "%s" } } */ queryString := fmt.Sprintf("{\"selector\":{\"DocType\":\"billObj\",\"HoldrCmID\":\"%s\"}}", holdrCmID) //經過自定義的getBillByQueryString函數進行數據查詢操做 result, err := getBillByQueryString(stub, queryString) if err != nil { return shim.Error("根據持票人的證件號碼批量查詢持票人持有票據列表時發生錯誤" + err.Error()) } return shim.Success(result) } //queryWaitBills函數:批量查詢指定用戶的待背書票據列表 func queryWaitBills(stub shim.ChaincodeStubInterface, args []string) peer.Response { if len(args) != 1 { return shim.Error("必須指定待背書人的證件號碼") } waitEndorseCmID := args[0] queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"billObj\",\"WaitEndorseCmID\":\"%s\"}}", waitEndorseCmID) result, err := getBillByQueryString(stub, queryString) if err != nil { return shim.Error("根據待背書人的證件號碼批量查詢待背書票據列表時發生錯誤" + err.Error()) } return shim.Success(result) } //自定義函數:getBillByQueryString:根據指定的查詢字符串(CouchDB查詢語句)查詢數據 func getBillByQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) { //經過stub.GetQueryResult方法獲取迭代器iterator iterator, err := stub.GetQueryResult(queryString) if err != nil { return nil, err } //延遲關閉迭代器iterator defer iterator.Close() //定義字節緩衝變量 var buffer bytes.Buffer //定義分割符 var isSplit bool //對迭代器進行遍歷操做 for iterator.HasNext() { //經過迭代器的Next()方法獲取下一個對象的Key與Value值(*queryresult.KV) result, err := iterator.Next() if err != nil { return nil, err } if isSplit { buffer.WriteString(";") } //定義格式 // key:result.key result.Value buffer.WriteString("key:") buffer.WriteString(result.Key) buffer.WriteString(",value:") buffer.WriteString(string(result.Value)) //獲取到第一個值後,將isSplit設置爲true,用於跟第二個值進行分割 isSplit = true } //返回buffer對象的字節類型 return buffer.Bytes(), nil } func main() { //啓動鏈碼CouchDBChaincode err := shim.Start(new(CouchDBChaincode)) //若有報錯,提示報錯信息 if err != nil { fmt.Errorf(err.Error()) } }
上傳鏈碼包testdb至:fabric-samples/chaincode中
# ls /home/bruce/hyfa/fabric-samples/chaincode/testdb/ domain.go main.go
# cd /home/bruce/hyfa/fabric-samples/chaincode/testdb/ # go build # ls domain.go main.go testdb
進入chaincode容器進行操做
# docker container exec -it chaincode bash #進入chaincode容器進行操做 # cd testdb/ # CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=testCouchDB:1.0 ./testdb 2018-08-05 10:33:37.063 UTC [shim] SetupChaincodeLogging -> INFO 001 Chaincode log level not provided; defaulting to: INFO 2018-08-05 10:33:37.063 UTC [shim] SetupChaincodeLogging -> INFO 002 Chaincode (build level: ) starting up ...
進入cli容器進行操做
# 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"]}'
# peer chaincode invoke -n testCouchDB -C myc -c '{"Args":["billInit"]}'
# 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" }
查詢結果能夠看到咱們定義的分隔符;
# 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" }
另外關於LevelDB,CouchDB仍是MongoDB,從此可能隨着Hyperledger Fabric的版本變化而採起不一樣的數據庫類型,咱們拭目以待,如今惟一能作的,就是在已有的資源下面用Hyperledger Fabric爲業務場景創造最大的業務價值。