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-abcimake
編譯成功, 可以看到如下資訊:
編譯完成後,會在 bin 目錄下產生一個叫做 c-dummy 的可執行程式,執行該程式:
cd bin./c-dummy
啟動 Tendermint
c-dummy 啟動後,開始啟動 Tendermint。如果是首次執行Tendermint,需要先進行初始化再啟動節點
tendermint inittendermint node
如果之前有啟動過 Tendermint,先對 Tendermint 進行重設再啟動節點:
tendermint unsafe_reset_alltendermint node
Tendermint 的終端輸出:
c-dummy 的終端輸出:
代碼架構
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地址和連接埠
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: 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),可以選擇其他的資料存放區技術,比如樹,資料庫等等。 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
重新整理完成之後重新編譯即可:在c-abci/目錄下:
make