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:
说明链码成功安装至 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 菜鸟进阶攻略》