您好,登錄后才能下訂單哦!
Hyperledger Fabric 啟用CouchDB作為狀態(tài)數(shù)據(jù)庫(kù)
超級(jí)賬本采用背書(shū)/共識(shí)模型,模擬執(zhí)行和區(qū)塊驗(yàn)證是在不同角色的節(jié)點(diǎn)中分開(kāi)執(zhí)行的。模擬執(zhí)行是并發(fā)的,這樣可以提高擴(kuò)展性和吞吐量:
超級(jí)賬本包含以下元素:
每個(gè)Peer節(jié)點(diǎn)會(huì)維護(hù)四個(gè)DB,分別為:
狀態(tài)數(shù)據(jù)庫(kù)可選類型包括LevelDB和CouchDB。LevelDB是嵌入在peer進(jìn)程中的默認(rèn)鍵/值狀態(tài)數(shù)據(jù)庫(kù),CouchDB是一個(gè)可選的外部狀態(tài)數(shù)據(jù)庫(kù)。與LevelDB鍵/值存儲(chǔ)一樣,CouchDB可以存儲(chǔ)任何以chaincode建模的二進(jìn)制數(shù)據(jù)(CouchDB附件函數(shù)在內(nèi)部用于非json二進(jìn)制數(shù)據(jù))。但是,當(dāng)chaincode值(例如,資產(chǎn))被建模為JSON數(shù)據(jù)時(shí),作為JSON文檔存儲(chǔ),CouchDB支持對(duì)chaincode數(shù)據(jù)進(jìn)行豐富的查詢。
LevelDB和CouchDB都支持核心chaincode操作,例如獲取和設(shè)置一個(gè)鍵(資產(chǎn)),并根據(jù)鍵進(jìn)行查詢。鍵可以通過(guò)范圍查詢,可以對(duì)組合鍵進(jìn)行建模,以支持針對(duì)多個(gè)參數(shù)的等價(jià)查詢。例如,作為所有者的組合鍵,資產(chǎn)id可以用于查詢某個(gè)實(shí)體擁有的所有資產(chǎn)。這些基于key的查詢可以用于針對(duì)賬本的只讀查詢,以及更新總賬的事務(wù)。
如果將資產(chǎn)建模為JSON并使用CouchDB,那么就可以使用chaincode中的CouchDB JSON查詢語(yǔ)言對(duì)chaincode數(shù)據(jù)值執(zhí)行復(fù)雜的富查詢,這些類型的查詢對(duì)于理解賬本上的內(nèi)容很有幫助。對(duì)于這些類型的查詢,事務(wù)協(xié)議響應(yīng)通常對(duì)客戶端應(yīng)用程序有用,但通常不會(huì)作為事務(wù)提交到排序服務(wù)。事實(shí)上,也無(wú)法保證結(jié)果集在chaincode執(zhí)行與富查詢提交時(shí)間之間的穩(wěn)定性,因此使用富查詢的結(jié)果去執(zhí)行最終的事務(wù)更新操作是不合適的,除非可以保證結(jié)果集在chaincode執(zhí)行時(shí)間與提交時(shí)間之間的穩(wěn)定性,或者可以處理在后續(xù)交易中的潛在變化。例如,如果對(duì)Alice所擁有的所有資產(chǎn)執(zhí)行一個(gè)富查詢并將其傳輸給Bob,那么一個(gè)新的資產(chǎn)可能會(huì)被另一個(gè)事務(wù)分配給Alice,這是在chaincode執(zhí)行時(shí)間和提交時(shí)間之間的另一個(gè)事務(wù),可能此過(guò)程中會(huì)錯(cuò)過(guò)這個(gè)“虛值”。
CouchDB作為一個(gè)獨(dú)立的數(shù)據(jù)庫(kù)進(jìn)程與peer一起運(yùn)行,因此在設(shè)置、管理和操作方面有額外的考慮。我們可以考慮從默認(rèn)的嵌入式LevelDB開(kāi)始,如果需要額外的復(fù)雜的富查詢,可以轉(zhuǎn)移到CouchDB。將chaincode資產(chǎn)數(shù)據(jù)建模為JSON是一種很好的做法,這樣我們就可以在將來(lái)執(zhí)行需要的復(fù)雜的富查詢。
本文均采用Hyperledger Fabric1.2中fabric-samples中相關(guān)組件與資源,在測(cè)試環(huán)境(fabric-samples/chaincode-docker-devmode)通過(guò)Docker啟動(dòng)CouchDB服務(wù)
參考: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"` //對(duì)象類型定義
BillInfoID string `json:"BillInfoID"` //票據(jù)ID
BillInfoAmt string `json:"BillInfoAmt"` //票據(jù)金額
BillInfoType string `json:"BillInfoType"` //票據(jù)類型
BillIsseData string `json:"BillIsseData"` //出票日期
BillDueDate string `json:"BillDueDate"` //到期日期
HoldrAcct string `json:"HoldrAcct"` //持票人名稱
HoldrCmID string `json:"HoldrCmID"` //持票人ID
WaitEndroseAcct string `json:"WaitEndroseAcct"` //待背書(shū)人名稱
WaitEndorseCmID string `json:"WaitEndorseCmID"` //待背書(shū)人ID
}
請(qǐng)仔細(xì)閱讀注釋信息,此處不做代碼分割描述
package main
import (
"github.com/hyperledger/fabric/core/chaincode/shim"
"fmt"
"github.com/hyperledger/fabric/protos/peer"
"encoding/json"
"bytes"
)
//定義結(jié)構(gòu)體CouchDBChaincode,作為shim.ChaincodeStubInterface實(shí)現(xiàn)類對(duì)象
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 {
//獲取用戶意圖與參數(shù)
fun, args := stub.GetFunctionAndParameters()
//根據(jù)用戶意圖判斷使用何種實(shí)現(xiàn)函數(shù)
if fun == "billInit" {
return billInit(stub)
} else if fun == "queryBills" {
return queryBills(stub, args)
} else if fun == "queryWaitBills" {
return queryWaitBills(stub, args)
}
//如果用戶意圖不符合如上,進(jìn)行錯(cuò)誤提示
return shim.Error("非法操作,指定的函數(shù)名無(wú)效")
}
//billInit函數(shù):初始化票據(jù)數(shù)據(jù)
func billInit(stub shim.ChaincodeStubInterface) peer.Response {
/*
定義第一個(gè)票據(jù):
持票人名稱:AAA
持票人ID:AID
待背書(shū)人名稱:無(wú)
待背書(shū)人ID:無(wú)
*/
billA := BillStruct{
ObjectType: "billObj",
BillInfoID: "POC001",
BillInfoAmt: "1000",
BillInfoType: "111",
BillIsseData: "20180501",
BillDueDate: "20180508",
HoldrAcct: "AAA",
HoldrCmID: "AID",
WaitEndroseAcct: "",
WaitEndorseCmID: "",
}
//通過(guò)json.Marshal方法對(duì)票據(jù)進(jìn)行序列化操作
billAByte, _ := json.Marshal(billA)
//通過(guò)stub.PutState方法存儲(chǔ)序列化后的字節(jié)數(shù)組
err := stub.PutState(billA.BillInfoID, billAByte)
if err != nil {
return shim.Error("初始化第一個(gè)票據(jù)失敗:" + 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("初始化第二個(gè)票據(jù)失敗:" + 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("初始化第三個(gè)票據(jù)失敗:" + 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("初始化第四個(gè)票據(jù)失敗:" + err.Error())
}
return shim.Success([]byte("所有票據(jù)初始化成功"))
}
//queryBills函數(shù):批量查詢指定用戶的持票列表
func queryBills(stub shim.ChaincodeStubInterface, args []string) peer.Response {
//判斷是否有參數(shù)傳入
if len(args) != 1 {
return shim.Error("必須指定持票人的證件號(hào)碼")
}
//將第一個(gè)參數(shù)作為用戶ID
holdrCmID := args[0]
/*將CouchDB查詢字符串拼接成一個(gè)JSON串,格式如下:
{
"selector": {
"docType": "billObj",
"HoldrCmID": "%s"
}
}
*/
queryString := fmt.Sprintf("{\"selector\":{\"DocType\":\"billObj\",\"HoldrCmID\":\"%s\"}}", holdrCmID)
//通過(guò)自定義的getBillByQueryString函數(shù)進(jìn)行數(shù)據(jù)查詢操作
result, err := getBillByQueryString(stub, queryString)
if err != nil {
return shim.Error("根據(jù)持票人的證件號(hào)碼批量查詢持票人持有票據(jù)列表時(shí)發(fā)生錯(cuò)誤" + err.Error())
}
return shim.Success(result)
}
//queryWaitBills函數(shù):批量查詢指定用戶的待背書(shū)票據(jù)列表
func queryWaitBills(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 1 {
return shim.Error("必須指定待背書(shū)人的證件號(hào)碼")
}
waitEndorseCmID := args[0]
queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"billObj\",\"WaitEndorseCmID\":\"%s\"}}", waitEndorseCmID)
result, err := getBillByQueryString(stub, queryString)
if err != nil {
return shim.Error("根據(jù)待背書(shū)人的證件號(hào)碼批量查詢待背書(shū)票據(jù)列表時(shí)發(fā)生錯(cuò)誤" + err.Error())
}
return shim.Success(result)
}
//自定義函數(shù):getBillByQueryString:根據(jù)指定的查詢字符串(CouchDB查詢語(yǔ)句)查詢數(shù)據(jù)
func getBillByQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) {
//通過(guò)stub.GetQueryResult方法獲取迭代器iterator
iterator, err := stub.GetQueryResult(queryString)
if err != nil {
return nil, err
}
//延遲關(guān)閉迭代器iterator
defer iterator.Close()
//定義字節(jié)緩沖變量
var buffer bytes.Buffer
//定義分割符
var isSplit bool
//對(duì)迭代器進(jìn)行遍歷操作
for iterator.HasNext() {
//通過(guò)迭代器的Next()方法獲取下一個(gè)對(duì)象的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))
//獲取到第一個(gè)值后,將isSplit設(shè)置為true,用于跟第二個(gè)值進(jìn)行分割
isSplit = true
}
//返回buffer對(duì)象的字節(jié)類型
return buffer.Bytes(), nil
}
func main() {
//啟動(dòng)鏈碼CouchDBChaincode
err := shim.Start(new(CouchDBChaincode))
//如有報(bào)錯(cuò),提示報(bào)錯(cuò)信息
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
進(jìn)入chaincode容器進(jìn)行操作
# docker container exec -it chaincode bash #進(jìn)入chaincode容器進(jìn)行操作
# 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 ...
進(jìn)入cli容器進(jìn)行操作
# 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"]}'
如有更新請(qǐng)用如下命令進(jìn)行操作
# 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"
}
查詢結(jié)果可以看到我們定義的分隔符;
# 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"
}
另外關(guān)于LevelDB,CouchDB還是MongoDB,今后可能隨著Hyperledger Fabric的版本變化而采取不同的數(shù)據(jù)庫(kù)類型,我們拭目以待,現(xiàn)在唯一能做的,就是在已有的資源下面用Hyperledger Fabric為業(yè)務(wù)場(chǎng)景創(chuàng)造最大的業(yè)務(wù)價(jià)值。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。