CometBFT: ABCI

Takami Torao CometBFT 0.37.2 #Blockchain #CometBFT #Tendermint #Cosmos
  • このエントリーをはてなブックマークに追加

概要

ABCI (application blockchain interface) は CometBFT コアの各コンポーネント (コンセンサス層) とブロックチェーンアプリケーションの実装 (アプリケーション層) が通信するためのインターフェース仕様である。ブロックチェーンアプリケーション開発者が CometBFT と連携するための標準的なプロトコルを定義している。

ABCI は Fig 1 のようにコンセンサスアルゴリズムやブロック生成に関する責任を CometBFT コアに分離できるため、アプリケーション実装者はブロックチェーン機能、状態管理、スマートコントラクト実装などのアプリケーション固有の機能の実装に注力することができる。ただし、現実的には CometBFT が何の目的で各機能を呼び出しているかの詳細を知っておく必要があり、ABCI の方向性もブロックチェーンを効率化するためにインターフェースの詳細化と密結合化が進んでいる。

Fig 1. ABCI は CometBFT コアとアプリケーション実装を接続している。

アプリケーション要件によっては、実際にアプリケーション固有のブロックチェーンを構築するために ABCI 経由でアプリケーション層を実装するだけでは不十分な可能性がある。この場合、CometBFT のソースを修正する必要がある。

  1. アプリケーション層のみを開発する。これは CometBFT のバイナリをそのまま利用し、socket または grpc でアプリケーション実装と通信する方法である。

  2. アプリケーション層に加えて CometBFT の起動部分を実装する。つまり 1. に加えて main() から開始する処理を実装し CometBFT をライブラリとして利用する実装する方針である。

    1. CometBFT とアプリケーションを同一のバイナリまたは同一のプロセスで動作させたい。
    2. 認証や監視などの外部システムと統合したい。
  3. アプリケーション層に加えて CometBFT をフォークして任意の部分を実装する。CometBFT に対する要件が 2. では行えなかったケースだが、CometBFT のバージョンアップを追従する必要がありメンテナンスコストが高い。

本格的に独自のブロックチェーンを開発するのであれば IBC (inter-blockchain) の利用できる Cosmos SDK を用いる方が良いだろう。Cosmos SDK はコンセンサスエンジンに CometBFT を使用しアプリケーション実装とは ABCI で通信するため開発知識を共有することができる。

Table of Contents

  1. 概要
  2. 構成
  3. 呼び出し経路
  4. ABCI アプリケーション
  5. ABCI クライアント
  6. ABCI サーバ
  7. 参照

構成

ABCI の実体は RPC に基づく通信プロトコルの定義である。アプリケーション実装と CometBFT コアをプロセスまたはノードで分離できることから、アプリケーションのプロセスをより制約の強い環境や隔離されたコンテナ内で実行したり、ノード自体を DMZ 内で動作させるような構成ができる。

CometBFT コアから呼び出されるアプリケーション実装までのスタックを Fig 2 に示す。

Fig 2. ABCI によるアプリケーション呼び出しのスタック。構成は grpc, socket (TCP), local の 3 つの経路から選択することができる。

呼び出し経路

標準の ABCI 実装では Fig 2socket, grpc, local に対応する 3 つのアプリケーション呼び出し経路の構成を選択できる。

local 以外の socket や grpc 構成では、サーバ側 (アプリケーション側) のプロセスを CometBFT とは別に起動する必要がある。クライアント側では、設定の abcigrpc を指定すると grpcClient が選択され socket を指定すると socketClient が選択される。また設定の proxy_app に接続先アドレスの代わりに kvstore や noop といった標準ビルトイン実装のアプリケーションを指定すると localClient が選択される。デフォルトの動作は tcp://127.0.0.1:26658 への socket 接続である。標準実装では DefaultNewNode または RunReplayFile から呼び出す DefaultClientCreator で決定している。

これらの 3 つの ABCI 呼び出し経路には次のような特徴がある:

socket でのメッセージ送受信

ProtocolBuffers を用いてシリアライズされたリクエスト/レスポンスメッセージを単一の TCP ソケットまたは Unix ドメインソケット上で送受信する通信実装。現在の実装では、メッセージの送信はクライアント側でキューによって直列化され、またサーバ側でも受信順に逐次処理するため、呼び出し側が並行化されていたとしても ABCI アプリケーション実装が並行で実行されることはない。

Fig 3. socket での送受信処理。クライアント側の同期版の XxxSync は単純に非同期の動作のコールバックを待機する。
grpc でのメッセージ送受信

gRPC を使用して ABCIApplicationClient の実装を使用する通信実装。ただし will have significant performance overhead と言われており CometBFT では標準的に socket が使用されている。

gRPC 仕様の通信であるため Go 以外の言語で開発されたサーバ実装に対して開発が容易な点は選択肢の一つとなる。現在の実装では呼び出し側が並行化されていると ABCI アプリケーション実装も並行実行される。したがって、アプリケーションの特定の処理が非常に遅いようなケースでは、socket では後続の処理を巻き込んで遅延するが、grpc であれば遅い処理と並行して後続の処理を実行できるため有効に機能する可能性がある。

local でのメッセージ送受信

同一のプロセス内に存在する ABCI アプリケーション実装を通常のメソッド呼び出しと同じ機構で呼び出す実装。現在の実装ではメッセージの送受信 (つまりメソッドの呼び出し) はすべて Mutex によって直列化されているため、呼び出し側が並行化されていてもアプリケーション実装が並行で実行されることはない。

アプリケーションを local で起動するような main() を作成することで CometBFT コアとアプリケーションを同一のバイナリとして同一のプロセス内で動作させることができる。

選択したクライアント実装によって並行実行する ABCI の挙動が異なることに注意。アプリケーションを並行して呼び出した場合、socketClient と localClient では逐次的にアプリケーション実装が呼び出されるのに対して grpcClient では並行して呼び出しが行われる。具体的には、例えばアプリケーション実装で Info() に 1 秒かかると仮定し、異なる 5 つのスレッドで Info() を呼び出したとすると、socket と local ではすべてのスレッドが終了するまでに 5 秒程度かかるのに対して、grpcClient は 1 秒程度で終了する。このような挙動の違いは次のテストで transport を書き換えて実行すると確認できる。

torao@lazurite$ cat abci-parcall_test.go

このような挙動の違いは socket, local で正しく動作するアプリケーション実装が grpc では動作しないという互換性の問題を引き起こす可能性がある

ABCI アプリケーション

ABCI アプリケーション実装の主要な目的は CometBFT によって決定されたブロックを実行することである。これは、CometBFT が分散システムにおけるステートマシンレプリケーションとして機能するときのステートマシン (state machine) に相当する。

Application インターフェースに定義されている 14 個のメソッドはそれぞれ ABCI クライアントのメソッドに対応している。各メソッドは次に示す状況で呼び出される。

メソッド アプリケーションのメソッドが呼び出される状況
Info
Query
CheckTx
InitChain
PrepareProposal
  • [consensus] 提案ブロックを作成するときに呼び出される。アプリケーションが応答したトランザクションで提案ブロックが生成されることから、アプリケーションでトランザクションのフィルタリングや追加を行うことができる。
ProcessProposal
BeginBlock
DeliverTx
EndBlock
Commit
ListSnapshots
OfferSnapshot
LoadSnapshotChunk
  • [snapshot] P2P の状態同期で Chunk 要求を受け付けたときにアプリケーション状態のチャンクを取得するために呼び出される。応答のチャンクは Chunk 要求の応答として要求元に送信される。
ApplySnapshotChunk
Table 1. アプリケーション実装のメソッドが呼び出される状況の一覧。

アプリケーションの実装フェーズではこれらすべてのメソッドを注意深く実装する必要がある。ただし、動作確認用のアプリケーション実装であれば BaseApplication を匿名フィールドに定義して必要なメソッドのみを実装することもできる。

アプリケーション実装の Query メソッドは RPC から呼び出すこともできるため、例えば通貨や NFT のようにブロックチェーンアプリケーション固有の機能を問い合わせる用途に利用できる。実際、Table 2 に示すように CometBFT からも (ABCI の定義を追加するまでもないような) 問い合わせで使用されている。

Path 意味
/p2p/filter/addr/$REMOTE_ADDR 指定された $REMOTE_ADDR からの接続を受け付けたときに呼び出される。アプリケーションが CodeTypeOK 以外の応答をするとその接続は拒否される
/p2p/filter/id/$REMOTE_ID 指定された $REMOTE_ID からの接続を受け付けたときに呼び出される。アプリケーションが CodeTypeOK 以外の応答をするとそのピアは拒否される
Table 2. CometBFT コアでの Query メソッドを使用する状況の一覧。
Fig 4. アプリケーションインターフェースのための主要なクラス図。

ABCI クライアント

Client インターフェースは CometBFT コアからアプリケーション実装の機能を呼び出すためのメソッドを定義している。追加の Echo と Flush は ABCI 通信のみに作用しアプリケーション実装までは到達せずに SocketServerGRPCApplication で折り返している。Echo は単純に与えられたメッセージで応答するだけで ABCI CLI から疎通確認のために利用できる。Flush は Commit や Recheck 時に呼び出され、socket クライアント/サーバに対してストリームをフラッシュを指示しバッファリングされているすべてのリクエスト/レスポンスを確実に相手に送信する (grpc と local では無視される)。

Client インターフェースの接尾辞 Sync/Async はそれぞれ該当するアプリケーションメソッドの同期/非同期版であることを示している。ただし本質的に Async が非同期実行となっているのは socket 実装のみで、grpclocal は同期実行の結果をコールバックで通知しているだけである (したがって grpc と local での Async 呼び出しはアプリケーション実装の処理が終わるまでブロックされる)。またすべての標準クライアント実装の Sync 版は単に内部で Async を呼び出して応答があるまで待機しているだけである。

Async 版メソッドの返値である ReqRes は他の言語の Future/Promise パターンを模して CometBFT で独自に実装されたものである。しかし ReqRes では成功応答しか通知できないため、いくつかのレスポンスでは Code フィールドを使ってアプリケーション側の失敗を通知している。いずれのクライアント実装も通信エラーやサーバ側のエラーは回復不能な状況と見なされてクライアント処理が終了してメッセージ送受信ルーチンを終える。

Client インターフェースは AppConnConsensus, AppConnMempool, AppConnQuery, AppConnSnapshot の 4 つのインターフェースを継承しており、CometBFT コアからはこれらの目的に限定されたインターフェースを介してアプリケーション実装の機能を利用する (Client インターフェースでは各メソッドに対して同期/非同期の対称性を強制しているため無駄なメソッド実装が発生している)。

Fig 5. ABCI クライアント実装の主要なクラス。CometBFT コアからは proxy パッケージに定義された限定されたインターフェースを経由して利用する。

ABCI サーバ

ABCI サーバは ABCI クライアントからの接続を受け付け、アプリケーション実装の機能を呼び出して応答する。標準実装では接続方法の local 以外の socket grpc に対応するサーバが用意されている。

Cosmos のエコシステムでは ABCI アプリケーションをどのように開発し実行するかは Cosmos SDK の関心であることから、CometBFT では ABCI サーバを起動するための NewServer を用意しているだけである。CometBFT のみで ABCI アプリケーションを実装する場合、abci-cli のkvstoree2e テストのコードを参考に CometBFT をライブラリとして参照し、main() から ABCI サーバを起動するコードを作成する必要がある。

Fig 6. ABCI サーバ実装の主要なクラス。

参照

  1. ABCI++ (v0.37.2)