XCM:クロスコンセンサスメッセージフォーマット

Parachainsを備えたPolkadot1.0の最終リリースが近づくにつれ、クロスコンセンサスメッセージングフォーマット(略してXCM )は、最初の本番環境対応リリースに近づいています。これは、フォーマット、その目標、それがどのように機能するか、そして典型的なクロスチェーンタスクを達成するために使用できることの紹介です。

そもそも面白い事実の1つは、XCMは単なる「クロスチェーン」ではなく、「クロスコンセンサス」メッセージング形式です。この違いは、チェーン間だけでなく、スマートコントラクトやパレット間、橋やPolkadot'sSpreeのようなシャーディングされた飛び地を介して送信されるアイデアの種類を伝達するために設計されたフォーマットの目標の兆候です。

🤟プロトコルではなくフォーマット

XCMをよりよく理解するには、XCMの境界と、Polkadotテクノロジースタックのどこに収まるかを理解することが重要です。XCMはメッセージング形式です。メッセージングプロトコルではありません。システム間で実際にメッセージを「送信」するために使用することはできません。その有用性は、受信者が何をすべきかを表現することだけにあります。

ブリッジとコントラクトパレットを含まないPolkadotには、構成チェーン間でXCMメッセージを実際に通信するための3つの異なるシステム(UMP、DMP、XCMP)が付属しています。UMP(Upward Message Passing)を使用すると、パラチェーンはリレーチェーンにメッセージを送信できます。DMP(Downward Message Passing)を使用すると、リレーチェーンはメッセージをパラチェーンの1つに渡すことができます。XCMPは、おそらく最もよく知られているものであり、これにより、パラチェーンが相互にメッセージを送信できるようになります。XCMを使用して、これら3つの通信チャネルのそれぞれでメッセージの意味を表現できます。

XCMは、チェーン間でメッセージを送信するだけでなく、他のコンテキストでも役立ちます。チェーンのトランザクション形式が事前によくわかっているとは限りません。ビジネスロジックがほとんど変化しないチェーン(ビットコインなど)では、トランザクション形式(またはウォレットがチェーンに命令を送信するために使用する形式)は、まったく同じか、少なくとも互換性が無期限に維持される傾向があります。Polkadotやその構成要素であるパラチェーンなど、進化可能なメタプロトコルベースのチェーンを使用すると、1回のトランザクションでネットワーク全体でビジネスロジックをアップグレードできます。これにより、トランザクション形式を含むあらゆるものが変更される可能性があり、ウォレットメンテナ、特にオフラインに保つ必要のあるウォレット(パリティ署名者など)に潜在的な問題が発生します。XCMはバージョンが適切で、抽象的で一般的であるため、

🥅目標

XCMは、コンセンサスシステム間でアイデアを伝達する言語になることを目指しています。成長する生態系全体で適切に役立つためには、十分に一般的である必要があります。それは拡張可能でなければなりません。拡張性は必然的に変更を意味するため、将来にわたって利用でき、上位互換性がある必要があります。最後に、オンチェーンで実行するのに十分効率的であり、場合によっては従量制の環境で実行する必要があります。

すべての言語と同様に、一部の個人は他の要素よりもいくつかの要素を使用する傾向があります。XCMは、XCMをサポートするすべてのシステムが考えられるXCMメッセージを解釈できるように設計されているわけではありません。一部のメッセージは、一部のシステムでは合理的な解釈がありません。他のものは合理的かもしれませんが、リソースの制約のため、または同じコンテンツをより明確でより標準的な方法で表現できるため、インタプリタによって意図的にサポートされていません。システムは必然的に、可能なメッセージのサブセットのみをサポートします。リソースに大きな制約のあるシステム(スマートコントラクトなど)は、非常に限られた「方言」しかサポートしない場合があります。

この一般性は、XCMメッセージを実行するための料金の支払いなどの概念にまで及びます。XCMは、ガスメーター式のスマートコントラクトプラットフォームやコミュニティパラチェーンなどのさまざまなシステムで、システムパラチェーンとそのリレーチェーン間の信頼できる相互作用に至るまで使用できることがわかっているため、料金の支払いなどの要素を深く焼き付けたくありません。プロトコルで不可逆的に。

😬ネイティブメッセージ形式を使用しないのはなぜですか?

チェーンまたはスマートコントラクトのネイティブメッセージ/トランザクション形式に便乗することは、特定の状況では役立つ場合がありますが、XCMの目標にはあまり役立たないという大きな欠点がいくつかあります。まず、チェーン間の互換性が欠如しているため、複数の宛先にメッセージを送信することを意図しているシステムは、それぞれのメッセージを作成する方法を理解する必要があります。その点で、単一の宛先でさえ、時間の経過とともにそのネイティブトランザクション/メッセージ形式を変更する可能性があります。スマートコントラクトはアップグレードを取得する可能性があり、ブロックチェーンは新しい機能を導入したり、既存の機能を変更したりする可能性があり、そうすることでトランザクション形式を変更します。

第二に、チェーンの一般的なユースケースは、単一のトランザクションに簡単に適合しません。資金を引き出し、交換し、結果をすべて1つのトランザクション内に預けるには、特別なトリックが必要になる場合があります。一貫性のある予備資産フレームワークに必要な転送の転送通知は、他の人に気付かないチェーンには存在しません。

第三に、料金の支払いなどの操作は、スマートコントラクトメッセージのように料金の支払いがすでに交渉されていると想定するモデルに簡単に適合しません。対照的に、トランザクションエンベロープは、処理の支払いのためのシステムを提供しますが、一般に、コンセンサスシステム間で通信するときに意味をなさない署名を含むように設計されています。

🎬いくつかの初期ユースケース

XCMの目標は、一般的で、柔軟性があり、将来にわたって利用できるようにすることですが、もちろん、チェーン間のトークンの転送など、対処しなければならない実用的なニーズがあります。DeFiの世界全体で一般的な、交換サービスを実施するための一般的なインターフェイスと同様に、オプションの料金の支払い(おそらくこれらのトークンを使用)も別の方法です。最後に、XCM言語を使用して、プラットフォーム固有のアクションを実行できるようにする必要があります。たとえば、基板チェーン内では、ニッチ機能にアクセスするために、そのパレットの1つにリモートコールをディスパッチすることが望ましい場合があります。

その上、サポートしたいトークンを転送するための多くのモデルがあります。リモートチェーンのアカウントを単純に制御して、ローカルチェーンが資金を受け取り、最終的にはリモートチェーンのアドレスを持つことができるようにすることができます。制御する資金をそのリモートチェーン上の他のアカウントに転送します。

2つのコンセンサスシステムがあり、どちらも特定のトークンのネイティブホームです。USDTやUSDCなどのトークンを想像してみてください。これらのトークンには、いくつかの異なるチェーン上にインスタンスがあり、すべて完全に代替可能です。そのようなトークンを1つのチェーンで焼き付け、対応するトークンを別のサポートされているチェーンでミントすることが可能であるはずです。XCMの用語では、アセットの見かけの移動は、一方の側でアセットを破棄し、もう一方の側でクローンを作成することによって実際に発生するという考えから、これをテレポートと呼びます。

最後に、3番目のチェーンを指名したい2つのチェーンがあり、1つは資産がネイティブと見なされ、その資産の予備として使用されます。これらの各チェーンの資産の派生形は完全に裏付けられます、デリバティブ資産を、それを裏付けるリザーブチェーンの原資産と交換できるようにします。これは、2つのチェーンが必ずしも相互に信頼しているとは限らないが、(少なくとも問題のアセットに関する限り)アセットのネイティブチェーンを信頼する場合があります。ここでの例は、相互にDOTを送信したいいくつかのコミュニティパラチェーンがある場合です。それらはそれぞれ、Statemintチェーン(DOTのネイティブハブ)のパラチェーンによって制御されるDOTによって完全にサポートされるローカル形式のDOTを持っています。ローカル形式のDOTがチェーン間で送信されると、バックグラウンドで「実際の」DOTがStatemintのパラチェーンアカウント間を移動します。

この明らかに控えめなレベルの機能でさえ、比較的多数の構成があり、その使用法が望ましい場合があり、過剰適合を回避するためにいくつかの興味深い設計が必要です。

🫀XCMの構造

XCMフォーマットの中核にはXCVMがあります。一部の人には見えないかもしれませんが、これは(有効な)ローマ数字ではありません(ただし、そうであった場合は、おそらく905を意味します)。実際、これはクロスコンセンサス仮想マシンの略です。これは、命令がトランザクションとほぼ同じレベルになるように設計された、超高レベルの非チューリング完全コンピューターです。

XCMの「メッセージ」は、実際にはXCVMで実行される単なるプログラムです。1つ以上のXCM命令です。プログラムは、最後まで実行されるかエラーが発生するまで実行され、その時点で終了し(ここでは意図的に説明しないままにしておきます)、停止します。

XCVMには、多数のレジスタと、それをホストしているコンセンサスシステムの全体的な状態へのアクセスが含まれています。命令はレジスタを変更するか、コンセンサスシステムの状態を変更するか、またはその両方を行う可能性があります。

このような命令の一例はTransferAsset、資産をリモートシステム上の他のアドレスに転送するために使用されるものです。どの資産を譲渡し、誰に/どこに資産を譲渡するかを指示する必要があります。Rustでは、次のように宣言されています。

enum Instruction {
    TransferAsset {
        assets: MultiAssets,
        beneficiary: MultiLocation,
    }
    /* snip */
}

ご想像のとおり、assetsはどのアセットを転送するかを表すパラメータであり、beneficiary誰に/どこにアセットを配置するかを示します。もちろん、もう1つの情報、つまり、誰から/どこから資産を取得するかという情報が欠落しています。これは、OriginRegisterから自動的に推測されます。プログラムが開始されると、このレジスタは通常、メッセージが実際にどこから来たかを反映するようにトランスポートシステム(ブリッジ、XCMPなど)に従って設定され、と同じタイプの情報beneficiaryです。オリジンレジスタは保護されたレジスタとして動作します。特定の方法で変更するために使用できる命令が2つありますが、プログラムはそれを任意に設定することはできません。

使用されるタイプは、XCMの非常に基本的な考え方です。つまり、で表されるアセットと、で表されるMultiAssetコンセンサス内の場所MultiLocationです。オリジンレジスタはオプションですMultiLocation(必要に応じて完全にクリアできるため、オプションです)。

📍XCMの場所

タイプは、コンセンサスの世界内に存在するMultiLocation単一の場所を識別します。これは非常に抽象的なアイデアであり、Polkadotなどのスケーラブルなマルチシャードブロックチェーンから、パラチェーン上の低ERC-20アセットアカウントまで、コンセンサス内に存在するあらゆる種類のものを表すことができます。コンピュータサイエンスの用語では、サイズや複雑さに関係なく、実際には単なるグローバルシングルトンデータ構造です。

MultiLocation常に現在の場所を基準にした場所を表します。これはファイルシステムパスに少し似ていると考えることができますが、ファイルシステムツリーの「ルート」を直接表現する方法はありません。これは単純な理由によるものです。Polkadotの世界では、ブロックチェーンを他のブロックチェーンにマージしたり、他のブロックチェーンから分割したりできます。ブロックチェーンは非常に一人で人生を始めることができ、最終的にはより大きなコンセンサス内でパラチェーンになるために昇格することができます。そうすると、「ルート」の意味が一夜で変わり、XCMメッセージやその他のを使用するものに混乱が生じる可能性がありますMultiLocation。物事を単純にするために、この可能性を完全に排除します。

XCMの場所は階層的です。コンセンサスの一部の場所は、コンセンサスの他の場所に完全にカプセル化されています。Polkadotのパラチェーンは、Polkadotのコンセンサス全体の中に完全に存在し、内部の場所と呼ばれます。より厳密に言えば、コンセンサスシステムがあるときはいつでも、別のコンセンサスシステムの変更を意味する変更がある場合、前者のシステムは後者の内部にあると言えます。たとえば、Canvasスマートコントラクトは、それをホストするコントラクトパレットの内部にあります。ビットコインのUTXOはビットコインブロックチェーンの内部にあります。

これは、XCMが「誰」という2つの質問を区別しないことを意味します。そして、どこ?"。XCMのようなかなり抽象的なものの観点からは、違いはそれほど重要ではありません。2つはぼやけて、本質的に同じものになります。

MultiLocationsは、XCMメッセージを送信する場所、アセットを受信できる場所を識別するために使用されます。また、後で説明するように、アセット自体のタイプを説明するのにも役立ちます。とても便利なもの。

この記事のようにテキストで書き留めると、それらはいくつかの..(または「親」、カプセル化コンセンサスシステム)コンポーネントとそれに続くいくつかのジャンクションとして表され、すべてが。で区切られ/ます。(これは、Rustのような言語で表現する場合に一般的に発生することではありませんが、広く使用されている使い慣れたディレクトリパスに非常によく似ているため、書面で意味があります。)ジャンクションは、カプセル化されたコンセンサス内の内部の場所を識別します。システム。親/ジャンクションがまったくない場合は、場所はここにあるとだけ言います。

いくつかの例:

  • ../Parachain(1000):パラチェーン内で評価すると、これにより、インデックス1000の兄弟パラチェーンが識別されます(Rustでは次のように記述しParentThen(Parachain(1000)).into()ます)。
  • ../AccountId32(0x1234...cdef):パラチェーン内で評価すると、これは0x1234…cdefリレーチェーン上の32バイトのアカウントを識別します。
  • Parachain(42)/AccountKey20(0x1234...abcd):リレーチェーンで評価すると、これは0x1234…abcdパラチェーン番号42の20バイトのアカウント(おそらくイーサリアム互換のアカウントをホストするMoonbeamのようなもの)を識別します。

キー、インデックス、バイナリブロブ、複数の説明など、さまざまな方法でチェーン上にある場所を識別するためのさまざまな種類のジャンクションがあります。

💰XCMのアセット

XCMで作業する場合、ある種のアセットを参照する必要があることがよくあります。これは、存在するすべてのパブリックブロックチェーンが、内部経済とセキュリティメカニズムのバックボーンを提供するためにネイティブデジタル資産に依存しているためです。ビットコインなどのプルーフオブワークブロックチェーンの場合、ネイティブアセット(BTC)を使用して、ブロックチェーンを成長させ、二重支払いを防ぐ鉱夫に報酬を与えます。Polkadotなどのプルーフオブステークブロックチェーンの場合、ネイティブアセット(DOT)は担保の形式として使用され、ネットワークキーパー(スタッカーと呼ばれるは、有効なブロックを生成して現物で報奨を受けるためにリスクを負う必要があります。

一部のブロックチェーンは複数のアセットを管理します。たとえば、イーサリアムのERC-20フレームワークでは、さまざまなアセットをチェーン上で管理できます。イーサリアムのETHのように代替可能ではなく、代替可能ではない資産を管理するものもあります。これは、他に類を見ないインスタンスです。クリプトキティは、そのような代替不可能なトークンまたはNFTの初期の例でした。

XCMは、汗をかくことなく、このようなすべての資産を処理できるように設計されています。この目的のために、データ型MultiAssetとそれに関連する型MultiAssets、、WildMultiAssetおよびがありMultiAssetFilterます。MultiAssetRustで見てみましょう:

struct MultiAsset {
   id: AssetId,
   fun: Fungibility,
}

したがって、アセットを定義する2つのフィールドがあります。idこれfunは、XCMがアセットにどのようにアプローチするかをかなり示しています。まず、全体的な資産IDを提供する必要があります。代替可能な資産の場合、これは単に資産を識別します。NFTの場合、これはアセットの「クラス」全体を識別します。このクラス内にはさまざまなアセットインスタンスが含まれる場合があります。

enum AssetId {
   Concrete(MultiLocation),
   Abstract(BinaryBlob),
}

アセットIDは、2つの方法のいずれかで表現されます。具体的または抽象的のいずれか。Abstractは実際には使用されていませんが、アセットIDを名前で指定できます。これは便利ですが、受信者が名前を送信者が期待する方法で解釈することに依存していますが、これは必ずしも簡単ではない場合があります。コンクリートは一般的に使用されており、場所を使用して資産を明確に識別します。ネイティブアセット(DOTなど)の場合、アセットはアセットを作成するチェーン(この場合はPolkadotリレーチェーン、そのパラチェーンの1つからの場所..)として識別される傾向があります。主にチェーンのパレット内で管理される資産は、そのパレット内のインデックスを含む場所によって識別される場合があります。たとえば、カルラパラチェーンは、 Statemineパラチェーン上のアセットを場所とともに参照する場合があります../Parachain(1000)/PalletInstance(50)/GeneralIndex(42)

enum Fungibility {
   Fungible(NonZeroAmount),
   NonFungible(AssetInstance),
}

第二に、それらは代替可能または代替不可能でなければなりません。それらが代替可能である場合、ゼロ以外の量が関連付けられているはずです。それらが代替可能でない場合は、金額の代わりに、それらがどのインスタンスであるかを示す必要があります。これは通常、インデックスで表されますが、XCMでは、配列やバイナリブロブなどの他のさまざまなデータ型を使用することもできます。

これはカバーしますMultiAssetが、私たちが時々使用する他の3つの関連するタイプがあります。MultiAssetsそれらの1つであり、実際には単なるMultiAssetアイテムのセットを意味します。次にWildMultiAsset、; MultiAssetこれは、1つ以上のアイテムと照合するために使用できるワイルドカードです。実際には、サポートされているワイルドカードは2種類のみです。All(すべてのアセットとAllOf一致する)と、特定のID(AssetId)と代替可能性のすべてのアセットと一致するワイルドカードです。特に、後者の場合、量(代替可能性の場合)またはインスタンス(非代替可能性の場合)を指定する必要はなく、すべてが一致します。

最後に、がありMultiAssetFilterます。これは最も頻繁に使用され、実際にはワイルドカードまたは明確な(つまりワイルドカードではない)アセットのリストのいずれかを組み合わせMultiAssetsて指定できるようにするだけです。WildMultiAsset

Rust XCM APIでは、これらのデータ型を可能な限り簡単に操作できるようにするために、多くの変換を提供しています。たとえば、Polkadotリレーチェーンを使用しているときMultiAssetに、DOTアセットの100分割不可能な単位(Planck、知っている人の場合)に等しい代替可能性を指定するには、を使用します(Here, 100).into()

👉ホールディングレジスター

別のXCM命令を見てみましょうWithdrawAsset。一見すると、これは前半に少し似てTransferAssetいます。オリジンレジスタで指定された場所のアカウントから一部の資産を引き出します。しかし、それは彼らと何をしますか?—どこにも堆積しない場合、それは確かにかなり役に立たない操作です。そのRust宣言を見てみましょう:

WithdrawAsset(MultiAssets),

したがって、今回は1つのパラメーターのみがあります(タイプMultiAssetsであり、オリジンレジスタの所有権からどのアセットを撤回する必要があるかを決定します)。ただし、アセットを配置する場所は指定されていません。

撤回された未使用の資産は、一時的に保有登録簿と呼ばれるものに保管されます–(無期限に存続できない一時的な位置にあるため「保有」)。ホールディングレジスタで動作する命令がいくつかあります。非常に単純なものの1つはDepositAsset命令です。それを見てみましょう:

enum Instruction {
    DepositAsset {
        assets: MultiAssetFilter,
        max_assets: u32,
        beneficiary: MultiLocation,
    },
    /* snip */
}

あはは!TransferAsset賢明な読者は、これが命令の半分が欠けているように見えることを理解するでしょう。assetsどの資産を保有登録簿から削除してチェーンに預けるかを指定するパラメーターがあります。max_assetsXCMの作成者は、預ける予定の固有の資産の数を受信者に通知できます。(これは、資産の預け入れはコストのかかる操作になる可能性があるため、保有レジスターの内容を知る前に手数料を計算するときに役立ちます。)最後にbeneficiary、操作で以前に満たしたのと同じパラメーターがありTransferAssetます。

ホールディングレジスターで行うアクションを表す多くの指示がありDepositAsset、最も単純なものの1つです。他のいくつかはかなり洗練されています😬。

🤑XCMでの料金支払い

XCMでの料金支払いは、かなり重要なユースケースです。Polkadotコミュニティのほとんどのパラチェーンは、「トランザクションスパム」やサービス拒否攻撃にさらされないように、対話者が実行したい操作に対して料金を支払う必要があります。これに対する例外は、チェーンが対話者が適切に動作すると信じる十分な理由がある場合に存在します。これは、PolkadotリレーチェーンがPolkadotStatemintの共通善チェーンに対応する場合です。ただし、一般的なケースでは、料金はXCMメッセージとそのトランスポートプロトコルを使いすぎないようにするための良い方法です。XCMメッセージがPolkadotに到着したときに料金を支払う方法を見てみましょう。

すでに述べたように、XCMには、第一級市民としての料金と料金支払いの概念は含まれていません。たとえば、イーサリアムトランザクションモデルとは異なり、料金支払いは、著しく回避する必要があります。ゼロコストの抽象化を備えたRustのように、XCMでは料金の支払いに大きな設計オーバーヘッドはありません。

ただし、いくらかの料金の支払いが必要なシステムの場合、XCMはアセット付きの実行リソースを購入する機能を提供します。これを行うことは、大まかに言えば、次の3つの部分で構成されます。

  • まず、いくつかの資産を提供する必要があります。
  • 第二に、資産の計算時間(または基板用語では重み)の交換について交渉する必要があります。
  • 最後に、XCM操作が指示どおりに実行されます。

最初の部分は、アセットを提供する多数のXCM命令の1つによって管理されます。これらの1つ()はすでに知っていWithdrawAssetますが、後で見る他にもいくつかあります。保有レジスターの結果として生じる資産は、もちろん、XCMの実行に関連する料金の支払いに使用されます。手数料の支払いに使用されなかった資産は、一部の宛先アカウントに預け入れます。この例では、XCMがPolkadotリレーチェーンで発生しており、1 DOT(10,000,000,000の分割できないユニット)で発生していると想定します。

これまでのところ、XCM命令は次のようになっています。

WithdrawAsset((Here, 10_000_000_000).into()),

これにより、2番目の部分に進み、これらの資産(の一部)をXCMの支払いのための計算時間と交換します。このために、XCM命令がありBuyExecutionます。それを見てみましょう:

enum Instruction {
    /* snip */
    BuyExecution {
        fees: MultiAsset,
        weight: u64,
    },
}

最初の項目は、保有登録簿から取得し、手数料の支払いに使用するfees必要がある金額です。未使用の残高はすぐに返されるため、技術的には最大値です。

最終的に費やされる金額は、通訳システムによって決定されます—feesそれを制限するだけであり、通訳システムが希望する実行に対してより多く支払う必要がある場合、BuyExecution命令はエラーになります。2番目の項目は、購入する実行時間を指定します。これは通常、XCMプログラムの合計の重み以上である必要があります。

この例では、すべてのXCM命令に100万の重みがかかると想定します。つまり、これまでの2つの項目(WithdrawAssetおよびBuyExecution)では200万、次の項目ではさらに1つになります。これらの料金を支払う必要があるすべてのDOTを使用します(これは、宛先チェーンにクレイジーな料金がないことを信頼する場合にのみ良いアイデアです。私たちはそうすると仮定します)。これまでのXCMを見てみましょう。

WithdrawAsset((Here, 10_000_000_000).into()),
BuyExecution {
    fees: (Here, 10_000_000_000).into(),
    weight: 3_000_000,
},

XCMの3番目の部分は、保有登録簿に残っている資金を預けることです。このために、DepositAsset命令を使用します。保有レジスターにどれだけ残っているかは実際にはわかりませんが、預ける必要のある資産にワイルドカードを指定できるため、それは問題ではありません。それらをStatemintのソブリンアカウント(として識別されます)に配置しますParachain(1000)

したがって、最終的なXCM命令は次のようになります。

WithdrawAsset((Here, 10_000_000_000).into()),
BuyExecution {
    fees: (Here, 10_000_000_000).into(),
    weight: 3_000_000,
},
DepositAsset {
    assets: All.into(),
    max_assets: 1,
    beneficiary: Parachain(1000).into(),
},

⛓XCMのチェーン間でアセットを移動する

アセットを別のチェーンに送信することは、おそらくチェーン間メッセージングの最も一般的なユースケースです。あるチェーンが別のチェーンのネイティブ資産を管理できるようにすることで、あらゆる種類のデリバティブのユースケース(しゃれは意図されていません)が可能になります。最も単純なのは分散型取引所ですが、一般に分散型ファイナンスまたはDeFiとしてグループ化されます。

一般的に、アセットがチェーン間を移動する方法は2つあり、これはチェーンが互いのセキュリティとロジックを信頼するかどうかによって異なります。

✨テレポート

相互に信頼するチェーン(同じ全体的なコンセンサスとセキュリティの傘の下にある同種のシャード)の場合、Polkadotがテレポートと呼ぶフレームワークを使用できます。これは基本的に、送信側でアセットを破棄し、受信側でアセットを作成することを意味します。これはシンプルで効率的です。2つのチェーンの調整のみが必要であり、どちらかの側で1つのアクションのみが必要です。残念ながら、受信チェーンが送信チェーンを100%信頼して、ミントしているアセットを実際に破棄できない場合(実際、アセットの合意されたルールの範囲外でアセットをミントしない場合)、送信チェーンには実際にミントする根拠がありません。メッセージの裏にあるアセット。

1DOTをPolkadotリレーチェーンからStatemintのソブリンアカウントにテレポートしたXCMがどのように見えるかを見てみましょう。料金はすでにPolkadot側で支払われていると想定します。

WithdrawAsset((Here, 10_000_000_000).into()),
InitiateTeleport {
    assets: All.into(),
    dest: Parachain(1000).into(),
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: 3_000_000,
        },
        DepositAsset {
            assets: All.into(),
            max_assets: 1,
            beneficiary: Parent.into(),
        },
    ]),
}

ご覧のとおり、これは最後に見たストレートの引き出し-購入-預金パターンにかなり似ています。違いはInitiateTeleport、最後の2つの命令(BuyExecutionおよびDepositAsset)の周りに挿入される命令です。舞台裏では、送信者(Polkadotリレー)チェーンは、InitiateTeleport命令を実行するときにまったく新しいメッセージを作成しています。フィールドを取得しxcmて新しいXCM内に配置し、ReceiveTeleportedAssetこのXCMをレシーバー(Statemint)チェーンに送信します。Statemintは、メッセージを送信する前に、Polkadotリレーチェーンがその側の1DOTを破壊したことを信頼しています。(します!)

beneficiaryはと記載されており、賢明な読者は、これParent.into()がPolkadotリレーチェーンのコンテキストで何を指しているのか疑問に思うかもしれません。答えは「何もない」ですが、ここでは間違いはありません。パラメータ内のすべてxcmは受信側の観点から書き込まれるため、これはPolkadotリレーチェーンに供給されるXCM全体の一部であるにもかかわらず、実際にはStatemintでのみ実行されます。したがって、Statemintのコンテキストでは次のようになります。書いた。

Statemintが最終的にメッセージを受け取ると、次のようになります。

ReceiveTeleportedAsset((Parent, 10_000_000_000).into()),
BuyExecution {
    fees: (Parent, 10_000_000_000).into(),
    weight: 3_000_000,
},
DepositAsset {
    assets: All.into(),
    max_assets: 1,
    beneficiary: Parent.into(),
},

WithdrawAssetこれは以前のXCMとかなり似ていることに気付くかもしれません。唯一の大きな違いは、ローカルアカウントからの引き出しを通じて手数料と預金に資金を提供するのではなく、送信側(Polkadotリレーチェーン)でDOTが忠実に破壊されたことを信頼し、ReceiveTeleportedAssetメッセージを尊重することで、「魔法のように」存在することです。 。

特に、Polkadotリレーチェーンで送信した1 DOTのアセット識別子(Hereリレーチェーン自体をDOTのネイティブホームと呼びます)は、Statemint:Parent.into()での表現に自動的に変更されました。これは、Statemintのコンテキストからのリレーチェーンの場所です。

beneficiaryPolkadotリレーチェーンとしても指定されているため、そのソブリンアカウント(Statemint上)には、新しく作成された1DOTから手数料を差し引いた金額が入金されます。XCMは、アカウントまたは他の場所に簡単に名前を付けた可能性がありますbeneficiary。そのままでは、後でTransferAssetリレーチェーンから送信されたものを使用して、この1DOTを移動できます。

🏦リザーブ

チェーン間でアセットを転送する別の方法は、少し複雑です。リザーブと呼ばれるサードパーティが使用されます。この名前はリザーブバンキングに由来します。リザーブバンキングでは、発行された約束が価値があるという考えに信頼性を与えるために資産が「予備」に保持されます。たとえば、独立したパラチェーンで発行された「派生」DOTごとに1つの「実際の」(たとえば、ステートミントまたはリレーチェーン)DOTが引き換え可能であると合理的に信じることができる場合、パラチェーンのDOTを実際のDOTと経済的に同等であると見なすことができます。 (ほとんどの銀行は、部分準備銀行と呼ばれることを行っています。これは、準備金の額面よりも少ない金額を維持することを意味します)。これは、あまりにも多くの人が償還を希望するまでは問題なく機能します。そうすると、すべてが非常に速く失敗する可能性があります。

したがって、予備は「実際の」資産を保管する場所であり、転送の目的で、そのロジックとセキュリティは送信者と受信者の両方から信頼されています。その場合、送信者側と受信者側の対応する資産はデリバティブになりますが、それらは「実際の」準備資産で100%裏付けられます。パラチェーンが適切に動作したと仮定すると(つまり、バグがなく、そのガバナンスが予備金で逃げることを決定しなかった)、これにより、デリバティブDOTは基礎となる予備金DOTとほぼ同じ値になります。予備資産は、予備チェーンの送信者/受信者のソブリンアカウント(つまり、送信者または受信者チェーンによって制御可能なアカウント)に保持されているため、パラチェーンに問題が発生しない限り、十分に保護されるのには十分な理由があります。

転送メカニズムに戻ると、送信者は、送信者が所有する(そして同じ資産の独自のバージョンの準備金として使用する)資産を、送信者ではなく、受信者のソブリンアカウントに移動するように準備金に指示します。—受信者に新しいクレジットについて通知します。これは、送信者と受信者が互いのロジックやセキュリティを信頼する必要はなく、予備として使用されるチェーンのロジックのみを信頼する必要があることを意味します。ただし、これは3つの側面が調整する必要があることを意味し、全体的なコスト、時間、および複雑さが増します。

必要なXCMを見てみましょう。今回は、パラチェーン2000からパラチェーン2001に1 DOTを送信します。これは、パラチェーン1000でリザーブバックDOTを使用します。ここでも、料金は送信者側ですでに支払われていると想定します。

WithdrawAsset((Parent, 10_000_000_000).into()),
InitiateReserveWithdraw {
    assets: All.into(),
    dest: ParentThen(Parachain(1000)).into(),
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: 3_000_000,
        },
        DepositReserveAsset {
            assets: All.into(),
            max_assets: 1,
            dest: ParentThen(Parachain(2001)).into(),
            xcm: Xcm(vec![
                BuyExecution {
                    fees: (Parent, 10_000_000_000).into(),
                    weight: 3_000_000,
                },
                DepositAsset {
                    assets: All.into(),
                    max_assets: 1,
                    beneficiary: ParentThen(Parachain(2000)).into(),
                },
            ]),
        },
    ]),
},

約束通り、これはもう少し複雑です。それを見ていきましょう。外側の部分は、送信側(parachain 2000)で1 DOTを抽出し、Statemint(parachain 1000)で保持されている対応する1 DOTを引き出すことを扱いますInitiateReserveWithdraw。これはこの目的に使用され、かなり自明です。

WithdrawAsset((Parent, 10_000_000_000).into()),
InitiateReserveWithdraw {
    assets: All.into(),
    dest: ParentThen(Parachain(1000)).into(),
    xcm: /* snip */
}

これで、StatemintのHoldingRegisterに1つのDOTがあります。他のことを行う前に、Statemintで実行時間を購入する必要があります。これも、かなりおなじみのようです。

/*snip*/
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: 3_000_000,
        },
        DepositReserveAsset {
            assets: All.into(),
            max_assets: 1,
            dest: ParentThen(Parachain(2001)).into(),
            xcm: /* snip */
        },
    ]),
/*snip*/

1 DOTを使用して料金を支払い、XCM操作ごとに100万を想定しています。その1つの操作の支払いで、1 DOT(マイナスの手数料。怠惰なので使用するだけですAll.into())をパラチェーン2001のソブリンアカウントに預け入れますが、予備資産として預け入れます。つまり、Statemintに結果として生じる派生資産に対して実行されるいくつかの命令とともに、転送を通知する受信チェーンへの通知XCM。DepositReserveAsset指示は必ずしも意味をなさない。それdestが理にかなっているためには、リザーブチェーンに合理的に資金を保持できる場所である必要がありますが、リザーブチェーンがXCMを送信できる場所でもある必要があります。兄弟のパラチェーンはたまたま法案に完全に適合しています。

/*snip*/
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: 3_000_000,
        },
        DepositAsset {
            assets: All.into(),
            max_assets: 1,
            beneficiary: ParentThen(Parachain(2000)).into(),
        },
    ]),
/*snip*/

最後の部分は、パラチェーン2001に到着するメッセージの一部を定義します。テレポート操作を開始する場合と同様にDepositReserveAsset、この場合は新しいメッセージを作成して送信しますReserveAssetDeposited。受信側のパラチェーンに到達するのは、私たちが定義したXCMプログラムが含まれているにもかかわらず、このメッセージです。次のようになります。

ReserveAssetDeposited((Parent, 10_000_000_000).into()),
BuyExecution {
    fees: (Parent, 10_000_000_000).into(),
    weight: 3_000_000,
},
DepositAsset {
    assets: All.into(),
    max_assets: 1,
    beneficiary: ParentThen(Parachain(2000)).into(),
},

(これは、Statemintで実際に料金が発生せず、1 DOT全体がそれを超えたことを前提としています。これは特に現実的ではないため、assets回線の数はおそらく少なくなります。)

メッセージのほとんどはかなり見覚えがあるはずです。前のセクションで見たメッセージとの唯一の重要な違いReceiveTeleportedAssetは、トップレベルの命令ReserveAssetDepositedです。これは、「送信チェーンがアセットを焼き付けて同等のアセットを作成できるようにする」という意味ではなく、「送信チェーンが送信チェーンを意味する」という意味で、同様の目的を果たします。資産を受け取り、それらを予備として保持しているので、完全に裏付けされた派生物を作成できます。」いずれにせよ、宛先チェーンはそれらをホールディングレジスターにミントし、受信チェーンの送信者のソブリンアカウントにそれらを預け入れます。🎉

🏁結論

この記事は以上です。XCMとは何か、およびXCMがどのように機能するように設計されているかの基本を説明するのに役立つことを願っています。次の記事では、XCVMのアーキテクチャ、その実行モデルとエラー処理、XCMのバージョン管理システム、および相互依存するエコシステムでフォーマットへのアップグレードを管理する方法について詳しく説明します。そのクエリ応答システムとして、そしてXCMがSubstrateでどのように機能するか。また、XCMの将来の方向性、計画されている機能、およびXCMを進化させるためのプロセスについても説明します。\

ソース:https ://polkadot.network/blog/xcm-the-cross-consensus-message-format/

 パートII:バージョン管理と互換性
 パートIII:実行とエラー管理

#xcm  #polkadot 

What is GEEK

Buddha Community

XCM:クロスコンセンサスメッセージフォーマット

XCM:クロスコンセンサスメッセージフォーマット

Parachainsを備えたPolkadot1.0の最終リリースが近づくにつれ、クロスコンセンサスメッセージングフォーマット(略してXCM )は、最初の本番環境対応リリースに近づいています。これは、フォーマット、その目標、それがどのように機能するか、そして典型的なクロスチェーンタスクを達成するために使用できることの紹介です。

そもそも面白い事実の1つは、XCMは単なる「クロスチェーン」ではなく、「クロスコンセンサス」メッセージング形式です。この違いは、チェーン間だけでなく、スマートコントラクトやパレット間、橋やPolkadot'sSpreeのようなシャーディングされた飛び地を介して送信されるアイデアの種類を伝達するために設計されたフォーマットの目標の兆候です。

🤟プロトコルではなくフォーマット

XCMをよりよく理解するには、XCMの境界と、Polkadotテクノロジースタックのどこに収まるかを理解することが重要です。XCMはメッセージング形式です。メッセージングプロトコルではありません。システム間で実際にメッセージを「送信」するために使用することはできません。その有用性は、受信者が何をすべきかを表現することだけにあります。

ブリッジとコントラクトパレットを含まないPolkadotには、構成チェーン間でXCMメッセージを実際に通信するための3つの異なるシステム(UMP、DMP、XCMP)が付属しています。UMP(Upward Message Passing)を使用すると、パラチェーンはリレーチェーンにメッセージを送信できます。DMP(Downward Message Passing)を使用すると、リレーチェーンはメッセージをパラチェーンの1つに渡すことができます。XCMPは、おそらく最もよく知られているものであり、これにより、パラチェーンが相互にメッセージを送信できるようになります。XCMを使用して、これら3つの通信チャネルのそれぞれでメッセージの意味を表現できます。

XCMは、チェーン間でメッセージを送信するだけでなく、他のコンテキストでも役立ちます。チェーンのトランザクション形式が事前によくわかっているとは限りません。ビジネスロジックがほとんど変化しないチェーン(ビットコインなど)では、トランザクション形式(またはウォレットがチェーンに命令を送信するために使用する形式)は、まったく同じか、少なくとも互換性が無期限に維持される傾向があります。Polkadotやその構成要素であるパラチェーンなど、進化可能なメタプロトコルベースのチェーンを使用すると、1回のトランザクションでネットワーク全体でビジネスロジックをアップグレードできます。これにより、トランザクション形式を含むあらゆるものが変更される可能性があり、ウォレットメンテナ、特にオフラインに保つ必要のあるウォレット(パリティ署名者など)に潜在的な問題が発生します。XCMはバージョンが適切で、抽象的で一般的であるため、

🥅目標

XCMは、コンセンサスシステム間でアイデアを伝達する言語になることを目指しています。成長する生態系全体で適切に役立つためには、十分に一般的である必要があります。それは拡張可能でなければなりません。拡張性は必然的に変更を意味するため、将来にわたって利用でき、上位互換性がある必要があります。最後に、オンチェーンで実行するのに十分効率的であり、場合によっては従量制の環境で実行する必要があります。

すべての言語と同様に、一部の個人は他の要素よりもいくつかの要素を使用する傾向があります。XCMは、XCMをサポートするすべてのシステムが考えられるXCMメッセージを解釈できるように設計されているわけではありません。一部のメッセージは、一部のシステムでは合理的な解釈がありません。他のものは合理的かもしれませんが、リソースの制約のため、または同じコンテンツをより明確でより標準的な方法で表現できるため、インタプリタによって意図的にサポートされていません。システムは必然的に、可能なメッセージのサブセットのみをサポートします。リソースに大きな制約のあるシステム(スマートコントラクトなど)は、非常に限られた「方言」しかサポートしない場合があります。

この一般性は、XCMメッセージを実行するための料金の支払いなどの概念にまで及びます。XCMは、ガスメーター式のスマートコントラクトプラットフォームやコミュニティパラチェーンなどのさまざまなシステムで、システムパラチェーンとそのリレーチェーン間の信頼できる相互作用に至るまで使用できることがわかっているため、料金の支払いなどの要素を深く焼き付けたくありません。プロトコルで不可逆的に。

😬ネイティブメッセージ形式を使用しないのはなぜですか?

チェーンまたはスマートコントラクトのネイティブメッセージ/トランザクション形式に便乗することは、特定の状況では役立つ場合がありますが、XCMの目標にはあまり役立たないという大きな欠点がいくつかあります。まず、チェーン間の互換性が欠如しているため、複数の宛先にメッセージを送信することを意図しているシステムは、それぞれのメッセージを作成する方法を理解する必要があります。その点で、単一の宛先でさえ、時間の経過とともにそのネイティブトランザクション/メッセージ形式を変更する可能性があります。スマートコントラクトはアップグレードを取得する可能性があり、ブロックチェーンは新しい機能を導入したり、既存の機能を変更したりする可能性があり、そうすることでトランザクション形式を変更します。

第二に、チェーンの一般的なユースケースは、単一のトランザクションに簡単に適合しません。資金を引き出し、交換し、結果をすべて1つのトランザクション内に預けるには、特別なトリックが必要になる場合があります。一貫性のある予備資産フレームワークに必要な転送の転送通知は、他の人に気付かないチェーンには存在しません。

第三に、料金の支払いなどの操作は、スマートコントラクトメッセージのように料金の支払いがすでに交渉されていると想定するモデルに簡単に適合しません。対照的に、トランザクションエンベロープは、処理の支払いのためのシステムを提供しますが、一般に、コンセンサスシステム間で通信するときに意味をなさない署名を含むように設計されています。

🎬いくつかの初期ユースケース

XCMの目標は、一般的で、柔軟性があり、将来にわたって利用できるようにすることですが、もちろん、チェーン間のトークンの転送など、対処しなければならない実用的なニーズがあります。DeFiの世界全体で一般的な、交換サービスを実施するための一般的なインターフェイスと同様に、オプションの料金の支払い(おそらくこれらのトークンを使用)も別の方法です。最後に、XCM言語を使用して、プラットフォーム固有のアクションを実行できるようにする必要があります。たとえば、基板チェーン内では、ニッチ機能にアクセスするために、そのパレットの1つにリモートコールをディスパッチすることが望ましい場合があります。

その上、サポートしたいトークンを転送するための多くのモデルがあります。リモートチェーンのアカウントを単純に制御して、ローカルチェーンが資金を受け取り、最終的にはリモートチェーンのアドレスを持つことができるようにすることができます。制御する資金をそのリモートチェーン上の他のアカウントに転送します。

2つのコンセンサスシステムがあり、どちらも特定のトークンのネイティブホームです。USDTやUSDCなどのトークンを想像してみてください。これらのトークンには、いくつかの異なるチェーン上にインスタンスがあり、すべて完全に代替可能です。そのようなトークンを1つのチェーンで焼き付け、対応するトークンを別のサポートされているチェーンでミントすることが可能であるはずです。XCMの用語では、アセットの見かけの移動は、一方の側でアセットを破棄し、もう一方の側でクローンを作成することによって実際に発生するという考えから、これをテレポートと呼びます。

最後に、3番目のチェーンを指名したい2つのチェーンがあり、1つは資産がネイティブと見なされ、その資産の予備として使用されます。これらの各チェーンの資産の派生形は完全に裏付けられます、デリバティブ資産を、それを裏付けるリザーブチェーンの原資産と交換できるようにします。これは、2つのチェーンが必ずしも相互に信頼しているとは限らないが、(少なくとも問題のアセットに関する限り)アセットのネイティブチェーンを信頼する場合があります。ここでの例は、相互にDOTを送信したいいくつかのコミュニティパラチェーンがある場合です。それらはそれぞれ、Statemintチェーン(DOTのネイティブハブ)のパラチェーンによって制御されるDOTによって完全にサポートされるローカル形式のDOTを持っています。ローカル形式のDOTがチェーン間で送信されると、バックグラウンドで「実際の」DOTがStatemintのパラチェーンアカウント間を移動します。

この明らかに控えめなレベルの機能でさえ、比較的多数の構成があり、その使用法が望ましい場合があり、過剰適合を回避するためにいくつかの興味深い設計が必要です。

🫀XCMの構造

XCMフォーマットの中核にはXCVMがあります。一部の人には見えないかもしれませんが、これは(有効な)ローマ数字ではありません(ただし、そうであった場合は、おそらく905を意味します)。実際、これはクロスコンセンサス仮想マシンの略です。これは、命令がトランザクションとほぼ同じレベルになるように設計された、超高レベルの非チューリング完全コンピューターです。

XCMの「メッセージ」は、実際にはXCVMで実行される単なるプログラムです。1つ以上のXCM命令です。プログラムは、最後まで実行されるかエラーが発生するまで実行され、その時点で終了し(ここでは意図的に説明しないままにしておきます)、停止します。

XCVMには、多数のレジスタと、それをホストしているコンセンサスシステムの全体的な状態へのアクセスが含まれています。命令はレジスタを変更するか、コンセンサスシステムの状態を変更するか、またはその両方を行う可能性があります。

このような命令の一例はTransferAsset、資産をリモートシステム上の他のアドレスに転送するために使用されるものです。どの資産を譲渡し、誰に/どこに資産を譲渡するかを指示する必要があります。Rustでは、次のように宣言されています。

enum Instruction {
    TransferAsset {
        assets: MultiAssets,
        beneficiary: MultiLocation,
    }
    /* snip */
}

ご想像のとおり、assetsはどのアセットを転送するかを表すパラメータであり、beneficiary誰に/どこにアセットを配置するかを示します。もちろん、もう1つの情報、つまり、誰から/どこから資産を取得するかという情報が欠落しています。これは、OriginRegisterから自動的に推測されます。プログラムが開始されると、このレジスタは通常、メッセージが実際にどこから来たかを反映するようにトランスポートシステム(ブリッジ、XCMPなど)に従って設定され、と同じタイプの情報beneficiaryです。オリジンレジスタは保護されたレジスタとして動作します。特定の方法で変更するために使用できる命令が2つありますが、プログラムはそれを任意に設定することはできません。

使用されるタイプは、XCMの非常に基本的な考え方です。つまり、で表されるアセットと、で表されるMultiAssetコンセンサス内の場所MultiLocationです。オリジンレジスタはオプションですMultiLocation(必要に応じて完全にクリアできるため、オプションです)。

📍XCMの場所

タイプは、コンセンサスの世界内に存在するMultiLocation単一の場所を識別します。これは非常に抽象的なアイデアであり、Polkadotなどのスケーラブルなマルチシャードブロックチェーンから、パラチェーン上の低ERC-20アセットアカウントまで、コンセンサス内に存在するあらゆる種類のものを表すことができます。コンピュータサイエンスの用語では、サイズや複雑さに関係なく、実際には単なるグローバルシングルトンデータ構造です。

MultiLocation常に現在の場所を基準にした場所を表します。これはファイルシステムパスに少し似ていると考えることができますが、ファイルシステムツリーの「ルート」を直接表現する方法はありません。これは単純な理由によるものです。Polkadotの世界では、ブロックチェーンを他のブロックチェーンにマージしたり、他のブロックチェーンから分割したりできます。ブロックチェーンは非常に一人で人生を始めることができ、最終的にはより大きなコンセンサス内でパラチェーンになるために昇格することができます。そうすると、「ルート」の意味が一夜で変わり、XCMメッセージやその他のを使用するものに混乱が生じる可能性がありますMultiLocation。物事を単純にするために、この可能性を完全に排除します。

XCMの場所は階層的です。コンセンサスの一部の場所は、コンセンサスの他の場所に完全にカプセル化されています。Polkadotのパラチェーンは、Polkadotのコンセンサス全体の中に完全に存在し、内部の場所と呼ばれます。より厳密に言えば、コンセンサスシステムがあるときはいつでも、別のコンセンサスシステムの変更を意味する変更がある場合、前者のシステムは後者の内部にあると言えます。たとえば、Canvasスマートコントラクトは、それをホストするコントラクトパレットの内部にあります。ビットコインのUTXOはビットコインブロックチェーンの内部にあります。

これは、XCMが「誰」という2つの質問を区別しないことを意味します。そして、どこ?"。XCMのようなかなり抽象的なものの観点からは、違いはそれほど重要ではありません。2つはぼやけて、本質的に同じものになります。

MultiLocationsは、XCMメッセージを送信する場所、アセットを受信できる場所を識別するために使用されます。また、後で説明するように、アセット自体のタイプを説明するのにも役立ちます。とても便利なもの。

この記事のようにテキストで書き留めると、それらはいくつかの..(または「親」、カプセル化コンセンサスシステム)コンポーネントとそれに続くいくつかのジャンクションとして表され、すべてが。で区切られ/ます。(これは、Rustのような言語で表現する場合に一般的に発生することではありませんが、広く使用されている使い慣れたディレクトリパスに非常によく似ているため、書面で意味があります。)ジャンクションは、カプセル化されたコンセンサス内の内部の場所を識別します。システム。親/ジャンクションがまったくない場合は、場所はここにあるとだけ言います。

いくつかの例:

  • ../Parachain(1000):パラチェーン内で評価すると、これにより、インデックス1000の兄弟パラチェーンが識別されます(Rustでは次のように記述しParentThen(Parachain(1000)).into()ます)。
  • ../AccountId32(0x1234...cdef):パラチェーン内で評価すると、これは0x1234…cdefリレーチェーン上の32バイトのアカウントを識別します。
  • Parachain(42)/AccountKey20(0x1234...abcd):リレーチェーンで評価すると、これは0x1234…abcdパラチェーン番号42の20バイトのアカウント(おそらくイーサリアム互換のアカウントをホストするMoonbeamのようなもの)を識別します。

キー、インデックス、バイナリブロブ、複数の説明など、さまざまな方法でチェーン上にある場所を識別するためのさまざまな種類のジャンクションがあります。

💰XCMのアセット

XCMで作業する場合、ある種のアセットを参照する必要があることがよくあります。これは、存在するすべてのパブリックブロックチェーンが、内部経済とセキュリティメカニズムのバックボーンを提供するためにネイティブデジタル資産に依存しているためです。ビットコインなどのプルーフオブワークブロックチェーンの場合、ネイティブアセット(BTC)を使用して、ブロックチェーンを成長させ、二重支払いを防ぐ鉱夫に報酬を与えます。Polkadotなどのプルーフオブステークブロックチェーンの場合、ネイティブアセット(DOT)は担保の形式として使用され、ネットワークキーパー(スタッカーと呼ばれるは、有効なブロックを生成して現物で報奨を受けるためにリスクを負う必要があります。

一部のブロックチェーンは複数のアセットを管理します。たとえば、イーサリアムのERC-20フレームワークでは、さまざまなアセットをチェーン上で管理できます。イーサリアムのETHのように代替可能ではなく、代替可能ではない資産を管理するものもあります。これは、他に類を見ないインスタンスです。クリプトキティは、そのような代替不可能なトークンまたはNFTの初期の例でした。

XCMは、汗をかくことなく、このようなすべての資産を処理できるように設計されています。この目的のために、データ型MultiAssetとそれに関連する型MultiAssets、、WildMultiAssetおよびがありMultiAssetFilterます。MultiAssetRustで見てみましょう:

struct MultiAsset {
   id: AssetId,
   fun: Fungibility,
}

したがって、アセットを定義する2つのフィールドがあります。idこれfunは、XCMがアセットにどのようにアプローチするかをかなり示しています。まず、全体的な資産IDを提供する必要があります。代替可能な資産の場合、これは単に資産を識別します。NFTの場合、これはアセットの「クラス」全体を識別します。このクラス内にはさまざまなアセットインスタンスが含まれる場合があります。

enum AssetId {
   Concrete(MultiLocation),
   Abstract(BinaryBlob),
}

アセットIDは、2つの方法のいずれかで表現されます。具体的または抽象的のいずれか。Abstractは実際には使用されていませんが、アセットIDを名前で指定できます。これは便利ですが、受信者が名前を送信者が期待する方法で解釈することに依存していますが、これは必ずしも簡単ではない場合があります。コンクリートは一般的に使用されており、場所を使用して資産を明確に識別します。ネイティブアセット(DOTなど)の場合、アセットはアセットを作成するチェーン(この場合はPolkadotリレーチェーン、そのパラチェーンの1つからの場所..)として識別される傾向があります。主にチェーンのパレット内で管理される資産は、そのパレット内のインデックスを含む場所によって識別される場合があります。たとえば、カルラパラチェーンは、 Statemineパラチェーン上のアセットを場所とともに参照する場合があります../Parachain(1000)/PalletInstance(50)/GeneralIndex(42)

enum Fungibility {
   Fungible(NonZeroAmount),
   NonFungible(AssetInstance),
}

第二に、それらは代替可能または代替不可能でなければなりません。それらが代替可能である場合、ゼロ以外の量が関連付けられているはずです。それらが代替可能でない場合は、金額の代わりに、それらがどのインスタンスであるかを示す必要があります。これは通常、インデックスで表されますが、XCMでは、配列やバイナリブロブなどの他のさまざまなデータ型を使用することもできます。

これはカバーしますMultiAssetが、私たちが時々使用する他の3つの関連するタイプがあります。MultiAssetsそれらの1つであり、実際には単なるMultiAssetアイテムのセットを意味します。次にWildMultiAsset、; MultiAssetこれは、1つ以上のアイテムと照合するために使用できるワイルドカードです。実際には、サポートされているワイルドカードは2種類のみです。All(すべてのアセットとAllOf一致する)と、特定のID(AssetId)と代替可能性のすべてのアセットと一致するワイルドカードです。特に、後者の場合、量(代替可能性の場合)またはインスタンス(非代替可能性の場合)を指定する必要はなく、すべてが一致します。

最後に、がありMultiAssetFilterます。これは最も頻繁に使用され、実際にはワイルドカードまたは明確な(つまりワイルドカードではない)アセットのリストのいずれかを組み合わせMultiAssetsて指定できるようにするだけです。WildMultiAsset

Rust XCM APIでは、これらのデータ型を可能な限り簡単に操作できるようにするために、多くの変換を提供しています。たとえば、Polkadotリレーチェーンを使用しているときMultiAssetに、DOTアセットの100分割不可能な単位(Planck、知っている人の場合)に等しい代替可能性を指定するには、を使用します(Here, 100).into()

👉ホールディングレジスター

別のXCM命令を見てみましょうWithdrawAsset。一見すると、これは前半に少し似てTransferAssetいます。オリジンレジスタで指定された場所のアカウントから一部の資産を引き出します。しかし、それは彼らと何をしますか?—どこにも堆積しない場合、それは確かにかなり役に立たない操作です。そのRust宣言を見てみましょう:

WithdrawAsset(MultiAssets),

したがって、今回は1つのパラメーターのみがあります(タイプMultiAssetsであり、オリジンレジスタの所有権からどのアセットを撤回する必要があるかを決定します)。ただし、アセットを配置する場所は指定されていません。

撤回された未使用の資産は、一時的に保有登録簿と呼ばれるものに保管されます–(無期限に存続できない一時的な位置にあるため「保有」)。ホールディングレジスタで動作する命令がいくつかあります。非常に単純なものの1つはDepositAsset命令です。それを見てみましょう:

enum Instruction {
    DepositAsset {
        assets: MultiAssetFilter,
        max_assets: u32,
        beneficiary: MultiLocation,
    },
    /* snip */
}

あはは!TransferAsset賢明な読者は、これが命令の半分が欠けているように見えることを理解するでしょう。assetsどの資産を保有登録簿から削除してチェーンに預けるかを指定するパラメーターがあります。max_assetsXCMの作成者は、預ける予定の固有の資産の数を受信者に通知できます。(これは、資産の預け入れはコストのかかる操作になる可能性があるため、保有レジスターの内容を知る前に手数料を計算するときに役立ちます。)最後にbeneficiary、操作で以前に満たしたのと同じパラメーターがありTransferAssetます。

ホールディングレジスターで行うアクションを表す多くの指示がありDepositAsset、最も単純なものの1つです。他のいくつかはかなり洗練されています😬。

🤑XCMでの料金支払い

XCMでの料金支払いは、かなり重要なユースケースです。Polkadotコミュニティのほとんどのパラチェーンは、「トランザクションスパム」やサービス拒否攻撃にさらされないように、対話者が実行したい操作に対して料金を支払う必要があります。これに対する例外は、チェーンが対話者が適切に動作すると信じる十分な理由がある場合に存在します。これは、PolkadotリレーチェーンがPolkadotStatemintの共通善チェーンに対応する場合です。ただし、一般的なケースでは、料金はXCMメッセージとそのトランスポートプロトコルを使いすぎないようにするための良い方法です。XCMメッセージがPolkadotに到着したときに料金を支払う方法を見てみましょう。

すでに述べたように、XCMには、第一級市民としての料金と料金支払いの概念は含まれていません。たとえば、イーサリアムトランザクションモデルとは異なり、料金支払いは、著しく回避する必要があります。ゼロコストの抽象化を備えたRustのように、XCMでは料金の支払いに大きな設計オーバーヘッドはありません。

ただし、いくらかの料金の支払いが必要なシステムの場合、XCMはアセット付きの実行リソースを購入する機能を提供します。これを行うことは、大まかに言えば、次の3つの部分で構成されます。

  • まず、いくつかの資産を提供する必要があります。
  • 第二に、資産の計算時間(または基板用語では重み)の交換について交渉する必要があります。
  • 最後に、XCM操作が指示どおりに実行されます。

最初の部分は、アセットを提供する多数のXCM命令の1つによって管理されます。これらの1つ()はすでに知っていWithdrawAssetますが、後で見る他にもいくつかあります。保有レジスターの結果として生じる資産は、もちろん、XCMの実行に関連する料金の支払いに使用されます。手数料の支払いに使用されなかった資産は、一部の宛先アカウントに預け入れます。この例では、XCMがPolkadotリレーチェーンで発生しており、1 DOT(10,000,000,000の分割できないユニット)で発生していると想定します。

これまでのところ、XCM命令は次のようになっています。

WithdrawAsset((Here, 10_000_000_000).into()),

これにより、2番目の部分に進み、これらの資産(の一部)をXCMの支払いのための計算時間と交換します。このために、XCM命令がありBuyExecutionます。それを見てみましょう:

enum Instruction {
    /* snip */
    BuyExecution {
        fees: MultiAsset,
        weight: u64,
    },
}

最初の項目は、保有登録簿から取得し、手数料の支払いに使用するfees必要がある金額です。未使用の残高はすぐに返されるため、技術的には最大値です。

最終的に費やされる金額は、通訳システムによって決定されます—feesそれを制限するだけであり、通訳システムが希望する実行に対してより多く支払う必要がある場合、BuyExecution命令はエラーになります。2番目の項目は、購入する実行時間を指定します。これは通常、XCMプログラムの合計の重み以上である必要があります。

この例では、すべてのXCM命令に100万の重みがかかると想定します。つまり、これまでの2つの項目(WithdrawAssetおよびBuyExecution)では200万、次の項目ではさらに1つになります。これらの料金を支払う必要があるすべてのDOTを使用します(これは、宛先チェーンにクレイジーな料金がないことを信頼する場合にのみ良いアイデアです。私たちはそうすると仮定します)。これまでのXCMを見てみましょう。

WithdrawAsset((Here, 10_000_000_000).into()),
BuyExecution {
    fees: (Here, 10_000_000_000).into(),
    weight: 3_000_000,
},

XCMの3番目の部分は、保有登録簿に残っている資金を預けることです。このために、DepositAsset命令を使用します。保有レジスターにどれだけ残っているかは実際にはわかりませんが、預ける必要のある資産にワイルドカードを指定できるため、それは問題ではありません。それらをStatemintのソブリンアカウント(として識別されます)に配置しますParachain(1000)

したがって、最終的なXCM命令は次のようになります。

WithdrawAsset((Here, 10_000_000_000).into()),
BuyExecution {
    fees: (Here, 10_000_000_000).into(),
    weight: 3_000_000,
},
DepositAsset {
    assets: All.into(),
    max_assets: 1,
    beneficiary: Parachain(1000).into(),
},

⛓XCMのチェーン間でアセットを移動する

アセットを別のチェーンに送信することは、おそらくチェーン間メッセージングの最も一般的なユースケースです。あるチェーンが別のチェーンのネイティブ資産を管理できるようにすることで、あらゆる種類のデリバティブのユースケース(しゃれは意図されていません)が可能になります。最も単純なのは分散型取引所ですが、一般に分散型ファイナンスまたはDeFiとしてグループ化されます。

一般的に、アセットがチェーン間を移動する方法は2つあり、これはチェーンが互いのセキュリティとロジックを信頼するかどうかによって異なります。

✨テレポート

相互に信頼するチェーン(同じ全体的なコンセンサスとセキュリティの傘の下にある同種のシャード)の場合、Polkadotがテレポートと呼ぶフレームワークを使用できます。これは基本的に、送信側でアセットを破棄し、受信側でアセットを作成することを意味します。これはシンプルで効率的です。2つのチェーンの調整のみが必要であり、どちらかの側で1つのアクションのみが必要です。残念ながら、受信チェーンが送信チェーンを100%信頼して、ミントしているアセットを実際に破棄できない場合(実際、アセットの合意されたルールの範囲外でアセットをミントしない場合)、送信チェーンには実際にミントする根拠がありません。メッセージの裏にあるアセット。

1DOTをPolkadotリレーチェーンからStatemintのソブリンアカウントにテレポートしたXCMがどのように見えるかを見てみましょう。料金はすでにPolkadot側で支払われていると想定します。

WithdrawAsset((Here, 10_000_000_000).into()),
InitiateTeleport {
    assets: All.into(),
    dest: Parachain(1000).into(),
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: 3_000_000,
        },
        DepositAsset {
            assets: All.into(),
            max_assets: 1,
            beneficiary: Parent.into(),
        },
    ]),
}

ご覧のとおり、これは最後に見たストレートの引き出し-購入-預金パターンにかなり似ています。違いはInitiateTeleport、最後の2つの命令(BuyExecutionおよびDepositAsset)の周りに挿入される命令です。舞台裏では、送信者(Polkadotリレー)チェーンは、InitiateTeleport命令を実行するときにまったく新しいメッセージを作成しています。フィールドを取得しxcmて新しいXCM内に配置し、ReceiveTeleportedAssetこのXCMをレシーバー(Statemint)チェーンに送信します。Statemintは、メッセージを送信する前に、Polkadotリレーチェーンがその側の1DOTを破壊したことを信頼しています。(します!)

beneficiaryはと記載されており、賢明な読者は、これParent.into()がPolkadotリレーチェーンのコンテキストで何を指しているのか疑問に思うかもしれません。答えは「何もない」ですが、ここでは間違いはありません。パラメータ内のすべてxcmは受信側の観点から書き込まれるため、これはPolkadotリレーチェーンに供給されるXCM全体の一部であるにもかかわらず、実際にはStatemintでのみ実行されます。したがって、Statemintのコンテキストでは次のようになります。書いた。

Statemintが最終的にメッセージを受け取ると、次のようになります。

ReceiveTeleportedAsset((Parent, 10_000_000_000).into()),
BuyExecution {
    fees: (Parent, 10_000_000_000).into(),
    weight: 3_000_000,
},
DepositAsset {
    assets: All.into(),
    max_assets: 1,
    beneficiary: Parent.into(),
},

WithdrawAssetこれは以前のXCMとかなり似ていることに気付くかもしれません。唯一の大きな違いは、ローカルアカウントからの引き出しを通じて手数料と預金に資金を提供するのではなく、送信側(Polkadotリレーチェーン)でDOTが忠実に破壊されたことを信頼し、ReceiveTeleportedAssetメッセージを尊重することで、「魔法のように」存在することです。 。

特に、Polkadotリレーチェーンで送信した1 DOTのアセット識別子(Hereリレーチェーン自体をDOTのネイティブホームと呼びます)は、Statemint:Parent.into()での表現に自動的に変更されました。これは、Statemintのコンテキストからのリレーチェーンの場所です。

beneficiaryPolkadotリレーチェーンとしても指定されているため、そのソブリンアカウント(Statemint上)には、新しく作成された1DOTから手数料を差し引いた金額が入金されます。XCMは、アカウントまたは他の場所に簡単に名前を付けた可能性がありますbeneficiary。そのままでは、後でTransferAssetリレーチェーンから送信されたものを使用して、この1DOTを移動できます。

🏦リザーブ

チェーン間でアセットを転送する別の方法は、少し複雑です。リザーブと呼ばれるサードパーティが使用されます。この名前はリザーブバンキングに由来します。リザーブバンキングでは、発行された約束が価値があるという考えに信頼性を与えるために資産が「予備」に保持されます。たとえば、独立したパラチェーンで発行された「派生」DOTごとに1つの「実際の」(たとえば、ステートミントまたはリレーチェーン)DOTが引き換え可能であると合理的に信じることができる場合、パラチェーンのDOTを実際のDOTと経済的に同等であると見なすことができます。 (ほとんどの銀行は、部分準備銀行と呼ばれることを行っています。これは、準備金の額面よりも少ない金額を維持することを意味します)。これは、あまりにも多くの人が償還を希望するまでは問題なく機能します。そうすると、すべてが非常に速く失敗する可能性があります。

したがって、予備は「実際の」資産を保管する場所であり、転送の目的で、そのロジックとセキュリティは送信者と受信者の両方から信頼されています。その場合、送信者側と受信者側の対応する資産はデリバティブになりますが、それらは「実際の」準備資産で100%裏付けられます。パラチェーンが適切に動作したと仮定すると(つまり、バグがなく、そのガバナンスが予備金で逃げることを決定しなかった)、これにより、デリバティブDOTは基礎となる予備金DOTとほぼ同じ値になります。予備資産は、予備チェーンの送信者/受信者のソブリンアカウント(つまり、送信者または受信者チェーンによって制御可能なアカウント)に保持されているため、パラチェーンに問題が発生しない限り、十分に保護されるのには十分な理由があります。

転送メカニズムに戻ると、送信者は、送信者が所有する(そして同じ資産の独自のバージョンの準備金として使用する)資産を、送信者ではなく、受信者のソブリンアカウントに移動するように準備金に指示します。—受信者に新しいクレジットについて通知します。これは、送信者と受信者が互いのロジックやセキュリティを信頼する必要はなく、予備として使用されるチェーンのロジックのみを信頼する必要があることを意味します。ただし、これは3つの側面が調整する必要があることを意味し、全体的なコスト、時間、および複雑さが増します。

必要なXCMを見てみましょう。今回は、パラチェーン2000からパラチェーン2001に1 DOTを送信します。これは、パラチェーン1000でリザーブバックDOTを使用します。ここでも、料金は送信者側ですでに支払われていると想定します。

WithdrawAsset((Parent, 10_000_000_000).into()),
InitiateReserveWithdraw {
    assets: All.into(),
    dest: ParentThen(Parachain(1000)).into(),
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: 3_000_000,
        },
        DepositReserveAsset {
            assets: All.into(),
            max_assets: 1,
            dest: ParentThen(Parachain(2001)).into(),
            xcm: Xcm(vec![
                BuyExecution {
                    fees: (Parent, 10_000_000_000).into(),
                    weight: 3_000_000,
                },
                DepositAsset {
                    assets: All.into(),
                    max_assets: 1,
                    beneficiary: ParentThen(Parachain(2000)).into(),
                },
            ]),
        },
    ]),
},

約束通り、これはもう少し複雑です。それを見ていきましょう。外側の部分は、送信側(parachain 2000)で1 DOTを抽出し、Statemint(parachain 1000)で保持されている対応する1 DOTを引き出すことを扱いますInitiateReserveWithdraw。これはこの目的に使用され、かなり自明です。

WithdrawAsset((Parent, 10_000_000_000).into()),
InitiateReserveWithdraw {
    assets: All.into(),
    dest: ParentThen(Parachain(1000)).into(),
    xcm: /* snip */
}

これで、StatemintのHoldingRegisterに1つのDOTがあります。他のことを行う前に、Statemintで実行時間を購入する必要があります。これも、かなりおなじみのようです。

/*snip*/
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: 3_000_000,
        },
        DepositReserveAsset {
            assets: All.into(),
            max_assets: 1,
            dest: ParentThen(Parachain(2001)).into(),
            xcm: /* snip */
        },
    ]),
/*snip*/

1 DOTを使用して料金を支払い、XCM操作ごとに100万を想定しています。その1つの操作の支払いで、1 DOT(マイナスの手数料。怠惰なので使用するだけですAll.into())をパラチェーン2001のソブリンアカウントに預け入れますが、予備資産として預け入れます。つまり、Statemintに結果として生じる派生資産に対して実行されるいくつかの命令とともに、転送を通知する受信チェーンへの通知XCM。DepositReserveAsset指示は必ずしも意味をなさない。それdestが理にかなっているためには、リザーブチェーンに合理的に資金を保持できる場所である必要がありますが、リザーブチェーンがXCMを送信できる場所でもある必要があります。兄弟のパラチェーンはたまたま法案に完全に適合しています。

/*snip*/
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: 3_000_000,
        },
        DepositAsset {
            assets: All.into(),
            max_assets: 1,
            beneficiary: ParentThen(Parachain(2000)).into(),
        },
    ]),
/*snip*/

最後の部分は、パラチェーン2001に到着するメッセージの一部を定義します。テレポート操作を開始する場合と同様にDepositReserveAsset、この場合は新しいメッセージを作成して送信しますReserveAssetDeposited。受信側のパラチェーンに到達するのは、私たちが定義したXCMプログラムが含まれているにもかかわらず、このメッセージです。次のようになります。

ReserveAssetDeposited((Parent, 10_000_000_000).into()),
BuyExecution {
    fees: (Parent, 10_000_000_000).into(),
    weight: 3_000_000,
},
DepositAsset {
    assets: All.into(),
    max_assets: 1,
    beneficiary: ParentThen(Parachain(2000)).into(),
},

(これは、Statemintで実際に料金が発生せず、1 DOT全体がそれを超えたことを前提としています。これは特に現実的ではないため、assets回線の数はおそらく少なくなります。)

メッセージのほとんどはかなり見覚えがあるはずです。前のセクションで見たメッセージとの唯一の重要な違いReceiveTeleportedAssetは、トップレベルの命令ReserveAssetDepositedです。これは、「送信チェーンがアセットを焼き付けて同等のアセットを作成できるようにする」という意味ではなく、「送信チェーンが送信チェーンを意味する」という意味で、同様の目的を果たします。資産を受け取り、それらを予備として保持しているので、完全に裏付けされた派生物を作成できます。」いずれにせよ、宛先チェーンはそれらをホールディングレジスターにミントし、受信チェーンの送信者のソブリンアカウントにそれらを預け入れます。🎉

🏁結論

この記事は以上です。XCMとは何か、およびXCMがどのように機能するように設計されているかの基本を説明するのに役立つことを願っています。次の記事では、XCVMのアーキテクチャ、その実行モデルとエラー処理、XCMのバージョン管理システム、および相互依存するエコシステムでフォーマットへのアップグレードを管理する方法について詳しく説明します。そのクエリ応答システムとして、そしてXCMがSubstrateでどのように機能するか。また、XCMの将来の方向性、計画されている機能、およびXCMを進化させるためのプロセスについても説明します。\

ソース:https ://polkadot.network/blog/xcm-the-cross-consensus-message-format/

 パートII:バージョン管理と互換性
 パートIII:実行とエラー管理

#xcm  #polkadot 

XCM III:実行とエラー管理

XCMについて書いた最初の2つの記事(パートIパートII )では、その設計とバージョン管理の構造の基本を紹介しました。この記事では、その基礎となる設計と実行モデルについて詳しく見ていきます。XCMは、非常に高レベルの仮想マシンであるXCVMの命令セットに基づいているため、このマシンアーキテクチャに精通することになります。

XCVMは、非常に高レベルの非チューリング完全仮想マシンです。これは(スタックベースではなく)レジスタベースであり、いくつかの専用レジスタがあり、そのほとんどは高度に構造化されたデータを保持します。汎用プロセッサとは異なり、XCVMのレジスタは任意の値に自由に設定できませんが、それらがどのように変更されるかを制御する厳密なメカニズムがあります。ローカルチェーン状態と相互作用する特定の手段(WithdrawAssetおよびDepositAssetすでに見た命令など)を超えて、追加の「メモリ」はありません。ループの可能性はなく、明示的な分岐命令もありません。

すでに2つのレジスターを紹介しました。1つ以上の資産を一時的に保持でき、ローカルチェーンから資産を引き出すか、信頼できる外部から資産を受け取ることで移入できる保持レジスターです。ソース(例:別のチェーン); Origin Registerは、実行の開始時に、現在のXCM実行が開始されたコンセンサスシステムの場所を保持し、内部の場所に変更するか、完全にクリアすることしかできません。

他のレジスタのうち、3つは例外/エラー管理に関係し、2つは実行の重みの追跡に関係します。この記事では、それらすべてについて学習します。

🎬実行モデル

すでに述べたように、同じ命令を複数回再実行できるようにする明示的な条件付き命令やループプリミティブはありません。これにより、プログラムの制御フローを事前に決定することはかなり簡単になります。このプロパティは、実行ポイントの前にXCMメッセージが利用できる実行時間(Substrate / Polkadot全体の重みとして知られている)を決定する場合に役立ちます。

XCMの実行を期待するほとんどのコンセンサスプラットフォームは、実行開始前に最悪の場合の実行時間を決定できる必要があります。これは、ブロックチェーンが通常、システム全体が停止しないように、個々のブロックの処理に所定の制限よりも時間がかからないようにする必要があるためです。さらに、システムで料金の支払いが必要な場合は、支払いが行われるワークロードの前に必ず行う必要があり、この支払いが最悪の実行時間をカバーすることが重要です。

チューリング完全言語を可能にするシステム(イーサリアムなど)は、このチューリング完全性のために、プログラムから最悪の場合の実行時間を実際に計算することはできません。彼らは、ユーザーにプログラムの実行リソースを事前に決定するように要求し、実行時にそれを計測し、支払われた金額を超えた場合にそれを中断することによってこれを回避します。トランザクションが実行される前に状況が変化し、重みが正しくなくなる場合があります。幸いなことに、チューリング完全ではないXCVMなどの仮想マシンは、この計測と重量処方の必要性を回避できます。

🏋️‍♀️体重

重みは通常、代表的なハードウェアが特定の操作を実行するのにかかるピコ秒の整数として表されます。命令で見たBuyExecutionように、XCVMには、特定の命令を処理するときに実行時間/重みのこの概念が含まれています。

重量の計測はありませんが、XCVMプログラムが最終的に最悪の場合の重量予測よりも少なくなる可能性を考慮して、余剰重量レジスタと呼ばれるレジスタがあります。使用する重量を正確に予測できるため、ほとんどの手順はそれに触れていません。ただし、最悪の場合の重み予測が過大評価され、実行時にのみどの程度かがわかる状況がときどきあります。XCMメッセージの重みを過大評価してブロック実行時間を考慮しながら、元の重みが過大評価されている量を追跡し、それをアカウントから差し引くことで、チェーンはブロック実行時間の割り当てを最適化できます。

したがって、Surplus Weight Registerは、ブロック実行時間のアカウンティングには役立ちますが、支払われる金額が過大評価されないようにするという他の問題を解決するだけではありません。このためには、に付随する指示が必要ですBuyExecution。これは、余分な重量を取り、それを返金します。当然、この命令は存在し、と呼ばれRefundSurplusます。返金された重量レジスターと呼ばれる2番目のレジスターがあり、同じ余剰重量が複数回返金されないようにします。

😱フロー制御と例外

これまでのところ、XCVMの処理にはさらに2つのレジスタが暗黙的に含まれていますが、それでも知っておくことが重要です。まず、現在実行中のXCVMプログラムを格納するプログラムレジスタがあります。次に、現在実行中の命令インデックスを格納するプログラムカウンターがあります。これは、プログラムレジスタが変更されるとゼロにリセットされ、正常に実行されたすべての命令の最後に1ずつ増加します。

「例外的な」状況の可能性を処理する能力は、堅牢なコードを作成する上で非常に重要です。リモートシステムで予期していなかった(または実際に予測できなかった)何かが発生した場合、それを管理する何らかの方法が必要です。たとえそれが単に同じくらい多くのことを述べたレポートをオリジンに送り返すことであっても。

XCVM命令セットには、明示的な汎用分岐命令は含まれていませんが、実行モデルに組み込まれた一般的な例外処理フレームワークがあります。XCVMにはさらに2つのコードレジスタが含まれており、それぞれがプログラムレジスタのようなXCVMプログラムを保持しています。これらの2つのレジスタは、付録レジスタおよびエラーハンドラレジスタと呼ばれます。いくつかの一般的な言語でのtry/catch / finally例外システムに精通している場合、従うべきことは非常に思い出させるかもしれません。

前述のように、XCVMプログラムの実行は、その中の各命令に段階的に続きます。プログラムの最後までこれらの指示に従うと、2つのいずれかが発生します。プログラムの最後に正常に到達するか、エラーが発生します。実行が成功した最初のケ​​ースでは、エラーレジスタがクリアされ、その重みが余剰重みレジスタに追加されます。付録レジスタもクリアされ、その内容がプログラムレジスタに配置されます。プログラムレジスタが空のままの場合、停止します。それ以外の場合、プログラムカウンターはゼロにリセットされます。簡単に言うと、現在のプログラムエラーハンドラを破棄し、付録プログラムがある場合は実行を開始します。

この機能は、それ自体ではそれほど有用ではありませんが、エラーが発生した場合に何が起こるかと組み合わせると役立つ可能性があります。ここで、まだ実行されていない命令の重みが余剰重みレジスタに追加されます。エラーハンドラレジスタがクリアされ、その内容がプログラムレジスタに配置され、プログラムカウンタがゼロにリセットされます。簡単に言うと、現在のプログラムを破棄して、エラーハンドラの実行を開始します。付録レジスタをクリアしないため、エラーハンドラによってリセットされない限り、正常に終了すると実行されます。

その構成構造により、エラーハンドラーの任意の「ネスト」が可能になります。エラーハンドラーには、必要に応じてエラーハンドラーを含めることも、付録に独自の付録を含めることもできます。

これらのレジスタを操作できるようにする2つの命令があります:SetAppendixSetErrorHandler。ご想像のとおり、そのうちの1つは付録レジスタを設定し、もう1つはエラーハンドラレジスタを設定します。これらのそれぞれの予測される重みは、パラメーターの重みよりもわずかに大きくなります。ただし、実行されると、置き換えられるレジスタ内のXCMメッセージの重みが余剰重みレジスタに追加され、未使用の付録またはエラーハンドラの重みを再利用できるようになります。

☄️スローエラー

エラーが発生することを実際に確認し、そのエラーの一部の側面をカスタマイズすると便利な場合があります。これはテストコードの作成中に使用されましたが、最終的にライブチェーン内で使用される可能性があることは不可能ではありません。これは、XCVMで、Trap常にエラーが発生する命令を介して実行できます。スローされるエラータイプは、名前を共有しますTrap。命令とエラーの両方に整数の引数があり、エラースローアーと外部の見物人の間で何らかの形式の情報を渡すことができます。

簡単な例を次に示します。

WithdrawAsset((Here, 10_000_000_000).into()),
BuyExecution {
    fees: (Here, 10_000_000_000).into(),
    weight: Unlimited,
},
SetErrorHandler(Xcm(vec![
    RefundSurplus,
    DepositAsset {
        assets: All.into(),
        max_assets: 1,
        beneficiary: Parachain(2000).into(),
    },
])),
Trap(0),
DepositAsset {
    assets: All.into(),
    max_assets: 1,
    beneficiary: Parachain(3000).into(),
},

これTrapにより、ファイナルDepositAssetがスキップされ、代わりにエラーハンドラーDepositAssetが実行され、1 DOT(実行コストを差し引いたもの)がパラチェーン2000の所有下に置かれます。RefundSurplusエラーハンドラーコードの先頭で使用する傾向があります。実行すると、使用される予測重量(したがって購入重量)が過大評価される可能性が高いことがわかります。

🗞エラー報告

エラーを処理するコードを導入できることは非常に便利ですが、よく要求される機能の1つは、XCMメッセージの結果を元の送信者に報告できるようにすることです。QueryResponseあるコンセンサスシステムがいくつかの情報を別のコンセンサスシステムに報告できるようにする前の記事の指示に適合しました。残っているのは、XCMの結果をこれに挿入し、通知QueryResponseを期待している人に送信できるようにすることだけです。結果。

.という名前の命令を実行する命令が1つだけあることがわかりReportErrorます。これは、まだ遭遇していないレジスターであるエラーレジスターを使用して機能します。エラーレジスタはオプションタイプです(設定またはクリアのいずれかです)。設定されている場合は、数値インデックスとXCMエラータイプの2つの情報を保持します。

それは操作の非常に単純なメカニズムを持っています。まず、命令がエラーになるたびに常に設定されます。エラータイプはそのエラーのタイプに設定され、数値インデックスはプログラムカウンタレジスタの値に設定されます。ClearError第二に、命令が実行されたときにのみクリアされます。この命令は間違いのない命令の1つであり、それ自体がエラーになることは決して許されません。これですべてです。エラーが発生すると設定され、適切な命令を発行するとクリアされます。

ReportErrorこれで、命令がどのように機能するかを理解できるようにQueryResponseなります。エラーレジスタの内容を使用して命令を作成し、特定の宛先に送信するだけです。もちろん、実行が最初にエラーハンドラレジスタのコードにジャンプし、次に付録レジスタのコードにジャンプするときに命令がスキップされる前に発生するエラー。ただし、これに対する解決策は簡単です。ReportError付録に配置すると、メインコードで実行エラーが発生したかどうかに関係なく、確実に実行されます。

簡単な例を見てみましょう。アセット(1 DOT)をリレーチェーンからStatemint(parachain 1000)にテレポートし、そこで実行時間を購入してから、Statemintを予備として使用して、アセットをparachain 2000にデポジットします。元の(エラー報告なし) )メッセージは次のようになります。

WithdrawAsset((Here, 10_000_000_000).into()),
InitiateTeleport {
    assets: All.into(),
    dest: Parachain(1000).into(),
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: Unlimited,
        },
        DepositReserveAsset {
            assets: All.into(),
            max_assets: 1,
            dest: ParentThen(Parachain(2000)).into(),
            xcm: Xcm(vec![
                BuyExecution {
                    fees: (Parent, 10_000_000_000).into(),
                    weight: Unlimited,
                },
                DepositAsset {
                    assets: All.into(),
                    max_assets: 1,
                    beneficiary: Parent.into(),
                },
            ]),
        },
    ]),
}

基本的なエラー報告では、代わりにこれを使用します。

WithdrawAsset((Here, 10_000_000_000).into()),
InitiateTeleport {
    assets: All.into(),
    dest: Parachain(1000).into(),
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: Unlimited,
        },
        SetAppendix(Xcm(vec![
            ReportError {
                query_id: 42,
                dest: Parent.into(),
                max_response_weight: 10_000_000,
            },
        ])),
        DepositReserveAsset {
            assets: All.into(),
            max_assets: 1,
            dest: ParentThen(Parachain(2000)).into(),
            xcm: Xcm(vec![
                BuyExecution {
                    fees: (Parent, 10_000_000_000).into(),
                    weight: Unlimited,
                },
                SetAppendix(Xcm(vec![
                    ReportError {
                        query_id: 42,
                        dest: Parent.into(),
                        max_response_weight: 10_000_000,
                    },
                ])),
                DepositAsset {
                    assets: All.into(),
                    max_assets: 1,
                    beneficiary: ParentThen(Parachain(2000)).into(),
                },
            ]),
        },
    ]),
}

ご覧のとおり、唯一の変更点は、SetAppendixStatemintとparachain2000の両方でエラーまたはエラーがリレーチェーンに報告されるようにする2つの命令の導入です。これは、Relay ChainがQueryResponse、Statemintおよびparachain 2000から発信された、クエリID42および1,000万の重み制限を持つメッセージを認識および処理できるように設定されていることを前提としています。幸いなことに、これは確かにSubstrateが十分にサポートしているものですが、現時点では範囲外です。

🪤アセットトラップ

アセットを処理するプログラム中にエラーが発生した場合(ほとんどの場合、実行の料金を支払う必要があるためBuyExecution)、非常に問題になる可能性があります。BuyExecution重量制限が間違っていたり、支払いに使用した資産が不足しているなどの理由で、指示自体がエラーになる場合があります。あるいは、アセットがチェーンに送られ、それを便利な方法で処理できない場合もあります。これらの場合、他の多くの場合、メッセージのXCVMの実行は、他のレジスタと同様に一時的なものであり、忘れられると予想される資産が保持レジスタに残った状態で終了します。

チームとそのユーザーは、SubstrateのXCMにより、チェーンがこの損失を完全に回避できることを知って喜ぶでしょう🎉。このメカニズムは2つのステップで機能します。まず、保有登録簿がクリアされたときの資産は、完全に忘れられることはありません。XCVMの停止時に保持レジスタが空でない場合は、次の3つの情報を含むイベントが発行されます。保持レジスタの値。オリジンレジスタの元の値。そして、これら2つの情報のハッシュ。次に、SubstrateのXCMシステムは、このハッシュをストレージに配置します。メカニズムのこの部分は、アセットトラップと呼ばれます。

🎟クレームシステム

このメカニズムの2番目のステップは、HoldingRegisterの以前の内容を要求できるようにすることです。これは実際には、この目的のために特別に設計されたものではなく、まだ会っていない汎用の命令によって発生しClaimAssetます。Rustでの宣言方法は次のとおりです。

pub enum Instruction {
    /* snip */
    ClaimAsset { assets: MultiAssets, ticket: MultiLocation },
    /* snip */
}

この命令の名前は、やなど、私たちが出会った他の特定の「資金調達」命令を彷彿とさせるように見えるかもしれませWithdrawAssetReceiveTeleportedAsset。もしそうなら、それはかなり正当な理由です:そうです。他の人と同様に、資産(assetsここでの議論によって与えられた)を保有登録簿に入れようとします。WithdrawAssetたとえば、アカウントのチェーン上の資産残高を減らす場合とは異なり、オリジンレジスタの値が何であれ、利用可能なこれらClaimAssetの有効な請求を探します。assetsシステムが有効な主張を見つけるのを助けるために、情報はticket引数を介して提供されるかもしれません。有効な請求が見つかった場合、その請求はチェーンから削除され、資産は保有登録簿に追加されます。

さて、クレームを構成するものは、完全にチェーン自体次第です。チェーンが異なれば、さまざまな種類のクレームがサポートされる可能性があり、Substrateを使用すると、それらを簡単に作成できます。しかし、ご想像のとおり、当然のことながら、準備が整った特定の種類のクレームは、以前に削除されたホールディングレジスターのコンテンツのクレームです。

それでは、これが実際にどのように機能するかを見てみましょう。ユーザーのパラチェーン2000がStatemintにメッセージを送信し、そのメッセージでソブリンアカウントから0.01 DOTを引き出して手数料を支払い、さらに、ステートミント。次のようになります。

WithdrawAsset((Parent, 100_000_000).into()),
BuyExecution {
    fees: (Parent, 100_000_000).into(),
    weight: Unlimited,
},
SetAppendix(Xcm(vec![
    ReportError {
        query_id: 42,
        dest: ParentThen(Parachain(2000)).into(),
        max_response_weight: 10_000_000,
    },
    RefundSurplus,
])),
ReserveAssetDeposited((ParentThen(Parachain(2000)), 100).into()),
DepositAsset {
    assets: All.into(),
    max_assets: 2,
    beneficiary: ParentThen(Parachain(2000)).into(),
}

0.01 DOTでこれに十分な料金があり、Statemintがparachain 2000のネイティブ資産のオンチェーン預金をサポートしていると仮定すると(また、parachain 2000を予備として使用する場合)、これは問題なく機能するはずです。ただし、おそらくStatemintは、parachain2000のネイティブアセットを認識するように設定されていません。この場合、DepositAssetはアセットをどう処理するかわからないため、エラーをスローします。この失敗をパラチェーン2000に通知する付録を実行した後、100ユニットのパラチェーン2000のネイティブ資産と、場合によっては保有レジスタにいくつかのDOTが残ります。手数料が0.005DOTにすぎず、0.005DOTが残っていると仮定します。

次に、StatemintのXCMパレットによって、これらの新しく請求可能なアセットに対して次のようなイベントが記録されます。

Event::AssetsTrapped(
    /* snipped hash */,
    ParentThen(Parachain(2000)),
    vec![
        (Parent, 50_000_000).into(),
        (ParentThen(Parachain(2000)), 100),
    ].into(),
)

次のようなメッセージがparachain2000に返送されます。

QueryResponse {
    query_id: 42,
    response: ExecutionResult(Err((4, AssetNotFound))),
    max_weight: 10_000_000,
}

Parachain 2000は、後の段階で(おそらく、Statemintがネイティブ資産の預金を受け入れることができると判断した後)、かなり単純な方法でそれらの100ユニットを回収できるようになります。

ClaimAsset {
    assets: vec![
        (Parent, 50_000_000).into(),
        (ParentThen(Parachain(2000)), 100),
    ].into(),
    ticket: Here,
}
BuyExecution {
    fees: (Parent, 50_000_000).into(),
    weight: Unlimited,
},
DepositAsset {
    assets: All.into(),
    max_assets: 2,
    beneficiary: ParentThen(Parachain(2000)).into(),
}

この場合、クレームの特定に役立つ特別な情報はチケット引数を通じて提供されません。これは通常、アセットトラップの申し立てには問題ありませんが、他の種類の申し立てには使用する必要がある場合があります。

🏁結論

これで今のところは終わりです。これが、XCMの基盤となる仮想マシンについて、また予期しない状況の管理と回復にどのように役立つかを理解するのに役立つことを願っています。このシリーズの次の記事では、XCMの今後の方向性と、フォーマットの改善を提案する方法、およびSubstrateのXCM Rust実装をさらに深く掘り下げ、XCMを簡単に解釈する機能をチェーンに提供する方法について説明します。 。 

ソース:https ://polkadot.network/blog/xcm-part-three-execution-and-error-management/

#xcm  #polkadot 

XCM II:バージョン管理と互換性

XCMについて書いた最初の記事では、XCMの基本的なアーキテクチャ、目標、およびいくつかの単純なユースケースでの使用方法を紹介しました。ここでは、XCMの興味深い側面の1つを詳しく調べます。それは、接続する予定のネットワーク間で破損を引き起こすことなく、XCMが時間の経過とともにどのように変化するかです。

共通の言語を持つことは、人間の相互作用に関する非常に多くの問題を解決します。これにより、共同作業、競合の解決、および後で使用するための情報の記録が可能になります。しかし、言語はそれが表現できる概念と同じくらい有用であり、絶えず変化する世界では、言語はその概念のレパートリーを変更して適応させる必要があります。そうしないと、使用されなくなるリスクがあります。

残念ながら、言語を急激に変更すると、その主な目的が損なわれます。つまり、人々の間のコミュニケーションが容易になります。言語は変更する必要があるため、初心者が新しいフォームを理解できないようにすることなく、これらの変更を管理する方法が必要です。この点で非常に有用な発明の1つは、言語の概念パレットを一度に文書化してアーカイブし、将来の世代が歴史的なテキストをよりよく理解できるようにするための辞書でした。辞書のエディションは、言語の形式化された「バージョン」と考えることができます。

時代は変わるかもしれませんが、問題は不気味によく知られています。前回の記事で説明したように、XCMは非常に特殊な言語ですが、言語に他なりません。これはコンセンサスシステムが相互に通信するための手段であり、このXCMのニーズは暗号業界、特にPolkadotエコシステムの驚異的な速度で進化するため、これらの変更が妥協しないようにするための何らかの手段が必要です。 XCMの本来の目標は相互運用性です。コンセンサス空間だけでなく、コンセンサス時間でも相互運用性を解決する必要があります。

🔮バージョニング

XCMの言語は、使用中に時間の経過とともに変化すると予想されるため、非常に簡単な予防策の1つは、実際のメッセージコンテンツの前に通信しているXCMのバージョンを確実に特定することです。これを行うには、いくつかのバージョンラッパータイプを使用します。これは、XCMメッセージまたはそのコンポーネントをバージョンでラップするためにこのように名付けられました。Rustコードでは、これは非常に単純に見えます。

pub enum VersionedXcm {
    V0(v0::Xcm),
    V1(v1::Xcm),
    V2(v2::Xcm),
}

「ネットワーク経由」(またはコンセンサスシステム間)で送信される場合、XCMは常にこのバージョン管理されたコンテナーに配置されます。これにより、メッセージを解釈するには古すぎるシステムが安全に受信し、メッセージの形式がサポートされていないことを認識できるようになります。また、新しいシステムが古いメッセージを認識し、それに応じて解釈できるようにします。

XCMメッセージだけがバージョン管理されているわけではありません。XCMコードベースでは、バージョン、、、MultiLocationおよびMultiAssetそれに関連するタイプもあります。これは、チェーンのXCMロジックがアップグレードされたときに、それらを保存して後で解釈する必要がある場合があるためです。バージョン管理を行わないと、古いものを新しいものとして解釈しようMultiLocationとして、理解できない(さらに悪いことに、理解できるが元の意味とは異なる)ことがわかる場合があります。

💬互換性と翻訳

バージョン管理は最初のステップであり、使用されている言語のエディションを確実に識別できるようにします。それは、私たちがそれを解釈できることを保証するものではなく、私たちが優先的に使用するのと同じ版であることを保証するものでもありません。ここで互換性が生まれます。「互換性」とは、推奨バージョンではないXCMのバージョンで自分自身を解釈および表現し続ける能力を意味します。

選択したスケジュールでネットワークとそのバージョンのXCMをアップグレードできると予想される場合、まだアップグレードされていない、または実際にはすでにアップグレードされている他のネットワークと通信したい場合があるため、この互換性はかなり重要になります。 。これは、下位互換性上位互換性に分類できます。基本的に、下位互換性とは、アップグレードされたシステムがレガシーの世界で機能し続ける能力であり、上位の互換性とは、レガシーシステムがアップグレードされた世界で機能し続ける能力です。

私たちの場合、両方が必要ですが、実際的な制限があります。新しいバージョンのXCMが以前のバージョンには存在しなかった機能を提供する場合、古いシステムがこれらのメッセージを解釈できると期待するのは非現実的です。それは、「ソーシャルメディア」という用語をラテン語に翻訳し、それがジュリアスシーザーによって額面通りに理解されることを期待するのと少し似ています。一部の概念は、従来のコンテキストでは表現できません。

同様に、XCMに大幅な変更を加えると、その概念モデルから機能が削除される可能性があります。これはそれほど頻繁には発生しませんが、特定の古語を現代の同等の用語に変換する問題に似ています。興味深いことに、「ドット」の古語的な意味は、ここでの例かもしれません(これは、かなり特定の形式の基金を意味していました)。

したがって、XCMの新しいバージョンは、古いバージョンと新しいバージョンの両方とほとんど互換性があるように設計されていますが、通常、代替コンテキストでは意味がなく、翻訳できないXCMメッセージがあります。

🗣実用的なコミュニケーション

前述のように、独立して存在するすべてのメッセージにバージョン識別子が含まれていることを確認します。これは、システム間で送信されるメッセージまたはストレージに保持されるメッセージを意味します。ただし、すべてのメッセージ、場所、および資産が含まれているわけではありません。他のデータの一部として存在するデータは、そのバージョンがコンテキストから推測できるため、バージョン管理する必要はありません。

バージョンの識別と互換性/変換は、古いネットワークからメッセージを受信したり、新しいネットワークにメッセージを送信したりするのに役立ちますが、単独で使用すると、逆の場合にはあまり役に立ちません。これは、アップグレードされたネットワークからメッセージを受信するレガシーネットワーク自体には、新しいXCMを解釈可能な形式に変換できるロジックがないためです。むしろ、そのロジックは、変換コードが可能な送信側にのみ存在します。新しいメッセージを従来の用語で再表現します。

したがって、送信するメッセージが受信ネットワークによって解釈できることを保証するのは、送信ネットワークの責任である必要があります。具体的には、メッセージに使用されるXCMのバージョンは、受信ネットワークがサポートするXCMのバージョンよりも新しいものであってはなりません。

このため、PolkadotおよびKusamaリレーチェーン、Statemint、Statemine、Shell、およびSubstrate / FrameとそのXCMエンジンに基づくその他のチェーンはすべて、リモートチェーンでサポートされているXCMバージョンのレジストリを保持しています。XCMメッセージがこれらのチェーンによって送信されるときはいつでも、レジストリを調べて、最初にメッセージを送信するバージョンを決定します。メッセージを送信者と受信者のサポートされているXCMバージョンの古い方に変換します。最新の状態を維持するチェーンの場合、ほとんどの場合、これらは同じ最新リリースのバージョンであり、XCMの全機能セットを利用できるようになります。

このレジストリは通常、ガバナンスプロセスによって指示およびアップグレードされますが、これは、特に潜在的な宛先の数が増えるにつれて、少し面倒で面倒です。このため、バージョン追跡が導入されました。

🤝バージョンネゴシエーション

バージョン追跡は、XCMのバージョン管理ストーリーのパズルの最後のピースです。その機能は、潜在的な宛先チェーンのXCMバージョンを追跡するために必要なオフチェーンまたはガバナンスプロセスを削除することです。代わりに、プロセスは自律的かつオンチェーンで行われます。

基本的に、あるネットワークがXCMを使用して、サポートしている最新バージョンのXCMを別のネットワークに照会し、これが変更されるたびに通知を受けることを許可することで機能します。このクエリからの応答により、問題のネットワークにバージョンレジストリを設定して維持し、メッセージが可能な限り最新のわかりやすいバージョンで送信されるようにします。

具体的には、XCMには次の3つの貴重な指示がありSubscribeVersionます。UnsubscribeVersionそのリクエストをキャンセルする。、QueryResponseレスポンダーネットワークから開始ネットワークに情報を返す一般的な手段。これは、Rustでの外観です。

enum Instruction {
    SubscribeVersion {
        query_id: QueryId,
        max_response_weight: u64,
    },
    UnsubscribeVersion,
    /* snip */
}

したがってSubscribeVersion、2つのパラメータを取ります。1つ目query_idは、タイプQueryIdです。これは、返される応答を識別して区別できるようにするために使用される単純な整数です。応答が送信される結果となるすべてのXCM命令には、応答が認識され、それに応じて処理されることを保証するための同様の手段があります。2番目のパラメーターは呼び出されmax_response_weightWeight応答が返されるときに応答にかかる最大計算時間を示す値(整数)です。以下のようなquery_id、これは、この命令が生成するすべての応答メッセージに配置され、実行前に、重みが予測できない可変の重みコストを少なくとも最大に制限できるようにするために必要です。これがないと、応答メッセージの解釈にかかる時間の上限を取得できず、実行のスケジュールを設定できません。

UnsubscribeVersion主に、特定の場所で一度にアクティブにできるバージョンサブスクリプションは1つだけであるため、指示としてはかなり不毛です。これは、オリジンレジスタの内容以外にそれを識別するためにキャンセルが発生する可能性があることを意味します。

バージョンレジストリとその使用法の図。ここで、チェーンA(XCMバージョン2)はチェーンE(XCMバージョン3)とネゴシエートし、最終的にバージョン2メッセージを送信します。このメッセージは、Eが解釈する前に自動的にバージョン3に変換されます。

👂返信

知っておくべき3番目の命令はですQueryResponse。これは、あるチェーンが別のチェーンに応答できるようにする非常に汎用的な命令であり、そうすることで、いくつかの情報を報告します。ここにそれはRustにあります:

enum Instruction {
    QueryResponse {
        query_id: QueryId,
        response: Response,
        max_weight: u64,
    },
    /* snip */
}

で提供される値から入力されるため、3つのパラメーターのうち2つはすでにわかっていますSubscribeVersion。3つ目は呼び出さresponseれ、私たちが関心を持っている実際の情報が含まれています。これは新しいタイプResponseに配置され、それ自体が、あるネットワークが別のネットワークに通知するために使用する可能性のあるいくつかのタイプの結合です。Rustでは次のようになります。

pub enum Response {
    Null,
    Assets(MultiAssets),
    ExecutionResult(Result<(), (u32, XcmError)>),
    Version(XcmVersion),
}

現在の目的では、Versionアイテムのみが必要ですが、今後の記事で説明するように、他のアイテムは他のコンテキストに役立ちます。

⏱実行時間

QueryResponse一般的に、(有効であると仮定して)実行時間を購入するための指示は必要ありませんBuyExecution。そもそも送信を要求したのは現在解釈中のネットワークでした。同様に、私たちはSubscribeVersion送信者と受信者の両方の共通の利益のために広く何かであると考えているので、それが支払われる必要があるとは期待していません。いずれにせよ、支払いは、それが生成する応答の非同期的で予測不可能な性質のために、計算するのがかなり難しいでしょう。

🤖自動化

これらのXCM命令により、ネットワークは完全にオンチェーンロジックを使用して、対話者がサポートする最新バージョンを判別できますが、このバージョン検出の「ハンドシェイク」をいつ開始するかという問題があります。トランスポートチャネルの作成は、そのチャネルを介して送信される可能性のある1つの(おそらく多くの)データ形式であるXCMのレベルよりも概念的に低いレベルであるため、XCMを送信するためのチャネルが作成される場合は通常実行できません。ここで水を濁らせると、レイヤードデザインの独立性が損なわれる可能性があります。さらに、一部のクロスコンセンサストランスポートプロトコルはチャネルベースではないため、開始時にバージョンネゴシエーションの可能性が排除されます。

PolkadotリレーチェーンやStatemintなどの基板チェーン内での解決策は、送信のためにメッセージをラップする必要があるが、宛先の最新バージョンが不明な場合に、このバージョン検出プロセスを自動的に開始することです。これには、最初のメッセージが次善のXCMバージョンで送信されるというわずかな欠点があります。これは、バージョン応答が受信されるまで発生します。これが実際的な問題である場合、ガバナンスが介入して、その宛先のXCMの初期バージョンをデフォルトとは異なるものにすることができます(通常、本番環境でまだ予想される最も古いXCMバージョンに設定されます)。

⌨️XCM内のコードの互換性

バージョン管理に関して取り組むべき最後のポイントは、コードのオーサリングです。XCMのオーバーザワイヤー形式とはまったく異なり、コードの互換性は、XCMスタックのRust実装を時間の経過とともに使用する(基板ベースの)プロジェクトのコードベースに何が起こらなければならないかを扱います。

明らかに、進化する言語を使用してアイデアを表現することを目的とするコードベースは、時代に合わせて変更および適応する必要があります。特定のバージョン変更で発生する可能性のある変更を指示するのに役立つセマンティックバージョニング(SemVer)システムがすでにあります。ただし、これはAPIやABIを扱う場合に非常に役立ちますが、全体的なデータ形式や言語を検討する場合にはあまり役立ちません。ありがたいことに、XCMはSemVerをほとんど必要としないように設計されています。

XCMソフトウェアの新しいバージョンでは、新しいXCMメッセージと古いXCMメッセージ、および場所やアセットなどの内部データ型を変換できることがわかっています。これを行うには、XCM言語の複数のバージョンをXCMコードベースに一度に保持します。Rustのモジュールシステムはこれを簡単にし、新しいXCMバージョンは単に新しいRustモジュールに対応します。データ型のRust宣言VersionedXcm(この記事の冒頭)を確認すると、基になるXcmデータ型の特定のバージョンのそれぞれのタグ付き共用体であり、それぞれが独自のモジュール、、、v0&cv1にあります。v2

XCMとそのデータ型を使用するトランザクションとAPIは、新旧の形式で同等に構築可能なバージョン管理されたバリアントのみを使用する傾向があるため、最終的には、最新のXCMソフトウェアを使用するようにコードベースを更新できます(Rustでは、これはコードにほとんどまたはまったく変更を加えていないクレートとして知られています。XCMクレートをアップグレードすると、ネットワークは他の同様にアップグレードされたネットワークとより適切に相互運用できますが、ネットワークが使用するXCM言語のフラグメントをアップグレードする必要はありません。

これは、チームがXCMクレートを最新の状態に保ち、すべてを迅速に反復および進化させ続けるための強力なインセンティブとして機能することを願っています。

🏁結論

これにより、XCMのバージョニングシステムと、通信に使用する言語がネットワーク間で異なる速度と時間で進化し、開発者チームに大きな運用上のオーバーヘッドが発生することなく、ソブリンチェーンのネットワークの通信を維持する方法について理解できたと思います。彼らの論理を維持する人。

次回の記事では、XCMの最も興味深い部分の1つである、実行モデルと例外管理機能についてさらに詳しく見ていきます。

パートIIIを読む:実行とエラー管理 

ソース:https ://polkadot.network/blog/xcm-part-two-versioning-and-compatibility/

#xcm  #polkadot 

XCM II: Versioning and Compatibility

In the first article I wrote on XCM, I introduced its basic architecture, goals and how it could be used for some simple use cases. Here we will move on to inspect one interesting aspect of XCM in depth: how XCM can change over time without introducing breakage between the very networks it is meant to connect.

Having a common language solves an awful lot of problems with human interaction. It allows us to work together, resolve conflicts and record information for later use. But language is only as useful as the concepts which it is able to express, and in an ever-changing world a language must change and adapt its conceptual repertoire or risk falling into disuse.

Unfortunately, changing a language too abruptly compromises its primary purpose — facilitating communication between people. Since languages must change, there must be ways of managing these alterations without making new forms unintelligible to the uninitiated. One very useful invention in this regard was the dictionary for helping to document and archive the conceptual palette of a language at one time so that future generations be better able to comprehend historic texts. An edition of a dictionary could be thought of as formalised “version” of a language.

Times may change but the problems remain eerily familiar. As I explained in the previous article, XCM is nothing but a language, albeit a very specialised one. It is a means for consensus systems to talk to one another, and as the needs for this XCM evolve at the breakneck speed of the crypto industry and the Polkadot ecosystem in particular, then there must be some means to ensure that these changes do not compromise the original goal of XCM: interoperability. We now need to solve not just interoperability in consensus space, but also in consensus time.

🔮 Versioning

Since we expect the language of XCM to change over time while very much being in use, one very simple precaution to take is to ensure that we identify which version of XCM we are communicating prior to the actual message content. We do this by using a number of version-wrapper types, so named because they wrap up an XCM message or a component thereof by a version. In Rust code, this looks very simple:

pub enum VersionedXcm {
    V0(v0::Xcm),
    V1(v1::Xcm),
    V2(v2::Xcm),
}

When sent “over the wire” (or, rather, between consensus systems), XCM is always placed in this versioned container. This ensures that systems too old to be able to interpret the message can safely receive them and recognise that the message’s format is unsupported by them. It also allows newer systems to recognise and accordingly interpret older messages.

Not just XCM messages are versioned; in the XCM codebase we also version MultiLocation, MultiAsset, as well as its associated types. This is because they may need to be stored and later interpreted when the chain’s XCM logic has been upgraded. Without versioning, we might otherwise attempt to interpret an old MultiLocation as a new one and find that it is incomprehensible (or worse, comprehensible but different to the original meaning).

💬 Compatibility & Translation

Versioning is a first step and ensures that we can identify the edition of the language which is being used. It does not ensure that we can interpret it and certainly does not ensure that it is the same edition we preferentially use. This is where compatibility comes in. By “compatibility” we mean the ability to continue to interpret and express ourselves in a version of XCM which is not our preferred version.

If we expect to be able to upgrade our network and its version of XCM at a schedule of our choosing, then this compatibility becomes rather important, since we may want to communicate with other networks who have not yet upgraded or, indeed, have already upgraded. This can be broken down into backward compatibility and forward compatibility. Basically speaking, backward compatibility is the ability of an upgraded system to continue to function in a legacy world, and forward compatibility is the ability of a legacy system to continue to function in an upgraded world.

In our case we would like to have both, however there are practical limitations: where a new version of XCM provides for capabilities that did not exist in prior versions, it is unrealistic to expect older systems to be able to interpret these messages. It would be a bit like trying to translate the term “social media” into Latin and then expecting it to be understood at face value by Julius Caesar. Some concepts simply cannot be expressed in a legacy context.

Similarly, significant changes to XCM might result in capabilities being removed from its conceptual model. This happens less often, but is similar to the problem of translating certain archaic terms into modern day equivalents. Interestingly enough, the archaic meaning of “dot” might be an example here (it used to mean a rather particular form of financial endowment).

Therefore new versions of XCM are designed to be mostly compatible with both older and newer versions, but generally there will be XCM messages which simply don’t make sense in the alternative context and will not be translatable.

🗣 Practical Communication

As mentioned before, we ensure that all messages which exist independently include a version identifier. This means messages sent between systems or messages persisted in storage. It does not include all messages, locations and assets though — data which exists as a part of other data need not be versioned since its version can be inferred from its context.

Version identification and compatibility/translation is helpful for receiving messages from an older network or sending messages to a newer network, but — taken alone — is less useful when going the other way. This is because a legacy network receiving a message from an upgraded network does not itself have the logic to be able to translate the new XCM into some form it can interpret — rather, that logic exists only on the sending side which has the translation code able to re-express the new message in legacy terms.

It must therefore be the responsibility of the sending network to ensure that the message it sends is capable of being interpreted by the receiving network. In concrete terms, the version of XCM used for the message must be no newer than the version of XCM that the receiving network supports.

For this reason, the Polkadot and Kusama Relay Chains, Statemint, Statemine, Shell and any other chains based on Substrate/Frame and its XCM engine, all keep a registry of the XCM versions supported by the remote chains. Whenever an XCM message is sent by these chains, it first determines what version to send the message in by consulting its registry. It translates the message to the older of the sender and the receiver’s supported XCM versions. For chains that stay up to date, then most of the time these will be the same, latest released, version, making available the full feature set of XCM.

This registry would normally be dictated and upgraded by governance processes, which is a bit cumbersome and tedious, especially as the number of potential destinations grows. For this reason, version-tracking was introduced.

🤝 Version Negotiation

Version-tracking is the final piece in the puzzle of XCM’s versioning story. Its function is to remove any off-chain or governance processes needed for tracking the XCM version of potential destination chains. Instead, the process happens autonomously and on-chain.

Essentially it works by allowing one network to use XCM to query another for the latest version of XCM which it supports, and to be notified whenever this changes. Replies that come from this query allow the network in question to populate and maintain its version registry, ensuring that messages are sent with the latest comprehensible version possible.

Specifically, there are three valuable instructions in XCM: SubscribeVersion, allowing one to ask another to notify it of its XCM version now and as it changes; UnsubscribeVersion to cancel that request; and QueryResponse, a general means of returning some information from the responder network back to the initiating network. This is what they look like in Rust:

enum Instruction {
    SubscribeVersion {
        query_id: QueryId,
        max_response_weight: u64,
    },
    UnsubscribeVersion,
    /* snip */
}

So SubscribeVersion takes two parameters. The first, query_id is of type QueryId, which is simply an integer used to allow us to identify and distinguish between the responses that come back. All XCM instructions which result in a response being sent have a similar means to ensure that their response can be recognised and dealt with accordingly. The second parameter is called max_response_weight and is a Weight value (also an integer) indicating the maximum amount of computation time that the reply should take by us when it returns. Like the query_id, this will be placed into any response messages that this instruction generates and is needed to ensure that any weight unpredictable, variable weight costs can at least be limited to a maximum prior to execution. Without this we would be unable to get an upper limit on the time that the reply message might take to interpret and thus be unable to schedule it for execution.

UnsubscribeVersion is rather barren as an instruction, primarily because only one version subscription is allowed to be active for a given location at once. This means that cancellation can happen with nothing more to identify it than the contents of the Origin Register.

An illustration of the version registry and its usage. Here, Chain A (XCM version 2) negotiates with Chain E (XCM version 3) and ultimately sends a version 2 message, which E would automatically translate to version 3 prior to interpreting it.

👂 Replying

The third instruction to be aware of is QueryResponse, which is a very much general purpose instruction allowing one chain to reply to another, and in doing so, report some information. Here it is in Rust:

enum Instruction {
    QueryResponse {
        query_id: QueryId,
        response: Response,
        max_weight: u64,
    },
    /* snip */
}

We already know two of of the three parameters, since they get filled from the values provided in SubscribeVersion. The third is called response and contains the actual information we care about. It is placed in a new type Response, itself a union of several types of which one network might wish to use to inform another network. It looks like this in Rust:

pub enum Response {
    Null,
    Assets(MultiAssets),
    ExecutionResult(Result<(), (u32, XcmError)>),
    Version(XcmVersion),
}

For our present purposes, only the Version item is needed, though as we will see in forthcoming articles, other items are useful for other contexts.

⏱ Execution time

Generally, we do not require QueryResponse instructions to purchase their own execution time with BuyExecution since (assuming they are valid), it was the now-interpreting network which requested that they be sent in the first place. Similarly we consider SubscribeVersion to be something broadly in the common interest of both sender and receiver and so wouldn’t expect that it need be paid for. In any case, the payment would be rather difficult to calculate due to the asynchronous and unpredictable nature of the responses it would generate.

🤖 Automation

While these XCM instructions allow a network to use entirely on-chain logic to determine the latest version that their interlocutor supports, there is still the question of when to initiate this version-discovery “handshake”. It cannot generally be done when a channel for sending XCM is created since transport-channel creation is of a conceptually lower level to that of XCM, which is one (perhaps of many) data formats which may be sent over that channel. To muddy the waters here could compromise the independence of the layered design. Furthermore, some cross-consensus transport protocols are not channel-based at all which would preclude the possibility of version negotiation on their inception.

Within Substrate chains such as the Polkadot Relay Chain and Statemint, the solution is to initiate this version discovery process automatically when a message needs to be wrapped for sending but the latest version of the destination is unknown. This has the slight drawback that the first messages would be sent under a suboptimal XCM version, which would happen until the version response was received. If this were a practical problem, then governance could step in to force the initial version of XCM for that destination to be something different to the default (generally set to the earliest XCM version still to be expected in production).

⌨️ Code Compatibility within XCM

The final point to address with regards to versioning is code authoring. Quite different to the over-the-wire format of XCM, code compatibility deals with what must happen to the codebases of (Substrate-based) projects which use the Rust implementation of the XCM stack over time as it evolves.

Clearly the codebases which aim to use an evolving language to express ideas must change and adapt with the times. We already have the Semantic Versioning (SemVer) system which helps dictate what changes may happen over particular version changes. However, this is really useful when dealing with APIs and ABIs and less so when considering an overall data format or language. Thankfully, XCM is designed to have little need for SemVer.

We know that newer versions of the XCM software are capable of translating between new and old XCM messages as well as their internal datatypes like locations and assets. It is able to do this by keeping several versions of the XCM language in the XCM codebase at once. Rust’s module system makes this trivial, with a new XCM version simply corresponding to a new Rust module. If we review the Rust declaration of the VersionedXcm datatype (right at the beginning of this article), it is simply the tagged union of each of the specific versions of the underlying Xcm datatype, each found in their own module v0, v1, v2, &c.

Since the transactions and APIs which use XCM and its datatypes tend to use only the versioned variants which are equally constructible with old and new formats, the end result is that codebases can be updated to use the most recent XCM software (in Rust, this is known as a crate) with few or no changes to their code. Upgrading the XCM crate allows a network to better interoperate with other similarly upgraded networks, but upgrading any fragments of XCM language that the network uses need not happen until a later time.

This acts as, I would hope, a strong incentive for teams to keep their XCM crates updated and thus keep everything iterating and evolving quickly.

🏁 Conclusion

I hope this has enlightened you regarding XCM’s versioning system and how it can be used to keep a network of sovereign chains communicating as the language they use to communicate evolves at differing rates and times between networks, and without a significant operational overhead on the developer teams who maintain their logic.

In the next installment, we will take a much deeper look into one of the most interesting parts of XCM: its execution model and exception management capabilities.

Read Part III: Execution & Error Management 

Source: https://polkadot.network/blog/xcm-part-two-versioning-and-compatibility/

#xcm #polkadot 

XCM: The Cross-Consensus Message Format

As the final Polkadot 1.0 release, complete with Parachains draws close, the Cross-Consensus Messaging format, XCM for short, is approaching its first production-ready release. This is an introduction to the format, its goals, how it works and can be used to achieve typical cross-chain tasks.

One fun fact to begin with… XCM is the “cross-consensus” messaging format, rather than just “cross-chain”. This difference is a sign of the goals of the format which is designed for communicating the kinds of ideas sent not just between chains, but also smart-contracts and pallets, and over bridges and sharded enclaves like Polkadot’s Spree.

🤟 A Format, not a Protocol

To understand XCM better, it’s important to understand its boundaries and where it fits in the Polkadot technology stack. XCM is a messaging format. It is not a messaging protocol. It cannot be used to actually “send” any message between systems; its utility is only in expressing what should be done by the receiver.

Not including bridges and the contracts pallet, Polkadot comes with three distinct systems for actually communicating XCM messages between its constituent chains: UMP, DMP and XCMP. UMP (Upward Message Passing) allows parachains to send messages to their relay chain. DMP (Downward Message Passing) allows the relay chain to pass messages down to one of their parachains. XCMP, is perhaps the best known of them, and this allows the parachains to send messages between themselves. XCM can be used to express the meaning of the messages over each of these three communication channels.

In addition to sending messages between chains, XCM is also useful in other contexts, for transacting with a chain whose transaction format you don’t necessarily know well in advance. With chains whose business logic changes little (for example Bitcoin), the transaction format — or the format used by wallets to send instructions to the chain —tends to remain exactly the same, or at least compatible, indefinitely. With highly evolvable metaprotocol-based chains such as Polkadot and its constituent parachains, the business logic can be upgraded across the network with a single transaction. This can change anything, including the transaction format, introducing a potential problem for wallet maintainers, especially for wallets which are required to be kept offline (such as Parity Signer). Since XCM is well-versioned, abstract and general, it can be used as a means of providing a long-lasting transaction format for wallets to use to create many common transactions.

🥅 Goals

XCM aims to be a language communicating ideas between consensus systems. It should be general enough for it to be properly useful throughout a growing ecosystem. It should be extensible. Since the extensibility will inevitably imply change, it should also be future-proof and forwards-compatible. Finally, it should be efficient enough to run on-chain, and possibly in a metered environment.

Like all languages, some individuals will tend to use some elements more than others. XCM is not designed in such a way that every system which supports XCM is expected to be able to interpret any possible XCM message. Some messages will not have reasonable interpretations under some systems. Others might be reasonable, but still intentionally unsupported by the interpreter owing to resource constraints or because the same content can be expressed in a clearer and more canonical manner. Systems will inevitably only support a subset of possible messages. Heavily resource-constrained systems (like smart contracts) may support only a very limited “dialect”.

This generality extends even as far as concepts like payment of fees for executing the XCM message. Since we know that XCM may be used on diverse systems including a gas-metered smart contract platform and community parachains all the way to trusted interactions between system parachains and their relay chain, we do not want to bake elements such as fee payment too deep and irreversibly in the protocol.

😬 Why not just use the native message format?

Piggybacking on the native message/transaction format of a chain or smart contract can be useful in certain circumstances, but does have some big drawbacks that make it less useful for the goals of XCM. Firstly, there is a lack of compatibility between chains, so a system which intends to send messages to more than one destination would need to understand how to author a message for each. On that note, even a single destination may alter its native transaction/message format over time. Smart contracts might get upgrades, blockchains might introduce new features or alter existing ones and in doing so change their transaction format.

Secondly, common use-cases on chains do not easily fit into a single transaction; special tricks may be required to withdraw funds, exchange them and then deposit the result all inside a single transaction. Onward notifications of transfers, needed for a coherent reserve-asset framework, do not exist in chains unaware of others.

Thirdly, operations such as the payment of fees do not easily fit into a model which assumes fee-payment has already been negotiated like smart contract messages. Transaction envelopes, in comparison, provide some system for payment of processing, but are also generally designed to contain a signature which is not something that makes sense when communicating between consensus systems.

🎬 Some Initial Use-cases

While the goal of XCM is to be general, flexible and future-proof, there are of course practical needs which it must address, not least the transfer of tokens between chains. The optional payment of fees (perhaps using those tokens) is another, as is a general interface for conducting an exchange service, common throughout the DeFi world. Finally, it should be possible to use the XCM language to conduct some platform-specific action; for example, within a Substrate chain, it can be desirable to dispatch a remote call into one of its pallets to access a niche feature.

On top of that, there are many models for transferring tokens which we would want to support: We might want to simply control an account on a remote chain, allowing the local chain to have an address on the remote chain for receiving funds and to eventually transfer those funds it controls into other accounts on that remote chain.

We might have two consensus systems, both of which are native homes for a particular token. Imagine a token such as USDT or USDC, which has instances — all perfectly fungible— on several different chains. It should be possible to burn such a token on one chain and mint a corresponding token on another supported chain. In the parlance of XCM, we call this teleporting owing to the idea that the apparent movement of an asset in fact happens by destroying it on one side and creating a clone on the other side.

Finally, there may be two chains which want to nominate a third chain, one on which an asset might be considered native, to be used as a reserve for that asset. The derivative form of the asset on each of those chains would be fully backed, allowing the derivative asset to be exchanged for the underlying asset on the reserve chain backing it. This might be the case where the two chains do not necessarily trust each other, but (at least as far as the asset in question is concerned) are willing to trust the native chain of the asset. An example here would be where we have several community parachains which would like to send DOT between each other. They each have a local form of DOT which is backed fully by DOT controlled by the parachain on the Statemint chain (a native hub for DOT). When the local form of DOT is sent between the chains, in the background the “real” DOT is moving between parachain accounts on Statemint.

Even this apparently modest level of functionality has a relatively large number of configurations whose usage might be desirable and requires some interesting design to avoid overfitting.

🫀 The Anatomy of XCM

At the core of the XCM format lies the XCVM. Contrary to how it might look to some, this is not a (valid) roman numeral (though if it were, it’d probably mean 905). In fact this stands for Cross-Consensus Virtual Machine. It’s an ultra-high level non-Turing-complete computer whose instructions are designed to be roughly at the same level as transactions.

A “message” in XCM is actually just a programme that runs on the XCVM. It is one or more XCM instructions. The programme executes until it either runs to the end or hits an error, at which point it finishes up (I’m leaving that intentionally unexplained for now) and halts.

The XCVM includes a number of registers, as well as access to the overall state of the consensus system which is hosting it. Instructions might change a register, they might change the state of the consensus system or both.

One example of such an instruction would be TransferAsset which is used to transfer an asset to some other address on the remote system. It needs to be told which asset(s) to transfer and to whom/where the asset is to be transferred. In Rust, it is declared like this:

enum Instruction {
    TransferAsset {
        assets: MultiAssets,
        beneficiary: MultiLocation,
    }
    /* snip */
}

As you might guess, assets is the parameter which expresses which assets are to be transferred, and beneficiary states to whom/where they are to be put. We are of course missing one other piece of information, namely from whom/where the assets are to be taken. This is automatically inferred from the Origin Register. When the programme begins, this register is generally set according to the transport system (the bridge, XCMP or whatever) to reflect where the message actually came from, and it is the same type of information as the beneficiary. The Origin Register operates as a protected register — the programme cannot set it arbitrarily though there are two instructions which can be used to alter it in certain ways.

The types used are quite fundamental ideas in XCM: assets, represented by MultiAsset and locations-within-consensus, represented by MultiLocation. The Origin Register is an optional MultiLocation (optional, because it can be cleared entirely if desired).

📍 Locations in XCM

The MultiLocation type identifies any single location that exists within the world of consensus. It is quite an abstract idea and can represent all manner of things that exist within consensus, from a scalable multi-shard blockchain such as Polkadot all the way down to a lowly ERC-20 asset account on a parachain. In computer science terms, it’s really just a global singleton data structure, regardless of its size or complexity.

MultiLocation always expresses a location relative to the current location. You can think of it a bit like a file system path but where there is no way of directly expressing the “root” of the file system tree. This is for a simple reason: In the world of Polkadot, blockchains can be merged into, and split from, other blockchains. A blockchain can begin life very much alone, and eventually be elevated to become a parachain within a larger consensus. If it did that, then the meaning of “root” would change overnight and this could spell chaos for XCM messages and anything else using MultiLocation. To keep things simple, we exclude this possibility altogether.

Locations in XCM are hierarchical; some places in consensus are wholly encapsulated within other places in consensus. A parachain of Polkadot exists wholly within the overall Polkadot consensus and we call it an interior location. Putting it more strictly, we can say that whenever there is a consensus system any change in which implies a change in another consensus system, then the former system is interior to the latter. For example, a Canvas smart contract is interior to the contracts pallet which hosts it. An UTXO in Bitcoin is interior to the Bitcoin blockchain.

This means that XCM doesn’t distinguish between the two questions “who?” and “where?”. From the point of view of something fairly abstract like XCM, the difference isn’t really important — the two blur and become essentially the same thing.

MultiLocations are used to identify places to send XCM messages, places which can receive assets and then can even help describe the type of an asset itself, as we will see. Very useful things.

When written down in text like this article, they are expressed as some number of .. (or “parent”, the encapsulating consensus system) components followed by some number of junctions, all separated by /. (This isn’t what generally happens when we express them in a language like Rust, but it makes sense in writing since it’s quite a lot like the familiar directory paths that are in widespread use.) Junctions identify an interior location within its encapsulating consensus system. If there are no parents/junctions at all, then we just say that the location is Here.

Some examples:

  • ../Parachain(1000): Evaluated within a parachain, this would identify our sibling parachain of index 1000. (In Rust we would write ParentThen(Parachain(1000)).into().)
  • ../AccountId32(0x1234...cdef): Evaluated within a parachain, this would identify the 32-byte account 0x1234…cdef on the relay chain.
  • Parachain(42)/AccountKey20(0x1234...abcd): Evaluated on a relay chain, this would identify the 20-byte account 0x1234…abcd on parachain number 42 (presumably something like Moonbeam which hosts Ethereum-compatible accounts).

There are many different types of junction for identifying places you might find on-chain in all sorts of ways such as keys, indices, binary blobs and plurality descriptions.

💰 Assets in XCM

When working in XCM it’s often needed to refer to an asset of some sort. This is because practically all public blockchains in existence rely on some native digital asset to provide the backbone for its internal economy and security mechanism. For Proof-of-Work blockchains such as Bitcoin, the native asset (BTC) is used to reward the miners who grow the blockchain and prevent double-spending. For Proof-of-Stake blockchains such as Polkadot, the native asset (DOT) is used as a form of collateral, where network keepers (known as stakers) must risk it in order to generate valid blocks and be rewarded in kind.

Some blockchains manage multiple assets, e.g. Ethereum’s ERC-20 framework allows for many different assets to be managed on-chain. Some manage assets which are not fungible such as Ethereum’s ETH but rather are non-fungible — one-of-a-kind instances; Crypto-kitties was an early example of such non-fungible tokens or NFTs.

XCM is designed to be able to handle all such assets without breaking a sweat. For this purpose there is the datatype MultiAsset together with its associated types MultiAssets, WildMultiAsset and MultiAssetFilter. Let’s look at MultiAsset in Rust:

struct MultiAsset {
   id: AssetId,
   fun: Fungibility,
}

So there’s two fields which define our asset: id and fun, this is pretty indicative of how XCM approaches assets. Firstly, an overall asset identity must be provided. For fungible assets this simply identifies the asset. For NFTs this identifies the overall asset “class” — different asset instances may within this class.

enum AssetId {
   Concrete(MultiLocation),
   Abstract(BinaryBlob),
}

The asset identity is expressed in one of two ways; either Concrete or Abstract. Abstract is not really in use, but it allows asset IDs to be specified by name. This is convenient, but relies on the receiver interpreting the name in the way that the sender expects which may not always be so easy. Concrete is in general usage and uses a location to identify an asset unambiguously. For native assets (such as DOT), the asset tends to be identified as the chain which mints the asset (the Polkadot Relay Chain in this case, which would be the location .. from one its parachains). Assets that are primarily administered within a chain’s pallet may be identified by a location including their index within that pallet. For example, the Karura parachain might refer to an asset on the Statemine parachain with the location ../Parachain(1000)/PalletInstance(50)/GeneralIndex(42).

enum Fungibility {
   Fungible(NonZeroAmount),
   NonFungible(AssetInstance),
}

Secondly, they must be either fungible or non-fungible. If they’re fungible, then there should be some associated non-zero amount. If they’re not fungible, then instead of an amount, there should be some indication of which instance they are. This is commonly expressed with an index, but XCM also allows various other datatypes to be used such as arrays and binary blobs.

This covers MultiAsset, but there are three other associated types that we sometimes use. MultiAssets is one of them and really just means a set of MultiAsset items. Then we have WildMultiAsset; this is a wildcard which can be used to match against one or more MultiAsset items. There are actually only two kinds of wildcard that it supports: All (which matches against all assets) and AllOf which matches against all assets of a particular identity (AssetId) and fungibility. Notably, for the latter, the amount (in the case of fungibles) or instance(s) (for non-fungibles) does not need to be specified and all are matched.

Finally, there is MultiAssetFilter. This is used most often and is really just a combination of MultiAssets and WildMultiAsset allowing either a wildcard or a list of definite (i.e. not wildcard) assets to be specified.

In the Rust XCM API, we provide a lot of conversions to make working with these datatypes as painless as possible. For example, to specify the fungible MultiAsset which equals 100 indivisible units of the DOT asset (Planck, for those in the know) when we are on the Polkadot Relay Chain, then we would use (Here, 100).into().

👉 The Holding Register

Let’s take a look at another XCM instruction: WithdrawAsset. On the face of it, this is a bit like the first half of TransferAsset: it withdraws some assets from the account of the place specified in the Origin Register. But what does it do with them? — if they don’t get deposited anywhere then it’s surely a pretty useless operation. Let’s look at its Rust declaration:

WithdrawAsset(MultiAssets),

So, there’s only one parameter this time (of type MultiAssets and which dictates which assets must be withdrawn from the ownership of the Origin Register). But there is no location specified in which to put the assets.

The withdrawn and unspent assets are temporarily held in what is known as the Holding Register – (“holding” because they’re in a temporary position which cannot persist indefinitely). There are a number of instructions which operate on the Holding Register. One very simple one is the DepositAsset instruction. Let’s take a look at it:

enum Instruction {
    DepositAsset {
        assets: MultiAssetFilter,
        max_assets: u32,
        beneficiary: MultiLocation,
    },
    /* snip */
}

Aha! The astute reader will see that this looks rather like the missing half of the TransferAsset instruction. We have the assets parameter which specifies which of the assets should be removed from the Holding Register to be deposited on-chain. max_assets lets the XCM author inform the receiver how many unique assets are intended to be deposited. (This is helpful when calculating fees in advance of knowing the contents of the Holding Register since depositing an asset can be a costly operation.) Finally there is the beneficiary, which is the same parameter we met earlier in the TransferAsset operation.

There are many instructions which express actions to do on the Holding Register, and DepositAsset is one of the simplest. Some others are rather more sophisticated 😬.

🤑 Fee payment in XCM

Fee payment in XCM is a rather important use-case. Most parachains in the Polkadot community will require their interlocutors to pay their way for any operations that they wish to conduct, lest they leave themselves open to “transaction spam” and a denial-of-service attack. Exceptions to this exist when chains have good reason to believe that their interlocutor will be well-behaved—this is the case when the Polkadot Relay Chain corresponds with the Polkadot Statemint common-good chain. However for the general case, fees are a good way of ensuring that XCM messages and their transport protocols cannot be over-used. Let’s look at how fees can be paid when XCM messages arrive into Polkadot.

As already mentioned, XCM does not include the idea of fees and fee-payment as a first-class citizen: Unlike, say, the Ethereum transaction model, fee payment is not something baked into the protocol that use-cases which have no need for must conspicuously circumvent. Like Rust with its zero-cost abstractions, fee payment comes with no great design overhead in XCM.

For systems that do require some fee payment though, XCM provides the ability to buy execution resources with assets. Doing so, broadly speaking, consists of three parts:

  • Firstly, some assets need to be provided.
  • Secondly, the exchange of assets for compute time (or weight, in Substrate parlance) must be negotiated.
  • Finally, the XCM operations will be performed as instructed.

The first part is managed by one of a number of XCM instructions which provide assets. We already know one of these (WithdrawAsset), but there are several others which we will see later. The resultant assets in the Holding Register will of course be used for paying fees associated with executing the XCM. Any assets not used to pay fees we will be depositing in some destination account. For our example, we’ll assume that the XCM is happening on the Polkadot Relay Chain and that it’s for 1 DOT (which is 10,000,000,000 indivisible units).

So far our XCM instruction looks like:

WithdrawAsset((Here, 10_000_000_000).into()),

This brings us to the second part, exchanging (some of) these assets for compute time to pay for our XCM. For this we have the XCM instruction BuyExecution. Let’s take a look at it:

enum Instruction {
    /* snip */
    BuyExecution {
        fees: MultiAsset,
        weight: u64,
    },
}

The first item fees is the amount which should be taken from the Holding Register and used for fee-payment. It’s technically just the maximum since any unused balance is immediately returned.

The amount that ends up being spent is determined by the interpreting system — fees only limits it and if the interpreting system needs to be paid more for the execution desired, then the BuyExecution instruction will result in error. The second item specifies an amount of execution time to be purchased. This should generally be no less than the weight of the XCM programme in total.

In our example we’ll assume that all XCM instructions take a million weight, so that’s two million for our two items so far (WithdrawAsset and BuyExecution) and a further one for what’s coming next. We’ll just use all the DOT that we have to pay those fees (which is only a good idea if we trust the destination chain not to have crazy fees — we’ll assume that we do). Let’s have a look at our XCM so far:

WithdrawAsset((Here, 10_000_000_000).into()),
BuyExecution {
    fees: (Here, 10_000_000_000).into(),
    weight: 3_000_000,
},

The third part of our XCM comes in depositing the funds remaining in the Holding Register. For this we will just use the DepositAsset instruction. We don’t actually know how much is remaining in the Holding Register, but that doesn’t matter since we can specify a wildcard for the asset(s) which should be deposited. We’ll place them in the sovereign account of Statemint (which is identified as Parachain(1000).

Our final XCM instruction therefore looks like this:

WithdrawAsset((Here, 10_000_000_000).into()),
BuyExecution {
    fees: (Here, 10_000_000_000).into(),
    weight: 3_000_000,
},
DepositAsset {
    assets: All.into(),
    max_assets: 1,
    beneficiary: Parachain(1000).into(),
},

⛓ Moving Assets between Chains in XCM

Sending an asset to another chain is probably the most common use-case for inter-chain messaging. Allowing one chain to administer another chain’s native asset allows for all sorts of derivative use-cases (no pun intended), the simplest being a decentralised exchange but generally grouped together as decentralised finance or DeFi.

Generally speaking there are two ways that assets move between chains and this depends on whether the chains trust each other’s security and logic or not.

✨ Teleporting

For chains that trust each other (such a homogeneous shards under the same overall consensus and security umbrella), we can use a framework that Polkadot calls teleporting, which basically just means destroying an asset on the sending side and minting it on the receiving side. This is simple and efficient — it only requires the coordination of the two chains and only involves one action on either side. Unfortunately, if the receiving chain cannot 100% trust the sending chain to actually destroy the asset which it is minting (and indeed not to mint assets outside of the agreed rules for the asset), then the sending chain really has no basis for minting the asset on the back of a message.

Let’s look at how the XCM would look which teleported (most of) 1 DOT from the Polkadot Relay Chain to its sovereign account on Statemint. We’ll assume that the fees are already paid on the Polkadot side.

WithdrawAsset((Here, 10_000_000_000).into()),
InitiateTeleport {
    assets: All.into(),
    dest: Parachain(1000).into(),
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: 3_000_000,
        },
        DepositAsset {
            assets: All.into(),
            max_assets: 1,
            beneficiary: Parent.into(),
        },
    ]),
}

As you can see, this looks fairly similar to the straight withdraw-buy-deposit pattern that we saw last. The difference is the InitiateTeleport instruction which is inserted around the last two instructions (BuyExecution and DepositAsset). Behind the scenes, the sender (Polkadot Relay) chain is creating a whole new message when it executes the InitiateTeleport instruction; it takes the xcm field and places it inside a new XCM, ReceiveTeleportedAsset, and sends this XCM on to the receiver (Statemint) chain. Statemint trusts the Polkadot Relay Chain to have destroyed the 1 DOT on its side prior to sending the message. (It does!)

The beneficiary is stated as Parent.into(), and an astute reader might be wondering what this could refer to in the context on the Polkadot Relay Chain. The answer would be “nothing”, but there is no mistake here. Everything in the xcm parameter is written from the perspective of the receiving side, so despite this being a part of the overall XCM which is fed into the Polkadot Relay Chain, it is only actually executed on Statemint, and thus it is in Statemint’s context which is it written.

When Statemint eventually gets the message, it looks like this:

ReceiveTeleportedAsset((Parent, 10_000_000_000).into()),
BuyExecution {
    fees: (Parent, 10_000_000_000).into(),
    weight: 3_000_000,
},
DepositAsset {
    assets: All.into(),
    max_assets: 1,
    beneficiary: Parent.into(),
},

You might notice that this looks rather similar to the previous WithdrawAsset XCM. The only major difference is that rather than funding the fees and deposit through a withdrawal from a local account, it is being “magicked” into existence by trusting that the DOT was faithfully destroyed on the sending (Polkadot Relay Chain) side and honouring the ReceiveTeleportedAsset message.

Notably, the asset identifier of the 1 DOT we sent on Polkadot Relay Chain (Here, referring to the Relay Chain itself the native home for DOT) has been automatically mutated into its representation on Statemint: Parent.into(), which is the Relay Chain’s location from Statemint’s context.

The beneficiary is specified as the Polkadot Relay Chain also and so its sovereign account (on Statemint) is credited with newly minted 1 DOT minus fees. The XCM might just have easily named an account or other place for the beneficiary. As it is, a later TransferAsset sent from the Relay Chain could be used to move this 1 DOT.

🏦 Reserves

The alternative way to transfer assets across chains is slightly more complicated. A third-party is used known as the reserve. The name comes from reserve banking, where assets are held “in reserve” to give credibility to the idea that some issued promise is valuable. For example, if we can reasonably believe exactly 1 “real” (e.g. Statemint or Relay Chain) DOT is redeemable for each “derivative” DOT issued on an independent parachain, then we can treat the parachain’s DOT as being economically equivalent to real DOT, (most banks do something called fractional reserve banking, which means they keep less than the face-value in reserve). This works fine until too many people wish to redeem, and then everything can go quite wrong quite fast.

So, the reserve is the place which stores the “real” assets and, for the purposes of transferral, whose logic and security is trusted by both sender and receiver. Any corresponding assets on the sender and receiver side would then be derivatives, but they would be backed with the “real” reserve asset 100%. Assuming that the parachain behaved well (i.e. that it was bug-free and its governance didn’t decide to run off with the reserve), this would make the derivative DOT more or less of the same value as the underlying reserve DOT. The reserve assets are held in the sender/receiver’s sovereign account (i.e. the account controllable by the sender or receiver chain) on the reserve chain, so there’s good reason that unless something went wrong with the parachain, they’d be well guarded.

Back to the transfer mechanism, the sender would instruct the reserve to move the assets which the sender owns (and uses as a reserve for its own version of the same asset) into the receiver’s sovereign account, and the reserve — not the sender! — informs the receiver about their new credit. This means that the sender and receiver do not need to trust each other’s logic or security, but only that of the chain used as the reserve. However it does imply that the three sides need to coordinate, which increases the overall cost, time and complexity.

Let’s look at the XCM required. This time we’ll be sending 1 DOT from parachain 2000 to parachain 2001, which use reserve-backed DOT on parachain 1000. Again, we’ll assume the fees are already paid on the sender side.

WithdrawAsset((Parent, 10_000_000_000).into()),
InitiateReserveWithdraw {
    assets: All.into(),
    dest: ParentThen(Parachain(1000)).into(),
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: 3_000_000,
        },
        DepositReserveAsset {
            assets: All.into(),
            max_assets: 1,
            dest: ParentThen(Parachain(2001)).into(),
            xcm: Xcm(vec![
                BuyExecution {
                    fees: (Parent, 10_000_000_000).into(),
                    weight: 3_000_000,
                },
                DepositAsset {
                    assets: All.into(),
                    max_assets: 1,
                    beneficiary: ParentThen(Parachain(2000)).into(),
                },
            ]),
        },
    ]),
},

This is a little more complex, as promised. Let’s walk through it. The outer part deals with extracting the 1 DOT on the sender side (parachain 2000) and withdrawing the corresponding 1 DOT held on Statemint (parachain 1000) — it uses InitiateReserveWithdraw for this purpose and it pretty self-explanatory.

WithdrawAsset((Parent, 10_000_000_000).into()),
InitiateReserveWithdraw {
    assets: All.into(),
    dest: ParentThen(Parachain(1000)).into(),
    xcm: /* snip */
}

Now we have 1 DOT in the Holding Register on Statemint. Before we can do anything else, we need to buy some execution time on Statemint. This, again, looks pretty familiar:

/*snip*/
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: 3_000_000,
        },
        DepositReserveAsset {
            assets: All.into(),
            max_assets: 1,
            dest: ParentThen(Parachain(2001)).into(),
            xcm: /* snip */
        },
    ]),
/*snip*/

We’re using our 1 DOT to pay for fees, and we’re assuming one million per XCM operation. With that one operation paid for, we deposit the 1 DOT (minus fees, and we’re lazy so we just use All.into()) into the sovereign account for parachain 2001, but do so as a reserve asset meaning that we also require Statemint to send a notification XCM to that receiving chain informing it of the transfer along with some instructions to be executed on the resulting derivate assets. DepositReserveAsset instructions don’t always make much sense; for it to make sense, the dest must be a location which can reasonably hold funds on the reserve chain, but also one to which the reserve chain can send an XCM. Sibling parachains happen to fit the bill perfectly.

/*snip*/
    xcm: Xcm(vec![
        BuyExecution {
            fees: (Parent, 10_000_000_000).into(),
            weight: 3_000_000,
        },
        DepositAsset {
            assets: All.into(),
            max_assets: 1,
            beneficiary: ParentThen(Parachain(2000)).into(),
        },
    ]),
/*snip*/

The final part defines part of the message which arrives at parachain 2001. Like with initiating a teleport operation, DepositReserveAsset composes and sends a new message in this case ReserveAssetDeposited. It is this message, albeit containing the XCM programme which we define, which arrives at the receiving parachain. It will look something like this:

ReserveAssetDeposited((Parent, 10_000_000_000).into()),
BuyExecution {
    fees: (Parent, 10_000_000_000).into(),
    weight: 3_000_000,
},
DepositAsset {
    assets: All.into(),
    max_assets: 1,
    beneficiary: ParentThen(Parachain(2000)).into(),
},

(This assumes that no fees were actually taken on Statemint and the whole 1 DOT made it over. That’s not especially realistic, so the assets line is probably going to have a lower number.)

Most of the message should look pretty familiar; the only significant difference with the ReceiveTeleportedAsset message we saw in the last section is the top-level instruction ReserveAssetDeposited, which fulfills a similar purpose, only rather than meaning “the sending chain burned assets so you can mint equivalent assets”, it means “the sending chain received assets and is holding them for you in reserve, so you can mint fully-backed derivates”. Either way the destination chain mints them into the Holding Register, and we deposit them in the sender’s sovereign account on the receiving chain. 🎉

🏁 Conclusion

That’s it for this article; I hope it was helpful in explaining what XCM is and the basics of how it is designed to work. In the next article(s?), we will take a deeper look into the XCVM’s architecture, its execution model and its error handling, XCM’s versioning system and how upgrades to the format can be managed in a well-connected interdependent ecosystem, as well as its query-response system and how XCM works in Substrate. We will also discuss some of the future directions of XCM, planned features and the process for evolving it. \

Source: https://polkadot.network/blog/xcm-the-cross-consensus-message-format/

 Part II: Versioning & Compatibility
 Part III: Execution & Error Management

#xcm #polkadot