1 鏈碼介紹
智能合約在 Hyperledger Fabric 中稱為鏈碼(chaincode),是提供分布式賬本的狀態處理邏輯。鏈碼被部署在fabric 的網絡節點中,能夠獨立運行在具有安全特性的受保護的 Docker 容器中,以 gRPC 協議與相應的 peer 節點進行通信,以操作分布式賬本中的數據。
一般鏈碼分為兩種:系統鏈碼和用戶鏈碼。
1.1 系統鏈碼
負責 Fabric 節點自身的處理邏輯,包括系統配置、背書、校驗等工作,在 Peer 節點啟動時會自動完成注冊和部署。系統鏈碼分為以下五種:
-
配置系統鏈碼(Configuration System Chaincode,CSCC):負責處理 Peer 端的 Channel 配置;
-
生命周期系統鏈碼(Lifecycle System Chaincode,LSCC):負責對用戶鏈碼的生命周期進行管理;
-
查詢系統鏈碼(Query System Chaincode,QSCC): 提供賬本查詢 API。如獲取區塊和交易等信息;
-
背書管理系統鏈碼(Endorsement System Chaincode,ESCC):負責背書(簽名)過程, 並可以支持對背書策略進行管理;
-
驗證系統鏈碼(Validation System Chaincode,VSCC):處理交易的驗證,包括檢查背書策略以及多版本並發控制。
1.2 用戶鏈碼
用戶鏈碼不同於系統鏈碼,系統鏈碼是 fabric 的內置鏈碼,而用戶鏈碼是由應用程序開發人員根據不同場景需求編寫的基於分布式賬本的狀態的業務處理邏輯代碼,運行在鏈碼容器中,通過 Fabric 提供的接口與賬本狀態進行交互。
用戶鏈碼向下可對賬本數據進行操作,向上可以給企業級應用程序提供調用接口。
1.3 鏈碼生命周期
管理 Chaincode 的生命周期共有五個命令:
-
install:將已編寫完成的鏈碼安裝在網絡節點中;
-
instantiate:對已安裝的鏈碼進行實例化;
-
upgrade:對已有鏈碼進行升級,鏈代碼可以在安裝后根據具體需求的變化進行升級;
-
package:對指定的鏈碼進行打包的操作。
-
singnpackage:對已打包的文件進行簽名。

2 鏈碼的使用
我們使用 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。
2.1 安裝鏈碼
使用 install 命令安裝鏈碼:
# peer chaincode install -n mycc -v 1.0 -p github.com/chaincode/chaincode_example02/go/
- -n: 指定要安裝的鏈碼的名稱
- -v: 指定鏈碼的版本
- -p: 指定要安裝的鏈碼源代碼的所在路徑
執行結果返回:
[chaincodeCmd] install -> INFO 04c Installed remotely response:<status:200 payload:"OK" >
說明鏈碼成功安裝至 peer 節點中。
注意:鏈碼需要根據指定的背書策略安裝在需要背書的所有 peer 節點中。未安裝鏈碼的節點不能執行鏈碼邏輯,但仍可以驗證交易並提交到賬本中。
2.2 實例化鏈碼
設置通道名稱的環境變量:
# 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')"
- -o: 指定 Oderer 服務節點地址
- --tls: 開啟 TLS 驗證
- --cafile: 指定了 Orderer 的根證書路徑,用於驗證 TLS 握手
- -n: 指定要實例化的鏈碼名稱,必須與安裝時指定的鏈碼名稱相同
- -v: 指定要實例化的鏈碼的版本號,必須與安裝時指定的鏈碼版本號相同
- -C: 指定通道名稱
- -c: 實例化鏈碼時指定的參數
- -P: 指定背書策略
背書策略的背書實體一般表示為: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...])
// 需要三個組織 org1、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'))
// 需要 org1、org2、org3 的 member 的至少兩個背書簽名
OutOf(2, 'Org1MSP.member', 'Org2MSP.member', 'Org3MSP.member')
注意:鏈碼需要安裝在多個背書的 Peer 節點中,但實例化只需執行一次。
2.3 查詢鏈碼
鏈碼部署成功之后,可以通過特定的命令調用鏈碼,從而發起交易或查詢請求,對賬本數據進行操作。
使用 query 命令查詢鏈碼:
# peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'
- -n: 指定要調用的鏈碼名稱
- -C: 指定通道名稱
- -c 指定調用鏈碼時所需要的參數
執行成功后,返回輸出結果 100。
2.4 調用鏈碼
客戶端發起交易,對賬本數據進行更改,需要將背書之后的交易發送給排序節點上鏈。因此,需要開啟 TLS 驗證並指定對應的 orderer 證書路徑。
需要注意,鏈碼執行查詢操作和執行事務(改變賬本數據)操作的流程是不同的:
- 鏈碼查詢操作:客戶端接收到背書節點的交易提案響應后不會將交易請求提交給 Orderer 節點,即查詢操作不需要上鏈,任選一個背書節點進行鏈碼查詢操作即可;
- 鏈碼事務操作:客戶端先需要根據指定背書策略收集到足夠的交易提案的背書簽名,再將背書后的交易提交給 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"]}'
- -o: 指定orderer節點地址
- --tls: 開啟TLS驗證
- --cafile: 指定了 Orderer 的根證書路徑,用於驗證 TLS 握手
- -n: 指定鏈碼名稱
- -C: 指定通道名稱
- -c: 指定調用鏈碼的所需參數
執行返回以下結果,說明交易執行成功:
[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"]}'
2.5 鏈碼的打包與簽名
鏈碼部署除了正常的安裝、實例化操作步驟之外,還有一種部署方式,即先將鏈碼進行打包,然后對已打包的文件進行簽名,最后再進行安裝與實例的操作。
使用如下的命令進行打包操作:
# peer chaincode package -n exacc -v 1.0 -p github.com/chaincode/chaincode_example02/go/ -s -S -i "AND('Org1MSP.admin')" ccpack.out
參數說明:
- -s: 創建一個可以被多個所有者簽名的包
- -S: 可選參數,使用 core.yaml 文件中被 localMspId 相關屬性值定義的 MSP 對包進行簽名
- -i: 指定鏈碼的實例化策略(指定誰可以實例化鏈碼)
打包后的文件可以直接使用 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"]}'
2.6 升級鏈碼
首先,先對修改之后的鏈碼進行安裝:
# 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 時重設狀態信息。
2.7 查看鏈碼日志
使用 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"}
3 dev 模式下的鏈碼測試
3.1 啟動測試網絡
上述過程是在 first-network 網絡下的鏈碼測試,但是該網絡下的鏈碼測試過於復雜,需要指定很多參數,如果只是想測試所編寫的鏈碼的正確性,可以使用 dev 開發模式。
進入 chaincode-docker-devmode
目錄:
$ cd ./fabric-samples/chaincode-docker-devmode/
該目錄下存在如下五個文件:
- docker-compose-simple.yaml:網絡啟動依賴的配置文件,該配置文件中指定了四個容器,分別為:orderer、peer、cli、chaincode
- msp:網絡環境的 MSP,包含一系列的證書及私鑰
- script.sh:cli 運行的創建並加入通道的腳本
- myc.tx:通道交易配置文件
- orderer.block: 排序服務初始區塊配置文件
使用如下命令啟動網絡:
$ 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
3.2 構建並啟動鏈碼
使用如下命令進入 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 的鏈碼
- mycc: 指定鏈碼名稱
- 0: 指定鏈碼初始版本號
- ./go: 指定鏈碼文件
開啟一個新的終端,使用如下命令進行 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"}
3.3 關閉測試網絡
使用如下命令關閉測試網絡:
$ docker-compose -f docker-compose-simple.yaml down
參考
- 《Hyperledger Fabric 菜鳥進階攻略》