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

参考

  1. 《Hyperledger Fabric 菜鸟进阶攻略》

Fabric1.4:链码管理与测试的更多相关文章

  1. Redis Cluster部署、管理和测试

    背景: Redis 3.0之后支持了Cluster,大大增强了Redis水平扩展的能力.Redis Cluster是Redis官方的集群实现方案,在此之前已经有第三方Redis集群解决方案,如Twen ...

  2. Fabric1.4 链码开发,开发模式下的测试

    关闭之前已启动的网络环境 sudo docker-compose -f docker-compose-cli.yaml down 进入devmode目录:  cd ~/go/src/github.co ...

  3. 测试管理_测试人员招聘[持续更新ing]

    招聘之难,难于上青天. 如何招聘到一位称心如意的员工想必是每个公司和管理者都要面临而且头疼的问题.尤其在初建团队或团队缺人的情况下问题会显得更加严重. 作为一个测试管理者,如何招聘到合适的测试人员是必 ...

  4. Android 内存溢出管理与测试

    今天发现正在做的项目,时不时的会报错:dalvikvm heap out of memory on a 7458832-byte allocation (堆分配的内存溢出) 为什么会内存溢出呢?我以前 ...

  5. 【flask】使用配置类管理app测试环境-demo版

    如果对app.config是什么还心有疑惑,或者对于这种配置方式很陌生,参考:flask项目配置 app.config classConfig.py: class BaseConfig(object) ...

  6. 软件测试自动化的最新趋势对开源测试管理软件ITEST的启示

    https://www.infoq.cn/article/c-LHJS2ksuDxp1WkrGl4 理面提到几点,DevOps 的关键原则是开发团队.测试团队和运营团队协作,无缝发布软件.这意味着集中 ...

  7. 【转】腾讯移动品质中心TMQ [腾讯 TMQ] 测试管理平台大比拼

    简介 测试管理平台是贯穿测试整个生命周期的工具集合,它主要解决的是测试过程中团队协作的问题,比如缺陷管理.用例管理.测试任务管理等. 目前市面上比较流行的测试管理工具有QC. Mantis. BugZ ...

  8. 系统管理模块_用户管理1_实现用户有关的功能_测试功能、解决事务的问题、对密码进行MD5摘要

    系统管理模块__用户管理1__实现用户有关的功能 了解用户管理要做什么(增删改查初始化密码) 设计实体 分析功能有几个对应几个请求 增删改查有6个请求,初始化密码一个 实现增删改查一组功能的步骤流程 ...

  9. C++中的内存管理

    在C++中也是少不了对内存的管理,在C++中只要有new的地方,在写代码的时候都要想着delete. new分配的时堆内存,在函数结束的时候不会自动释放,如果不delete我分配的堆内存,则会造成内存 ...

随机推荐

  1. html--多媒体文件

    添加多媒体文件 1.添加多媒体文件标记 <embed src="" width="" height=""></embed& ...

  2. HDU 1051

    题意:给你n个木块的长和宽,现在要把它送去加工,这里怎么说呢,就是放一个木块花费一分钟,如果后面木块的长和宽大于等于前面木块的长和宽就不需要花费时间,否则时间+1,问把这个木块送去加工的最短时间. 思 ...

  3. java面向接口编程之适配器模式

    使用一个现成的类,但是它的接口不完全符合你的需求,我只想要它其中的一个方法,不想覆写其他的方法. 比如,窗体有变大,变小,关闭的行为,但是我现在只需要关闭行为;   package reviewDem ...

  4. H3C IGP与EGP

  5. http请求头包括了哪些常见内容

    Host: www.study.com                // 请求的地址域名和端口,不包括协议 Connection: keep-alive    // 连接类型,持续连接 Upgrad ...

  6. H3C IP地址拒绝及释放

  7. webpack打包前删除之前的所有文件

    安装插件: npm install --save-dev clean-webpack-plugin 在webpack.prod.conf.js 中引入:  const { CleanWebpackPl ...

  8. H3C 端口接入控制方式

  9. VUE框架思想

    学习VUE的第一步就是先了解这个框架的的核心思想 Vue.js的核心思想就是,它是一套__渐进式的自底层向上增量开发__的__MVVM__结构的框架 什么是框架? 简单的讲,框架就是将与业务无关的重复 ...

  10. ZR993

    ZR993 首先,这种和平方有关的,首先应当考虑根号做法 这道题目,我们可以直接暴力\(\log_{10}w + 10\)判断一个数是否能够由原数变化的到 直接\(O(\sqrt{n})\)枚举所有的 ...