HOME
BitXHub 跨链插件(Fabric)源码解读

前言

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

跨链交易流程

典型的跨链调用流程

(各应用链申请/注册跨链功能、权限验证及中继链处理等细节此处省略。)

本文所描述的跨链流程中,插件是作为一个独立的服务运行在应用链上的,所以更准确来说应该叫应用链适配器,而趣链BitXHub跨链平台的这一部分是作为一个插件,由网关启动的,但两者逻辑角色和核心功能较一致(下文统称为插件)。

所有需要进行跨链交易/接收跨链请求的应用链需要安装插件(适配器)并部署提供的跨链合约和业务合约。具体交易流程如下:

  • 用户通过客户端/ SDK 调用涉及跨链交易的业务合约时,应用链将调用跨链合约并抛出跨链事件;
  • 应用链插件将轮询/订阅跨链合约抛出的跨链事件并发送至跨链网关;
  • 跨链网关将从监听到的跨链事件中提取相应方法和参数转换为目标应用链可识别的交易形式(BitXHub自主构建了统一的跨链通用传输协议IBTP)并提交到中继链,作一些处理后再发送到目标应用链的跨链网关;
  • 目标链的跨链网关提取跨链交易请求信息中的合约方法和参数部分,调用插件提供的构建交易接口(一般通过各应用链架构对应的SDK实现)调用相应业务合约;
  • 接收到插件接口提交的调用合约请求后,业务合约执行跨链交易并返回交易状态;
  • 插件接收交易执行状态结果,并结合自己对于超时控制、接口调用问题等错误处理,返回执行状态结果;
  • 跨链网关接收到插件返回的执行结果后,选择相应回调函数并传递给中继链,如成功则回调函数为commit提交,失败则为rollback回滚;
  • 中继链将回调函数信息传递给交易发起方跨链网关;
  • 交易发起方跨链网关通过插件提供的相应接口调用相应回调函数;
  • 插件接收到跨链网关请求后,调用跨链合约中的回调方法(一般通过个应用链架构对应的SDK实现);
  • 跨链合约调用业务合约,执行回调方法相应交易(如状态锁定等)。

插件机制

接口设计

插件主要负责与应用之间的交互,并以主动监听/被动接口调用的方式参与跨链交易过程。其主要提供以下接口:

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采用了验证引擎作为其跨链体系的一部分。验证流程如下:

  • 用户通过客户端/SDK提交验证规则;
  • 跨链网关调用内置合约将响应应用链验证规则注册到中继链;
  • 跨链交易传到中继链后,验证引擎查找响应验证规则并进行验证;

中继链验证引擎主要通过应用链传入的验证者信息、proof字段(通过getProof() 接口获取)和payload字段进行验证:

  • 如没有部署验证规则,直接返回验证失败;
  • 先对proof字段的背书信息进行签名校验,验证是否与注册在中继链的应用链验证者信息匹配;
  • 对比payload字段和proof字段中的内容是否一致;
  • 通过验证的跨链交易将会被传入执行引擎中继续执行;

▲ 接口实现

除了对事件进行订阅监听外,插件还提供了一系列查询接口供网关调用,以完成相应跨链操作。

1)getProof()

获取验证引擎所需要的proof信息以进行交易验证。

2)getChainID()

获取链的ID。

3)其他接口

其他更多接口实现细节详见meshplus/pier-client-fabric/client.go

跨链合约

跨链合约是实现插件监听的重要部分,当业务需要跨链时,将会统一调用跨链合约,并与应用链对应的跨链网关进行交互。

跨链合约提供了一系列接口供业务合约进行实现,因此按照一定的规范撰写业务合约则能简化跨链业务的开发和维护。(跨链合约编写的规范详见:跨链网关编写文档)

1)事件实现

“跨链合约是怎样将跨链事件抛出给插件的呢?”

在跨链合约Invoke()方法中,跨链合约首先通过GetFunctionAndParameters()方法获取了合约调用者(也就是业务合约)的调用方法和相应参数,然后通过对方法名进行判断,从而调用不同的合约。

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

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

SetEvent()是shim包下的一个接口,主要传入事件名称与payload参数。

2)业务合约

分析完了跨链合约,我们来看看业务合约是如何调用跨链合约的呢?

以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