T4 · RPC とは何か
DApp は実際には誰と通信しているのか?
- 練習する能力
- Builder
- 実践
- 同じクエリを 2 社の RPC サービスで実行し、遅延、レート制限、利用できる過去データの範囲を比べる。
- AI Lab
- AI にリトライ、タイムアウト、複数ノードへのフェイルオーバーを備えたプロバイダーラッパーを書かせ、抜けた冪等性処理を自分で補う。
現実の問題
ローカルで正常だった DApp が、公開から三日目におかしくなります。
- ページを開くと残高が 0 で、何度か更新すると正しくなるユーザーがいます。
- 取引履歴が数件抜け、翌日には戻るユーザーがいます。
- 自分で試すと正常です。
ログには大量の 429、その下にはタイムアウト、さらに五分前より小さいブロック高が返った記録があります。
ここで気づきます。**自分のコードは、一度もチェーンそのものに触れていません。**触れたのは HTTP エンドポイントです。自分で書かず、配置せず、監視せず、障害時にもログを見られないサービスです。
その奥には誰がいるのでしょう。なぜデータを返せるのでしょう。いつ返さなくなるのでしょう。誤ったデータを返したら、どう気づけるでしょう。
思考実験
読み取り専用のデータベースレプリカからデータを得る帳票システムを作るとします。このレプリカには五つの特徴があります。
- **速度制限があります。**一定の毎秒クエリ数を超えると拒否され、クエリの「重さ」も違います。一行の取得と一千万行の走査では、消費枠が数百倍違います。
- **遅れることがあります。**主データベースへの書き込み後、同期完了時刻は保証されません。二度目の問い合わせが別のマシンへ振り分けられ、最初より古い結果になることもあります。
- 履歴がないことがあります。ディスク節約のため最近のデータしか保持せず、古いデータには空の結果ではなく意味の分からないエラーを返します。
- **偽のデータを返せます。**それを防ぐ仕組みはありません。受け取った JSON 自体から正しさを判断できません。
- **自分の管理下にありません。**停止すれば待つしかありません。
自分に問いかけてください。この五つを前提に、信頼できる帳票システムを作れますか?
作れます。ただし構成で五つを明示的に扱います。速度制限にはバックオフとキュー、遅延には単調性の検証、履歴不足には境界の把握、偽データには相互検証、管理外の障害には代替ソースが必要です。
この五つが、この章の全内容です。
「読み取り専用レプリカ」を「RPC エンドポイント」に置き換えても、すべて成り立ちます。違いは一つだけです。データベースは少なくとも自社のものですが、このエンドポイントは違います。
あなたが決める
公開間近の製品に使う RPC の構成を選びます。
結果を観察する
四つの選択肢の違いを、五つの軸にまとめます。
| 軸 | 公開無料エンドポイント | マネージドサービス | 自前ノード | 複数ソースと自社ラッパー |
|---|---|---|---|---|
| 費用 | ゼロ | 使用量に比例 | 固定、初期費用が高い | 最も高いが管理できる |
| 速度制限 | 厳しく不透明 | 明確な枠、超過課金 | ハードウェアだけが制約 | 自分で配分 |
| 履歴の深さ | 通常は浅い | 契約次第、アーカイブは追加料金 | 自分で決める | メソッド別に振り分け |
| 信頼 | 相手を全面的に信頼 | 相手を全面的に信頼 | 自分だけを信頼 | 相互検証可能 |
| 障害時 | 待つしかない | 待つしかない | 自分で直す | 自動フォールバック |
五つのうち、お金で解決できないのは「信頼」だけです。
ほかの四つは取捨選択です。利用枠やアーカイブに払うか、自前運用に人を使うかです。ただし「この JSON が本物とどう分かるか」は、いくら払っても同じです。自分で検証しなければ分かりません。
RPC を理解する最も重要な一文です。**第三者 RPC を使うことは、返されたデータを信頼することです。**製品の分散性の上限は、接続した時点でそのエンドポイントに制約されます。
モデルを作る
一回の呼び出しが実際に通る経路:
- 自分のコード
- Provider ラッパー
- HTTPS でサービスの入口へ
- 負荷分散がインスタンスを選ぶ
- あるノードプロセス
- そのローカルデータベース
製品の上限を決める三つの軸:
一、速度制限。毎秒のリクエスト数ではなく、計算量の枠として理解します。残高一件の読み取りと百万ブロック分のログ走査では消費量が数百倍違う場合があります。使用量の曲線はユーザー数よりも呼ぶメソッドで決まります。
最初の最適化は、ログ範囲を狭め、バッチをまとめ、不変データをキャッシュすることです。T15 でキャッシュ層を扱います。
**二、データの深さ。**ノードは三種あります。最近の状態だけを保存するもの(大多数)、全ブロックを保存するが状態を刈り込むもの、各過去ブロック高の完全な状態を保持するアーカイブノードです。
古い高さの残高やストレージを最初の二種に聞くと、空ではなくエラーになります。このエラーは通常「履歴がない」とは見えません。初めて見る人はパラメータを間違えたと思いがちです。
**三、一貫性。**同時刻でも二つのインスタンスの最新高は違い得て、チェーン先端は再編成されます。つまり次の二点があります。
- 同じ論理リクエストを二度送ると、二度目が古い状態を読むことがあります。
- 「最新」として読んだデータが、数秒後には存在しないことがあります。
対処は毎回「最新」を使わず、明確なブロックタグを使うことです。
| タグ | 意味 | 用途 |
|---|---|---|
| latest | このインスタンスが最新と考えるブロック | 揺れを許容できる表示 |
| safe / finalized | プロトコルが巻き戻りにくい/巻き戻らないと指定 | 資金に関わる判断 |
| 具体的な高さ | 指定した一つのブロック | バッチ索引、照合、再実行が必要な処理 |
| pending | メモリプールの未処理内容を含む | nonce の照会専用、残高には使わない |
**実務上の規則は、二度実行して同じ結果が必要な処理では latest ではなく高さを固定することです。**特に索引器が該当します。latest を使う索引器は再実行できず、障害の再現もできません。
自社ラッパーに必要な機能:
| 機能 | 解決する問題 |
|---|---|
| タイムアウト | 上流の停止で自社の接続プールを潰さない |
| リトライと指数バックオフ | 一時的な揺れと 429 |
| サーキットブレーカー | 失敗し続けるサービスへの無駄な呼び出しを止める |
| メソッド別ルーティング | 高価なアーカイブと安い通常問い合わせを分ける |
| 高さの単調性保護 | 遅れたソースへの切り替えで高さを逆行させない |
| 相互検証 | 重要データを二つのソースに聞き、不一致で通知する |
最後の二つが「複数バックエンド」と「高可用性」を分けます。最初の四つだけなら負荷分散、最後の二つが加われば信頼しないことを扱っています。
何と呼ぶか
チェーンクライアントを動かし、データを保存し、検証を実行するマシンです。
保存量により、最近の状態だけ、全ブロックと刈り込んだ状態、各高さの完全な状態を持つアーカイブに分かれます。種類が過去の照会範囲を決め、費用も決めます。
チェーンが公開するインターフェースのプロトコルです。リクエストはメソッド名と引数配列を持つ JSON で、応答は result または error を持ちます。
メソッド名はクライアント間で標準化され、どの SDK よりも安定しています。SDK の API は半年で変わっても、eth_getBalance は十年変わっていません。この講座が直接使う理由です。
コード内で RPC 呼び出しを担当する抽象層です。
リトライ、タイムアウト、キャッシュ、ルーティング、検証を挟める唯一の場所です。業務コードが各所で HTTP を直接送れば、この章の機能を置く場所がありません。
サービスによる呼び出し量の制限です。通常は件数ではなくメソッドごとの計算量で重み付けされます。
失敗は明示的な 429 と、エラーオブジェクトへの劣化の二種です。後者は error を調べないコードが正常な空結果と扱うため最も危険です。
過去の各高さの完全な状態を保存するノードです。任意の過去時点の残高、ストレージスロット、コントラクト呼び出し結果に必要です。
保存費用は通常ノードの十倍以上で、サービスの料金帯も高くなります。多くのデータ製品の費用構造がここで決まります。
どのブロックの状態で計算するかを指定するパラメータです。latest、finalized、具体高には別々の用途があります。
**非常に過小評価されているパラメータです。**誤ると 99% は正しく動き、残り 1% で再現できない誤結果を出します。
実践
**全工程は読み取りだけです。取引を送らず、秘密鍵も実資産も不要です。**公開無料とマネージドの無料枠を一つずつ使うと違いが明瞭です。
遅延を比較します。
for i in $(seq 1 10); do
curl -s -o /dev/null -w '%{time_total}\n' -X POST <エンドポイント A> \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
done両方で実行し、中央値と最大値を比較します。平均だけでなく最大値を見てください。ユーザーが感じる停止は長い裾から生じます。
高さが一致するか比較します。
同時刻に両方へ eth_blockNumber を送り、十六進を十進へ変換して差を見ます。
次に同じサービスへ十回連続で聞き、後の値が前より小さいことがあるか確認します。あれば別インスタンスに割り当てられています。これが高さの単調性を守る理由です。
履歴の深さを比較します。
curl -s -X POST <エンドポイント A> \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getBalance",
"params":["<活動中の任意アドレス>","0x<かなり古いブロック高>"]}'最近から過去へ高さを戻し、二分探索で各サービスがエラーを出し始める高さを探します。それが履歴境界です。
エラーをよく読んでください。多くは「アーカイブなし」でなく、状態検索の内部エラーです。形を覚えておけば、本番ログで一目で気づけます。
ログ問い合わせの範囲上限を比較します。
curl -s -X POST <エンドポイント A> \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getLogs","params":[{
"fromBlock":"0x...","toBlock":"0x...",
"address":"<活動中のトークンコントラクトのアドレス>"}]}'100 ブロックから倍増し、拒否まで進めます。各上限と、ブロック幅か返却件数かを記録します。この二種では索引器の構成がまったく違います。
速度制限を比較します。
eth_blockNumber を百回連続で送り、何回目から 429 やエラーが返るか数えます。
次に重いメソッド(広範囲の eth_getLogs など)で繰り返します。二つの数を比べれば、計算量課金を実感できます。
制限時の HTTP ステータスと本文を確認します。**Retry-After ヘッダーはありますか。HTTP 層のエラーですか、JSON の error フィールドですか。**リトライは両方を扱う必要があります。
相互検証を一度行います。
同じアドレス、同じ具体的なブロック高(latest は不可)で、両方に eth_getBalance を送り比較します。
コントラクトの eth_call でも同じことをします。
結果は完全に一致するはずです。不一致なら少なくとも一方に問題があります。本番で自分からそれを発見できる唯一の手段がこれです。
結論を一つの表にします。
各サービスの遅延中央値と P99、履歴境界高、ログ範囲上限、制限閾値、価格を記録します。
選定と将来の Provider ルーティング規則の根拠です。三か月後に再測定します。数値は変わるからです。
AI Lab
二段階で行い、第二段階を重視します。
第一段階:
RPC Provider ラッパーを書いてください。
複数のバックエンド、タイムアウト、指数バックオフ付きリトライ、
メソッド別ルーティング(アーカイブ用は指定先)、サーキットブレーカーが必要です。
指定した言語を使い、指定していない第三者ライブラリは使わないでください。
第二段階:
このラッパーを再実行可能な索引器に使うと仮定します。
上の実装が「正常に見えるが一貫しない」結果を返す条件を一つずつ指摘し、
それぞれの修正案を示してください。初回のモデルがほぼ必ず漏らす三点を、第二段階で引き出します。
- 冪等性。
eth_sendRawTransactionのタイムアウト後に同じ署名済みバイト列を再送するのは比較的安全です。ノードは既知の取引と扱います。ただし再構築して再署名すれば二重に送金します(T3)。モデルは両者を混同しがちです。 - **高さの逆行。**ソース A から数ブロック遅い B に切り替えると、索引器が処理済み地点より古い状態を読み、誤ったデータを書き始めます。
- **タイムアウトは未実行ではありません。**応答を待てなかっただけで、上流は処理済みかもしれません。読み取りなら問題は小さくても、書き込みでは致命的です。
修正案から一つ選び、ローカルで状況を作って検証します。一つのソースを故意に古い高さを返すサービスへ置き換え、業務側へ逆行したデータが届くか確かめます。
**モデルにラッパーを書かせる人は多くても、何を問いただすべきか知る人は少数です。**この Lab はその力を鍛えます。
AI の回答後、自分で必ず検証すること
- ライブラリ名、クラス名、メソッドのシグネチャが実在し、公式文書で同名を確認できる
- HTTP の 429 と JSON 本文の error フィールドを両方失敗として扱う
- 冪等メソッドと書き込みを区別し、取引ブロードキャストのリトライが安全か確認する
- 別ソースへの切り替え時に、新ソースのブロック高が低い場合を扱う
- タイムアウトを未実行と同一視していない。上流では実行済みかもしれない
- 特に高さと残高の十六進・十進変換が正しい
- 正常応答をデータの正しさと同一視していない
実例
大手マネージド RPC の地域障害で、多数のフロントエンドが同時にチェーンデータを読めなくなりました。
皮肉にもチェーン自体は通常どおりブロックを生成していました。チェーンは止まらず、製品が止まったのです。
分散型を名乗るアプリ群が、同じ集中型の単一障害点を共有する構造が露呈しました。多くのチームが事後に複数ソース対応を始めましたが、本来は構成レビューで指摘すべきでした。
無料公開エンドポイントからプール状態を読み、見積もりを計算する取引画面がありました。混雑時にそのソースが数ブロック遅れました。
ユーザーは数十秒前の価格を見ます。その価格で送る取引はスリッページ上限を超えて失敗するか、さらに悪い場合は明らかに不利な価格で約定します。
原因は無料であることではなく、latest で価格に敏感なデータを読み、鮮度を確認しないことです。ブロック高も読み、自分が把握する最新高との差が大きければ見積もりを拒否するのが正しい対処です。
あるデータ製品が、コントラクトの配備から現在までの全イベントを取得しようとしました。巨大なログ範囲を指定すると拒否されました。
そこで固定幅への分割、並列取得、区間ごとのチェックポイント、失敗時のリトライへ変えました。完成後、自分たちが索引器の原型を作ったと気づきました。
**ほぼすべてのオンチェーンデータ基盤は、ログ範囲の制限に直面した瞬間から始まります。**T17 で再編成と再開処理まで完成させます。
「より速い」カスタム RPC をウォレットへ追加させる詐欺があります。
そのソースは残高に任意の数字を返せます。被害者は多額の資産が届いたと画面で確認し、取引完了と思って品物を渡します。実際にはチェーンで送金は一度も起きていません。
第三者 RPC の返却データを信頼する、という説明の最も直接的な実演です。画面の一つ一つの数字は、あるエンドポイントがそうだと主張した数字にすぎません。
変数を一つ変える
自分で検証します。自前ノード(最も徹底し最も高価)、独立した複数ソースの相互検証(安く不一致は分かるが正しい側は決められない)、またはブロックヘッダーだけを信頼し他のデータには検証可能な証明を要求する軽量クライアント方式の三通りです。
第三の方式で T2 の Merkle 構造が活躍します。状態値と状態ルートまでの経路を受け取り、自分で計算します。
多くの製品は費用対効果で第二を選びます。少なくとも自分がどれを選んだかは知っておくべきです。
従量課金が現実の費用項目となり、その伸び方は収益と一致するとは限りません。
本当の最適化は安いサービスへの変更ではなく、呼び出しを減らすことです。読み取りが多く書き込みが少ないデータをキャッシュし、頻繁なポーリングを購読へ変え、ユーザー別の問い合わせをバッチへ集約し、保存できるものをデータベースに保存します。T15、T16 が扱います。
自前ノードが得になる境界は通常ここで来ます。分散化したいからではなく、計算すると安いからです。
遅延と呼び出し量は大幅に下がりますが、新しい問題として切断中のデータが失われます。
再接続後は最後に処理した高さから補完しないと、ブロック区間が黙って抜けます。再編成通知もサービスごとに異なり、巻き戻しイベントを送るものと送らないものがあります。
購読は最適化で、代替ではありません。信頼できるシステムには、高さを指定して再実行できる補償経路が常に必要です。
メソッド名は違います(アカウントは getAccountInfo、残高は getBalance)。ただし速度制限、履歴深度、一貫性、信頼、単一障害点の五軸はすべて成り立ちます。
より厳しい点もあります。Solana は既定で一定期間の履歴しか保持せず、古いものには専用サービスが必要です。半年前の取引を調べる要件の費用構造は EVM と大きく違います。T6 のアカウントモデルでは問い合わせの形まで変わると分かります。
持ち帰る問い
全データが一つの第三者から来てユーザーに検証手段もなければ、集中型 API と信頼構造は同じです。チェーンの価値は誰もが独立して検証できることです。それがユーザーへ届くかは、この層の設計で決まります。
RPC 料金はユーザーでなく自社が払います。見落としやすい単位経済性の問題です。一人のアクティブユーザーが一日で消費する計算量が、製品を拡大できるか決めます。百人の時に計算するほうが、十万人になってからよりずっと安く済みます。
誤データでユーザーが損をしたら誰が負担しますか。契約上は多くの場合、自社です。有名サービスを使うことはリスク管理ではありません。相互検証と鮮度検証こそが対策です。資金の判断では特に重要です。
章末チェック
メソッド名はクライアント間で標準化され十年変わらず、SDK の API は半年で変わるからです。
より実際的には、**SDK では送ったリクエスト数が分からなくなりやすいからです。**簡単そうな一行の裏で三回 RPC を呼ぶかもしれません。直接書けば使用量が明示されます。
本番で SDK を使うのは構いませんが、頭の中で JSON-RPC に戻せる必要があります。
latest はそのノードが最新と考えるブロックで、再編成で変わります。finalized はプロトコルが巻き戻らないと指定するブロックで、latest より遅れるのが代償です。
入金計上、枠の解除、清算など、資金を判定する読み取りには finalized または十分深い具体高を使います。表示には latest を使えます。
さらに再実行が必要な索引や照合では具体高が必須で、二つのタグはどちらも使えません。
正常に起こり得る現象です。負荷分散で別ノードへ届き、同期が異なることがあります。先端の再編成でも高さは戻ります。
Provider は観測した最大高を覚え、それより小さければリトライ、ソース変更、または業務層への通知を行います。逆行したデータをそのまま渡してはいけません。
一年前の残高、過去取引の再計算、あるブロックのコントラクト呼び出し再現、詳細な取引トレースなど、過去時点の状態が必要な場面です。
区別してください。**過去のブロックと取引の読み取りはアーカイブ不要で、過去の状態には必要です。**混同されがちですが費用は十倍ほど違います。
選定前に必要なものを確認すれば、多くの費用を節約できます。
唯一の正解はありません。次を確認します。
- 業務コードが個別に HTTP を送らず、自社 Provider を通る。
- 独立した二つ以上のソースがあり、切り替え時の高さ逆行を扱う。
- 冪等な読み取りと書き込みでリトライを区別し、タイムアウト後の挙動が明確。
- HTTP の 429 と JSON の error を両方扱う。
- アーカイブ用メソッドを別に振り分け、全体を高価な契約にしない。
- 資金判定の読み取りで使うブロックタグが明確。
- 各ソースの遅延、エラー率、高さ差、枠消費を監視する。
- 重要データの相互検証と不一致通知がある。
2、3、8 を書ければ、単に API をつなぐ段階を超え、基盤を作っています。
要点
RPC はユーザーとチェーンをつなぐ唯一の入口だ。レート制限とデータの完全性がプロダクトの上限を決める。