
Solana障害の全史:原因、修正、得られた教訓
ピッ、ピッ、ピッ。ピッ、ピッ、ピッ。
けたたましい着信音でスティーブンの眠りは破られ、夢から突然現実へと引き戻されます。暗闇の中、ベッド脇のテーブルで画面が明るく光り、端末が激しく振動しています。ピッ、ピッ、ピッ。 うめき声を漏らしながら眠い目をこすり、端末に手を伸ばします。目を細めてメッセージを確認すると、血の気が引きます。ノードがダウンしています。ためらうことなく、半分しか服を着ていない状態でベッドから飛び起き、次々に届くメッセージを横目にスマートフォンのロック解除に手間取ります。そして気づきます。クラスター全体がダウンしているのです。
まさにその瞬間、世界各地の都市やタイムゾーンで、何百人ものノードオペレーターが同じ事実に気づき、スマートフォンを見つめています。恐れていた瞬間、つまり障害が発生したのです。
はじめに
あらゆる分散システムと同様に、Solanaも、単一の実装上の欠陥や見落とされがちなエッジケースがネットワーク全体の障害につながり得るという現実の下で動いています。障害はサービスを中断させますが、複雑な分散インフラを維持するうえでは避けられません。これは分散型ブロックチェーン、中央集権型取引所、さらにはAmazonやMicrosoftのような大手クラウドサービスプロバイダーでも同様です。
問題は障害が発生するかどうかではなく、いつ発生するか、そして将来のインシデントに適応して耐性を高めるためにネットワークがどう進化するかです。厳格なシミュレーションテスト、インセンティブ付きテストネット、積極的なバグ報奨金プログラムを実施しても、どれほど適切に設計されたシステムであっても、起こり得るすべての障害モードを予測することはできません。最も価値のある教訓は、実環境での運用から得られます。
過去5年間に、Solanaでは7件の障害が発生しました。そのうち5件はクライアントのバグ、2件は大量のトランザクションスパムをネットワークが処理できなかったことが原因です。初期のSolanaには、優先手数料やローカル手数料市場など、後にネットワーク負荷の軽減に不可欠と判明する主要な輻輳管理メカニズムがありませんでした。こうしたメカニズムがなかったため、ネットワークが実質的にスパムを促す状態となり、2022年を通じて長期間にわたる性能低下と輻輳が発生しました。
この記事では、Solanaの各障害について、根本原因、引き金となった事象、解決のために講じられた対策を詳しく分析します。さらに、ネットワーク再起動、バグ報告、ライブネス障害とセーフティ障害の基本概念についても解説します。順番に読むのが最適ですが、各セクションは単独でも理解できるように構成されているため、関心のあるトピックや障害事例へ直接移動できます。
ライブネスとセーフティ
Brewerの定理とも呼ばれるCAP定理によると、分散システムが実現できるのは、次の3つの特性のうち2つだけです。
- 一貫性 - すべての読み取りで、それ以前のすべての書き込みが確認できます。
- 可用性 - すべてのリクエストが応答を受け取ります。
- 分断耐性 - ネットワークが分断されてもシステムは動作を継続します。
ブロックチェーンには分断耐性が不可欠です。ネットワークの中断は避けられないためです。そのため、AP(可用性+分断耐性)とCP(一貫性+分断耐性)のどちらかを選ぶ必要があります。ファイナリティが高速な多くのPoSチェーンと同様に、Solanaは可用性より一貫性を優先するCPシステムです。重大な障害時には、古いデータを提供したり安全でない書き込みを許可したりせず、停止します。これによりノードソフトウェアが手動介入を必要とする回復不能な状態に陥る可能性はありますが、ユーザーの資金の安全性は維持されます。
ライブネス障害: バリデーターの停止、ネットワーク分断、コンセンサスの停滞などにより、ブロックチェーンの進行が止まり、トランザクションの確認やブロックの生成ができなくなる状態です。CAP定理では、可用性の喪失に相当します。
セーフティ障害: ブロックチェーンの確定済み状態が不適切に変更またはフォークされる状態です。履歴の競合や二重支払いにつながる可能性があり、多くの場合、コンセンサスのバグや悪意ある攻撃が原因です。CAP定理では、一貫性の喪失に相当します。
Solanaはライブネスよりセーフティを優先します。そのため、状態破損のリスクを冒すのではなく、ネットワークに極端な負荷がかかった場合やコンセンサス障害が発生した場合に停止します。障害はサービスを中断させ、アプリケーション、ユーザー、バリデーターに影響を与える可能性がありますが、不整合または破損した台帳がもたらす壊滅的な結果よりは望ましい選択です。
ネットワークの再起動
Solanaネットワークを再起動する際は、楽観的確認が行われた最後のブロックスロットを特定し、そのスロットの信頼できるローカル状態スナップショットからノードを再起動します。再起動スロットはオンチェーンで決定されないため、バリデーターオペレーターはオフチェーンのコンセンサスを形成し、安全なロールバック地点について合意する必要があります。この調整はSolana Tech Discordの#mb-validatorsチャンネルで公開され、プロのバリデーターオペレーターがリアルタイムで連絡を取ります。ほとんどのオペレーターは、ブロック生成が停止した瞬間に通知する自動アラートシステムを備えており、迅速に対応できます。
正しい再起動スロットについてコンセンサスが得られると、オペレーターは台帳ツールを使って新しいローカルスナップショットを生成し、バリデーターを再起動して、総ステークの80%以上がオンラインに復帰するのを待ちます。その後に初めて、ネットワークはブロックの生成と検証を再開します。クラスターの再起動時にオフラインのステークが最大20%であることを確認することで、再起動直後にノードがフォークしたり再びオフラインになったりした場合でも、オンライン状態を維持するための十分な安全余裕を確保できます。
バグ報告
バグ報奨金プログラムでは、ソフトウェアの脆弱性を発見して報告したセキュリティ研究者に報酬を提供します。悪用される前にバグを発見するための積極的なインセンティブとなるため、これは極めて重要な防御線です。Agaveクライアントに潜在的な脆弱性を発見したセキュリティ研究者や開発者には、適切なセキュリティ窓口を通じて報告することが推奨されています。詳細な開示ガイドラインはAgaveのGitHubリポジトリで確認できます。
重大な脆弱性に関する有効な報告には、深刻度に応じて次の報奨金が提供されます。
- 資金の損失: 最大25,000 SOL
- コンセンサスまたはセーフティ違反: 最大12,500 SOL
- ライブネスまたは可用性の喪失: 最大5,000 SOL
さらに、FireDancerクライアントには、Immunefiを通じて運営される独自のバグ報奨金プログラムがあります。重大な発見に対する最高報奨額は500,000 USDCです。
障害事例
以下のセクションでは、2020年3月16日のMainnet Beta開始以降にSolanaで発生した障害と性能低下期間を、時系列に沿って詳しく分析します。主要なインシデント、その根本原因、その後のネットワーク改善を取り上げ、安定性と耐障害性を高めるためにSolanaがどのように進化してきたかを明らかにします。
Turbineのバグ:2020年12月
停止時間: 約6時間
根本的な問題: ブロック伝播のバグ
修正:
- スロット番号ではなくハッシュでブロックを追跡
- より早く障害を検出できるTurbine内の箇所を修正
- 最初に検出された障害をgossip経由ですべてのバリデーターへ伝播
この障害は、以前から知られていたブロック修復とコード処理の問題が、Solanaのブロック伝播メカニズムであるTurbineの未特定のバグによって引き起こされたものです。あるバリデーターが同じスロットに対して異なる2つのブロックを送信し、それぞれを別のパーティション(AとB)へ伝播する一方で、3つ目のパーティションが独自に不整合を検出したことで障害が発生しました。
各パーティションが保持していたステークは少数だったため、チェーンを進めるための特別多数のコンセンサスを得られませんでした。根本的な問題は、Solanaの内部データ構造がブロックとその計算済み状態を追跡する方法にありました。システムはProof of History(PoH)のスロット番号(u64識別子)を使用して、そのスロットの状態とブロックを参照していました。ネットワークがパーティションに分割されると、ノードはブロックAとBを同一のものと誤認し、適切な修復とブロック同期ができなくなりました。
各パーティションは相手も同じブロックを保持していると想定したため、根本的な競合が発生しました。
- ブロックAを保持するノードは、ブロックBから派生したフォークを拒否
- ブロックBを保持するノードは、ブロックAから派生したフォークを拒否
パーティション間で状態遷移が異なっていたため、バリデーターはフォークを修復または調整できず、ファイナリティに到達できませんでした。
この問題への対策は、サービスがスロット番号ではなくハッシュでブロックを追跡できるようにすることでした。同じスロットに対するブロックがいくつ存在してパーティションを形成しても、異なるスロットのブロックによって形成されたパーティションと同様に扱われます。ノードは考えられるすべてのフォークを修復でき、コンセンサスによってパーティションを解消できるようになります。
バグが障害の最初の原因でしたが、停止時間の大半は十分なステーク比率がオンラインに戻るのを待つことに費やされました。Solanaがブロック生成を再開するには、少なくとも80%のステーク参加が必要なためです。
Grape Protocol IDO:2021年9月
停止時間: 17時間
根本的な問題: ボットトランザクションによるメモリオーバーフロー
修正:
- プログラムの書き込みロックを無視
- トランザクション転送のレート制限
- 設定可能なRPC再試行動作
- TPUで投票トランザクションを優先
2021年9月14日、クラウドファンディングプラットフォームRaydium AcceleRaytorでGrape ProtocolのオンチェーンInitial DEX Offering(IDO)が開始された後、Solanaネットワークは大規模な停滞に見舞われました。IDO開始から12分以内に、ボットによる前例のない大量のトランザクションでネットワークが圧倒され、root化されたスロットの生成が停止しました。これらのボットは事実上、分散型サービス拒否(DDoS)攻撃を実行し、トランザクション負荷をネットワークの処理能力以上に押し上げました。
輻輳のピーク時には、次の状況が発生しました。
- 一部のバリデーターは毎秒300,000件を超えるトランザクションを受信していました。
- 生のトランザクションデータは1 Gbpsを超え、毎秒120,000パケットに達しました。
- トラフィックがネットワークインターフェースの物理的上限を超える場合もあり、バリデーターに到達する前にスイッチポートでパケット損失が発生しました。
あるボットは、グローバルSPLトークンプログラムや現在は廃止されたSerum DEXプログラムを含む18個の主要アカウントを書き込みロックするようにトランザクションを構成していました。これにより、これらのアカウントとやり取りするすべてのトランザクションがブロックされ、Solanaの並列処理能力が大幅に低下しました。トランザクションを個別に実行できなくなったネットワークではボトルネックが発生し、トランザクションが逐次処理され、輻輳がさらに悪化しました。
プログラムの書き込みロックを無視する修正はすでに開発され、リリースが予定されていました。その後、ネットワークの再起動時にこのアップグレードが有効化され、この攻撃経路は恒久的に排除されました。
IDOイベント中、バリデーターはボットによる大量のトランザクションを受信し、余剰トランザクションを次のリーダーへ転送したため、輻輳が増幅しました。ネットワークの再起動では、将来のトランザクションストームによってリーダーが圧倒されないように、トランザクション転送のレート制限が導入されました。
SolanaのRPCノードには、信頼性を高めるため、失敗したトランザクションを自動的に再試行する機能があります。しかし、極端な輻輳時には、この再試行メカニズムによってトランザクションの氾濫が悪化し、古いトランザクションが流通し続けたため、ネットワークの回復が妨げられました。Solana 1.8では設定可能なRPC再試行動作が導入され、アプリケーションは有効期限の短縮や指数バックオフ戦略によって再試行を最適化できるようになりました。
激しい輻輳時に、Solanaのリーダーはコンセンサスの維持に不可欠な投票トランザクションを含めることができませんでした。その結果、確認済み投票が不足してコンセンサスが停滞し、新しいrootブロックの生成が停止しました。その後のSolanaクライアントでは、投票トランザクションを優先するメカニズムが導入され、将来同様の事態が起きても通常のトランザクションに埋もれないようになりました。
2つ目のバグ:整数オーバーフロー
ネットワークの再起動中に、2つ目の問題が明らかになりました。バリデーターから、アクティブなステーク量が激しく変動しているとの報告がありました。この問題は、ステーク比率が誤って100倍され、取り得る最大値を超えたバグに起因していました。インフレーションメカニズムによって大量の新しいSOLトークンが生成され、64ビット符号なし整数がオーバーフローしていました。このバグはすぐに特定され、2回目の再起動前に修正されました。
激しい輻輳:2022年1月
停止時間: なし
根本原因: 過剰な重複トランザクション 部分的な修正:
- Solana 1.8.12および1.8.14のリリース
- SigVerify重複排除の最適化
- エグゼキューターキャッシュの性能改善
2022年1月6日から12日にかけて、Solana mainnetでは深刻なネットワーク輻輳が発生し、性能低下と部分的な障害につながりました。ボットが過剰な重複トランザクションを送信したことで、ネットワーク容量が大幅に低下したことが原因です。ブロック処理に想定以上の時間がかかり、次のリーダーがフォークしたため、スループットがさらに低下しました。ピーク時には、トランザクションの成功率が最大70%低下しました。クライアントは、複雑化し計算負荷が高まるネットワーク上のトランザクション処理に苦戦し、需要に対応する能力の限界が露呈しました。
1月21日から23日にかけても輻輳が続き、さらなる不安定化が発生しました。1月22日には、スパム送信されたバッチRPC呼び出しによってシステムが圧倒され、不正利用のため公開RPCエンドポイント(https://api.mainnet-beta.solana.com)がオフラインになりました。
これらの問題に対処するため、Solana 1.8.12リリースでは特にプログラムキャッシュの枯渇が対象とされ、バージョン1.8.14ではSysvarキャッシュ、SigVerifyの破棄、SigVerifyの重複排除が改善されました。
Candy Machineスパム:2022年4月/5月
停止時間: 8時間
根本的な問題: ボットアカウントからのトランザクションスパム
修正:
- Candy Machineプログラムへのボット税
- Solana v1.10でのメモリ改善
2022年4月30日、Solanaではトランザクションリクエストが前例のない規模で急増しました。一部のノードでは毎秒600万リクエストに達し、ノードあたり100 Gbpsを超えるトラフィックが発生したと報告されています。この急増は、Metaplex Candy Machineプログラムを通じて新たにミントされるNFTを確保しようとするボットによって引き起こされました。このミントメカニズムは先着順で動作していたため、大量のトランザクションをネットワークへ送り込み、ミントを勝ち取る強い経済的インセンティブが生まれていました。
トランザクション量が急増すると、バリデーターはメモリ不足でクラッシュし、最終的にコンセンサスが停滞しました。投票のスループットが不十分だったため、それ以前のブロックを確定できず、放棄されたフォークをクリーンアップできませんでした。その結果、バリデーターは評価すべき膨大な数のフォークに圧倒され、再起動後も処理能力を超えたため、ネットワークの復旧には手動介入が必要となりました。
この障害は2021年9月のインシデントと類似していましたが、Solanaは耐障害性の向上を示しました。前回の障害より10,000%多いトランザクションリクエストを受けたにもかかわらず、ネットワークははるかに長く稼働を続けました。これは、以前のスケーリング課題を受けてバリデーターコミュニティが実施した改善の成果です。
正規のスナップショットについて合意した後、ネットワークの再起動には1.5時間もかかりませんでした。Solana v1.10には、コンセンサスが遅延または停滞した状態にノードが耐えられる時間を延ばすためのメモリ使用量の改善が含まれていました。
しかし、根本的な問題は未解決のままでした。リーダーは依然として、同じアカウントデータを奪い合うトランザクションを有効なスパム防止策なしに先着順で処理しており、ユーザーはトランザクションの緊急性に応じて優先順位を付けられませんでした。この問題に対処するため、実用的な解決策として3つの長期的なメカニズムが提案されました。
QUICの採用:以前のSolanaでは、RPCノードから現在のリーダーへGulf Stream経由でトランザクションを送信する際に、UDP(User Datagram Protocol)ネットワークプロトコルを使用していました。UDPは高速で効率的ですが、コネクションレスであり、フロー制御や受信確認がありません。そのため、不正な動作を抑止または軽減する有効な方法がありません。ネットワークトラフィックを制御するため、バリデーターのトランザクション取り込みプロトコル(TPUのFetch Stage)がQUICで再実装されました。
QUICは、TCPとUDP双方の長所を提供することを目指しています。UDPのように高速な非同期通信を実現しながら、TCPの安全なセッションと高度なフロー制御戦略も備えています。これにより個々のトラフィック送信元に制限を設け、ネットワークが正当なトランザクションの処理に集中できます。QUICには独立したストリームという概念もあるため、1件のトランザクションが破棄されても残りのトランザクションはブロックされません。QUICは最終的に1.13.4リリースでSolana Labsクライアントへ統合されました。
ステーク加重Quality of Service(SWQoS):バリデーターが保有するステークに基づいてネットワークトラフィックを優先し、ステークが多いバリデーターほど効率的にトランザクションを送信できる新しいシステムが導入されました。このメカニズムでは、総ステークの3%を保有するバリデーターは、リーダーに送信される全パケットの最大3%を送信できます。SWQoSはシビル耐性対策として機能し、悪意ある攻撃者が低品質なトランザクションをネットワークへ大量に送り込むことを難しくします。この手法は、送信元を考慮せず無差別にトランザクションを受け入れていた従来の先着順モデルに代わるものです。
優先手数料の導入: トランザクションが取り込まれた後も、共有アカウントデータへのアクセスを巡って競合します。以前は、この競合が単純な先着順で解決されていたため、ユーザーがトランザクションの緊急性を示す方法はありませんでした。誰でもトランザクションを送信できるため、この段階での優先順位付けにステーク加重を使うことは適切ではありません。この問題に対処するため、Compute Budgetプログラムに新しい命令が追加され、実行およびブロックへの取り込み時に徴収される追加手数料をユーザーが指定できるようになりました。手数料とコンピュートユニットの比率によってトランザクションの実行優先度が決まり、より動的で市場主導型のトランザクション順序付けが可能になります。
Candy Machineのボット税
ボットによるスパムに対抗するため、MetaplexはCandy Machineプログラムとやり取りするミントトランザクションに、ハードコードされた0.01 SOLのボット税を迅速に導入しました。このスパム対策メカニズムは、正当なユーザーの偶発的なミスには過度な負担を課さず、悪意ある活動を抑止する最小限の手数料を課すものです。この税は次のような特定の状況で適用されました。
- Candy Machineが稼働していない状態でのミント試行
- アイテムが残っていない状態でのミント試行
- mintまたはset collectionが最後の命令ではないトランザクション
- 誤ったコレクションIDの使用
- Set Collection命令の不一致
- コレクション設定命令とミント命令の間での署名者兼支払者の不一致
- 許可されていないプログラムが関与する疑わしいトランザクション
- 必要な許可リストトークンを保有せず、AllowListで保護されたCandy Machineからミントを試行
この経済的な抑止策は非常に効果的でした。ミントスナイパーの資金は急速に失われ、スパム活動は収束しました。最初の数日だけで、ボット運用者は合計426 SOL以上を失いました。
Durable Nonceのバグ:2022年6月
停止時間: 4時間半
根本的な問題: コンセンサス障害につながったDurable Nonceのバグ
修正:
- Durable Nonceトランザクションの一時無効化
- Solana 1.10.23への更新
ランタイムのバグにより、特定のDurable Nonceトランザクションがrecent_blockhashフィールドでDurable Nonceではなく最近のblockhashを使用した場合、通常のトランザクションとして1回、nonceトランザクションとしてもう1回、合計2回処理される可能性がありました。これによりバリデーター間で非決定論的な動作が生じ、一部のノードは2回目の実行を拒否し、別のノードは受け入れました。重大な点として、3分の1を超えるバリデーターがそのブロックを受け入れたため、必要な3分の2の多数がコンセンサスに到達できませんでした。
標準的なトランザクションとは異なり、Durable Nonceトランザクションには有効期限がないため、二重実行を防ぐ固有のメカニズムが必要です。各アカウントに紐付けられたオンチェーンのnonce値を使用して直列処理され、この値はDurable Nonceトランザクションが処理されるたびに更新されます。更新後は、同じnonceトランザクションが再び有効になることはありません。
問題を軽減するため、Durable Nonceトランザクションは一時的に無効化されました。その後、Solana 1.10.23で修正が実装され、nonceとblockhashのドメインを分離することで重複実行を防止しました。この更新では、nonceアカウントを進める際にblockhashを固定文字列とともにハッシュ化し、blockhashをnonce値として無効にしました。その結果、通常のトランザクションとして一度実行されたトランザクションをDurableトランザクションとして再実行することも、その逆もできなくなりました。さらに、新しいDurableNonce型がnonceアカウント状態の従来のblockhash値に置き換わり、型安全性が追加され、将来の同様の問題を防止できるようになりました。
Durable Nonceとその用途について詳しくは、以前のHeliusブログ記事をご覧ください。
重複ブロックのバグ:2022年9月
停止時間: 8時間半
根本的な問題: フォーク選択ルールのバグによるコンセンサス障害
修正:
- クライアントパッチ
この障害は、あるバリデーターが同じブロック高で誤って重複ブロックを生成したことが引き金となりました。バリデーターのプライマリノードと予備のフォールバックノードが同時にアクティブになり、同じノードIDを使用しながら異なるブロックを提案したためです。この状態は障害発生前から少なくとも24時間続いており、その間、ネットワークはそのバリデーターの重複したリーダースロットを正しく処理していました。
最終的にネットワークは、フォーク選択ロジックのバグによって回復不能なフォークに遭遇し、クラスターが停止しました。このバグにより、ブロック生成者は直前のブロック上に構築できなくなり、コンセンサスが失敗しました。
Solanaではフォークが日常的に発生し、通常は投票の過半数を得たフォーク、つまり最も重いフォークにバリデーターが揃うことで解決します。バリデーターが誤ったフォークを選択した場合、ネットワークとの同期を維持するため、最も重いフォークへ切り替える必要があります。しかしこのケースでは、最も重いbankのスロットが最後に投票したスロットと一致すると、バリデーターはそのbankへ戻れませんでした。この欠陥によってバリデーターが動けなくなり、コンセンサスが進行せず、最終的にネットワークが停止しました。
上の例では、障害のあるバリデーターCがリーダースロット5から8で重複ブロックを生成します。次のリーダーを引き継いだバリデーターGは、重複ブロックの一方だけを観測し、それに基づいてフォークを延長します。しかし、次のリーダーであるバリデーターDは、バリデーターCからの重複ブロックを両方検出して破棄し、代わりにスロット4上でフォークを構築します。
ネットワークが進行すると、バリデーターGが構築したフォークがステークの過半数から投票を得て、正規チェーンとなります。自身のフォークが劣勢だと認識したバリデーターDは、バリデーターGのフォークへ切り替えようとします。しかし、フォーク選択ロジックのバグにより移行に失敗します。この問題は、2つのフォークに共通する祖先、すなわちスロット5の重複ブロックが正しく処理されず、バリデーターDが過半数を得たフォークを認識できなかったために発生しました。その結果、バリデーターDは自身のフォークから動けず、メインチェーンに復帰できませんでした。
この問題はコアチームによるレビュー後に解決されました。パッチがmasterブランチにマージされ、すべてのリリースブランチにバックポートされました。
大規模ブロックがTurbineを圧迫:2023年2月
停止時間: 約19時間
根本的な問題: shred転送サービスの重複排除ロジックの障害
修正:
- Turbineの重複排除ロジックとフィルタリングを複数改善
- 大規模ブロックを生成した場合にブロック生成者を強制終了させるクライアントパッチを追加
あるバリデーターのカスタムshred転送サービスが誤動作し、そのリーダースロット中に、標準的なブロックより数桁大きい異常に巨大なブロック(約150,000 shreds)を送信しました。これによりバリデーターの重複排除フィルターが圧倒され、データが継続的に再転送されました。新しいブロックが生成されるにつれて問題が積み重なり、最終的にプロトコルが飽和しました。
異常なネットワークトラフィックの急増によってTurbineが圧倒され、ブロックデータは大幅に低速なフォールバック用のBlock Repairプロトコルで送信せざるを得なくなりました。Turbineは大規模ブロックをフィルタリングして除外できるよう設計されていますが、shred転送サービスはこのフィルタリングロジックより上流で動作するため、効果が低下しました。性能低下中、ブロックリーダーは自動的に投票専用モードへ移行しました。これは、経済活動に関わる投票以外のトランザクションをリーダーが除外する安全メカニズムです。
問題の根本原因は、shred転送サービス内の重複排除ロジックの障害であり、shredの冗長な再送信を防止できませんでした。さらに、再送信パイプラインの重複排除フィルターは、もともとTurbineツリー内のループを防ぐよう設計されていなかったため、問題が悪化しました。
ネットワークは、正常に動作することが最後に確認されたバリデーターソフトウェアのバージョンへダウングレードしたうえで、手動で再起動されました。これらの問題を軽減するため、Solana v1.13.7とv1.14.17では重複排除ロジックが強化され、フィルターの飽和を防ぐ能力が向上し、より堅牢なネットワーク性能が確保されました。
無限再コンパイルループ:2024年2月
停止時間: 約5時間
根本的な問題: JITキャッシュで無限再コンパイルループを引き起こすバグ
修正:
- v1.17.20でレガシーローダーを無効化
Agaveバリデーターは、参照するトランザクションを実行する前に、すべてのプログラムをジャストインタイム(JIT)コンパイルします。性能を最適化するため、頻繁に使用されるプログラムのJIT出力はキャッシュされ、不要な再コンパイルを減らします。Agave v1.16では、既存のキャッシュメカニズムであるLoadedProgramsが、効率を複数向上させる新しい実装のExecutorsCacheに置き換えられました。
LoadedProgramsはキャッシュされたプログラムについて、フォークを考慮したグローバルなビューを提供し、アカウンティングデータの重複を減らすとともに、トランザクション実行スレッドが協調して新しいプログラムを読み込めるようにして、コンパイルの競合を防ぎました。このシステムの主要機能は、プログラムがアクティブになるスロット(effective slot heightと呼ばれます)を追跡し、オンチェーンのプログラムデータが更新された際にキャッシュの無効化を検出することでした。
ほとんどのプログラムのeffective slot heightは、オンチェーンアカウントに保存されたデプロイスロットから導出されていました。しかし、レガシーローダーを使ってデプロイされたプログラムでは、このデプロイスロットがアカウントに保持されませんでした。LoadedProgramsは回避策として、これらのプログラムにゼロのeffective slot heightを割り当てました。
deploy命令が検出され、プログラムのバイトコードが置き換えられたことが示された場合には例外が発生しました。この場合、LoadedProgramsは正しいeffective slot heightを持つエントリを一時的に挿入しました。しかし、トランザクションがこのエントリを参照することはなかったため、非常に削除されやすい状態でした。削除されるとJIT出力が破棄され、プログラムは未読み込みとしてマークされましたが、effective slot heightは保持されました。
その後、トランザクションがこの未読み込みのプログラムを参照すると、LoadedProgramsはプログラムを再コンパイルし、そのeffective slot heightにエントリを再挿入しました。通常であれば、次のイテレーションでプログラムを実行できるようになります。しかし、レガシーローダーのプログラムでは、新しいJIT出力にセンチネル値であるゼロのスロット高が割り当てられ、以前の未読み込みエントリより後ろに配置されました。その結果、LoadedProgramsはプログラムを読み込み済みとして認識できず、イテレーションのたびに再コンパイルが続く無限ループが発生しました。
Agave v1.16のLoadedProgramsは協調読み込みをサポートしていなかったため、原因となったトランザクションをブロックに詰め込むことができました。その後、このブロックがネットワーク全体へ伝播され、すべてのバリデーターがリプレイして同じ無限再コンパイルループに入りました。障害時にはクラスターのステークの95%以上がAgave v1.17を実行していたため、ほとんどのバリデーターがこのブロックで停止し、ネットワークが止まりました。
このバグは前週、Devnetクラスターの障害調査中に特定されており、パッチのデプロイが予定されていました。選択された軽減策は、変更をAgave v1.17へバックポートし、ネットワーク再起動時にfeature gateを直ちに削除することでした。 これにより、バグの引き金となったレガシーローダーが無効化され、再発が防止されました。
協調的な脆弱性パッチ適用:2024年8月
停止時間: なし
根本的な問題: ELFアドレスアラインメントに関する誤った前提
修正:
- パッチ更新
8月5日、Anzaのコアエンジニアは、外部の研究者から報告されたAgaveクライアントの脆弱性について通知を受けました。攻撃者がこの欠陥を悪用するとリーダーバリデーターをクラッシュさせ、ネットワーク全体を停止させる可能性がありました。これを受け、Anzaのエンジニアは迅速にパッチを開発し、複数の第三者セキュリティ企業が監査しました。
SolanaプログラムはLLVMを使用してExecutable and Linkable Format(ELF)へコンパイルされます。この脆弱性は、生成されたELFファイル内のアドレスアラインメントに関する誤った前提に起因していました。通常、ELFのサニタイズではさまざまな整合性チェックが実施されますが、.textセクションのアラインメントは検証されていませんでした。この見落としにより、悪意を持って細工されたELFファイルがアラインメントのずれた.textセクションを定義し、仮想マシンを無効なアドレスへジャンプさせる可能性がありました。その結果、ホストでセグメンテーション違反が発生し、バリデーターがクラッシュします。
攻撃者は次の方法でこの脆弱性を悪用できました。
- CALL_REGオペコードを使用する悪意あるSolanaプログラムを作成します。
- ELFファイルを操作して.textセクションのアラインメントをずらします。
- そのプログラムをネットワークにデプロイして呼び出し、バリデーターをクラッシュさせます。
パッチ更新プロセス
パッチ更新を公開すると、脆弱性の内容がすぐにすべての人に明らかになります。十分なステークがアップグレードを完了する前に、攻撃者が脆弱性をリバースエンジニアリングし、ネットワークを停止させる時間を得る可能性がありました。このような事態を避けるには、決定的多数のバリデーターがパッチリリースを可能な限り迅速に導入する必要がありました。
8月7日までに、Solana Foundationの複数のメンバーが、さまざまなコミュニケーションプラットフォームのプライベートメッセージを通じてバリデーターへ連絡し、予定されている重大なパッチについて知らせるとともに、インシデントの日付と固有識別子を確認できるハッシュ化されたメッセージを共有しました。Anza、Jito、Solana Foundationの複数の著名なメンバーがX、GitHub、LinkedInでこのハッシュを共有し、メッセージが正確であることを検証しました。共有されたハッシュの例:
翌日もコアメンバーはバリデーターへの連絡を続け、緊急性と機密保持の重要性を強調しました。事前に決められた8月8日午後2時(UTC)になると、バリデーターオペレーターはパッチのダウンロード、検証、適用手順を含む追加メッセージを受け取りました。パッチはAgaveのメインリポジトリではなく、著名なAnzaエンジニアのGithubリポジトリでホストされました。手順には、ダウンロードしたパッチファイルを提供されたshasumsと照合する検証も含まれていました。
8月8日午後8時(UTC)までに、ステークの特別多数にパッチが適用され、ネットワークの安全性が確保されました。その後、脆弱性と対応するパッチが公開され、残りのすべてのバリデーターにアップグレードが呼びかけられました。
パッチが非公開で配布され、水面下でバリデーターが調整されたことから、Solanaの分散性について懸念が生じました。インシデントの直後、Solana FoundationのエグゼクティブディレクターであるDan Albertは、メディアインタビューでこうした批判に応えました。
「中央集権化と、連携できることを混同しないのが重要だと考えています。世界中にはブロックを生成するノードが1,500台あり、それとほぼ同数の個人によって運用されています。…彼ら、あるいはその一部と自発的に連絡を取れることを、中央集権化と混同すべきではありません」
中央集権化と、連携できることを混同しないのが重要だと考えています。世界中にはブロックを生成するノードが1,500台あり、それとほぼ同数の個人によって運用されています。…彼ら、あるいはその一部と自発的に連絡を取れることを、中央集権化と混同すべきではありません。

まとめ
本稿執筆時点で、Solanaは1年以上にわたり障害を起こしておらず、mainnet-betaから「beta」表記を外すための重要なマイルストーンを達成しています。ネットワークの成熟に伴って障害頻度は低下しているように見えます。また、Firedancerの導入によってクライアントの多様性が高まり、未発見のバグやエッジケースがクラスター全体の停止を引き起こすリスクが低減すると期待されています。しかし、Helius創設者のMert Mumtazを含む一部のコミュニティリーダーは、障害は今後も続くと予測しています。答えは時間が明らかにするでしょう。
本稿の以前のバージョンをレビューしてくださったZantetsu(Shinobi Systems)氏とOxIchigo氏に深く感謝します。
関連リソース
関連記事
Heliusを購読
Solana開発の最新情報や新しい記事の公開通知を受け取れます


