C语言实现的ABCI

本文主要介绍用 C 语言实现的 Tendermint ABCI,以及如何在此之上构建一个属于自己的应用

原文作者:许莉
英文版本:C-ABCI

简介

首先简单介绍一下 Tendermint 和 ABCI。

Tendermint 的核心就是共识引擎,它主要负责两点:

  • 节点之间共享交易和区块
  • 建立一个规范且不可改变的交易顺序(也就是区块链)

ABCI(Application BlockChain Interface)是 Tendermint 与应用程序之间的一个接口,它可以使用各种语言来实现。目前已经实现的语言有 C++JavaScriptJava和 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
2
cd ~/c-abci
make

编译成功, 可以看到如下信息:
Makfile执行成功结果
编译完成后,会在 bin 目录下生成一个叫做 c-dummy 的可执行程序,执行该程序:

1
2
cd bin
./c-dummy

启动 Tendermint

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

1
2
tendermint init
tendermint node

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

1
2
tendermint 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 三者之间处理流程:

  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文件格式
coreC-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
24
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),可以选择其他的数据存储技术,比如树,数据库等等!

GitHub Wiki:C-ABCI编译运行出现的问题

```c #include <stdio.h> #include <string.h> char coins[12]; // 存放硬币状态 char status[5]; // 存放称量结果 // 比较函数,返回值为-1表示轻,1表示重,0表示平衡 int compare(char left[], char right[], char status[]) { int left_sum = 0, right_sum = 0; int len = strlen(left); for (int i = 0; i < len; i++) { left_sum += coins[left[i] - 'A']; right_sum += coins[right[i] - 'A']; } if (left_sum < right_sum) { return strcmp(status, "up") == 0 ? 1 : -1; } else if (left_sum > right_sum) { return strcmp(status, "up") == 0 ? -1 : 1; } else { return 0; } } int main() { int n; scanf("%d", &n); while (n--) { memset(coins, 0, sizeof(coins)); // 初始化 for (int i = 0; i < 3; i++) { char left[5], right[5]; scanf("%s%s%s", left, right, status); if (strcmp(status, "even") == 0) { // 如果是平衡的,那么称量的硬币都是真币 int len = strlen(left); for (int j = 0; j < len; j++) { coins[left[j] - 'A'] = 1; coins[right[j] - 'A'] = 1; } } } // 第三次称量,此时假币已经确定是轻还是重 for (int i = 0; i < 12; i++) { if (coins[i] == 0) { // 如果这枚硬币是假币 coins[i] = -1; // 先假设假币比真币轻 // 检查是否能够确定假币比真币重 if (compare("ABEF", "CDGH", "up") == -1 && // ABEF比CDGH轻 compare("ABEF", "CDGH", "even") == 0 && // ABEF和CDGH平衡 compare("ABEF", "IJKL", "even") == 0 && // ABEF和IJKL平衡 compare("CDGH", "IJKL", "even") == 0 && // CDGH和IJKL平衡 compare("AEIM", "BFJN", "up") == -1 && // AEIM比BFJN轻 compare("AEIM", "BFJN", "even") == 0 && // AEIM和BFJN平衡 compare("AEIM", "CGKO", "even") == 0 && // AEIM和CGKO平衡 compare("BFJN", "CGKO", "even") == 0 && // BFJN和CGKO平衡 compare("AFKP", "BGLQ", "up") == -1 && // AFPK比BGLQ轻 compare("AFKP", "BGLQ", "even") == 0 && // AFPK和BGLQ平衡 compare("AFKP", "CHLQ", "even") == 0 && // AFPK和CHLQ平衡 compare("BGLQ", "CHLQ", "even") == 0) { // BGLQ和CHLQ平衡 coins[i] = 1; // 假币比真币重 } break; } } // 输出结果 for (int i = 0; i < 12; i++) { if (coins[i] == -1) { printf("%c is the counterfeit coin and it is light.\n", i + 'A'); break; } else if (coins[i] == 1) { printf("%c is the counterfeit coin and it is heavy.\n", i + 'A'); break; } } } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值