本文主要介绍用 C 语言实现的 Tendermint ABCI,以及如何在此之上构建一个属于自己的应用
简介
首先简单介绍一下 Tendermint 和 ABCI。
Tendermint 的核心就是共识引擎,它主要负责两点:
- 节点之间共享交易和区块
- 建立一个规范且不可改变的交易顺序(也就是区块链)
ABCI(Application BlockChain Interface)是 Tendermint 与应用程序之间的一个接口,它可以使用各种语言来实现。目前已经实现的语言有 C++,JavaScript,Java 和 Erlang,尚无 C 语言实现,故而本文实现了 C 版本的 ABCI。
如果对于 Tendermint 和 ABCI 尚不熟悉,或者想要了解更多有关内容,可自行参阅以下资料:
C-ABCI 的GitHub源码:chainx-org/c-abci。
运行示例
安装 Tendermint
在编译启动 C-ABCI 之前,首先需要安装 Tendermint,这里是官方的安装指南。
编译执行 c-dummy
Tendermint 安装完成之后,从 GitHub 下载 C-ABCI 源码到本地:1
git clone https://github.com/chainx-org/c-abci.git ~/c-abci
进入到目录 c-abci ,执行 make
对源码进行编译:1
2cd ~/c-abci
make
编译成功, 可以看到如下信息:
编译完成后,会在 bin 目录下生成一个叫做 c-dummy 的可执行程序,执行该程序:1
2cd bin
./c-dummy
启动 Tendermint
c-dummy 启动后,开始启动 Tendermint。如果是首次执行Tendermint,需要先进行初始化再启动节点1
2tendermint init
tendermint node
如果之前有启动过 Tendermint,先对 Tendermint 进行重置再启动节点:1
2tendermint unsafe_reset_all
tendermint node
这就是整个启动过程了,下面有个C-ABCI启动过程的视频:
代码架构
Tendermint 提供了 GRPC 和 TSP 两种通信方式,C-ABCI 使用了后者,用基于 TCP 协议的 Socket 来完成通信模块。Tendermint 会保持3个连接:内存池连接(Mempool Connection)、共识连接(Consensus Connection)、查询连接(Query Connection),三个连接简介。在 C-ABCI 的实现中,每个连接都拥有一个独立的进程来专门处理此连接的所有请求,后期可能会增加用独立线程来处理的版本。
前面提到 ABCI 是一个接口,对 C 语言来说,它其实就是一个库。C-ABCI 就是一个用 C 语言实现的库,应用程序调用这个库来与 Tendermint 进行数据交互。C-ABCI 对于 Tendermint 与应用程序之间通信的具体数据并不感兴趣,它只是作为一个传递者而已!C-ABCI 与 Tendermint 之间数据的传输是通过 TCP Socket 来实现的,与应用程序之间数据的传输则是通过回调函数来实现的。
应用程序、C-ABCI、Tendermint 三者之间处理流程:
- Tendermint 向 C-ABCI 发送请求
- C-ABCI 接收请求,并解析数据,然后调用应用程序实现的回调函数,并将解析的数据通过回调函数的参数传递给应用程序
- 应用程序所实现的回调函数会根据不同的请求类型对数据进行不同的处理,并将处理的结果通过回调函数的返回值返回给 C-ABCI
- C-ABCI 将返回的结果按照 Tendermint 要求的数据格式进行处理,并将处理的最后数据响应给 Tendermint
C-ABCI 源码中,一共有 7 个目录,除了 include
目录之外每个目录都代表着一个模块,对于 socket
,encoding
,dlist
三个目录,是完全独立的,可以移出来放在任何项目中使用,后期有时间会把这三个独立的模块抽取出来继续完善!
下面具体说明一下每个目录的作用:
目录 | 功能 |
---|---|
include | 头文件目录,包含所有模块的头文件 |
socket | 通信模块,主要功能是实现TCP协议的通信,提供了绑定监听端口,连接端口,关闭端口,以及接收,发送数据的接口 |
encoding | 字符转换模块,主要功能是实现大小端整型数据与字符串之间的转换,分别提供了大端和小端不同位数的无符号整型与无符号字符串之间互相转换的接口 |
dlist | 数据存储模块,主要功能是使用循环双向链表来实现数据的存储,提供了链表的创建,销毁,增加,删除,查找接口 |
type | 数据类型处理模块,主要功能是实现数据结构体的的相关操作,提供结构体的创建,销毁等接口。Tendermint使用的数据类型保存在一个types.proto文件中,使用第三方软件protobuf-c软件将此文件生成C文件格式 |
core | C-ABCI的核心模块,主要功能就是实现一个服务端,给应用程序提供了初始化服务,开始服务以及停止服务的接口 |
demo | 实现了一个简单的应用程序,关于数据存储使用了dlist模块。 |
应用程序示例
在 C-ABCI 的源码中,demo
目录中实现了一个简单的应用程序,可以参考这个应用程序来实现自己的应用程序。
C-ABCI中有多个目录,但是编写一个应用程序不用每个目录都需要去了解,只需要了解:
core
:核心模块type
:数据类型处理模块
下面结合 demo
讲述一下如何使用上面所说的两个模块在 C-ABCI 上编写一个属于自己的应用程序。
应用程序的 main
函数中只需要调用 core
提供的三个接口,就完成了整个框架的编写(对照 demo
中 main.c
理解)
初始化C-ABCI服务
此接口是绑定和监听传入的IP地址和端口1
int server_init(const char *ipaddr, const char *port);
启动C-ABCI服务
只要没有出错,此接口不会返回,会一直等待新的连接,传入的app参数就是由应用程序实现的回调函数1
int server_start(Application app)
停止C-ABCI服务
此接口主要是关闭监听的端口1
void server_stop();
这样,应用程序的框架代码就已经完成了。剩下所需要做的事情就是实现回调函数了,回调函数的实现:(demo中的dummy.c):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24void *ABCIApplication(Types__Request *request)
{
switch( request->value_case )
{
case TYPES__REQUEST__VALUE_INFO:
return Info();
case TYPES__REQUEST__VALUE_SET_OPTION:
return SetOption(request->set_option);
case TYPES__REQUEST__VALUE_DELIVER_TX:
return DeliverTx(request->deliver_tx);
case TYPES__REQUEST__VALUE_CHECK_TX:
return CheckTx(request->check_tx);
case TYPES__REQUEST__VALUE_COMMIT:
return Commit();
case TYPES__REQUEST__VALUE_QUERY:
return Query(request->query);
case TYPES__REQUEST__VALUE_INIT_CHAIN:
return InitChain(request->init_chain);
case TYPES__REQUEST__VALUE_BEGIN_BLOCK:
return BeginBlock(request->begin_block);
case TYPES__REQUEST__VALUE_END_BLOCK:
return EndBlock(request->end_block);
}
}
每个应用程序回调函数的实现都是如此。回调函数的参数是由 C-ABCI 提供,根据不同的请求会有不同的具体实现函数,这些具体实现函数就是应用程序代码编写的重点了,也就是应用程序的业务处理的逻辑代码。业务逻辑代码写完,那么一个应用程序就完成了,剩下的就是编译运行了!
在 demo
中只实现了个别请求的具体实现,逻辑代码也非常的简单的,只是将请求的数据保存起来而已!demo 中对于数据存储这一块使用的是循环双向链表( dlist
模块),应用程序可以不用使用C-ABCI提供的数据存储模块(dlist
),可以选择其他的数据存储技术,比如树,数据库等等!
GitHub Wiki:C-ABCI编译运行出现的问题