本文主要介绍用 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。
在编译启动 C-ABCI 之前,首先需要安装 Tendermint,这里是官方的安装指南。
Tendermint 安装完成之后,从 GitHub 下载 C-ABCI 源码到本地:
git clone https://github.com/chainx-org/c-abci.git ~/c-abci
进入到目录 c-abci ,执行 make
对源码进行编译:
cd ~/c-abci
make
编译完成后,会在 bin 目录下生成一个叫做 c-dummy 的可执行程序,执行该程序:
cd bin
./c-dummy
c-dummy 启动后,开始启动 Tendermint。如果是首次执行Tendermint,需要先进行初始化再启动节点
tendermint init
tendermint node
如果之前有启动过 Tendermint,先对 Tendermint 进行重置再启动节点:
tendermint unsafe_reset_all
tendermint node
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 三者之间处理流程:
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
理解)
int server_init(const char *ipaddr, const char *port);
int server_start(Application app)
void server_stop();
这样,应用程序的框架代码就已经完成了。剩下所需要做的事情就是实现回调函数了,回调函数的实现:(demo中的dummy.c):
void *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
),可以选择其他的数据存储技术,比如树,数据库等等!
c-dummy 正常启动后,执行 tendermind node
,出现如下错误:
E[08-31|11:13:55.061] abci.socketClient failed to connect to tcp://127.0.0.1:46658. Retrying... module=abci-client connection=query
这是由于 c-dummy 默认绑定的地址端口与 Tendermint 配置文件中的地址端口不一致,三种解决方法:
打开配置文件 ~/.tendermint/config.toml
vim ~/.tendermint/config.toml
将配置文件中 proxy_app
字段的值修改为 c-dummy 程序默认绑定的地址端口:127.0.0.0:46658:
proxy_app = "tcp://127.0.0.1:46658"
保存退出配置文件,重新启动Tendermint程序:
tendermint node
首先查看 ~/.tendermint/config.toml
配置文件中 proxy_app
字段的值,假设值为:
proxy_app = "tcp://127.0.0.1:46677"
那么进入 c-abci/demo 目录中:
cd c-abci/demo
修改main.c文件中的一行代码,原代码为:
strcpy(port, "46658");
修改为:
strcpy(port, "46677");
保存退出,在demo目录下编译:
make
然后重新启动c-dummy和Tendermint程序即可
c-dummy 127.0.0.1 46677
编译成功,但是启动c-dummy时报错:
error while loading shared libraries: libc-abci.so: cannot open shared object file: No such file or directory
这是由于动态连接库的配置文件中没有添加c-abci/lib的路径,将库的绝对路径添加到配置文件中:
sudo vim /etc/ld.so.conf.d/local.conf
输入用户密码,然后在此配置文件中添加两行数据,此文件如果不存在可以直接新建:
/home/lily/work/c-abci/lib
/home/lily/work/c-abci/type/protobuf-c
退出保存文件即可(注意,这里写的是我的绝对路径,每个人的绝对路径会不一致:/home/lily/work的部分不一样)
修改完配置文件之后,刷新动态库的配置:
sudo ldconfig
刷新完之后,重新启动c-dummy程序即可
在make编译的时候报错:
/usr/bin/ld: cannot find -lc-abci
/usr/bin/ld: cannot find -lencoding
/usr/bin/ld: cannot find -lsocket
/usr/bin/ld: cannot find -ltypes
/usr/bin/ld: cannot find -ldlist
这个原因跟上面c-dummy启动产生的问题是一样的,所以解决方案也是一样,修改配置文件然后刷新:
sudo vim /etc/ld.so.conf.d/local.conf
输入用户密码,然后在此配置文件中添加两行数据:
/home/lily/work/c-abci/lib
/home/lily/work/c-abci/type/protobuf-c
退出保存文件即可(注意绝对路径)
修改完配置文件之后,刷新动态库的配置:
sudo ldconfig
刷新完成之后重新编译即可:在c-abci/目录下:
make