
加入PolkaWorld 社區,共建Web 3.0!
隨著最終的Polkadot 1.0 版本和平行鏈的臨近,跨共識消息格式(簡稱XCM)即將發布其第一個生產就緒版本。這是對格式、其目標、工作原理的介紹,可用於實現典型的跨鏈任務。
隨著最終的Polkadot 1.0 版本和平行鏈的臨近,跨共識消息格式(簡稱XCM)即將發布其第一個生產就緒版本。這是對格式、其目標、工作原理的介紹,可用於實現典型的跨鏈任務。
隨著最終的Polkadot 1.0 版本和平行鏈的臨近,跨共識消息格式(簡稱XCM)即將發布其第一個生產就緒版本。這是對格式、其目標、工作原理的介紹,可用於實現典型的跨鏈任務。
mdnice編輯器
隨著最終的Polkadot 1.0 版本和平行鏈的臨近,跨共識消息格式(簡稱XCM)即將發布其第一個生產就緒版本。這是對格式、其目標、工作原理的介紹,可用於實現典型的跨鏈任務。
我們從一個有趣的事實講起。 XCM 是“跨共識”消息格式,而不僅僅是“跨鏈”。這種差異是該格式在最終實現的目標上的標誌,該格式不僅在鏈之間交流,而且在智能合約和模塊之間,以及通過橋和分片(如Polkadot 的Spree)中發送各種想法。
🤟 一種格式,而不是一個協議
mdnice編輯器
不包括橋和合約模塊,Polkadot 帶有三個不同的系統,用於在其組成鏈之間實際通信XCM 消息:UMP、DMP 和XCMP。 UMP(向上消息傳遞)允許平行鏈向它們的中繼鏈發送消息。 DMP(向下消息傳遞)允許中繼鏈將消息向下傳遞到其平行鏈。 XCMP 可能是其中最著名的,這允許平行鏈之間發送消息。 XCM 可用於通過這三個通信通道中的任意一個來表達消息的含義。
除了在鏈之間發送消息之外,XCM 在其他語境也很有用,比如,可以用於在你之前不是很了解它交易格式的鏈上進行交易。對於業務邏輯變化很小的鏈(例如比特幣),交易格式—— 或者錢包用來向鏈發送指令的格式—— 往往會無限期地保持完全相同,或者至少兼容。使用高度可進化的基於元協議的鏈,例如Polkadot 及其組成的平行鏈,業務邏輯可以通過單個交易跨網絡升級。這可以改變任何事情,包括交易格式,給錢包維護者帶來潛在的問題,特別是對於需要離線保存的錢包(例如Parity Signer)。由於XCM 版本良好、抽象且通用,因此它可以用作一種為錢包提供持久交易格式的手段,用於創建許多常見交易。
🥅 目標
mdnice編輯器
像所有語言一樣,有些人會比其他人更傾向於使用某些元素。 XCM 的設計方式並不是讓每個支持XCM 的系統都能夠解釋任何可能的XCM 消息。有些消息在某些系統下不會有合理的解釋。其他的可能是合理的,但由於資源限製或因為可以以更清晰、更規範的方式表達相同的內容,解釋器仍然故意不支持。系統將不可避免地只支持可能消息的一個子集。資源嚴重受限的系統(如智能合約)可能只支持非常有限的“方言”。
這種普遍性甚至延伸到諸如為執行XCM 消息支付費用之類的概念。由於我們知道XCM 可用於多種系統,包括gas 計量的智能合約平台和社區平行鏈,一直到系統平行鏈與其中繼鏈之間的可信交互,因此我們不想將費用支付等元素烤得太深和在協議中變得不可逆轉。
其次,鏈上的常見用例不容易適合單個交易;可能需要特殊的技巧來提取資金,交換資金,然後將結果全部存入單個交易中。連貫的儲備資產框架所需的後續轉移通知並不存在於不知道其他人的鏈中。
mdnice編輯器
🎬 一些初始用例
mdnice編輯器
第三,諸如支付費用之類的操作不容易適應假設已經像智能合約消息一樣協商費用支付的模型。相比之下,交易信封提供了一些支付處理的系統,但通常也被設計為包含一個簽名,這在共識系統之間進行通信時沒有意義。
mdnice編輯器
🎬 一些初始用例
最重要的是,有許多我們希望支持的代幣轉移模型:我們可能只想簡單地控制遠程鏈上的帳戶,允許本地鏈在遠程鏈上擁有一個地址以接收資金並最終將其控制的資金轉移到該遠程鏈上的其他賬戶中。
XCM 剖析
我們可能有兩個共識系統,它們都是特定代幣的“本地家園”。想像一下像USDT 或USDC 這樣的代幣,它在幾個不同的鏈上都有實例,並且完全可以互換。應該可以在一條鏈上銷毀這樣的代幣,並在另一條支持的鏈上鑄造相應的代幣。在XCM 的說法中,我們之所以稱之為傳送(teleport),是因為資產的轉移實際上是通過在一側銷毀它,並在另一側創建一個克隆來實現的。
最後,可能有兩條鏈想要提名第三條鏈,其中一條鏈上的資產可能被視為本地資產,用作該資產的儲備。每個鏈上資產的衍生形式將得到完全支持,允許衍生資產交換為支持它的儲備鏈上的基礎資產。這可能是兩條鏈不一定相互信任的情況,但(至少就相關資產而言)願意信任資產的本地鏈。這裡的一個例子是我們有幾個社區平行鏈想要在彼此之間發送DOT。它們每個都有一個本地形式的DOT,由Statemint 鏈(DOT 的本地中心)上的平行鏈控制的DOT 完全支持。當在鏈之間發送本地形式的DOT 時,在後台,“真正的”DOT 在Statemint 上的平行鏈帳戶之間移動。
enum Instruction {
TransferAsset {
assets: MultiAssets,
beneficiary: MultiLocation,
}
/* snip */
}
即使這種看似適度的功能水平也有相對大量的配置,它們的使用可能是可取的,並且需要一些有趣的設計來避免過度擬合。
XCM 格式的核心在於XCVM。與某些人的看法相反,這不是(有效的)羅馬數字(儘管如果是,它可能意味著905)。事實上,這代表跨共識虛擬機。這是一台超高級別的非圖靈完備計算機,其指令設計為與交易大致處於同一級別。
XCVM 包括許多Register,以及訪問託管它的共識系統的整體狀態。指令可能會改變一個Register,它們可能會改變共識系統的狀態,或者兩者兼而有之。
mdnice編輯器
正如你可能猜到的那樣,資產是表示要轉讓哪些資產的參數,而受益人則說明這些資產要交給誰/在哪裡。當然,我們還缺少另一條信息,即從誰/何處獲取資產。這是從原Register自動推斷出來的。程序開始時,這個Register 一般是根據傳輸系統(網、XCMP 或者其他什麼)來設置的,以反映消息實際來自哪裡,和受益人是同一類型的信息。 Origin Register 作為一個受保護的Register 運行—— 程序不能任意設置它,儘管有兩條指令可以用來以某種方式改變它。
mdnice編輯器
使用的類型是XCM 中非常基本的思想:資產,由MultiAsset 表示,共識內的位置,由MultiLocation 表示。 Origin Register 是一個可選的MultiLocation(可選,因為如果需要它可以完全清除)。
mdnice編輯器
📍 在XCM 的位置
MultiLocation 類型標識存在於共識世界中的任何單個位置。這是一個相當抽象的想法,可以代表共識中存在的所有事物,從可擴展的多分片區塊鏈(如Polkadot)一直到平行鏈上的低級ERC-20 資產賬戶。在計算機科學術語中,它實際上只是一個全局單例數據結構,無論其大小或複雜性如何。
XCM 中的位置是分層的,共識中的一些地方完全封裝在共識中的其他地方。 Polkadot 的平行鏈完全存在於整個Polkadot 共識中,我們稱之為內部位置。更嚴格地說,我們可以說,只要有一個共識系統的任何變化都意味著另一個共識系統的變化,那麼前一個系統是後者的內部系統。例如,Canvas 智能合約位於託管它的合約模塊的內部。比特幣中的UTXO 是比特幣區塊鏈的內部。
例如:
這意味著XCM 沒有區分“誰” 和“在哪裡” 兩個問題。從像XCM 這樣相當抽象的東西的角度來看,區別並不重要—— 兩者模糊並成為本質上相同的東西。
當用本文這樣的文本寫下來時,它們表示為一些.. (或“父”,封裝共識系統)組件,後跟一些連接點,所有連接點都用/ 分隔。 (當我們用Rust 之類的語言表達它們時,通常不會發生這種情況,但它在書面上是有意義的,因為它很像廣泛使用的熟悉的目錄路徑。)連接在其封裝共識中標識了一個內部位置系統。如果根本沒有父節點/連接點,那麼我們只說位置是這裡。
../Parachain(1000): 在平行鏈中進行評估,這將識別我們索引為1000 的兄弟平行鏈。 (在Rust 中,我們將編寫ParentThen(Parachain(1000)).into())
mdnice編輯器
Parachain(42)/AccountKey20(0x1234...abcd): 在中繼鏈上進行評估,這將識別平行鏈編號42 上的20 字節帳戶0x1234…abcd (大概類似於承載以太坊兼容帳戶的Moonbeam) 。
mdnice編輯器
struct MultiAsset {
id: AssetId,
fun: Fungibility,
}
有許多不同類型的連接點,用於以各種方式識別你可能在鏈上找到的位置,例如鍵、索引、二進制blob 和復數描述。
plain
enum AssetId {
Concrete(MultiLocation),
Abstract(BinaryBlob),
}
mdnice編輯器
enum Fungibility {
Fungible(NonZeroAmount),
NonFungible(AssetInstance),
}
💰 XCM 中的資產
在XCM 中工作時,通常需要引用某種資產。這是因為幾乎所有現有的公共區塊鏈都依賴於一些原生數字資產來為其內部經濟和安全機制提供支柱。對於比特幣等工作量證明區塊鏈,原生資產(BTC)用於獎勵開發區塊鏈的礦工並防止雙重支出。對於Polkadot 等權益證明區塊鏈,原生資產(DOT) 用作一種抵押形式,網絡管理員(稱為權益人)必須承擔風險才能生成有效區塊並獲得實物獎勵。
一些區塊鏈管理多種資產,例如以太坊的ERC-20 框架允許在鏈上管理許多不同的資產。一些管理不可替代的資產,例如以太坊的ETH,而是不可替代的—— 獨一無二的實例;Crypto-kitties 是此類不可替代代幣或NFT 的早期示例。
👉 Holding Register
XCM 旨在能夠毫不費力地處理所有此類資產。為此,有數據類型MultiAsset 及其關聯類型MultiAssets、WildMultiAsset 和MultiAssetFilter。讓我們看看Rust 中的MultiAsset:
WithdrawAsset(MultiAssets),
所以有兩個字段定義了我們的資產:id 和fun,這很好地表明了XCM 如何處理資產。首先,必須提供整體資產身份。對於可替代資產,這只是標識資產。對於NFT,這標識了整個資產“類別” —— 不同的資產實例可能在這個類別中。
資產身份以兩種方式之一表示;無論是具體的還是抽象的。 Abstract 並沒有真正使用,但它允許通過名稱指定資產ID。這很方便,但依賴於接收者以發送者期望的方式解釋名稱,這可能並不總是那麼容易。 Concrete 在一般用途中使用並使用位置來明確地識別資產。對於原生資產(例如DOT),資產往往被識別為鑄造資產的鏈(在這種情況下是Polkadot 中繼鏈,這將是其平行鏈的位置.. )。主要在鏈模塊內管理的資產可以通過包括其在該模塊內的索引的位置來識別。例如,Karura 平行鏈可能指的是Statemine 平行鏈上的資產,位置為../Parachain(1000)/PalletInstance(50)/GeneralIndex(42) 。
enum Instruction {
DepositAsset {
assets: MultiAssetFilter,
max_assets: u32,
beneficiary: MultiLocation,
},
/* snip */
}
其次,它們必須是可替代的或不可替代的。如果它們是可替代的,那麼應該有一些相關的非零數量。如果它們不可替代,那麼應該有一些指示它們是哪個實例而不是數量。 (這通常用索引表示,但XCM 還允許使用各種其他數據類型,例如數組和二進制blob。) 這涵蓋了MultiAsset,但我們有時會使用其他三種相關類型。 MultiAssets 就是其中之一,實際上只是意味著一組MultiAsset 項目。然後我們有WildMultiAsset;這是一個通配符,可用於匹配一個或多個MultiAsset 項目。它實際上只支持兩種通配符:All(匹配所有資產)和AllOf 匹配特定身份(AssetId) 和可替代性的所有資產。值得注意的是,對於後者,不需要指定數量(在可替代的情況下)或實例(對於非可替代的),並且全部匹配。
最後,還有MultiAssetFilter。這是最常用的,實際上只是MultiAssets 和WildMultiAsset 的組合,允許指定通配符或明確(即非通配符)資產列表。
在Rust XCM API 中,我們提供了很多轉換,以盡可能輕鬆地處理這些數據類型。例如,當我們在Polkadot 中繼鏈上時,要指定等於100 個不可分割的DOT 資產單位的可替代MultiAsset(普朗克,對於那些知道的人),那麼我們將使用(Here, 100).into ()。
我們再來看看另一個XCM 指令:WithdrawAsset。從表面上看,這有點像TransferAsset 的前半部分:它從起源的賬戶中提取了一些資產。但這與他們有什麼關係?如果他們沒有在任何地方存入,那麼這肯定是一個非常無用的操作。如果我們查看它的Rust 聲明,我們會發現更多有關其用法的線索:
所以,這次只有一個參數(MultiAssets 類型,它規定哪些資產必須從Origin Register 的所有權中撤出)。但是沒有指定放置資產的位置。
這些暫時持有的未動用資產稱為持有Register。 (“Holding”是因為它們處於不能無限期持續的臨時位置,“Register”是因為它有點像CPU Register,是一個存放工作數據的地方。)有許多指令對持有寄存器進行操作。一種非常簡單的指令是DepositAsset 指令。讓我們來看看它:
啊哈!精明的讀者會發現這看起來很像TransferAsset 指令中缺失的一半。我們有assets 參數,該參數指定應從持有寄存器中刪除哪些資產以存放在鏈上。 max_assets 讓XCM 作者通知接收者打算存入多少獨特的資產。 (這在知道Holding Register 的內容之前計算費用時很有幫助,因為存入資產可能是一項代價高昂的操作。)最後是受益人,這與我們之前在TransferAsset 操作中遇到的參數相同。有許多指令表示要在Holding Register 上執行的操作,而DepositAsset 是最簡單的指令之一。其他一些則更複雜。
🤑 XCM 中的費用支付
XCM 中的費用支付是一個相當重要的用例。 Polkadot 社區中的大多數平行鏈都會要求其對話者為他們希望進行的任何操作付費,以免為“交易垃圾” 和拒絕服務攻擊敞開大門。當鏈有充分的理由相信它們的對話者會表現良好時,也存在例外情況—— 當Polkadot 中繼鏈與Polkadot Statemint 公共利益鏈通信時就是這種情況。但是,對於一般情況而言,費用是確保XCM 消息及其傳輸協議不會被過度使用的好方法。我們來看看XCM 消息到達Polkadot 時如何支付費用。
正如前文提到的一樣,XCM 不包括作為一等公民的費用和費用支付:與以太坊交易模型不同,對於費用支付不是協議中不需要的東西,就必須進行規避。就像Rust 的零成本抽像一樣,費用支付在XCM 中沒有很大的設計開銷。
WithdrawAsset((Here, 10_000_000_000).into()),
對於確實需要支付一定費用的系統,XCM 提供了使用資產購買執行資源的能力。概括來講,這包括了三個部分:
enum Instruction {
/* snip */
BuyExecution {
fees: MultiAsset,
weight: u64,
},
}
首先,需要提供一些資產。
其次,必須就計算時間(用Substrate 中的說法就是weight)交換資產。
最後,XCM 操作將按照指示執行。
WithdrawAsset((Here, 10_000_000_000).into()),
BuyExecution {
fees: (Here, 10_000_000_000).into(),
weight: 3_000_000,
},
第一部分由提供資產的多個XCM 指令之一管理。我們已經知道其中的一個指令了( WithdrawAsset ),但還有其他幾個我們稍後會看到。 Holding Register中的所得資產當然將用於支付與執行XCM 相關的費用。任何未用於支付費用的資產都將被存入某個目的地賬戶。在我們的示例中,我們假設XCM 發生在Polkadot 中繼鏈上,交易的是1 個DOT(即10,000,000,000 個不可分割的單位)。
目前我們的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。為此,我們有XCM 指令BuyExecution 。我們來看看它是什麼樣的:
第一個項目fees 是應從Holding Register 中提取並用於支付費用的金額。從技術上講,這只是最大值,因為任何未使用的餘額都會立即退還。
在我們的示例中,我們假設所有XCM 指令的weight 為100 萬,因此到目前為止,我們的兩個項目(WithdrawAsset 和BuyExecution)為200 萬,接下來還有一個。我們將只使用我們必須支付這些費用的所有DOT(僅當我們相信目的地鏈沒有瘋狂的費用時,這樣做才合理- 假設情況如此)。到這裡,讓我們看看我們的XCM:
所以我們最終的XCM 指令是這樣的:
mdnice編輯器
將資產發送到另一條鏈可能是鏈間消息傳遞的最常見用例。允許一條鏈管理另一條鏈的本地資產,將允許各種衍生用例(無雙關語),最簡單的是去中心化交易所,但通常歸為去中心化金融或De-Fi。
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(),
},
]),
}
mdnice編輯器
一般來說,資產在鏈之間移動有兩種方式,這取決於鏈之間是否信任彼此的安全性和邏輯。
mdnice編輯器
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(),
},
✨ 傳送(Teleporting)
我們來看看XCM 將(大部分的)1 個DOT 從Polkadot 中繼鏈傳送到它在Statemint 上的主權帳戶時是什麼樣的。我們假設Polkadot 這邊已經支付了費用。
beneficiary (受益人)被聲明為Parent.into() ,精明的讀者可能想知道這在Polkadot 中繼鏈的上下文中指的是什麼。答案是“什麼也不是”,但這裡沒有錯誤。 xcm 參數中的所有內容都是從接收方的角度編寫的,因此儘管這是整個XCM 的一部分,它被饋送到Polkadot 中繼鏈,但它實際上只在Statemint 上執行,因此它的上下文是跟著Statemint 走的。
mdnice編輯器
你可能注意到了,這看起來跟之前的WithdrawAsset XCM 非常像。唯一的主要區別是,它不是通過從本地賬戶提款來為費用和存款提供資金,而是通過相信DOT 在發送方(Polkadot 中繼鏈)上確實被銷毀並尊重ReceiveTeleportedAsset 消息。值得注意的是,我們在Polkadot 中繼鏈上發送的1 個DOT 的資產標識符(Here,指的是中繼鏈本身是DOT 的原生環境)已自動變異為它在Statemint 上的表示: Parent. into(),即Statemint 上下文中中繼鏈的位置。
mdnice編輯器
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(),
},
]),
},
]),
},
beneficiary 也被指定為Polkadot 中繼鏈,因此其(在Statemint 上的)主權賬戶被記入新鑄造的1 DOT 減去費用。 XCM 可能只是輕鬆地為beneficiary 指定了一個帳戶或其他地方。實際上,可以使用從中繼鏈發送的後續TransferAsset 來移動這1 個DOT。
WithdrawAsset((Parent, 10_000_000_000).into()),
InitiateReserveWithdraw {
assets: All.into(),
dest: ParentThen(Parachain(1000)).into(),
xcm: /* snip */
}
mdnice編輯器
/*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*/
🏦 準備金(Reserves)
/*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*/
跨鏈轉移資產的另一個方式稍微複雜一些。用到了稱為準備金(reserve)的第三方。這個名字來自銀行的準備金制度,也就是資產被“儲備” 起來,來讓某些已發布的價值承諾具有可信度。例如,如果我們有理由相信在一條獨立的平行鏈上發行的每個“衍生” DOT 恰好可以兌換1 個“真” DOT(例如Statemint 或中繼鏈上的DOT),那麼我們就可以將平行鏈的DOT 視為在經濟上等同於真實的DOT 。 (大多數銀行都做部分準備金銀行業務,這意味著他們保留的準備金少於面值。這種做法平時沒什麼問題,但是當太多人都希望贖回,就會很快出問題。)所以,準備金是存儲“真實” 資產的地方,出於傳輸目的,其邏輯和安全性受到發送方和接收方的信任。發送方和接收方的任何相應資產都將是衍生品,但它們將得到100% 的“真實” 儲備資產支持。假設平行鍊錶現良好(即它沒有漏洞,並且其治理沒有決定偷走準備金跑路),這將使衍生品DOT 與基礎儲備DOT 的價值基本相同。儲備資產保存在儲備鏈上的發送者/接收者的主權賬戶(即由發送者或接收者鏈控制的賬戶)中,所以除非平行鏈出現問題,否則有充分的理由相信這些資產會受到很好的保護。
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(),
},
說回到轉賬機制,發送方將指示準備金把發送方擁有的資產(並將其用作其自己版本的相同資產的準備金)轉移到接收方的主權賬戶中,而準備金(而不是發送方!)通知接收方他們的新資產。這意味著發送方和接收方不需要信任彼此的邏輯或安全性,而只需信任用作準備金的鏈的邏輯或安全性。然而,這確實意味著三方需要協調,這增加了整體成本、時間和復雜性。
讓我們看看所需的XCM。這次我們將從平行鏈2000 發送1 個DOT 到平行鏈2001,它在平行鏈1000 上使用準備金支持的DOT。同樣,我們假設費用已經在發送方支付過了。
就像我之前說的,這會有點複雜。讓我們來看看這個過程。外部部分負責在發送方(平行鏈2000)上提取1 個DOT 並撤回Statemint(平行鏈1000)上相應的1 個DOT —— 為此它使用了InitiateReserveWithdraw ,其作用就是字面意思(開啟準備金提取) 。
“
現在我們在Statemint 的Holding Register 中持有1 個DOT。在我們可以做其他事情之前,我們需要在Statemint 上購買一些執行時間。這個過程看起來也很眼熟: