T1 · Blockchain データとは何か
チェーンには実際に何が保存されているのか?
- 練習する能力
- BuilderOnchain Literacy
- 実践
- 直近 10 ブロックを取得し、各ブロックの取引件数、ガス使用量、最初の取引の受取先を表示するスクリプトを書く。
- AI Lab
- AI に State と Event の違いを説明させ、Event だけを見ると誤った結論になる例を挙げさせる。
現実の問題
プロダクト担当から、あるアドレスが USDC を受け取ったら、利用者へ通知するという要件が来ました。
五分でできそうですが、文書を開くと最初の問いで迷います。「受け取った」をどこで読み取るべきでしょうか。
- 数秒ごとに残高を調べ、増えたら通知する?
- USDC コントラクトの Transfer イベントを監視する?
- 各ブロックの取引を取得し、一件ずつ解析する?
- 第三者の API を使う?
すべて demo は作れても、実際の動作は異なります。通知の欠落、重複、再編後に存在しない送金の通知などが起こります。
選択を誤る代償は性能ではなく正確性です。選ぶ前に、実際に保存されているものを理解します。
思考実験
チェーンを一つではなく、二つのものとして考えます。
一つ目は、追記しかできない流水台帳です。
一定間隔でブロックができ、取引が順番に入ります。後から変更も割込みもできません。台帳は行動だけを記録し、誰が何を呼び、どんな引数で、手数料はいくらかを示します。
二つ目は、流水台帳から導いた現在の状態表です。
各アカウントの残高や、コントラクトの各保存スロットの値です。表を直接書き込むのではなく、最初のブロックからすべての取引を順に実行した結果です。
では、一取引はどの状態を変えたのでしょうか。
流水台帳には直接は書かれていません。コントラクト A の transfer を、アドレス B と量 100 で呼んだと記録するだけです。何スロットが変わり、他のコントラクトが呼ばれたかは、実際に実行する必要があります。
そこで三つ目のログがあります。実行中にコントラクトが、B から C へ 100 を送ったと自ら知らせ、その声を取引レシートに残して外部へ伝えます。
- Transaction 何をしたいか
- State 世界がどう変わったか
- Event 自ら何をしたと知らせるか
あなたが決める
通知の要件に戻り、どれを選びますか。
結果を観察する
四つの選択肢は、オンチェーンデータを読んでいるつもりでも、実際には一層の投影を読んでいることを示します。
| 読むもの | 実体 | 欠けるもの |
|---|---|---|
| 残高 | 現在状態の断面 | 途中の過程、同ブロック内の複数変化 |
| 取引一覧 | 外部からの行動 | 内部呼び出しと送金 |
| Event ログ | コントラクトの任意の説明 | Event がない、非標準の場合 |
| 実行トレース | 完全な呼び出し木 | ほぼ欠けないが、遅く高価 |
許容できる欠落で選びます。残高表示は状態、資金照合はトレース、通知と索引は Event が適します。
最も正確な層ではなく、要件に合う層を選びます。
モデルを作る
チェーンのデータを層に分けます。
Block ブロック:取引の容器
├─ header ヘッダー:親ハッシュ、高さ、時刻、状態ルート
└─ transactions[] 本体:順序付きの取引
└─ Transaction 取引:from、to、value、data、nonce、gas
└─ Receipt 取引レシート:実行後に生成
├─ status 成功か失敗か
├─ gasUsed 実際の消費
└─ logs[] コントラクトのイベント
└─ Log topics(索引可能)+ data(内容)
State 状態:すべての取引の実行結果
├─ account balance 残高
├─ contract code コントラクトのコード
└─ contract storage 保存スロット四つを覚えると、遠回りを減らせます。
- Receipt は実行後に存在します。 ブロックに入っても成功とは限らず、
statusを確認します。送信を成功とするのはよくある bug です(T14)。 - topics は索引可能で、data は違います。 アドレスと topics で効率的に絞れますが、通常のデータ項目ではできません。
- State は現在だけです。 過去の高さにはアーカイブノードが必要で、費用の落とし穴になります(T4)。
- Event は状態ではありません。 実際と異なるイベントも出せ、攻撃に使われたこともあります。状態を確認するには状態を読みます。
何と呼ぶか
取引の容器です。parentHash がつなぎ、過去を変えると後のすべてが無効になります。
外部アカウントによる状態変更の要求です。何をするかを記録し、結果は記録しません。
現在の全残高、コード、保存値の総体です。全履歴の実行結果で、別に書き込まれたデータではありません。
成功、Gas、ログなど、実行後の結果を記します。
成功はブロック内の有無ではなく、Receipt の status で判断します。
チェーン外へ伝えるため、取引レシートに書く構造化データです。コントラクト自身は読めません。
topics は索引項目(通常、先頭はイベント署名のハッシュ)、data は残りです。
実行中にコントラクトが行う呼び出しや送金です。
独立した取引ではなく、一覧には出ず、トレースで復元します。エクスプローラーに見える資金を自分のプログラムが見落とす原因です。
実践
SDK を使わず、JSON-RPC を直接呼び、生のデータを見ます。
最新の高さを取得します。
curl -s -X POST <あなたの-RPC-アドレス> \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'戻り値は十六進数です。多くの数値が十六進数文字列なので注意してください。
ブロックの完全な内容を取得します。
curl -s -X POST <あなたの-RPC-アドレス> \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["latest",true]}'第二引数が true なら完全な取引、false ならハッシュだけです。大きさを比較すると、索引で制御する理由が分かります。
取引レシートを取得し、status と logs を見つけます。
curl -s -X POST <あなたの-RPC-アドレス> \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["<取引ハッシュ>"]}'エクスプローラーの同じ取引と照合します。
一定範囲をイベントで絞ります。
curl -s -X POST <あなたの-RPC-アドレス> \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getLogs","params":[{
"fromBlock":"0x...","toBlock":"0x...",
"address":"<トークンのコントラクトアドレス>",
"topics":["<Transfer イベント署名のハッシュ>"]}]}'索引の中心です。範囲を広げ、どこから拒否されるかを確認します。その制限が分割方法を決めます。
スクリプトにし、表を出します。
最新十ブロックの高さ、時刻、取引数、総 Gas、最初の取引の受取人を表示します。
これが段階の成果物の最初の版です。
テストネットか公開の読取エンドポイントだけを使い、秘密鍵は不要です。 取引は送信しません。
AI Lab
二段階で問い、第二が特に重要です。
第一段階:
Blockchain の State と Event の違いと、不一致が起こる理由を説明してください。
第二段階:
Transfer の監視だけでは誤った結論になる具体的な状況を三つ示してください。
各状況で、欠けるもの、結果、正しい方法を説明してください。案を出させるより、案の反例を探させるほうが役立ちます。
三つの状況から実例を探して検証します。なければ、実際のアドレスやハッシュを求めてください。提示されたハッシュが存在しないことも、多くの場合に発見します。
一度は自分で経験すべきことです。
AI の回答後、自分で必ず検証すること
- JSON-RPC の名前と引数順が公式仕様と一致する
- 索引可能なのは topics で、data の項目を絞れると述べていない
- Event だけでは欠ける例を実際のチェーンで確認できる
- 十六進数と十進数の変換を誤っていない
- ブロック内の存在を取引成功と取り違えていない
実例
ETH や SOL の送金はコントラクトを通らないため、Transfer イベントを生みません。
監視だけではすべて見落とし、トークンだけ通知され、主通貨は通知されません。
取引の value を使い、内部送金はトレースで覆います。
自動で税や焼却分を引くトークンがあります。
イベントに 100 とあっても、実際は 97 しか届かず、イベントだけの残高は一致しません。
照合は状態を基準にし、イベントは索引の手掛かりとします。
集約コントラクト経由なら、最外部の to は集約先で、最終受取人ではありません。
一覧だけでは相手を誤ります。エクスプローラーの内部取引は、トレースで復元されています。
全資金流はトレース、通知だけならイベントを使います。
末尾の再編で処理済みのブロックが戻り、取引は別ブロックへ入るか、二度と入りません。
すぐ通知すると、後に存在しない送金の通知となります。
リスクに応じて確認数を定め、索引を巻き戻せるようにします。T17 で詳しく扱います。
変数を一つ変える
状態は正しく変わっても、外部には見えません。
観測可能性はコントラクト設計の一部で、チェーンが自動で与えるものではありません。 T7 で、時点と項目を設計します。
普通のノードは最近の履歴しか保持せず、エラーになります。アーカイブの保存費用は一桁大きくなります。
ここでデータ事業の費用が決まることがあります。T4 で能力と費用を比較します。
全体の状態木ではなく個々のアカウントへ分散し、読書き対象を事前に宣言して並列化します。
ログも異なります。T6 で比較します。比較はモデルを理解する最も速い方法です。
持ち帰る問い
起きたことを正しく読む方法を扱いました。基本でも、後のすべての前提です。データが誤れば、その上もすべて誤ります。
Blockchain の独自の答えは、誰でも同じ履歴を独立して検証できることです。ノードを動かし、API を信頼せず同じ結果を得られます。
索引の欠落による損失は通常利用者が負い、自分も後で気付かないかもしれません。正確性を、見た目でなく再生可能性で保証する理由です。
章末チェック
言えません。失敗した取引も入り、手数料がかかります。
取引レシートの status を見ます。チェーンへ入っただけで成功と表示するのは、よくある bug です。
なります。任意の説明なので、実際と異なるイベントも出せます。
索引と通知は Event、資金照合は State を基準にします。
topics だけが索引で、data は違うからです。作者が、通常最大三つの索引項目を選びます。
絞りたい項目は、イベント定義の時点で索引可能にする必要があります。
回答例です。
eth_getLogsでアドレスと Transfer トピックを使い、範囲別に取得します。- 許容リスクに合わせた確認数を待ちます。
- 処理済みの高さを記録し、任意の高さから再生できます。
- 親ハッシュの不連続で再編を検知し、巻き戻して再処理します。
- 定期的に状態と照合し、差を通知します。
第三、第四項目まで書ければ、多くの demo 開発者より先へ進んでいます。
要点
チェーンが保存するのは状態とそれを変える取引であり、イベントは外の世界に見せる投影にすぎない。