在BitXHub跨链体系中,中继链的共识算法是不可或缺的一个重要组成部分。共识算法是用于保证中继链系统的一致性,这里的一致性包括交易顺序的一致性、账本一致性、节点状态的一致性等。由于跨链场景的不同,中继链提供一种可插拔的共识算法插件机制,方便接入不同种类的共识算法。本文主要对共识算法的整体设计,接口设计和插件化编译与接入进行介绍。
一、整体设计
共识插件模块的整体设计如图:
交易池模块:主要有交易过滤和交易排序的功能,为了防止交易的重放,每一笔新接收的交易都需要通过布隆过滤器进行过滤。过滤成功后的交易,会注入到交易队列中按交易时间进行排序,保证交易的先来后到和即时确认。
共识插件模块:主要是共识插件接口的实现逻辑,包含接收交易,节点共识,打包出块,区块确认等功能的封装。
· 接收交易:接收来自API层传入的交易,将交易注入交易池模块中,随后通过节点的网络模块广播交易;
· 节点共识:包含当前分布式节点共识状态的确认以及网络的共识通信,共识状态可以判断当前分布式网络是否已经准备就绪,准备就绪的标准之一是已经选举出主节点。网络的共识通信是节点的共识消息通信,比如节点选举通信、节点共识消息通信、节点配置变动通信等等;
· 打包出块:节点收到共识消息后,根据出块时间定时收集交易出块。
· 区块确认:区块的交易执行完成并持久化之后,执行引擎通知共识插件模块区块已经确认。
二、接口设计
对于跨链场景来说,一个比较棘手的问题是不同种类共识算法接入的适配不同。为了简化不同共识算法的适配问题,我们在中继链中采用了插件机制,其中共识算法主要负责交易的打包和区块的确认,而所有具体在共识算法上进行操作的部分全部封装到共识算法插件中,并按照中继链与共识算法交互的需求确定了一套适合的插件接口。
这样对于中继链来说,对接任何新类型共识算法的时候,都不需要修改自身,而是根据确定的接口开发一个新的共识算法插件即可。
下面以接入Raft共识算法为例,交易经过共识模块的流程如图所示:
需要提供的接口主要分为以下四个部分:
>Prepare接口
包含检查共识、检查交易和广播交易部分,Prepare接口接收到gRPC或者Restful服务传入的交易后,把接收的交易注入交易池中排序,然后将该交易广播给集群内的其它节点。如果交易在进入交易池之前就已经存在,那么Prepare阶段会抛弃该交易不进行广播。
>Step接口
包含共识区块部分,Step接口接收四种类型的消息结构:
· 共识消息(CONSENSUS)类型:由Raft发送的共识消息,其中消息的内容主要有共识的日志信息、日志索引以及当前的任期信息等。
· 广播交易(BROADCAST_TX)类型:由其它节点广播的交易消息,该节点接受到会将交易存在交易池中。
· 获取交易(GET_TX)类型:由其它节点发出获取交易的请求消息,如果某节点在出块阶段发现部分交易不存在交易池中,则会向全网异步发送获取丢失交易的请求消息。该节点接收到消息后发现交易确实存在交易池或者区块的历史数据中,则将该交易发送给丢失交易的节点。
· 响应交易(GET_TX_ACK)类型:和获取交易类型相对应,丢失交易的节点获取到其它节点发送的交易后,将交易存入交易池中进行出块。
//消息结构体
message RaftMessage {
enum Type {
CONSENSUS = 0; //共识消息类型
BROADCAST_TX = 1; //广播交易类型
GET_TX = 2; //获取交易类型
GET_TX_ACK = 3; // 响应交易类型
}
Type type = 1; // 消息类型
uint64 fromId = 2; //消息来源方
bytes data = 3; //消息数据
}
>Commit接口
包含定时出块和区块打包阶段,Commit接口返回的是一个通道,该通道包含共识完成后的区块结构信息。交易在进入交易池后,集群中的主节点会定时从交易池中收集交易,将收集好的交易组成交易哈希列表,结合当前的块高构建成共识消息的结构体Ready。
//共识消息结构体
message Ready {
repeated bytes txHashes = 1; //交易哈希列表
uint64 height = 2; //区块高度
}
主节点将Ready提交到Raft共识引擎中,在集群的共识完成后的出块阶段,根据Ready结构体的交易哈希列表和区块高度重组区块,当区块构造完成后则通过Commit接口返回,打包好的区块随后转入执行引擎执行区块内部的交易,执行完成后交与存储层进行持久化。
>ReportState接口
包含区块确认阶段,ReportState接口接收的是执行引擎刚执行完区块的高度和哈希,中继链调用该接口通知插件共识的区块交易已经执行完成并且持久化了。通过该接口,共识算法插件可以完成一些区块的收尾工作,比如记录当前共识日志的索引、持久化布隆过滤器的filter,删除冗余的区块交易等等。
三、插件编译与接入
>插件编译
我们采用Go语言提供的插件模式,实现中继链对于插件的动态加载。首先,编写Makefile编译文件:
SHELL := /bin/bash
CURRENT_PATH = $(shell pwd)
GO = GO111MODULE=on go
plugin:
@mkdir -p build
$(GO) build –buildmode=plugin -o build/raft.so etcdraft/*.go
运行下面的命令,能够得到raft.so文件。
$ make plugin
>插件接入
接入共识算法插件应修改节点的bitxhub.toml,该文件在中继链的配置目录中。
[order]
plugin = “plugins/raft.so”
将你编写的动态链接文件和order.toml文件,分别放到节点的plugins文件夹和配置目录下。
./
├── api
├── bitxhub.toml
├── certs
│ ├── agency.cert
│ ├── ca.cert
│ ├── node.cert
│ └── node.priv
├── key.json
├── logs
├── network.toml
├── order.toml //共识算法配置文件
├── plugins
│ ├── raft.so //共识算法插件
├── start.sh
└── storage
结合我们提供的中继链,就能接入到跨链平台来。