はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

今回は、VM間通信でリクエストが失敗したときに、タイムアウトを待たずにAppErrorという専用メッセージで明示的にエラーを通知できる仕組みを提案しているACP25についてまとめていきます!

以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

概要

ACP25は、VirtualMachine(VM)がアプリケーション固有のエラー状態を別のVMへ通知できる仕組みを追加するものです。 現状では、あるVMがAppRequest(アプリケーション固有のリクエストメッセージ)を送信しても、相手が処理できなかった場合に「失敗した」ということを直接知らせる手段がありません。 そのため、リクエストを送信したVMは一定時間待ってから「応答がなかった=失敗」とみなすしかありません。

この仕組みを改善するために、新たに「AppError」というエラーメッセージを導入し、失敗が発生した時に相手VMから明示的にエラーを返せるようにしようというのがACP25の目的です。

動機

AvalancheのVM同士は、AppRequest、AppResponse、AppGossipといったメッセージタイプを使って独自のP2Pアプリケーションプロトコルを構築できます。 このうちAppRequestは、必ずAppResponseによって成功結果が返されることを前提としています。 ところが、処理が失敗するケース(リソース不足や要求が不正など)が起きたとき、現状では「応答なし=失敗」と解釈するしかなく、タイムアウトが唯一の手段となっています。

この状態にはいくつかの問題があります。

  • 無駄な待ち時間が発生する エラーを明示的に返せないため、クライアントはタイムアウトが切れるまで待たなければならず、失敗を検知するまでに余計な遅延が生じます。

  • ピア評価ができない ネットワークレイヤーはVM内部の実装を知らないため、あるノードが「正しくエラーを返した」のか「応答がない」のかを区別できません。 その結果、ピアの信頼性を適切にスコアリングすることができません。

  • 失敗処理の統一性が欠けている すべてのクライアントがタイムアウト頼りになるため、どのVMも「応答がない場合にどう扱うか」を独自に決める必要があります。 これにより、システム全体としての一貫性や効率が下がってしまいます。

そこで新たにAppErrorというネイティブなアプリケーションエラー型を導入すれば、失敗が起きた時にVMは即座に相手へエラーを返せるようになります。 これにより以下の改善が期待できます。

  • タイムアウトに頼らずに素早く失敗を検知でき、リクエスト処理の待ち時間を大幅に短縮できる。
  • Avalancheノードがエラーレスポンスを認識できるため、相手ノードを適切に評価(スコアリング)できる。
  • 成功時と失敗時の双方において「必ず応答がある」という前提を作れるため、アプリケーション間のやり取りがより明確で予測可能になる。

仕様

AppErrorメッセージ

ACP25では、以下のようなprotobuf定義が追加されています。

protobuf
message AppError {
  bytes chain_id = 1;
  uint32 request_id = 2;
  uint32 error_code = 3;
  string error_message = 4;
}

このメッセージは、AppRequestへの応答として返されるエラーレスポンスを表します。 それぞれのフィールドの意味は以下です。

フィールド名 型 説明
chain_id bytes 元のAppRequestで指定されたチェーンIDを必ず使用します。どのチェーンに関するリクエストなのかを明確に紐付けるためです。
request_id uint32 元のAppRequestのリクエストIDを必ず使用します。これにより、どのリクエストに対するエラーなのかを識別できます。
error_code uint32 アプリケーションが定義するエラーコードです。同じ条件では同じエラーコードを使うことが推奨され、クライアント側でエラーを機械的に判別できるようにします。負の値はプロトコルが定義するエラー用に予約されており、0以上はVMが自由に使えます。
error_message string 人間が読める形式のエラーメッセージです。ただし、この内容はエラー判定に使うべきではなく、判定には必ずerror_codeを用います。

この設計により、リクエストとエラーレスポンスの対応関係を明確に維持しながら、標準化されたエラー通知が可能になります。

予約済みエラーコード

Avalancheプロトコルでは、いくつかのエラーコードがすでに予約されています。 これにより、基本的なエラーケースについては全クライアントが共通理解を持てるようになります。

エラーコード 説明
0 未定義(undefined)
-1 ネットワークタイムアウト

この表からわかるように、負の値(ここでは-1)がプロトコル定義エラーに割り当てられています。 一方で、0は特別に「未定義」として扱われます。 それ以外の正の値はVMごとに自由に定義できます。

エラーハンドリングのルール

この仕様では、AppRequestに対して必ず以下のどちらかの応答を返す必要があります。

  1. 正常に処理できた場合はAppResponseを返す。
  2. エラーが発生した場合はAppErrorを返す。

いずれの場合も、応答は元のAppRequestに指定されていたdeadline(期限)までに返す必要があります。 これにより、リクエストを受けた側が無視することなく、必ず成功または失敗の結果を返すことが保証されます。

この仕組みは、従来のようにタイムアウト任せで失敗を検出する方法と比べて、はるかに効率的です。 エラーが発生したら即座にAppErrorで通知できるため、クライアントは無駄に待つことなく処理を続けることができます。

互換性

新しく導入されるAppErrorメッセージは、既存のネットワーク動作に直接影響を与えるため、ネットワーク全体での有効化(アップグレード)が必要になります。 従来の仕様では、AppRequestに対してAppResponseが返されない場合、送信者はタイムアウトを待つしかありませんでした。 今回の変更によって、AppRequestには必ず次のいずれかの応答が求められるようになります。

  • 成功時: AppResponse
  • 失敗時: AppError

この変更が有効化されるまでは、従来通りタイムアウトで失敗を検出しますが、有効化後は応答が必須になるため、リクエスト処理がより明確で効率的になります。

参考実装

ACP25の実装は、すでにAvalancheGoのリポジトリでプルリクエストとして公開されています。 これにより、開発者は具体的にどのように定義や処理が行われているかを確認できます。

  • メッセージ定義: ava-labs/avalanchego/pull/2111 このプルリクエストでは、AppErrorメッセージのprotobuf定義が導入されています。

  • メッセージ処理: ava-labs/avalanchego/pull/2248 このプルリクエストでは、受信したAppErrorをどのように扱うか、またAppRequestに応答する際のロジックが実装されています。

これらのリファレンスを通じて、開発者は実際のコードを確認し、提案内容がどのようにソフトウェアに組み込まれているのかを理解することができます。

セキュリティ

新しいメッセージ型を導入することで、セキュリティ上のリスクも考慮する必要があります。 特に重要なのは、ピア(他のノード)が任意にAppErrorメッセージを送信できるという点です。

これはつまり、悪意のあるノードが意図的にエラーメッセージを返し、相手のVMでエラーハンドリングを強制的に発動させる可能性があるということです。 こうした挙動は、以下のようなリスクにつながります。

  • クライアント側が過剰にエラーを受け取り、処理が不要に中断される。
  • 攻撃者が意図的に誤ったerror_codeやerror_messageを送信することで、混乱を引き起こす可能性がある。

したがって、実装にあたっては以下のような点が求められます。

  • エラーメッセージの内容を過信せず、プロトコル上で定義されたerror_codeの扱いを厳密に守る。
  • 特定のノードから頻繁に不適切なAppErrorが送られてくる場合、そのノードを不正ピアとしてスコアリングする仕組みを導入する。

こうした対策を講じることで、新しい仕組みを悪用した攻撃リスクを軽減しつつ、VM間通信におけるエラーハンドリングを強化できます。

最後に

今回は「VM間通信でリクエストが失敗したときに、タイムアウトを待たずにAppErrorという専用メッセージで明示的にエラーを通知できる仕組みを提案しているACP25」についてまとめてきました! いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

他の媒体でも情報発信しているのでぜひ他も見ていってください!