はじめに

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

今回は、AvalancheのC-ChainとSubnet-EVMにAvalancheWarpMessagingを統合し、EVM上でクロスサブネット通信を標準機能として提供する提案しているACP30についてまとめていきます!

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

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

概要

ACP30では、Avalanche Warp Messaging(以下AWM)をC-ChainおよびSubnet-EVMに統合し、Avalanche上のEVM環境においてクロスサブネット通信(Cross-Subnet Communication)を可能にすることを目的としています。 これにより、EVM互換のチェーン同士がシームレスにメッセージをやり取りできるようになり、Avalancheエコシステムにおける相互運用性が大幅に向上します。

動機

Avalancheの大きな特徴の1つは、Subnet(サブネット)と呼ばれる独立したブロックチェーンを柔軟に作成できることです。 各Subnetは独自のルールを持ちつつ、Avalancheネットワーク全体の一部として稼働します。 これらのSubnetのバリデータ集合(validators set)は、AvalancheのP-Chainに登録されます。 P-ChainはAvalancheの管理チェーンとして機能し、ネットワーク全体でどのバリデータがどのSubnetを検証しているかを一元的に管理しています。

AWMを導入することで、以下のようなメリットがあります。

  • Subnet間通信の効率化 各SubnetはP-Chainを通じて他のSubnetのバリデータ集合を読み取ることができます。 これにより、AWMメッセージを検証する時に、従来のようにSubnet間で直接バリデータ情報を交換する必要がなくなります。

  • 軽量でオンデマンドな通信 P-Chainを利用することで、任意のSubnetが他のSubnetと必要に応じて通信できる仕組みが整います。 これは、事前に複雑な設定や継続的な情報共有を行わなくても、Subnet間で信頼できる通信を可能にする軽量プロトコルです。

  • 標準実装の提供 ACP30によって、C-ChainおよびSubnet-EVMにAWMが実装され、Avalancheネットワーク上で初の標準的なクロスサブネット通信手段が提供されます。 これにより、EVMを利用する開発者は、Avalancheネットワーク内でより柔軟かつ強力なDApp(分散型アプリケーション)を構築できるようになります。

仕様

Precompileの基本情報

  • WarpPrecompileのアドレス: 0x0200000000000000000000000000000000000005

EVMが特別に用意する組み込みコントラクトの1つで、このアドレスで呼び出せます。

インターフェイス

WarpPrecompileは以下のインターフェイスを実装します。

solidity
pragma solidity ^0.8.0;

struct WarpMessage {
  bytes32 sourceChainID;
  address originSenderAddress;
  bytes payload;
}

struct WarpBlockHash {
  bytes32 sourceChainID;
  bytes32 blockHash;
}

interface IWarpMessenger {
  event SendWarpMessage(address indexed sender, bytes32 indexed messageID, bytes message);

  function sendWarpMessage(bytes calldata payload) external returns (bytes32 messageID);

  function getVerifiedWarpMessage(uint32 index) external view returns (WarpMessage calldata message, bool valid);

  function getVerifiedWarpBlockHash(uint32 index) external view returns (WarpBlockHash calldata warpBlockHash, bool valid);

  function getBlockchainID() external view returns (bytes32 blockchainID);
}

各要素の役割

要素 内容
WarpMessage 検証済みWarpメッセージ。送信元チェーンID、送信者アドレス、ペイロードを保持します。
WarpBlockHash 検証済みブロックハッシュ。チェーンIDとハッシュ値を持ちます。
sendWarpMessage Warpメッセージ送信要求を出します。ログが出力され、各バリデータが署名対象メッセージを認識します。
getVerifiedWarpMessage 事前検証済みメッセージをインデックス指定で取得します。
getVerifiedWarpBlockHash 事前検証済みブロックハッシュをインデックス指定で取得します。
getBlockchainID このチェーンのBlockchainIDを返します。EthereumのChainIDとは別物です。

WarpPredicateと事前検証

Warpメッセージはトランザクション実行前にEIP2930のAccessListを利用して事前検証されます。 この事前検証によって、ブロックには「どのメッセージが有効か」を示すビットセットが記録され、EVM実行時や過去ブロックの検証時に再利用できます。

EIP2930については以下の記事を参考にしてください。

手順

  1. AccessListに入っているWarp用データをバイト列から読み取り、項目ごと(送信元チェーンID・送信者アドレス・メッセージ本体・署名群など)に正しい型で復元します。
  2. 復元したデータから「署名付きWarpメッセージ」を構成する各項目を取り出し、形式が正しいか(必須フィールドがあるか、サイズが合っているか等)を確認します。
  3. AvalancheWarpMessagingの仕様に従って署名を検証します。 具体的には、送信元サブネットのバリデータ集合(重み付き)に対し、集約BLS署名が必要しきい値以上の重みを満たしているかを確認します(C-Chainは既定で67%〔67/100〕、Subnet-EVMは設定で変更可能)。 検証結果はトランザクションごとの結果ビットセットに書き込み、後続のEVM実行時に参照できるようにします。

AccessList内のインデックス規則

EIP2930のAccessListは「アドレスとストレージキー配列の組」を並べたアクセスエントリの配列です。 Warpでは、このエントリのアドレスがWarpPrecompileアドレス(0x0200...0005)のものだけを数え、出現順でインデックスを振ります(0始まり)。 他のアドレスのエントリは数えません。

AccessListの並びとWarpメッセージのindex

AccessList内の順番 address(例) storageKeys(例) これは数えるか Warpメッセージindex
0 0x0200000000000000000000000000000000000005(Warp) [kA, kB] はい 0
1 0x1111111111111111111111111111111111111111(任意) [x1] いいえ ―
2 0x0200000000000000000000000000000000000005(Warp) [kC] はい 1
3 0x2222222222222222222222222222222222222222(任意) [] いいえ ―
4 0x0200000000000000000000000000000000000005(Warp) [kD, kE] はい 2

この例では、WarpPrecompileアドレスのエントリが出現順に0,1,2…のindexを持ちます。 コントラクトからgetVerifiedWarpMessage(1)を呼ぶと、上表のindex=1(AccessList順で2番目のWarpエントリ)に対応する検証済みメッセージを参照します。 他アドレス(例: 0x1111…や0x2222…)のエントリはインデックス計算に含めません。

実装の呼び出し規約

  • calldata先頭4バイトを関数セレクタとして読み取り、対応する関数を呼び出します。
  • 入出力のエンコードはSolidityABI仕様に準拠します。

参考実装は以下で確認できます。

Gasコスト

WarpPrecompileでは、事前検証と実行の両方でガスがかかります。

検証時のコスト

項目 ガス量
署名検証1回 20,000
メッセージ1バイトあたり 100
署名者1人あたり 500

この設計は、既存のEVMプリコンパイルと同程度の処理性能を維持するように調整されています。

実行時のコスト

関数 ガス量 補足
sendWarpMessage(payload) 41,500 + 8 × payloadバイト数 ログ出力、BLS署名提示、メッセージ保存を含む
getBlockchainID() 2 メモリ内の値を返すだけ
getVerifiedWarpMessage / getVerifiedWarpBlockHash 2 + 100 × メッセージバイト数 構造体に展開する分を加算

互換性

AvalancheWarpMessaging(AWM)を有効化しても、既存のEVMのオペコードや他のPrecompile(組み込みコントラクト)の挙動は変更されません。 あくまでWarpPrecompileを新たに有効にする追加提案であり、既存コントラクトは明示的にWarpPrecompileを呼び出さない限り影響を受けません。 この有効化はAvalancheC-Chainで実施されるほか、指定されたペイロード形式の送信・検証に対応している任意のSubnet上のVMでも、スケジュールして有効化できます。

セキュリティ

AWMの検証では、送信元サブネットのバリデータ集合(その時点の検証者と重み)を、P-Chainの特定のBlock Heightで参照する必要があります。 どのBlock Heightを参照するかは、Snowman++ブロック拡張(ProposerVMがブロックに付与する拡張メタデータ)で指定されます。 P-Chainの現在状態はPlatformVMが保持しており、reversediff-layers(現在状態から過去状態を再現するための差分の積み重ね)を適用することで、任意の過去時点のバリデータ集合を再現できます。

古いBlock Heightを参照するAWMを後から検証し直す場合は、P-Chainの最新ステートから対象のBlockHeightまでReverseDiffLayersを順に適用して当時のステートを再現する必要があります。 サブネットやP-Chainがブロックを積み重ねるほど適用すべき差分が増えるため、検証コストは時間とともに大きくなります。

この問題に対してEVMは、ブロック内にエンコードされた結果ビットセットを利用します。 AWMは当該ブロック生成時にネットワークで検証・受け入れ済みであるため、履歴を確認する時はこのビットセットを読むだけで有効/無効を判定できます。 これにより、過去のP-Chainステートを再計算せずに履歴検証を効率化できます。

処理の流れ

  1. ブロック生成時 送信元サブネットの、指定されたP-ChainのBlock HeightにおけるValidatorSetに基づいてAWMの署名検証(クォーラム判定)を行い、その結果を結果ビットセットとしてブロックに書き込みます。
  2. 将来の履歴検証(素直なやり方) 過去のAWMを改めて検証する場合は、P-Chainの最新ステートから対象のBlock HeightまでReverseDiffLayersを順に適用してValidatorSetを再現する必要があり、ブロックが増えるほど計算コストが大きくなります。
  3. 実際の最適化 過去ブロックに埋め込まれた結果ビットセットを参照して有効/無効を判断します。 すでに検証済みで受け入れ済みであることを前提にするため、P-Chainの過去ステートを辿る必要はありません。

概念図

sequenceDiagram
  participant Proposer as ブロック提案ノード
  participant PlatformVM as PlatformVM(P-Chain)
  participant EVM as EVM(実行)
  participant Block as ブロック(結果ビットセット)

  Proposer->>PlatformVM: 指定P-ChainのBlockHeightのValidatorSetを取得
  Proposer->>Proposer: AWMの署名検証(クォーラム判定)
  Proposer-->>Block: トランザクションごとの結果ビットセットを書き込む
  Note over Block: 検証結果がブロックに保持される

  EVM->>Block: 過去ブロックの結果ビットセットを参照
  EVM-->>EVM: AWMの有効/無効を即時判定(P-Chain過去ステートの再計算なし)

OpenQuestions

論点

AWMの検証では、参照されたP-ChainのBlock Height時点のValidatorSet(検証者と重みの一覧)を取得して署名を確かめます。 ところが、このValidatorSet参照処理(PlatformVMから必要な情報を読む処理)の実コストは、参照するBlock Heightが古いほど大きくなります。 理由は、最新のステートから過去のステートへ戻すReverseDiffLayers(最新→過去へ巻き戻すための差分列)を多く適用する必要があり、計算やI/Oが増えるためです。 現時点の実装では、この参照コストはガス計算に含めていません。 問題は、「ノードごとに変動しやすいこの参照コストを、EVMのガスとしてどう設計するか」です。

論点 内容
現状のガス計算 ValidatorSetの参照処理はガス計算の対象外(実装から除外)です。
コストが変動する理由 参照するBlock Heightが古いほどReverseDiffLayersの適用回数が増え、計算量やI/Oが増えます。各バリデータのローカル状態やキャッシュ状況でも実コストが変わります。
論点 可変でノード依存のコストを一律のガス見積もりに落とすのは難しく、過大/過小見積もりは検証遅延や手数料の不公平につながります。
方向性 検証処理の並列化でブロック検証の待ち時間への影響をほぼ気にならない水準まで抑え、ガス計算はトランザクションに追加されたAWMのデータ量(帯域)を中心に反映します。
用語 説明
ValidatorSet あるBlock Height時点の検証者一覧とその重みです。
ReverseDiffLayers 最新ステートから過去ステートを再現するための逆向き差分列です。

進行中の取り組み

開発中の変更では、P-Chain上のValidatorSet参照処理とAWMの署名検証を並列実行し、ブロック検証レイテンシへの影響をほとんど感じない水準まで抑えることを狙っています。 これにより、「古いBlock Heightを参照すると遅くなる」というパターンを、検証全体の遅延要因から外しやすくなります。

並列化の概念図

flowchart TD
  A[Tx受領: AWM含む] --> B{各Txの検証フェーズ}
  B -->|並列| C[ValidatorSet参照処理<br/>PlatformVM, Block Height]
  B -->|並列| D[AWM署名データの復元/前処理]
  C --> E[しきい値判定用の鍵と重みを取得]
  D --> F[BLS集約署名の検証準備]
  E --> G[署名検証(クォーラム判定)]
  F --> G
  G --> H[結果ビットセットに反映]
  H --> I[ブロックに結果をエンコード]

このパイプラインは、参照処理と検証準備を重ね合わせることで、待ち時間の体感をほぼ消す設計に寄せています。 結果は結果ビットセットとしてブロックに保存され、将来の履歴検証はこのビットセットを読むだけで再計算を回避できます。

ガス設計の含意

並列化でレイテンシの影響が小さくなる前提が整えば、ガスは主に以下の要素に結びつけて設計できます。

  • AWMをトランザクションに載せたことで増えるデータ量(バイト数)
  • 署名検証の基礎コスト(BLS集約署名の検証、署名者数による加算)

この方向に寄せることで、過去のBlock Height参照に起因するノード実装依存のばらつきをガス代に反映する必要はなく、予測しやすく公平なガス設計に近づけます。

最後に

今回は「AvalancheのC-ChainとSubnet-EVMにAvalancheWarpMessagingを統合し、EVM上でクロスサブネット通信を標準機能として提供する提案しているACP30」についてまとめてきました! いかがだったでしょうか?

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

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