本文对BitXHub的meshplus/pier-client-fabric插件源码作深入解读,学习其优秀的功能设计与代码结构。
趣链科技的BitXHub跨链平台是业界较为完善的跨链开源解决方案,主要通过中继链、网关和插件机制对跨链流程中的功能、安全性和灵活性等进行了优化。本文对BitXHub的meshplus/pier-client-fabric插件源码作深入解读,学习其优秀的功能设计与代码结构。

典型的跨链调用流程
(各应用链申请/注册跨链功能、权限验证及中继链处理等细节此处省略。)
本文所描述的跨链流程中,插件是作为一个独立的服务运行在应用链上的,所以更准确来说应该叫应用链适配器,而趣链BitXHub跨链平台的这一部分是作为一个插件,由网关启动的,但两者逻辑角色和核心功能较一致(下文统称为插件)。
所有需要进行跨链交易/接收跨链请求的应用链需要安装插件(适配器)并部署提供的跨链合约和业务合约。具体交易流程如下:
1)调用链码
插件接收跨链网关发送的交易参数,封装为已适配应用链接受的参数格式并调用链码,如执行rollback、commit等相应回调方法。
2)查询跨链交易
应用链将跨链交易调用信息存在payload字段中,如合约、用户等,插件对这些信息进行解析与封装,提供相应接口供跨链网关查询。
3)查询历史交易信息
插件需要提供历史交易查询接口,以便于当跨链事件因网络传输等原因未收到时供跨链网关主动进行查询。
4)查询应用链基本信息
插件需要提供其所适配应用链相关信息的查询接口以便于跨链网关进行查询,如应用链名称、类型等。
接下来将对BitXHub跨链插件(Fabric)的核心功能模块源码进行解读。
插件项目采用的是典型的“生产者-消费者”模型,很适合这样需要轮询/订阅接收数据的并发场景。这种模型用到了任意时刻只有一个goroutine对channel中的某一个数据进行访问的特性。
1)订阅/轮询跨链事件
插件需要构建一个生产者对象并订阅自己相应子链的跨链事件。


订阅合约事件的方法是调用fabric-sdk-go的RegisterChaincodeEvent()方法,需注意的是,当不需要监听事件时,需要调用Unregister()方法来取消订阅。
方法中的ccID是需要监听的链码ID,eventFilter是需要监听的链码时间,而这个方法会返回一个channel接收数据(当取消订阅时,channel会关闭)。
将订阅了跨链合约的对象(即生产者)与消费者都置于无限循环中,当有跨链事件抛出时,生产者将会不断地向 channel 中放入数据,而消费者也不断地从通道中取出数据。

因为生产者和消费者都在无限循环中,生产者的goroutine不会退出,channel 持续写入数据,而当没有新事件时,消费者将会阻塞,等待生产者接收新的数据并写入channel。
看了整体的设计模式,我们从程序的主入口看看整个插件项目运行的机制。
1)初始化
在client程序初始化中,首先根据自定义的结构构造了消费者对象。

2)运行程序运行的入口,就是对跨链合约进行轮询,并启动消费者对象。

3)关闭关闭插件,即停止程序运行并取消订阅事件。

在consumer包中取消订阅事件。

再深一层看,取消订阅事件是调用了fabric-sdk-go的Unregister()方法,会取消该事件的订阅并关闭相应通道。

在跨链交易流程中,交易的存在性和有效性验证是必不可少的一部分。BitXHub采用了验证引擎作为其跨链体系的一部分。验证流程如下:
中继链验证引擎主要通过应用链传入的验证者信息、proof字段(通过getProof() 接口获取)和payload字段进行验证:
除了对事件进行订阅监听外,插件还提供了一系列查询接口供网关调用,以完成相应跨链操作。
1)getProof()
获取验证引擎所需要的proof信息以进行交易验证。

2)getChainID()
获取链的ID。

3)其他接口
其他更多接口实现细节详见meshplus/pier-client-fabric/client.go。
跨链合约是实现插件监听的重要部分,当业务需要跨链时,将会统一调用跨链合约,并与应用链对应的跨链网关进行交互。
跨链合约提供了一系列接口供业务合约进行实现,因此按照一定的规范撰写业务合约则能简化跨链业务的开发和维护。(跨链合约编写的规范详见:跨链网关编写文档)
“跨链合约是怎样将跨链事件抛出给插件的呢?”
在跨链合约Invoke()方法中,跨链合约首先通过GetFunctionAndParameters()方法获取了合约调用者(也就是业务合约)的调用方法和相应参数,然后通过对方法名进行判断,从而调用不同的合约。

我们着重来分析一下当调用了EmitInterchainEvent()时,跨链合约做了什么。(相应说明见注释)


以上就是调用跨链合约时所做的,本质上其实只是在跨链合约中通过SetEvent()设置了一个触发一个事件,再在插件中通过RegisterChaincodeEvent()进行订阅监听。

SetEvent()是shim包下的一个接口,主要传入事件名称与payload参数。
分析完了跨链合约,我们来看看业务合约是如何调用跨链合约的呢?
以data_swapper.go数据交换合约为例。

如想在Data Swapper业务合约中发起跨链交易,传入相应参数,data_swapper.go合约程序通过switch...case...在调用get方法时首先对输入参数数组args []string的长度进行判断,当长度为1时,正常调用自身合约进行查询,而当长度为2时,首先通过fabric提供的ToChaincodeArgs()方法将参数从string转为链码参数数组格式。

然后直接在业务链码中通过InvokeChaincode()方法调用跨链合约,并传入参数和通道ID,至此就完成了一次跨链Data Swapper链码调用。
以上是对跨链交易流程与 BitXHub 跨链插件(Fabric)源码解读,也希望在此过程中加深对跨链机制和相关平台的理解,未来能更好地参与到其开源建设中。
作者简介
张宇香港大学 计算机学院研究方向:区块链、跨链
[1] BitXHub Document
[2] meshplus/pier-client-fabric
[3] 十问 BitXHub:谈谈跨链平台的架构设计
[4] 跨链合约编写文档
[5] Hyperledger Fabric Go SDK 事件分析
[6] 一文读懂验证引擎的设计理念 | BitXHub