智能合約在 Hyperledger Fabric 中稱爲鏈碼(chaincode),是提供分佈式帳本的狀態處理邏輯。鏈碼被部署在fabric 的網絡節點中,可以獨立運行在具備安全特性的受保護的 Docker 容器中,以 gRPC 協議與相應的 peer 節點進行通訊,以操做分佈式帳本中的數據。git
通常鏈碼分爲兩種:系統鏈碼和用戶鏈碼。github
負責 Fabric 節點自身的處理邏輯,包括系統配置、背書、校驗等工做,在 Peer 節點啓動時會自動完成註冊和部署。系統鏈碼分爲如下五種:docker
配置系統鏈碼(Configuration System Chaincode,CSCC):負責處理 Peer 端的 Channel 配置;shell
生命週期系統鏈碼(Lifecycle System Chaincode,LSCC):負責對用戶鏈碼的生命週期進行管理;緩存
查詢系統鏈碼(Query System Chaincode,QSCC): 提供帳本查詢 API。如獲取區塊和交易等信息;安全
背書管理系統鏈碼(Endorsement System Chaincode,ESCC):負責背書(簽名)過程, 並能夠支持對背書策略進行管理;bash
驗證系統鏈碼(Validation System Chaincode,VSCC):處理交易的驗證,包括檢查背書策略以及多版本併發控制。網絡
<br>併發
用戶鏈碼不一樣於系統鏈碼,系統鏈碼是 fabric 的內置鏈碼,而用戶鏈碼是由應用程序開發人員根據不一樣場景需求編寫的基於分佈式帳本的狀態的業務處理邏輯代碼,運行在鏈碼容器中,經過 Fabric 提供的接口與帳本狀態進行交互。分佈式
用戶鏈碼向下可對帳本數據進行操做,向上能夠給企業級應用程序提供調用接口。
<br>
管理 Chaincode 的生命週期共有五個命令:
**install:**將已編寫完成的鏈碼安裝在網絡節點中;
**instantiate:**對已安裝的鏈碼進行實例化;
**upgrade:**對已有鏈碼進行升級,鏈代碼能夠在安裝後根據具體需求的變化進行升級;
**package:**對指定的鏈碼進行打包的操做。
**singnpackage:**對已打包的文件進行簽名。
<div align=center><img src="https://pic1.zhimg.com/v2-9b5be494f045aff54c0af53344e88644_r.jpg" width=80%></div> <br>
咱們使用 fabric v1.4.3 版本的 fabric-samples 提供的 first-network 網絡進行說明,修改 first-network/scripts/script.sh
腳本中的下列代碼:
# 將判斷語句中的 true 改成 false,first-network 網絡就不會進行鏈碼的安裝、實例化等操做 if [ "${NO_CHAINCODE}" != "false" ]; then ## Install chaincode on peer0.org1 and peer0.org2 echo "Installing chaincode on peer0.org1..." installChaincode 0 1 echo "Install chaincode on peer0.org2..." installChaincode 0 2 #... #... fi
啓動 first-network 網絡:
$ ./byfn.sh up
進入 CLI 客戶端容器,CLI 客戶端默認以 Admin.org1 身份鏈接 peer0.org1 節點:
$ docker exec -it cli bash
檢查當前節點(peer0.org1.example.com)以加入哪些通道:
# peer channel list
執行結果返回:
Channels peers has joined: mychannel
說明當前節點已經加入通道 mychannel。
使用 install 命令安裝鏈碼:
# peer chaincode install -n mycc -v 1.0 -p github.com/chaincode/chaincode_example02/go/
執行結果返回:
[chaincodeCmd] install -> INFO 04c Installed remotely response:<status:200 payload:"OK" >
說明鏈碼成功安裝至 peer 節點中。
注意:鏈碼須要根據指定的背書策略安裝在須要背書的全部 peer 節點中。未安裝鏈碼的節點不能執行鏈碼邏輯,但仍能夠驗證交易並提交到帳本中。
<br>
設置通道名稱的環境變量:
# export CHANNEL_NAME=mychannel # echo $CHANNEL_NAME
設置 orderer 節點的證書路徑的環境變量:
# export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem # echo $ORDERER_CA
使用 instantiate 命令進行鏈碼的實例化:
# peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a", "100", "b","200"]}' -P "OR ('Org1MSP.peer','Org2MSP.peer')"
背書策略的背書實體通常表示爲:MSP.ROLE
,其中 MSP 是 MSP ID
,ROLE 支持 client、peer、admin 和 member 四種角色。 例如: Org1MSP.admin
表示 Org1 這個 MSP 下的任意管理員; Org1MSP.member
表示 Org1 這個 MSP 下的任意成員。
背書策略語法結構以下:
// 基礎表達式形式,EXPR 能夠是 AND、OR 和 OutOf 邏輯符,E 是實體或者嵌套的表達式 EXPR(E[, E...]) // 須要三個組織 org一、org2 和 org3 的 member 共同背書籤名 AND('Org1MSP.member', 'Org2MSP.member', 'Org3MSP.member') // 須要 org1 和 org2 其中一個組織的 member 背書籤名 OR('Org1MSP.member', 'Org2MSP.member') // 須要 Org1 的 admin 背書,或者 Org2 和 Org3 下的 member 共同背書籤名 OR('Org1MSP.admin', AND('Org2MSP.member', 'Org3MSP.member')) // 須要 org一、org二、org3 的 member 的至少兩個背書籤名 OutOf(2, 'Org1MSP.member', 'Org2MSP.member', 'Org3MSP.member')
**注意:**鏈碼須要安裝在多個背書的 Peer 節點中,但實例化只需執行一次。
<br>
鏈碼部署成功以後,能夠經過特定的命令調用鏈碼,從而發起交易或查詢請求,對帳本數據進行操做。
使用 query 命令查詢鏈碼:
# peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'
執行成功後,返回輸出結果 100。
<br>
客戶端發起交易,對帳本數據進行更改,須要將背書以後的交易發送給排序節點上鍊。所以,須要開啓 TLS 驗證並指定對應的 orderer 證書路徑。
須要注意,鏈碼執行查詢操做和執行事務(改變帳本數據)操做的流程是不一樣的:
使用 invoke 命令調用鏈碼:
# peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -c '{"Args":["invoke","a","b","10"]}'
執行返回如下結果,說明交易執行成功:
[chaincodeCmd] chaincodeInvokeOrQuery -> INFO 04c Chaincode invoke successful. result: status:200
再次查詢 a 帳戶的餘額,若是執行結果返回 90,說明交易被正確執行了:
# peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'
**注意:**若是交易須要多個背書節點的背書,可使用 --peerAddresses
標誌指定節點。例如:交易須要 peer0.org1 和 peer0.org2 的共同背書:
# peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["invoke","a","b","10"]}'
<br>
鏈碼部署除了正常的安裝、實例化操做步驟以外,還有一種部署方式,即先將鏈碼進行打包,而後對已打包的文件進行簽名,最後再進行安裝與實例的操做。
使用以下的命令進行打包操做:
# peer chaincode package -n exacc -v 1.0 -p github.com/chaincode/chaincode_example02/go/ -s -S -i "AND('Org1MSP.admin')" ccpack.out
參數說明:
打包後的文件能夠直接使用 install 命令安裝,如:peer chaincode install ccpack.out
,可是通常對打包後的文件簽名再進一步安裝。
使用以下的命令對打包文件進行簽名操做(添加當前 MSP 簽名到簽名列表中):
# peer chaincode signpackage ccpack.out signedccpack.out
signedccpack.out
包含一個用本地 MSP 對包進行的附加簽名。
安裝已簽名的打包文件:
# peer chaincode install signedccpack.out
對已安裝的鏈碼進行實例化操做,指定背書策略:
# peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n exacc -v 1.0 -c '{"Args":["init","a", "100", "b","200"]}' -P "OR ('Org1MSP.peer','Org2MSP.peer')"
測試:
使用以下命令查詢鏈碼,輸出結果爲 100:
# peer chaincode query -C $CHANNEL_NAME -n exacc -c '{"Args":["query","a"]}'
使用以下命令調用鏈碼進行轉帳操做:
# peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n exacc -c '{"Args":["invoke","a","b","10"]}'
使用以下命令再次查詢鏈碼,輸出結果爲 90:
# peer chaincode query -C $CHANNEL_NAME -n exacc -c '{"Args":["query","a"]}'
<br>
首先,先對修改以後的鏈碼進行安裝:
# peer chaincode install -n mycc -v 2.0 -p github.com/chaincode/chaincode_example02/go/
而後,使用以下命令對已安裝的鏈碼進行升級:
# peer chaincode upgrade -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -v 2.0 -c '{"Args":["init","a", "100", "b","200"]}' -P "OR ('Org1MSP.peer','Org2MSP.peer')"
測試:
使用以下命令查詢鏈碼,輸出結果爲 100:
# peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'
使用以下命令調用鏈碼進行轉帳操做:
# peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -c '{"Args":["invoke","a","b","10"]}'
使用以下命令再次查詢鏈碼,輸出結果爲 90:
# peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'
須要注意的是,升級過程當中,chaincode 的 Init 函數會被調用以執行數據相關的操做,或者從新初始化數據;因此須要多加當心,以免在升級 chaincode 時重設狀態信息。
<br>
使用 docker ps
命令能夠查看到當前網絡中有如下三個鏈碼容器啓動:
dev-peer0.org1.example.com-mycc-1.0
dev-peer0.org1.example.com-mycc-2.0
dev-peer0.org1.example.com-exacc-1.0
使用 docker logs
命令能夠查看鏈碼日誌:
$ docker logs dev-peer0.org1.example.com-mycc-1.0 ex02 Init Aval = 100, Bval = 200 ex02 Invoke Query Response:{"Name":"a","Amount":"100"} ex02 Invoke Aval = 90, Bval = 210 ex02 Invoke Query Response:{"Name":"a","Amount":"90"}
$ docker logs dev-peer0.org1.example.com-mycc-2.0 ex02 Init Aval = 100, Bval = 200 ex02 Invoke Query Response:{"Name":"a","Amount":"100"} ex02 Invoke Aval = 90, Bval = 210 ex02 Invoke Query Response:{"Name":"a","Amount":"90"}
$ docker logs dev-peer0.org1.example.com-exacc-1.0 ex02 Init Aval = 100, Bval = 200 ex02 Invoke Query Response:{"Name":"a","Amount":"100"} ex02 Invoke Aval = 90, Bval = 210 ex02 Invoke Query Response:{"Name":"a","Amount":"90"}
<br>
上述過程是在 first-network 網絡下的鏈碼測試,可是該網絡下的鏈碼測試過於複雜,須要指定不少參數,若是隻是想測試所編寫的鏈碼的正確性,可使用 dev 開發模式。
進入 chaincode-docker-devmode
目錄:
$ cd ./fabric-samples/chaincode-docker-devmode/
該目錄下存在以下五個文件:
使用以下命令啓動網絡:
$ docker-compose -f docker-compose-simple.yaml up -d
Creating network "chaincodedockerdevmode_default" with the default driver Creating orderer ... Creating orderer ... done Creating peer ... Creating peer ... done Creating cli ... Creating chaincode ... Creating cli Creating cli ... done
以開發模式開啓 peer,還啓動了兩個容器:chaincode 容器,用於鏈碼環境;CLI 容器,用於與鏈碼進行交互,其中建立和鏈接通道的命令已經被嵌入 CLI 容器中了,因此能夠直接進行鏈碼調用。
**注意:**啓動該網絡前,應先刪除其餘網絡活躍的容器,要不能運行過程可能會出現問題。使用下列兩個命令刪除活躍的容器和清理網絡緩存:
$ docker rm -f $(docker ps -aq) $ docker network prune
<br>
使用以下命令進入 chaincode 容器 :
$ docker exec -it chaincode bash
出現以下結果,則運行成功:
root@d32997378218:/opt/gopath/src/chaincode#
查看該 chaincode 容器的定義:
chaincode: container_name: chaincode image: hyperledger/fabric-ccenv tty: true environment: - GOPATH=/opt/gopath - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock - FABRIC_LOGGING_SPEC=DEBUG - CORE_PEER_ID=example02 - CORE_PEER_ADDRESS=peer:7051 - CORE_PEER_LOCALMSPID=DEFAULT - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp working_dir: /opt/gopath/src/chaincode command: /bin/sh -c 'sleep 6000000' volumes: - /var/run/:/host/var/run/ - ./msp:/etc/hyperledger/msp - ./../chaincode:/opt/gopath/src/chaincode depends_on: - orderer - peer
該容器的當前目錄 /opt/gopath/src/chaincode
對應本地系統中的 ./../chaincode
,咱們使用該目錄下的 chaincode_example02 鏈碼進行測試。進入 chaincode_example02/go/
目錄編譯鏈碼:
# cd chaincode_example02/go/ # go build
使用以下命令啓動並運行鏈碼:
# CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=mycc:0 ./go
命令含義:
**CORE_PEER_ADDRESS:**用於指定 peer,其中 7052 端口是鏈碼的專用監聽端口(7051 是 peer 節點監聽的網絡端口)
**CORE_CHAINCODE_ID_NAME:**用於註冊到 peer 的鏈碼
開啓一個新的終端,使用以下命令進行 CLI 容器:
$ docker exec -it cli bash
進入 CLI 容器後,使用以下命令安裝鏈碼:
# peer chaincode install -p chaincodedev/chaincode/chaincode_example02/go -n mycc -v 0
使用以下命令實例化鏈碼:
# peer chaincode instantiate -n mycc -v 0 -c '{"Args":["init","a", "100", "b","200"]}' -C myc
測試:
使用以下命令查詢鏈碼,輸出結果爲 100:
# peer chaincode query -n mycc -c '{"Args":["query","a"]}' -C myc
使用以下命令調用鏈碼進行轉帳操做:
# peer chaincode invoke -n mycc -c '{"Args":["invoke","a","b","10"]}' -C myc
使用以下命令再次查詢鏈碼,輸出結果爲 90:
# peer chaincode query -n mycc -c '{"Args":["query","a"]}' -C myc
日誌:
chaincode 容器在測試過程會打印鏈碼執行輸出的日誌:
ex02 Init Aval = 100, Bval = 200 ex02 Invoke Query Response:{"Name":"a","Amount":"100"} ex02 Invoke Aval = 90, Bval = 210 ex02 Invoke Query Response:{"Name":"a","Amount":"90"}
<br>
使用以下命令關閉測試網絡:
$ docker-compose -f docker-compose-simple.yaml down
<br>
- 《Hyperledger Fabric 菜鳥進階攻略》