Hyperledger Fabric启用CouchDB为状态数据库

阿里云幸运券

Hyperledger Fabric 启用CouchDB作为状态数据库

一.概述

  1. 数据请求流
    超级账本采用背书/共识模型,模拟执行和区块验证是在不同角色的节点中分开执行的。模拟执行是并发的,这样可以提高扩展性和吞吐量:

背书节点:模拟执行链码
Peer节点:验证交易并提交
Hyperledger Fabric启用CouchDB为状态数据库

2.超级账本存储元素
超级账本包含以下元素:

账本编号:快速查询存在哪些账本
账本数据: 实际的区块数据存储
区块索引: 快速查询区块/交易
状态数据: 最新的世界状态数据
历史数据: 跟踪键的历史
每个Peer节点会维护四个DB,分别为:

账本索引库(IdStore):存储ChainID
状态数据库(StateDB): 存储world state
历史数据库(HistoryDB): 存储Key的版本变化
区块索引库(BlockIndex):存储Block索引
Hyperledger Fabric启用CouchDB为状态数据库

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

Hyperledger Fabric启用CouchDB为状态数据库
腾讯云代金券

三.编写链码
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”]}’

Hyperledger Fabric启用CouchDB为状态数据库

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”]}’

Hyperledger Fabric启用CouchDB为状态数据库

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为业务场景创造最大的业务价值。

腾讯云代金券

原文链接

https://blog.51cto.com/clovemfong/2154997

服务推荐

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值