HyperledgerFabric启用CouchDB为状态数据库一.概述1.数据请求流超级账本采用背书/共识模型,模拟执行和区块验证是在 不同角色的节点中分开执行的。模拟执行是并发的,这样可以提高扩展性和吞吐量:背书节点:模拟执行链码Peer节点:验证交易并提交2.超 级账本存储元素超级账本包含以下元素:账本编号:快速查询存在哪些账本账本数据:实际的区块数据存储区块索引:快速查询区块/交易状态 数据:最新的世界状态数据历史数据:跟踪键的历史每个Peer节点会维护四个DB,分别为:账本索引库(IdStore):存储Cha inID状态数据库(StateDB):存储worldstate历史数据库(HistoryDB):存储Key的版本变化区块索引 库(BlockIndex):存储Block索引3.状态数据库状态数据库可选类型包括LevelDB和CouchDB。LevelDB是 嵌入在peer进程中的默认键/值状态数据库,CouchDB是一个可选的外部状态数据库。与LevelDB键/值存储一样,CouchD B可以存储任何以chaincode建模的二进制数据(CouchDB附件函数在内部用于非json二进制数据)。但是,当chainco de值(例如,资产)被建模为JSON数据时,作为JSON文档存储,CouchDB支持对chaincode数据进行丰富的查询。Lev elDB和CouchDB都支持核心chaincode操作,例如获取和设置一个键(资产),并根据键进行查询。键可以通过范围查询,可以 对组合键进行建模,以支持针对多个参数的等价查询。例如,作为所有者的组合键,资产id可以用于查询某个实体拥有的所有资产。这些基于ke y的查询可以用于针对账本的只读查询,以及更新总账的事务。如果将资产建模为JSON并使用CouchDB,那么就可以使用chainco de中的CouchDBJSON查询语言对chaincode数据值执行复杂的富查询,这些类型的查询对于理解账本上的内容很有帮助。对 于这些类型的查询,事务协议响应通常对客户端应用程序有用,但通常不会作为事务提交到排序服务。事实上,也无法保证结果集在chainco de执行与富查询提交时间之间的稳定性,因此使用富查询的结果去执行最终的事务更新操作是不合适的,除非可以保证结果集在chaincod e执行时间与提交时间之间的稳定性,或者可以处理在后续交易中的潜在变化。例如,如果对Alice所拥有的所有资产执行一个富查询并将其传 输给Bob,那么一个新的资产可能会被另一个事务分配给Alice,这是在chaincode执行时间和提交时间之间的另一个事务,可能此 过程中会错过这个“虚值”。CouchDB作为一个独立的数据库进程与peer一起运行,因此在设置、管理和操作方面有额外的考虑。我们可 以考虑从默认的嵌入式LevelDB开始,如果需要额外的复杂的富查询,可以转移到CouchDB。将chaincode资产数据建模为J SON是一种很好的做法,这样我们就可以在将来执行需要的复杂的富查询。二.启用CouchDB本文均采用HyperledgerFa bric1.2中fabric-samples中相关组件与资源,在测试环境(fabric-samples/chaincode-doc ker-devmode)通过Docker启动CouchDB服务1.配置CouchDB启动信息参考:fabric-samples/f irst-network/docker-compose-couch.yamlcouchdb0:container_name: couchdb0image:hyperledger/fabric-couchdb#PopulatetheCOUCHDB _USERandCOUCHDB_PASSWORDtosetanadminuserandpasswordhttp ://www.letaoqpyx.com#forCouchDB.ThiswillpreventCouchDBfr omoperatinginan"AdminParty"mode.environment:-COUCHDB_USE R=-COUCHDB_PASSWORD=#Comment/Uncommenttheportmappingifyo uwanttohide/exposetheCouchDBservice,#forexamplemapitt outilizeFauxtonUserInterfaceindevenvironments.ports:-"5 984:5984"networks:-byfn修改:fabric-samples/chaincode-docker-devm ode/docker-compose-simple.yaml末尾添加并修改couchdb:container_name:c ouchdbimage:hyperledger/fabric-couchdb#PopulatetheCOUCHDB_U SERandCOUCHDB_PASSWORDtosetanadminuserandpassword#for CouchDB.ThiswillpreventCouchDBfromoperatinginan"AdminP arty"mode.environment:-COUCHDB_USER=-COUCHDB_PASSWORD=#Co mment/Uncommenttheportmappingifyouwanttohide/exposetheC ouchDBservice,http://www.f-1.cc#forexamplemapittoutilize FauxtonUserInterfaceindevenvironments.ports:-"5984:5984" 2.配置CouchDB连接信息参考fabric-samples/first-network/docker-compose-couc h.yamlpeer0.org1.example.com:environment:-CORE_LEDGER_STATE_S TATEDATABASE=CouchDB-CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADD RESS=couchdb0:5984#TheCORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME andCORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD#providethecrede ntialsforledgertoconnecttoCouchDB.Theusernameandpasswo rdmust#matchtheusernameandpasswordsetfortheassociated CouchDB.-CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=-CORE_LEDGE R_STATE_COUCHDBCONFIG_PASSWORD=depends_on:-couchdb0修改:fabric-s amples/chaincode-docker-devmode/docker-compose-simple.yaml中peer模 块修改前peer:container_name:peerimage:hyperledger/fabric-peeren vironment:-CORE_PEER_ID=peer-CORE_PEER_ADDRESS=peer:7051-CO RE_PEER_GOSSIP_EXTERNALENDPOINT=peer:7051-CORE_PEER_LOCALMSPID= DEFAULT-CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock-COR E_LOGGING_LEVEL=DEBUG-CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/ mspvolumes:-/var/run/:/host/var/run/-./msp:/etc/hyperledger/ mspworking_dir:/opt/gopath/src/github.com/hyperledger/fabric/pe ercommand:peernodestart--peer-chaincodedev=true-oorderer:7 050ports:-7051:7051-7053:7053depends_on:-orderer修改后peer: container_name:peerimage:hyperledger/fabric-peerenvironment: -CORE_PEER_ID=peer-CORE_PEER_ADDRESS=peer:7051-CORE_PEER_GO SSIP_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_COUCHDBCO NFIG_COUCHDBADDRESS=couchdb:5984-CORE_LEDGER_STATE_COUCHDBCONFI G_USERNAME=-CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=volumes: -/var/run/:/host/var/run/-./msp:/etc/hyperledger/mspworking_d ir:/opt/gopath/src/github.com/hyperledger/fabric/peercommand:p eernodestart--peer-chaincodedev=true-oorderer:7050ports:- 7051:7051-7053:7053depends_on:-orderer-couchdb注意JSON文件的格式以 及配置信息的一致性,如couchdb名称等3.启动测试环境#docker-compose-fdocker-compose- simple.yamlup-d#dockercontainerls三.编写链码1.代码结构代码包:testdb代码文件 domain.go//数据结构代码main.go//业务测试代码2.数据结构packagemaintypeBillStru ctstruct{ObjectTypestring`json:"DocType"`//对象类型定义BillInfoIDs tring`json:"BillInfoID"`//票据IDBillInfoAmtstring`json:"BillIn foAmt"`//票据金额BillInfoTypestring`json:"BillInfoType"`//票据类型 BillIsseDatastring`json:"BillIsseData"`//出票日期BillDueDatestr ing`json:"BillDueDate"`//到期日期HoldrAcctstring`json:"HoldrAcct "`//持票人名称HoldrCmIDstring`json:"HoldrCmID"`//持票人IDWaitEndroseAc ctstring`json:"WaitEndroseAcct"`//待背书人名称WaitEndorseCmIDstrin g`json:"WaitEndorseCmID"`//待背书人ID}3.测试代码请仔细阅读注释信息,此处不做代码分割描述pac kagemainimport("github.com/hyperledger/fabric/core/chaincode/s him""fmt""github.com/hyperledger/fabric/protos/peer""encoding/ json""bytes")//定义结构体CouchDBChaincode,作为shim.ChaincodeStubInterfa ce实现类对象typeCouchDBChaincodestruct{}//重写shim.ChaincodeStubInter face接口的Init方法func(tCouchDBChaincode)Init(stubshim.ChaincodeS tubInterface)peer.Response{returnshim.Success(nil)}//重写shim.C haincodeStubInterface接口的Invoke方法func(tCouchDBChaincode)Invoke (stubshim.ChaincodeStubInterface)peer.Response{//获取用户意图与参数fu n,args:=stub.GetFunctionAndParameters()//根据用户意图判断使用何种实现函数if fun=="billInit"{returnbillInit(stub)}elseiffun=="query Bills"{returnqueryBills(stub,args)}elseiffun=="queryWai tBills"{returnqueryWaitBills(stub,args)}//如果用户意图不符合如上,进行错误提 示returnshim.Error("非法操作,指定的函数名无效")}//billInit函数:初始化票据数据funcbil lInit(stubshim.ChaincodeStubInterface)peer.Response{/定义第一个票据 :持票人名称:AAA持票人ID:AID待背书人名称:无待背书人ID:无/billA:=BillStruct{Objec tType:"billObj",BillInfoID:"POC001",BillInfoAmt:"1000",BillInf oType:"111",BillIsseData:"20180501",BillDueDate:"20180508",Hol drAcct:"AAA",HoldrCmID:"AID",WaitEndroseAcct:"",WaitEndorseCm ID:"",}//通过json.Marshal方法对票据进行序列化操作http://www.gw638.cnbillABy te,_:=json.Marshal(billA)//通过stub.PutState方法存储序列化后的字节数组err: =stub.PutState(billA.BillInfoID,billAByte)iferr!=nil{retu rnshim.Error("初始化第一个票据失败:"+err.Error())}billB:=BillStruct{ ObjectType:"billObj",BillInfoID:"POC002",BillInfoAmt:"1000",B illInfoType:"111",BillIsseData:"20180501",BillDueDate:"20180508 ",HoldrAcct:"AAA",HoldrCmID:"AID",WaitEndroseAcct:"BBB",Wait EndorseCmID:"BID",}billBByte,_:=json.Marshal(billB)err=s tub.PutState(billB.BillInfoID,billBByte)iferr!=nil{return shim.Error("初始化第二个票据失败:"+err.Error())}billC:=BillStruct{Ob jectType:"billObj",BillInfoID:"POC003",BillInfoAmt:"1000",Bill InfoType:"111",BillIsseData:"20180501",BillDueDate:"20180508", HoldrAcct:"BBB",HoldrCmID:"BID",WaitEndroseAcct:"CCC",WaitEnd orseCmID:"CID",}billCByte,_:=json.Marshal(billC)err=stub .PutState(billC.BillInfoID,billCByte)iferr!=nil{returnshi m.Error("初始化第三个票据失败:"+err.Error())}billD:=BillStruct{Objec tType:"billObj",BillInfoID:"POC004",BillInfoAmt:"1000",BillInf oType:"111",BillIsseData:"20180501",BillDueDate:"20180508",Hol drAcct:"CCC",HoldrCmID:"CID",WaitEndroseAcct:"BBB",WaitEndors eCmID:"BID",}billDByte,_:=json.Marshal(billD)err=stub.Pu tState(billD.BillInfoID,billDByte)iferr!=nil{returnshim.E rror("初始化第四个票据失败:"+err.Error())}returnshim.Success([]byte("所 有票据初始化成功"))}//queryBills函数:批量查询指定用户的持票列表funcqueryBills(stubshim .ChaincodeStubInterface,args[]string)peer.Response{//判断是否有参数 传入iflen(args)!=1{returnshim.Error("必须指定持票人的证件号码")}//将第一个 参数作为用户IDholdrCmID:=args[0]/将CouchDB查询字符串拼接成一个JSON串,格式如下:{" selector":{"docType":"billObj","HoldrCmID":"%s"}}/query String:=fmt.Sprintf("{\"selector\":{\"DocType\":\"billObj\",\"H oldrCmID\":\"%s\"}}",holdrCmID)//通过自定义的getBillByQueryString函数进行 数据查询操作result,err:=getBillByQueryString(stub,queryString)if err!=nil{returnshim.Error("根据持票人的证件号码批量查询持票人持有票据列表时发生错误"+e rr.Error())}returnshim.Success(result)}//queryWaitBills函数:批量查询 指定用户的待背书票据列表funcqueryWaitBills(stubshim.ChaincodeStubInterface, args[]string)peer.Response{iflen(args)!=1{returnshim.E rror("必须指定待背书人的证件号码")}waitEndorseCmID:=args[0]queryString:= fmt.Sprintf("{\"selector\":{\"docType\":\"billObj\",\"WaitEndors eCmID\":\"%s\"}}",waitEndorseCmID)result,err:=getBillByQuery String(stub,queryString)iferr!=nil{returnshim.Error("根据待背 书人的证件号码批量查询待背书票据列表时发生错误"+err.Error())}returnshim.Success(res ult)}//自定义函数:getBillByQueryString:根据指定的查询字符串(CouchDB查询语句)查询数据func getBillByQueryString(stubshim.ChaincodeStubInterface,queryStri ngstring)([]byte,error){//通过stub.GetQueryResult方法获取迭代器iterat oriterator,err:=stub.GetQueryResult(queryString)iferr!=ni l{returnnil,err}//延迟关闭迭代器iteratordeferiterator.Close()// 定义字节缓冲变量varbufferbytes.Buffer//定义分割符varisSplitbool//对迭代器进 行遍历操作foriterator.HasNext(){//通过迭代器的Next()方法获取下一个对象的Key与Value值 (queryresult.KV)result,err:=iterator.Next()iferr!=nil{ returnnil,err}ifisSplit{buffer.WriteString(";")}//定义格式/ /key:result.keyresult.Valuebuffer.WriteString("key:")buffer.W riteString(result.Key)buffer.WriteString(",value:")buffer.Write String(string(result.Value))//获取到第一个值后,将isSplit设置为true,用于跟第二个值进行 分割isSplit=true}//返回buffer对象的字节类型returnbuffer.Bytes(),nil} funcmain(){//启动链码CouchDBChaincodeerr:=shim.Start(new(CouchD BChaincode))//如有报错,提示报错信息iferr!=nil{fmt.Errorf(err.Error() )}}四.安装链码1.上传链码上传链码包testdb至:fabric-samples/chaincode中#ls/home/ bruce/hyfa/fabric-samples/chaincode/testdb/domain.gomain.go2.编 译链码#cd/home/bruce/hyfa/fabric-samples/chaincode/testdb/#gobui ld#lsdomain.gomain.gotestdb3.启动链码进入chaincode容器进行操作#docker containerexec-itchaincodebash#进入chaincode容器进行操作#cdtestdb/ #CORE_PEER_ADDRESS=peer:7052CORE_CHAINCODE_ID_NAME=testCouchDB: 1.0./testdb2018-08-0510:33:37.063UTC[shim]SetupChaincodeLog ging->INFO001Chaincodeloglevelnotprovided;defaultingto: INFO2018-08-0510:33:37.063UTC[shim]SetupChaincodeLogging-> INFO002Chaincode(buildlevel:)startingup...4.安装与实例化链码进入cli 容器进行操作#dockercontainerexec-itclibash#peerchaincodeinstal l-ntestCouchDB-v1.0-pchaincodedev/chaincode/testdb#peerch aincodeinstantiate-ntestCouchDB-v1.0-Cmyc-c''{"Args":["in it"]}''如有更新请用如下命令进行操作#peerchaincodeinstall-ntestCouchDB-v1. 1-pchaincodedev/chaincode/testdb#peerchaincodeupgrade-ntes tCouchDB-v1.1-Cmyc-c''{"Args":["init"]}''五.测试链码1.初始化票据#peer chaincodeinvoke-ntestCouchDB-Cmyc-c''{"Args":["billInit"] }''2.查询指定用户所持票据#peerchaincodequery-ntestCouchDB-Cmyc-c'' {"Args":["queryBills","AID"]}''key:POC001,value:{"BillDueDate" :"20180508","BillInfoAmt":"1000","BillInfoID":"POC001","Bil lInfoType":"111","BillIsseData":"20180501","HoldrAcct":"AAA" ,"HoldrCmID":"AID","WaitEndorseCmID":"","WaitEndroseAcct":" ","docType":"billObj"};key:POC002,value:{"BillDueDate":"20 180508","BillInfoAmt":"1000","BillInfoID":"POC002","BillInfo Type":"111","BillIsseData":"20180501","HoldrAcct":"AAA","Ho ldrCmID":"AID","WaitEndorseCmID":"BID","WaitEndroseAcct":"BBB","docType":"billObj"}查询结果可以看到我们定义的分隔符;3.查询指定用户待背书票据#peerchaincodequery-ntestCouchDB-Cmyc-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,今后可能随着HyperledgerFabric的版本变化而采取不同的数据库类型,我们拭目以待,现在唯一能做的,就是在已有的资源下面用HyperledgerFabric为业务场景创造最大的业务价值。 |
|