Tendermint|C语言实现的ABCI

C-ABCI

  • 简介
  • 运行示例
    • 安装 Tendermint
    • 编译执行 c-dummy
    • 启动 Tendermint
  • 代码架构
  • 应用程序示例
  • 常见问题
    • 启动
      • 修改 Tendermint 的配置文件中的端口地址
      • 修改 c-dummy 程序中默认的绑定地址端口
      • 每次执行c-dummy程序时提供proxy_app字段中配置的IP地址端口参数
    • 编译
简介 本文主要介绍用 C 语言实现的 Tendermint ABCI,以及如何在此之上构建一个属于自己的应用。
首先简单介绍一下 Tendermint 和 ABCI。
Tendermint 的核心就是共识引擎,它主要负责两点:
  • 节点之间共享交易和区块
  • 建立一个规范且不可改变的交易顺序(也就是区块链)
ABCI(Application BlockChain Interface)是 Tendermint 与应用程序之间的一个接口,它可以使用各种语言来实现。目前已经实现的语言有 C++,JavaScript,Java 和 Erlang,尚无 C 语言实现,故而本文实现了 C 版本的 ABCI。
如果对于 Tendermint 和 ABCI 尚不熟悉,或者想要了解更多有关内容,可自行参阅以下资料:
  • Tendermint Intro
  • abci-overview
  • Tendermint Intro 中文翻译
C-ABCI 的源码已经放到了 GitHub 上:chainx-org/c-abci。
运行示例 安装 Tendermint 在编译启动 C-ABCI 之前,首先需要安装 Tendermint,这里是官方的安装指南。
编译执行 c-dummy Tendermint 安装完成之后,从 GitHub 下载 C-ABCI 源码到本地:
git clone https://github.com/chainx-org/c-abci.git ~/c-abci

进入到目录 c-abci ,执行 make 对源码进行编译:
cd ~/c-abci make

编译成功, 可以看到如下信息:
Tendermint|C语言实现的ABCI
文章图片

编译完成后,会在 bin 目录下生成一个叫做 c-dummy 的可执行程序,执行该程序:
cd bin ./c-dummy

启动 Tendermint c-dummy 启动后,开始启动 Tendermint。如果是首次执行Tendermint,需要先进行初始化再启动节点
tendermint init tendermint node

如果之前有启动过 Tendermint,先对 Tendermint 进行重置再启动节点:
tendermint unsafe_reset_all tendermint node

Tendermint 的终端输出:
Tendermint|C语言实现的ABCI
文章图片

c-dummy 的终端输出:
Tendermint|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 三者之间处理流程:
  1. Tendermint 向 C-ABCI 发送请求
  2. C-ABCI 接收请求,并解析数据,然后调用应用程序实现的回调函数,并将解析的数据通过回调函数的参数传递给应用程序
  3. 应用程序所实现的回调函数会根据不同的请求类型对数据进行不同的处理,并将处理的结果通过回调函数的返回值返回给 C-ABCI
  4. C-ABCI 将返回的结果按照 Tendermint 要求的数据格式进行处理,并将处理的最后数据响应给 Tendermint
C-ABCI 源码中,一共有 7 个目录,除了 include 目录之外每个目录都代表着一个模块,对于 socketencodingdlist 三个目录,是完全独立的,可以移出来放在任何项目中使用,后期有时间会把这三个独立的模块抽取出来继续完善!
下面具体说明一下每个目录的作用:
目录 功能
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 提供的三个接口,就完成了整个框架的编写(对照 demomain.c理解)
  • 初始化C-ABCI服务:此接口是绑定和监听传入的IP地址和端口
int server_init(const char *ipaddr, const char *port);

  • 开启C-ABCI服务:只要没有出错,此接口不会返回,会一直等待新的连接,传入的app参数就是由应用程序实现的回调函数
int server_start(Application app)

  • 停止C-ABCI服务:此接口主要是关闭监听的端口
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: returnCommit(); 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),可以选择其他的数据存储技术,比如树,数据库等等!
Tendermint启动时出现的问题
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 配置文件中的地址端口不一致,三种解决方法:
1.修改 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

2.修改 c-dummy 程序中默认的绑定地址端口 首先查看 ~/.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程序即可
3.每次执行c-dummy程序时提供proxy_app字段中配置的IP地址端口参数
c-dummy 127.0.0.1 46677

c-dummy启动时出现的问题
编译成功,但是启动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

【Tendermint|C语言实现的ABCI】刷新完成之后重新编译即可:在c-abci/目录下:
make

    推荐阅读