Engineering · XODE omni
スマホがノードだと言うには、何を証明すべきか
スマートフォンでブロックチェーンのライトノードを動かすのは、難しいが解ける問題だ。本当に難しいのはその先だ――そこに報酬を付けた瞬間、動かしてもいないのに動かしたと言うほうが、はるかに安くつくようになる。
「スマホがノード」という言葉の正確な意味
モバイル DePIN アプリでは、「あなたのスマホがノードになる」という謳い文句はありふれている。ほとんどの場合、その言葉が実際に意味しているのは、サーバーに定期的に信号を送るということだ。アプリが 5 分ごとにバックエンドへ「生きてるよ」と打刻し、サーバーがそれを数えて報酬を出す。
それはノードではない。出席簿だ。
omni では、私たちはこの区別をコードのレベルで切り分けておいた。アプリの内部には、状態フラグが二つある。
弱い主張
チェーンから直接取得
バックエンドを通さず、チェーンの RPC から最新ブロックを取ってきた。仲介者は減ったが、依然として誰かに教えられた数字だ。
強い主張
スマホが自ら検証
端末内のライトクライアントが、ファイナリティを直接確認した。聞いた数字ではなく、計算した数字だ。
二つ目のフラグは、ネイティブの smoldot ライトクライアントが warp sync を終えるまで 0 のままだ。検証されていなければ、検証済みとは言わない。この区別が、アプリ全体の設計の出発点だ。
報酬を付けた瞬間、問題が変わる
ノードを動かせばトークンを配る、としてみよう。その瞬間、最も合理的な行動はノードを動かすことではなく、ノードを動かしているように見せかける、いちばん安い方法を探すことになる。
エミュレーターを数百台立ち上げる。ハートビートのパケットだけを真似るスクリプトを書く。一台の端末で、アカウントだけを差し替える。バッテリーもデータも使わずに、報酬だけを持っていく。正直に動かす人は相対的に損をし、結局、誰も正直には動かさなくなる。
DePIN で難しいのは、仕事をさせることではなく、仕事をしたと証明させることだ。
ハートビートはなぜ嘘をつきやすいのか
いちばん素朴なハートビートは、こんな形をしている――アプリがサーバーに現在のブロック高を尋ね、その数字に署名して送り返す。サーバーは署名を確かめて報酬を出す。
では、このハートビートが実際に証明しているのは何か?「このキーを持つ誰かが、私たちがたった今教えた数字に署名した」ということだけだ。スマホである必要も、ノードである必要も、それどころかアプリである必要すらない。curl 一行で済む。
変更前
証明されること 「誰かが、教えられた数字に署名した」
変更後
証明されること 「この端末がチェーンを自ら追跡した」
そこで私たちは、検証をスマホ側に移した。ライトクライアントがチェーンに直接つながってファイナリティを確認すれば、ハートビートが署名するのは他人に教えられた数字ではなく、この端末が自ら計算した結果になる。偽造するには、真似事ではなく実際にライトクライアントを動かすしかない――そうなれば、それはもう仕事をしたことになる。
それでも、一層だけでは足りない
検証をスマホ側に移しても、なお残る穴がある。一台の高性能な端末が、ライトクライアントを一つ動かしながら、同じ結果を数百のアカウントに振り分けて署名できてしまう。仕事は一回、報酬は数百回。
だから、防御を層状に積み重ねた。それぞれの層は、別々の問いに答える。
Layer 1 · このノードは本当にチェーンに追従したか
オンデバイスのライトクライアント自身による検証。真似事では通過できない。
Layer 2 · これは実機で、このアカウントのものか
ハードウェアキーによるアサーション。端末を登録したうえで、バックエンドがアサーションを検証したハートビートだけを認める。エミュレーター軍団とアカウントの差し替えは、ここで食い止める。
Layer 3 · 一人が何台分まで得をするか
アカウントごとの月間上限。前の二層を両方とも通過しても、際限なく増やすことはできない。
肝心なのは三つ目だ。技術的な防御は、いつか破られると想定しなければならない。破られたときに被害が線形に膨らまないよう、最後の防衛線は経済的なものでなければならない。上限があれば、防御をかいくぐるためのコストが、得られる報酬をすぐに上回る。
失敗を正直に伝えること
この構造には、ユーザー体験の面で落とし穴が一つある。報酬が 0 になる理由はいくつもあるのに、画面にはふつう一つしか表示されない、ということだ。
月間上限に引っかかって 0 なのと、端末の検証が通らずに 0 なのとは、まったく別の状況だ。前者は来月まで待てば済むが、後者は今すぐ再認証しなければ解除されない。ところが、どちらも「本日の獲得 0」と表示されると、検証で詰まっているユーザーはただ待ち続けて、一か月を棒に振る。
同じ数字でも、原因が違えば違う文言を見せなければならない。直し方が違うからだ。
そこで、バックエンドのゲートは 0 を返すときに理由コードを一緒に送り、アプリはそのコードに応じて異なる案内を表示する。コードのコメントには、ルールとして刻んである――アテステーションの問題は、絶対に「月間上限に到達」と表示しないこと。
バッテリーとデータが本当の制約だ
ここまでが設計の話だとすれば、実際に人々がアプリを動かし続けるかどうかは、まったく別の戦いだ。スマホで動くノードは、二つの予算の枠内で生きなければならない――バッテリーとモバイルデータだ。どちらか一方でも超えれば、ユーザーはアプリを消す。報酬がいくらであろうと関係ない。
初期のビルドは、1 時間あたり 90MB を超えるデータを使っていた。ライトクライアントのピア探索ループが必要以上に回っていたうえに、チェックポイントが古く、毎回追いつかなければならない区間が長かった。探索ループを直し、チェックポイントを更新して、1 時間あたり 50MB 程度まで下げた。
こうした作業はブログのタイトルにはならないが、DePIN アプリの生死を分けるのは、たいていこちらのほうだ。誰も「ファイナリティ検証」のせいでアプリを消したりはしない。データ通信料のせいで消すのだ。
まとめ
スマホをノードにするという仕事には、三つの問題が重なっている。チェーンに追従させる問題(ライトクライアント)、追従したことを証明させる問題(検証の場所+ハードウェアキー)、そして動かし続けてもらう問題(バッテリーとデータ)だ。
三つのうち一つでも取りこぼせば、残りは意味を失う。検証のないノードは出席簿にすぎず、証明のない報酬は草刈り場と化し、バッテリーを食うアプリは誰も起動しない。
次の記事では、このノードが検証した結果をウォレットアプリがどのように取り込んで使うのか――つまり「サーバーに残高を尋ねないウォレット」を取り上げる。
