智能合约在 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 菜鸟进阶攻略》