1. 程式人生 > >Hyperledger Fabric啟用CouchDB為狀態數據庫

Hyperledger Fabric啟用CouchDB為狀態數據庫

下一個 iterator add 今後 ted 歷史 版本 資產 nta

Hyperledger Fabric 啟用CouchDB作為狀態數據庫

一.概述

  1. 數據請求流

超級賬本采用背書/共識模型,模擬執行和區塊驗證是在不同角色的節點中分開執行的。模擬執行是並發的,這樣可以提高擴展性和吞吐量:

  • 背書節點:模擬執行鏈碼
  • Peer節點:驗證交易並提交

技術分享圖片

2.超級賬本存儲元素

超級賬本包含以下元素:

  • 賬本編號:快速查詢存在哪些賬本
  • 賬本數據: 實際的區塊數據存儲
  • 區塊索引: 快速查詢區塊/交易
  • 狀態數據: 最新的世界狀態數據
  • 歷史數據: 跟蹤鍵的歷史

每個Peer節點會維護四個DB,分別為:

  • 賬本索引庫(IdStore):存儲ChainID
  • 狀態數據庫(StateDB): 存儲world state
  • 歷史數據庫(HistoryDB): 存儲Key的版本變化
  • 區塊索引庫(BlockIndex):存儲Block索引

技術分享圖片

3.狀態數據庫

狀態數據庫可選類型包括LevelDB和CouchDB。LevelDB是嵌入在peer進程中的默認鍵/值狀態數據庫,CouchDB是一個可選的外部狀態數據庫。與LevelDB鍵/值存儲一樣,CouchDB可以存儲任何以chaincode建模的二進制數據(CouchDB附件函數在內部用於非json二進制數據)。但是,當chaincode值(例如,資產)被建模為JSON數據時,作為JSON文檔存儲,CouchDB支持對chaincode數據進行豐富的查詢。

LevelDB和CouchDB都支持核心chaincode操作,例如獲取和設置一個鍵(資產),並根據鍵進行查詢。鍵可以通過範圍查詢,可以對組合鍵進行建模,以支持針對多個參數的等價查詢。例如,作為所有者的組合鍵,資產id可以用於查詢某個實體擁有的所有資產。這些基於key的查詢可以用於針對賬本的只讀查詢,以及更新總賬的事務。

如果將資產建模為JSON並使用CouchDB,那麽就可以使用chaincode中的CouchDB JSON查詢語言對chaincode數據值執行復雜的富查詢,這些類型的查詢對於理解賬本上的內容很有幫助。對於這些類型的查詢,事務協議響應通常對客戶端應用程序有用,但通常不會作為事務提交到排序服務。事實上,也無法保證結果集在chaincode執行與富查詢提交時間之間的穩定性,因此使用富查詢的結果去執行最終的事務更新操作是不合適的,除非可以保證結果集在chaincode執行時間與提交時間之間的穩定性,或者可以處理在後續交易中的潛在變化。例如,如果對Alice所擁有的所有資產執行一個富查詢並將其傳輸給Bob,那麽一個新的資產可能會被另一個事務分配給Alice,這是在chaincode執行時間和提交時間之間的另一個事務,可能此過程中會錯過這個“虛值”。

CouchDB作為一個獨立的數據庫進程與peer一起運行,因此在設置、管理和操作方面有額外的考慮。我們可以考慮從默認的嵌入式LevelDB開始,如果需要額外的復雜的富查詢,可以轉移到CouchDB。將chaincode資產數據建模為JSON是一種很好的做法,這樣我們就可以在將來執行需要的復雜的富查詢。

二. 啟用CouchDB

本文均采用Hyperledger Fabric1.2中fabric-samples中相關組件與資源,在測試環境(fabric-samples/chaincode-docker-devmode)通過Docker啟動CouchDB服務

1.配置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"

2.配置CouchDB連接信息

參考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名稱等

3.啟動測試環境

# docker-compose  -f docker-compose-simple.yaml  up -d
# docker container ls

技術分享圖片

三.編寫鏈碼

1.代碼結構

代碼包:testdb

代碼文件

  • domain.go //數據結構代碼
  • main.go //業務測試代碼

2.數據結構

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
}

3.測試代碼

請仔細閱讀註釋信息,此處不做代碼分割描述

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())
    }

}

四.安裝鏈碼

1.上傳鏈碼

上傳鏈碼包testdb至:fabric-samples/chaincode中

# ls /home/bruce/hyfa/fabric-samples/chaincode/testdb/
 domain.go  main.go

2.編譯鏈碼

# cd  /home/bruce/hyfa/fabric-samples/chaincode/testdb/
# go build 
# ls 
domain.go  main.go  testdb

3.啟動鏈碼

進入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 ...

4.安裝與實例化鏈碼

進入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"]}‘

五.測試鏈碼

1.初始化票據

# peer chaincode  invoke  -n testCouchDB -C myc -c ‘{"Args":["billInit"]}‘

2.查詢指定用戶所持票據

# 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"
}

查詢結果可以看到我們定義的分隔符;

3.查詢指定用戶待背書票據

# 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為業務場景創造最大的業務價值。

Hyperledger Fabric啟用CouchDB為狀態數據庫