基于 fabric 外部链码的一种链码测试方法

作者 tinywell 日期 2021-07-31
基于 fabric 外部链码的一种链码测试方法

fabric 的链码(chaincode)代表着区块链中智能合约的概念,承担了在区块链网络中运行约定事务处理规则的责任。基于 fabric 网络进行区块链相关业务,则意味着要开发相应的链码。fabric 链码支持通用语言:go、java、nodejs 等,而链码的运行在传统方式下是基于 docker 容器并由 fabric 中的 peer 节点管理其整个生命周期:链码镜像编译->容器启动运行。所以当需要对链码进行测试时,链码的运行依赖 fabric 完整的链码生命周期管理,既需要有一个 fabric 区块链网络并通过合约的安装部署流程来运行一个链码,并通过 fabric 的交易流程来调用链码。这使得链码的测试工作变得困难,前置条件门槛高,也不利于代码的迭代更新。

在 fabirc 2.0 之后,对于链码的生命周期新增了一种管理方式,同时支持外部链码的形式,这使得链码跟 peer 之间的联系变得没有那么紧密了,而这似乎可以利用来尝试一种新的链码测试方法。

问题

首先我们需要分析下,测试链码面临的问题:

  • 部署:传统模式下,链码是由 peer 节点从源码包开始,编译镜像然后启动容器实现链码环境的部署,这需要经过链码的安装过程和实例化过程,需要完整的 fabric 服务网络参与。当链码更新时,需要通过链码更新或者重新安装部署的流程来实现新服务的部署,这种方式会严重降低开发阶段效率;
  • 调用:链码由 peer 节点部署启动之后,便与 peer 节点建立通讯,所有对于链码的调用都需要通过 peer 的交易相关接口处理然后由 peer 再转交给链码执行,链码执行结果也是通过 peer 节点返回,并不能直接对链码中的方法进行调用。所以需要完整的 fabric 交易执行环境;
  • 数据:链码中可以对状态库进行操作,读取状态库数据、更新状态库数据,由于链码本身仅仅是一个合约执行引擎,账本数据由 peer 节点维护,所以链码在执行过程中涉及对状态库的读写动作,都需要请求到 peer 节点,由 peer 节点与状态库交互得到结果,这就使得链码不具备独立执行的能力;

上述问题使得链码的测试工作不太那么方便,不利于链码的快速迭代开发。但是 2.0 引入外部链码(external chaincode)之后,似乎出现了一个新的可能性。

新功能

2.0 版本最主要的新功能就是新的链码生命周期 lifecycle 以及外部链码 external chaincode 的支持。外部链码需要基于 lifecycle 才能运行。那么外部链码提供什么新特性呢?

顾名思义,外部链码就是在外部运行的链码,这个外部指的 fabric 外部,准确的说是 peer 外部。因为外部链码的构建和运行不再依赖 peer 节点了,我们不需要将源码交给 peer 节点去做编译部署动作,也不需要依赖 docker 的容器方式,而是可以作为一个完全独立通用的服务端程序来运行,可以用任何你想用的方式来部署外部链码,最后只需要将相关服务端的信息提供给 peer ,由 peer 节点作为客户端连接到链码服务上,与链码建立联系。

基于以上的新模式,对我们测试链码遇到的几个问题有什么帮助呢:

  • 部署:外部链码不再依赖 peer 节点构建运行,合约的部署将变得非常方便,开发模式下甚至直接裸 app run 起来就行(比如 go run);
  • 调用:外部链码是由链码作为服务端运行,调用时只要按照约定协议去访问服务端接口即可实现对链码的调用;
  • 数据:由于合约执行引擎的特性,不具备账本数据的维护能力,所以即使外部链码也无法做到完全的独立运行,依然需要一个能提供账本数据的支撑层,所以这一点并没有改变;

所以外部链码的新特性能够使得部分问题得到解决,但是由于账本数据的问题,链码依然无法独立运行。要想能够方便测试,绕不开一个提供账本数据维护的支持层。

账本数据支撑

账本数据绕不开,是不是说明我们就只能使用 peer 使用完整的 fabric 网络才能解决这个问题呢?不,我们其实并不需要完整的 fabric 功能支持,我们只需要代替 peer 节点并提供数据维护的能力就行。

分析链码与 peer 节点之间的通讯可知,链码与 peer 的交互有以下几种类型:

  • 链码注册:作为外部链码运行时,链码是一个服务端,当接收到 peer 端发过来的连接后,会立即主动发送注册消息给 peer 节点,告知 peer 节点自己的相关情况,然后通过几次通信与 peer 端一起调整各自状态,使得这个链码可以处理 peer 发过来的请求;
  • 交易处理:peer 接收到客户端的交易后,会根据请求参数调用对应的链码,peer 会封装交易消息发送给链码,等待链码处理;
  • 数据处理:链码在执行过程中,遇到数据处理相关操作时,会给 peer 节点发消息请求对应的数据,比如链码执行 GetState时,链码像 peer 节点发送 ChaincodeMessage_GET_STATE消息,等待 peer 节点返回结果。

分析清楚链码与 peer 之间的通讯后,我们就可以考虑开发一个替代 peer 节点这部分能力的仿真端,这个仿真端具备以下能力:

  • 能够与指定外部链码建立联系,维持稳定的链路
  • 能够响应所有链码的消息并正确回复
  • 能接受测试交易请求并根据请求调用对应链码执行测试
  • 有一个模拟的账本,维护数据

基于外部链码的链码测试方案

所以,我们的测试方案,就是以外部链码的方式来运行链码,以一个替代 peer 节点账本数据维护功能的仿真端作为代理人接受测试请求连接链码执行测试。

仿真端的核心模块为模拟账本的数据处理模块、与链码通讯处理各种消息的消息模块以及用于接收测试请求、链码注册请求的 RESTful 服务模块。

根据这个思路,我实现了一套外部链码测试工具 extcc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
external chaincode client,用于调用外部部署链码,方便测试链码逻辑

Usage:
extcc [command]

Available Commands:
help Help about any command
invoke 调用链码
register 链码注册
server 链码仿真服务

Flags:
-h, --help help for extcc

Use "extcc [command] --help" for more information about a command.

包含两个部分 3 个子命令:

  • server: 用来启动一个仿真服务,运行一套基于 map 的仿真账本数据;运行一套链码通讯服务,与外部链码建立连接并保持通讯;运行一套 RESTful 服务,来接受链码注册和测试交易的请求;
  • register:向仿真端发送外部链码注册请求,仿真端根据请求参数建立到外部链码服务的连接,然后通过消息服务将此链码连接切换到可服务状态,并通过心跳消息保持连接活性;
  • invoke: 向仿真端发送链码测试交易,仿真端根据请求参数找到对应的链码,并发送交易消息等待结果,完成一个链码交易的测试;

在这套方案下,链码的测试所以依赖的外部环境就是一个仿真端,通过 extcc server 即可完成部署,然后将链码以外部链码的形式启动,通过 extcc register 完成注册后,即可开始测试。相比于部署完整的 fabric 网络以及执行完整的链码生命周期管理来进行链码的方式来说,extcc 使用的资源、消耗的精力都是极少的。从实际情况来看,这是一个比较可行的链码测试方案。