2-6. RPC/WebSocket APIの処理を追う
2-2. トランザクション処理の流れ では submit を起点に書き込み系の流れを追いました。このページでは、API リクエスト全般が どのハンドラに振り分けられ、どんな前提条件でチェックされるか という共通の仕組みを見ていきます。account_info のような読み取り系コマンドの追い方も身につきます。
2つの入り口:JSON-RPC と WebSocket
Section titled “2つの入り口:JSON-RPC と WebSocket”xrpld は同じコマンド群を2つの経路で受け付けます。
flowchart LR http["HTTP POST(JSON-RPC)"] ws["WebSocket メッセージ"] handler["ServerHandler → RPC::Handler → doXxx()"] http --> handler ws --> handler
どちらの経路でも、最終的には同じハンドラ関数(doAccountInfo など)が呼ばれます。HTTP/WebSocket の受け口と RPC ディスパッチは次です。
src/xrpld/rpc/ServerHandler.hsrc/xrpld/rpc/detail/ServerHandler.cppsrc/xrpld/rpc/detail/Handler.cpp # kHandlerArray(コマンド登録)src/xrpld/rpc/detail/RPCCall.cppハンドラの登録テーブル
Section titled “ハンドラの登録テーブル”どのコマンド名がどの関数に対応するかは、src/xrpld/rpc/detail/Handler.cpp の kHandlerArray で一覧定義されています。
Handler const kHandlerArray[]{ {.name = "account_info", .valueMethod = byRef(&doAccountInfo), .role = Role::USER, .condition = Condition::NoCondition},
{.name = "submit", .valueMethod = byRef(&doSubmit), .role = Role::USER, .condition = Condition::NeedsCurrentLedger},
{.name = "tx", .valueMethod = byRef(&doTxJson), .role = Role::USER, .condition = Condition::NeedsNetworkConnection}, // ...};各エントリは「コマンド名・ハンドラ関数・必要な権限・前提条件」を結びつけています。新しい RPC コマンドを追加するときは、この配列に1行追加するのが出発点です。
Role — 必要な権限
Section titled “Role — 必要な権限”Role は、そのコマンドを誰が呼べるかを表します。
src/xrpld/rpc/Role.hsrc/xrpld/rpc/detail/Role.cppenum class Role { GUEST, USER, IDENTIFIED, ADMIN, PROXY, FORBID };| Role | 意味 |
|---|---|
GUEST | 制限付きの一般アクセス |
USER | 通常のクライアント(多くの読み取り・送信系) |
ADMIN | 管理者専用(ノード制御・鍵生成など) |
FORBID | 拒否 |
たとえばノードを停止する stop のような管理コマンドは ADMIN、account_info のような参照系は USER です。admin 系ハンドラは src/xrpld/rpc/handlers/admin/ にまとまっています。
Condition — 実行に必要な前提
Section titled “Condition — 実行に必要な前提”Condition は、コマンド実行前にノードが満たすべき状態を表すフラグです。
src/xrpld/rpc/detail/Handler.hsrc/xrpld/rpc/detail/Handler.cppenum class Condition { NoCondition = 0, NeedsNetworkConnection = 1, NeedsCurrentLedger = 1 << 1, NeedsClosedLedger = 1 << 2,};| Condition | 意味 |
|---|---|
NoCondition | 前提なし(スタンドアロンでも動く) |
NeedsNetworkConnection | ネットワークに接続していること |
NeedsCurrentLedger | 現在進行中の Ledger が必要 |
NeedsClosedLedger | 確定済みの Ledger が必要 |
submit が NeedsCurrentLedger を要求するのは、トランザクションを 進行中の OpenLedger に適用する必要があるからです。条件を満たさないノード(同期中など)に投げると、ここで弾かれます。
読み取り系ハンドラを追う:account_info
Section titled “読み取り系ハンドラを追う:account_info”書き込み系の submit に対して、読み取り系の代表が account_info です。
src/xrpld/rpc/handlers/account/AccountInfo.cppJson::ValuedoAccountInfo(RPC::JsonContext& context){ // 1. パラメータ(account / ledger_index 等)を読み取る // 2. 対象 Ledger を取得 // 3. keylet::account() で AccountRoot SLE を read() // 4. 結果を JSON に組み立てて返す}ポイントは、書き込みを伴わないため ReadView::read() だけを使い、2-3. Ledgerの仕組み で見た ApplyView は使わないことです。Condition::NoCondition なので、過去の確定 Ledger を指定すれば同期していなくても応答できます。
ハンドラがカテゴリ別に整理されている
Section titled “ハンドラがカテゴリ別に整理されている”src/xrpld/rpc/handlers/ は、コマンドの種類ごとにサブディレクトリへ分割されています。
| ディレクトリ | 主なコマンド |
|---|---|
account/ | account_info, account_lines, account_tx 等 |
ledger/ | ledger, ledger_entry, ledger_data 等 |
transaction/ | submit, tx, transaction_entry 等 |
orderbook/ | book_offers, path_find, amm_info 等 |
server_info/ | server_info, fee, feature 等 |
subscribe/ | subscribe, unsubscribe |
admin/ | 管理者向けコマンド |
utility/ | ping, random |
# 例: book_offers の実装を探すgrep -n "book_offers" src/xrpld/rpc/detail/Handler.cppgrep -rn "doBookOffers" src/xrpld/rpc/handlers/実際にコードを読む練習
Section titled “実際にコードを読む練習”src/xrpld/rpc/detail/Handler.cppのkHandlerArrayを眺め、コマンドと role/condition の対応を確認するsrc/xrpld/rpc/handlers/account/AccountInfo.cppを開き、read()でSLEを取得する流れを追うsrc/xrpld/rpc/handlers/transaction/Submit.cppを 2-2 の内容と照らし合わせて読む
次のステップ
Section titled “次のステップ”API リクエストの振り分けの仕組みが理解できました。これで Level 2 は完了です。Level 3 に進み、実際の good first issueの探し方 からコントリビューションを始めましょう。