XCM 基礎部分
指引:
Parachain Development · Polkadot Wiki「1」
XCM: The Cross-Consensus Message Format「2」
XCM消息格式
Overview of XCM: A Format, Not a Protocol
xcm-format 參考資料「3」
XCM is related to cross-chain in the same way that REST is related RESTful.
XCM是一種消息格式,而不是消息傳遞協議, 因為它無法在系統之間發送任何消息,僅僅是一種應如何執行消息傳輸的格式,也就是表達接收者應該做什麼。XCM還可用於通過UMP(向上信息傳遞)、DMP(向下消息傳遞)、XCMP(跨鏈消息傳遞)通信通道中的任意一個來表發消息的含義。
xcm消息裏帶有執行消息和位置信息
例如:鏈和鏈之間,合約與合約之間的xcm消息傳遞都可以用這套規範來實現,這些東西不能依托於鏈來實現,因為這套規範,如果依托於鏈本身,那每次鏈的升級都相當於做了一次xcm版本的升級。這很影響兼容性。
XCM 不僅僅是跨鏈,而是 " 跨共識 " 消息格式!-技術圈「4」
XCM 的核心 — XCVM(交叉共識虛擬機)
XCM format 的核心是 XCVM(交叉共識虛擬機),它是一種超高級非圖靈完備計算機,它的指令設計和交易大致處於同一水平。
XCM中的「消息」(就是上文的messages/消息)實際上只是XCVM上運行的程序。它是一個或多個 XCM指令。程序一直執行,直到它運行到最後或遇到錯誤,此時它完成並停止。
XCVM 包括許多寄存器,以及對托管它的共識系統的整體狀態的訪問。指令可能會更改寄存器,它們可能會更改共識系統的狀態或兩者兼而有之。
MultiLocations
可以理解為共識系統語義下的「位置」
需要註意的是,所有位置都是從消息解釋器的相對角度來看的。就如下面的例如...
從 Parachain A 的⻆度來看:
- Relay Chain: X1(Parent)
- Parachain A: Self
- Parachain B: X2(Parent, Parachain B)
- Contract A: X1(Contract A)
- Contract B: X3(Parent, Parachain B, Contract B)
- Alice: X2(Contract A, AcountId32{Alice})
- Bob: X1(AcountId32{Bob})
- Charlie: X3(Parent, Parachain B, AcountId32{Charlie})
- Dave: X4(Parent, Parachain B, Contract B, AcountId32{Dave})
消息路由
Cross-Consensus Protocols
隨著 XCM Format 的正式建立,這些 messages 需要協議的通用模式,Polkadot實現了兩個,用於在其組成的 parachain 之間處理XCM messages。
(分別是VMP和XCMP)
註:下文中的「消息」 意指上面提到的messages。
VMP(垂直消息傳遞)
實際上,有兩種垂直消息傳遞傳輸協議。
UMP (向上消息傳遞):允許平行鏈向他的中繼鏈發送消息
DMP (向下消息傳遞):允許中繼鏈將消息向下傳遞到他們的平行鏈之一。
註意,
DMP 的消息也可能來自平行鏈,例如:平行鏈A轉給平行鏈B的情況,這個過程是這樣子的。
首先平行鏈A將使用
UMP,將消息傳遞給中繼鏈,其次中繼鏈再根據
DMP將消息向下傳遞給平行鏈B
XCMP(跨鏈消息傳遞)
XCMP 允許平行鏈與其中繼鏈上其他的平行鏈交換消息。
跨鏈交易使用基於 Merkle 樹的簡單排隊機製來解決,以確保fidelity(保真度)。中繼鏈驗證人的任務是將一個平行鏈的輸出隊列上的交易移動到目標平行鏈的輸入隊列中。但是,只有關聯的 metadata 作為 hash 存儲在中繼鏈的存儲中。
輸入和輸出隊列有時在 Polkadot 代碼庫和相關文檔中分別稱為ingress(輸入隊列)和egress(輸出隊列)消息。(這裏隊列的概念下文會提到。)
XCMP-精簡版(HRMP)
水平中繼路由消息傳遞協議 (HRMP) 協議是未完全完成的功能的臨時替代品。雖然 XCMP 本身仍在開發中,但 HRMP 是一個有效的替代品。
HRMP 具有與 XCMP 相同的接口和功能,但對資源的要求更高,因為它將所有消息存儲在中繼鏈存儲中。當 XCMP 實施後,HRMP 計劃被棄用並逐步淘汰以支持它。
XCMP
(思想:中繼鏈只保存相關元數據的hash值,只做關於xcm的相關驗證。)
由於現在XCMP還沒有被完全開發出來,現在主要使用的是HRMP,上文也提到了,HRMP需要用到許多的資源,現在吞吐量 可能會是42條平行鏈上下。不過現在parity的開發者已經在把其他模塊交易移到其他平行鏈 比如國庫財政部分來提高吞吐能力和承載鏈數量。
小問題:
xcm消息執行失敗了,怎麼辦呢(因為中繼鏈最後敲定區塊,所以能夠解決回滾的問題)題外話:波卡網絡上平行鏈只負責出塊,所以沒有grandpa共識,最後敲定區塊上交給中繼鏈決定的。
消息分發
Polkadot's Messaging Scheme「5」
Polkadot 的跨鏈消息傳遞方案(XCMP)「6」
平⾏鏈階段中,收集⼈打包區塊的同時,也會將跨鏈交易放到平⾏鏈的出隊列中。跨鏈交易通過
XCMP 協議進⾏傳輸,根據收集⼈和驗證⼈的⽹絡連接情況,
具體的傳輸⽅法有三種:
- 發起鏈收集⼈直接發送給⽬標鏈收集⼈;
- 發起鏈驗證⼈發送給⽬標鏈收集⼈;發起鏈收集⼈發送給發起鏈驗證⼈,發起鏈驗證⼈傳遞給⽬標鏈驗證⼈,⽬標鏈驗證⼈再傳遞給⽬ 標鏈收集⼈。
- 跨鏈交易傳遞到⽬標鏈後,會把跨鏈交易放到平⾏鏈⼊隊列中。通過以上步驟,就完成了跨鏈交易在 鏈間的傳遞。
如果A和B不共享全節點,則需要依靠中繼鏈上的驗證⼈來傳遞消息。
隊列
中繼鏈驗證者應負責將某⼀平⾏鏈上輸出隊列中的交易移⾄⽬標平⾏鏈上的輸⼊隊列中。
- 收集⼈節點負責把平⾏鏈之間的信息傳遞。
- 收集⼈產⽣"出⼝"列表信息並會在"⼊⼝"接收到其它平⾏ 鏈信息。
- 當收集⼈產⽣了區塊並提交給驗證⼈,它會收集最新⼊⼝隊列信息並且處理它(構造新區塊 時會把以⾃⼰為⽬的地、還未被處理的跨鏈消息都處理下)。
- 驗證⼈將會對收集⼈提交的平⾏鏈區塊進⾏驗證,包括處理到該平⾏鏈預期⼊⼝的信息(看看跨鏈消息是否真的被處理過了,因此消息的⼀ 些元數據還是會上中繼鏈的)。
cumulus 的 pallets 中兩種隊列:
MQC(Message Queue Chain,消息隊列鏈)
消息隊列鏈是由驗證人創建的一個通用哈希鏈,用於跟蹤從發送方發送到單個接收方的每條消息以及順序。
MQC本身不保存在任何地方,而是只提供所有接受到的消息的最終證明。當驗證器接收到候選消息時,它從放置在upward_messages中的消息中按升序生成MQC
Feature XCMP
Cross-Consensus Message Format (XCM) · Polkadot Wiki「7」
平行鏈之間直接通信,在中繼鏈上只更新proofs和讀取confirmations,只包含驗證消息處理的必要信息。
假設平行鏈A上部署的一個合約要發一條跨鏈調用的消息給平行鏈B,從而調用位於鏈B上的合約完成資產轉移,其整體流程如下:
- (如圖中1)調用者(用戶)在鏈A上調用部署在鏈A上的智能合約,從而初始化一條以鏈B為目的地的跨鏈消息M;
- (如圖中2)鏈A的收集人(Collator)節點會將這條消息M連同其目的地(destination)、時間戳(timestamp)放到A的出口隊列(outbound messages queue)中;
- (如圖中3.1和3.2)鏈B的收集人在正常情況下會輪詢(routinely ping)其他所有的平行鏈的收集者節點以查看是否有自己的消息(以鏈B為目的地的消息)。如果在新一輪詢問(makes its next ping)中發現有以自己為目的地的消息,那麼其會將這條消息(比如這裏的消息M)放到自己的入口隊列(own inbound queue)中,以待在產生下一個區塊的時候處理該消息;
- (如圖中4)另外,鏈A的驗證人(Validator)也會通過讀取鏈A的出口隊列從而知道這條消息;鏈B的驗證人(Validator)也是。驗證人也需要知道這些消息,因為之後(見步驟6)它們都會對這條消息進行驗證(比如這裏的消息M,verify the message transmission happend);
- (如圖中5)當鏈B的收集人(Collator)節點開始構建(build)一個新區塊的時候,它會處理當前入口隊列中所有的消息(包括消息M);在處理過程中,消息M會執行鏈B中相應的智能合約以此完成預期的(跨鏈)資產轉移;
- (如圖中6)然後收集人(Collator)將這個區塊提交給驗證人(Validator),驗證人(Validator)會驗證消息M(以及其他消息)是否真的被處理了;如果這條消息被驗證確實處理了,並且這個區塊沒有其他不合法的地方,驗證者就會把該塊確認(include)進中繼鏈中。
跨鏈資產轉賬詳解:從基礎到實踐
上方都是關於XCM的基礎部分,有了上面的知識,我們就進一步擴展講講其中的一些應用實現,例如跨鏈資產轉賬。
關於平行鏈之間進行資產轉賬會有一些細節。
跨鏈資產轉賬方式
XCM其實定義了兩種轉賬的方式,一種是Asset Teleportation一種是Reserve Asset Transfer。
參考:How can I transfer assets using XCM?「8」
Asset Teleportation
這個轉賬模型只有兩個參與者,源(Source)和目的地(Destination)。
例子(偽代碼):
Rust // 鏈A的Alice向鏈B的Bob轉賬100個鏈A的native token_a
Transfer_teleport_asset(source_alice, dest_bob, token_a, 100);
過程:
- 首先先會在鏈A burn掉Alice的100個token_a,並記下burn掉的資產總量,
- 然後鏈A會創建一個名為 "ReceiveTeleportedAssets" 的XCM指令,並將burn掉的資產數量和鏈B的相對位置(這裏的相對位置其實就是上文中Mulitilocation的概念)作為這條XCM指令的參數,然後它會將這條指令發送到目的地(就是鏈B),在那裏它被處理並根據參數裏的信息mint新的資產。
- 最後鏈B會將新鍛造的資產存入Bob賬戶中。
缺點:
它要求來源和目的地都具有高度的相互信任。目的地必須相信來源已經燒毀了發送過來的資產,並且來源還必須相信目的地鑄造在來源處被燒毀的資產。
不支持這兩個條件中的任何一個都將導致資產的總發行量發生變化(在可替代代幣的情況下)或 NFT 的完全丟失/重復。
Reserve Asset Transfer
過程:chain A 上的 account1 想轉移某個資產到 chain B 上的 account2賬⼾⾥,那⾸先將 account 1的資產轉移⾄ chain A 上 的 chain B 代表賬⼾,再發送⼀條通知消息給 chain B,chain B 將對應的資產分配給 account2。
ps: 其實業界裏更推崇後者(reserve),相較於前者會更有保障。像orml-xtokens其實就是基於reserve方式實現的平行鏈多資產轉賬模塊。
為平行鏈添加跨鏈資產轉移的功能
我們接下來的目的就是創建兩條平行鏈,讓這兩條平行鏈支持多資產並且實現跨鏈資產轉賬。
實驗環境
我們會準備4個中繼鏈的驗證人節點以支持兩條平行鏈。因為我們要模擬鏈A到鏈B的跨鏈資產轉移以及平行鏈到中繼鏈的跨鏈資產轉移。
- 4個驗證人的中繼鏈
- 平行鏈A
- 平行鏈B
平行鏈的跨鏈轉賬一共有兩種場景:
1. 平行鏈轉中繼鏈(向上轉賬)
平行鏈轉中繼鏈都是轉的中繼鏈代幣,如果想讓自己的平行鏈能支持向中繼鏈跨鏈轉賬的功能其實只需要配置XcmConfig就行。
2. 平行鏈A轉平行鏈B(橫向轉賬)
平行鏈之間的轉賬會稍微復雜些,因為會涉及多資產轉賬的問題,這裏只需要配置XcmConfig以及添加orml模塊就行。
接下來我們直接分析整個完整的runtime配置來介紹一下配置跨鏈資產轉賬時需要註意的配置項以及其含義。
平行鏈轉中繼鏈
為兩條平行鏈添加支持向中繼鏈進行跨鏈轉賬的功能。
進行 runtime 配置
其實是關於XcmExecutor的配置,其中一項XcmConfig就是指定XcmSender. 這是你需要包含實現 XCMP 協議的pallet的地方。根據您要將 XCM 發送給誰,如果是要發送到中繼鏈,則需要包含parachain-system-pallet「9」,或者如果你要發送到同級平行鏈,則需要包含 xcmp-queue-pallet「10」。
Rust
/// queues.
pub type XcmRouter = (
// Two routers - use UMP to communicate with the relay chain:
// ================================
// 需要修改的地方:最後我們是需要支持平行鏈到中繼鏈,平行鏈到平行鏈,所以兩個配置我們都要加。
cumulus_primitives_utility::ParentAsUmp<ParachainSystem, PolkadotXcm>,
// ..and XCMP to communicate with the sibling chains.
XcmpQueue,
// ================================
);
// ......
// ......
// ......
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type Call = Call;
type XcmSender = XcmRouter;
// How to withdraw and deposit an asset.
type AssetTransactor = LocalAssetTransactor;
type OriginConverter = XcmOriginToTransactDispatchOrigin;
type IsReserve = NativeAsset;
type IsTeleporter = (); // Teleporting is disabled.
type LocationInverter = LocationInverter<Ancestry>;
type Barrier = Barrier;
type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
type Trader = UsingComponents<IdentityFee<Balance>, RelayLocation, AccountId, Balances, ()>;
type ResponseHandler = PolkadotXcm;
type AssetTrap = PolkadotXcm;
type AssetClaims = PolkadotXcm;
type SubscriptionService = PolkadotXcm;
}
平行鏈A轉平行鏈B
為平行鏈A和平行鏈B配置ORML相關庫以實現平行鏈之間的跨鏈資產轉移將會用到
ORML的一些依賴庫:
- orml-xtokens
為平行鏈提供跨鏈資產轉移的方式。
- orml-tokens
用於查詢多資產額度的模塊
- orml-currencies
可以使用currencies模塊實現鏈內的多資產轉賬,可以理解為 currency plus😁。
- orml-traits
共享一些trait,包括 BasicCurrency、MultiCurrency、Auction等trait。
- orml-xcm-support
提供 types、traits和 implementations 以支持XCM集成
最後實現的效果:
鏈A的Alice通過 xtokens 模塊進行跨鏈資產轉移將 token_a 轉移給鏈B的Bob,轉賬成功後,鏈B上的Bob通過 tokens 模塊查看 token_a 的余額,然後通過 currencies 模塊將一部分的token_a轉賬給鏈B上的Alice。
進行 runtime 配置
為了平行鏈能夠支持多資產轉移,我們除了需要添加上面的ORML依賴庫,還需要做一些定製化的配置。
鏈A和鏈B上需要實現CurrencyId和CurrencyIdConvert,一個是代幣的tokenSymbol的list一個是將tokenSymbol轉換成multilocation的轉換器。
引入orml標準庫
還需要進一步配置XcmExcuter,其中包括跨鏈轉賬時手續費的收費規則(XcmConfig::Trader)、XCM過濾器(Xcm?Config::Barrier)以及如何存取資產的配置(XcmConfig::AssetTransactor)
兩條鏈都需要配置這些內容,下面我主要以鏈A為例。鏈B同理。
1. 配置 CurrencyId 和 CurrencyIdConvert
假設條件:
平行鏈A -> TokenSymbol:AA -> ParachainId:1000
平行鏈B -> TokenSymbol:BB -> ParachainId:2000
Rust
#[derive(
Encode,
Decode,
Eq,
PartialEq,
Copy,
Clone,
RuntimeDebug,
PartialOrd,
Ord,
codec::MaxEncodedLen,
TypeInfo,
)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum CurrencyId {
// / Relay chain token.
ROC,
// Native TokenSymbol
AA,
// ===============================
// 1. 添加支持的代幣tokenSymbol
// ===============================
BB
}
pub type Amount = i128;
// ===============================
// 2. 設置 CurrencyIdConvert,(CurrencyId轉MultiLocation的轉換方式)
// ===============================
pub struct CurrencyIdConvert;
impl Convert<CurrencyId, Option<MultiLocation>> for CurrencyIdConvert {
fn convert(id: CurrencyId) -> Option<MultiLocation> {
match id {
CurrencyId::ROC => Some(Parent.into()), // 支持中繼鏈代幣的轉換
CurrencyId::AA => Some((Parent, Parachain(1000), GeneralKey("AA".into())).into()),
CurrencyId::BB => Some((Parent, Parachain(2000), GeneralKey("BB".into())).into()),
}
}
}
impl Convert<MultiLocation, Option<CurrencyId>> for CurrencyIdConvert {
fn convert(l: MultiLocation) -> Option<CurrencyId> {
let aa: Vec<u8> = "AA".into();
let bb: Vec<u8> = "BB".into();
if l == MultiLocation::parent() {
return Some(CurrencyId::ROC);
}
match l {
MultiLocation { parents, interior } if parents == 1 => match interior {
X2(Parachain(1000), GeneralKey(k)) if k == aa => Some(CurrencyId::AA),
X2(Parachain(2000), GeneralKey(k)) if k == bb => Some(CurrencyId::BB),
_ => None,
},
MultiLocation { parents, interior } if parents == 0 => match interior {
X1(GeneralKey(k)) if k == aa => Some(CurrencyId::AA),
X1(GeneralKey(k)) if k == bb => Some(CurrencyId::BB),
_ => None,
},
_ => None,
}
}
}
impl Convert<MultiAsset, Option<CurrencyId>> for CurrencyIdConvert {
fn convert(asset: MultiAsset) -> Option<CurrencyId> {
if let MultiAsset {
id: Concrete(id),
..
} = asset
{
Self::convert(id)
} else {
Option::None
}
}
}
2. 引入orml標準庫
配置完CurrencyId和CurrencyIdConvert之後就可以進行引入orml標準庫的工作了。
Rust
// xcm_config.rs
use orml_currencies::BasicCurrencyAdapter;
use orml_traits::parameter_type_with_key;
use orml_xcm_support::{
DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset,
};
//
// ......
//
parameter_types! {
pub const GetNativeCurrencyId: CurrencyId = CurrencyId::AA;
}
impl orml_currencies::Config for Runtime {
type Event = Event;
type MultiCurrency = Tokens;
type NativeCurrency = BasicCurrencyAdapter<Runtime, Balances, Amount, BlockNumber>;
type GetNativeCurrencyId = GetNativeCurrencyId;
type WeightInfo = ();
}
pub struct AccountIdToMultiLocation;
impl Convert<AccountId, MultiLocation> for AccountIdToMultiLocation {
fn convert(account: AccountId) -> MultiLocation {
X1(AccountId32 { network: NetworkId::Any, id: account.into() }).into()
}
}
parameter_types! {
pub SelfLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::parachain_id().into())));
pub const BaseXcmWeight: Weight = 100_000_000;
pub const MaxAssetsForTransfer: usize = 2;
pub const TreasuryPalletId: PalletId = PalletId(*b"aa/trsry");
}
parameter_type_with_key! {
pub ParachainMinFee: |location: MultiLocation| -> u128 {
#[allow(clippy::match_ref_pats)] // false positive
match (location.parents, location.first_interior()) {
(1, Some(Parachain(3000))) => 4_000_000_000,
_ => u128::MAX,
}
};
}
impl orml_xtokens::Config for Runtime {
type Event = Event;
type Balance = Balance;
type CurrencyId = CurrencyId;
type CurrencyIdConvert = CurrencyIdConvert;
type AccountIdToMultiLocation = AccountIdToMultiLocation;
type SelfLocation = SelfLocation;
type MinXcmFee = ParachainMinFee;
type XcmExecutor = XcmExecutor<XcmConfig>;
type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
type BaseXcmWeight = BaseXcmWeight;
type LocationInverter = LocationInverter<Ancestry>;
type MaxAssetsForTransfer = MaxAssetsForTransfer;
}
parameter_type_with_key! {
pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance {
// every currency has a zero existential deposit
match currency_id {
_ => 0,
}
};
}
parameter_types! {
pub ORMLMaxLocks: u32 = 2;
pub NativeTreasuryAccount: AccountId = TreasuryPalletId::get().into_account();
}
impl orml_tokens::Config for Runtime {
type Event = Event;
type Balance = Balance;
type Amount = Amount;
type CurrencyId = CurrencyId;
type WeightInfo = ();
type ExistentialDeposits = ExistentialDeposits;
// type OnDust = orml_tokens::TransferDust<Runtime, NativeTreasuryAccount>;
type OnDust = ();
type MaxLocks = ORMLMaxLocks;
type DustRemovalWhitelist = Nothing;
}
// orml unknown tokens
impl orml_unknown_tokens::Config for Runtime {
type Event = Event;
}
impl orml_xcm::Config for Runtime {
type Event = Event;
type SovereignOrigin = EnsureRoot<AccountId>;
}
// =====================================
// =====================================
// =====================================
// runtime/src/lib.rs
// Create the runtime by composing the FRAME pallets that were previously configured.
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
// ......
Tokens: orml_tokens::{Pallet, Storage, Event<T>, Config<T>},
XTokens: orml_xtokens::{Pallet, Storage, Call, Event<T>},
UnknownTokens: orml_unknown_tokens::{Pallet, Storage, Event},
Currencies: orml_currencies::{Pallet, Call, Event<T>},
OrmlXcm: orml_xcm::{Pallet, Call, Event<T>},
// ......
}
);
3. 配置XcmConfig
Rust
// ==================================
// Barrier 起到過濾xcm消息的作用,如果不滿足要求就會報 `Error: Barrier` 的錯誤
// ps: 如果在執行交易的時候,destweight太小會導致 `Error: Barrier`.
// ==================================
/// 配置parachain1000和parachain2000之間可以進行消息傳遞
match_type! {
pub type SpecParachain: impl Contains<MultiLocation> = {
// 當前上一級中繼鏈下的parachain 1000
MultiLocation {parents: 1, interior: X1(Parachain(1000))} |
// 當前上一級中繼鏈下的parachain 2000
MultiLocation {parents: 1, interior: X1(Parachain(2000))}
};
}
pub type Barrier = (
TakeWeightCredit,
AllowTopLevelPaidExecutionFrom<Everything>,
AllowUnpaidExecutionFrom<ParentOrParentsExecutivePlurality>,
// ^^^ Parent and its exec plurality get free execution
AllowUnpaidExecutionFrom<SpecParachain>,
);
// ==================================
// AssetTransactor 設置支持的資產類型
// ==================================
pub type LocalAssetTransactor = MultiCurrencyAdapter<
Currencies,
UnknownTokens,
IsNativeConcrete<CurrencyId, CurrencyIdConvert>,
AccountId,
LocationToAccountId,
CurrencyId,
CurrencyIdConvert,
DepositToAlternative<NativeTreasuryAccount, Currencies, CurrencyId, AccountId, Balance>,
>;
// ==================================
// Trader 配置跨鏈轉賬手續費的收費規則,這個手續費是其他鏈給我們鏈進行跨鏈轉賬的時候,我們平行鏈會收取一定的手續費。
// ==================================
use frame_support::{ExtrinsicBaseWeight, WEIGHT_PER_SECOND};
pub const MICROUNIT: Balance = 1_000_000;
pub const MILLICENTS: Balance = 1_000 * MICROUNIT;
pub const CENTS: Balance = 1_000 * MILLICENTS; // assume this is worth about a cent.
pub const DOLLARS: Balance = 100 * CENTS;
pub fn roc_per_second() -> u128 {
let base_weight = Balance::from(ExtrinsicBaseWeight::get());
let base_tx_fee = DOLLARS / 1000;
let base_tx_per_second = (WEIGHT_PER_SECOND as u128) / base_weight;
let fee_per_second = base_tx_per_second * base_tx_fee;
fee_per_second / 100
}
/// Trader - The means of purchasing weight credit for XCM execution.
/// We need to ensure we have at least one rule per token we want to handle or else
/// the xcm executor won't know how to charge fees for a transfer of said token.
pub type Trader = (
FixedRateOfFungible<RocPerSecond, ()>,
FixedRateOfFungible<NativePerSecond, ()>,
FixedRateOfFungible<NativeNewPerSecond, ()>,
FixedRateOfFungible<BbPerSecond, ()>,
);
parameter_types! {
pub RocPerSecond: (AssetId, u128) = (MultiLocation::parent().into(), roc_per_second());
pub NativePerSecond: (AssetId, u128) = (
MultiLocation::new(
1,
X2(Parachain(1000), GeneralKey(b"AA".to_vec()))
).into(),
// AA:ROC = 80:1
roc_per_second() * 80
);
pub NativeNewPerSecond: (AssetId, u128) = (
MultiLocation::new(
0,
X1(GeneralKey(b"AA".to_vec()))
).into(),
// AA:ROC = 80:1
roc_per_second() * 80
);
pub BbPerSecond: (AssetId, u128) = (
MultiLocation::new(
1,
X2(Parachain(2000), GeneralKey(b"BB".to_vec()))
).into(),
// BB:ROC = 100:1
roc_per_second() * 100
);
}
// ======================================
// XcmConfig
// ======================================
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type Call = Call;
type XcmSender = XcmRouter;
// How to withdraw and deposit an asset.
type AssetTransactor = LocalAssetTransactor;
type OriginConverter = XcmOriginToTransactDispatchOrigin;
type IsReserve = MultiNativeAsset;
type IsTeleporter = (); // Teleporting is disabled.
type LocationInverter = LocationInverter<Ancestry>;
type Barrier = Barrier;
type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
type Trader = Trader;
type ResponseHandler = PolkadotXcm;
type AssetTrap = PolkadotXcm;
type AssetClaims = PolkadotXcm;
type SubscriptionService = PolkadotXcm;
}
ps: 關於Trader機製的解釋。
轉賬的手續費,一般是把轉賬的手續費充到國庫。(轉賬的成本)
需要開發者自己設置weight的比例,這個weight的意思就是一秒鐘的時間消耗大概多少的手續費,這個weight其實就是時間復雜度。這裏需要註意一下:如果不調整的話,大夥轉賬消耗的都是一個代幣,但是一個ksm得幾百u,一個平行鏈代幣卻是幾u,這個相差就有點大,就有可能被攻擊(比如瘋狂的轉賬,導致交易堵塞,類似DDOS的攻擊)。
如果是非平行鏈的話,這個手續費會給驗證人,但是平行鏈沒有驗證人,只能給國庫。
如果Trader不指定任何賬戶,
類似這樣子的寫法,gas就相當於burn掉了。
如果是給國庫,那生態的人可以通過治理模塊支配國庫內的額度。
如果是直接burn掉,就類似通縮模型。轉的越多,代幣越少。
打開Hrmp
在這裏之前,請確保你鏈A和鏈B都進行了上面的配置。
我們本地需要啟動4個驗證人的一條中繼鏈,然後把兩條平行鏈註冊上去(一條1000一條2000)
打開Hrmp通道有兩種方法,一種是在中繼鏈上直接通過sudo打開,另外一種則是在平行鏈上利用orml-xcm打開hrmp。
註意的是,hrmp是一個單向的通道,我們需要實現雙向打通,就必須打通兩次(1000->2000, 2000->1000)
這裏以中繼鏈上通過sudo為例,後者可以根據acala的wiki為參考: Open HRMP Channel「11」.
在 Developer/Sudo 下 通過 parasSudoWrapper.sudoEstablishHrmpChannel 來打開 1000->2000 和 2000->1000 的hrmp通道。
1.打開 1000 -> 2000
2.打開 2000 -> 1000
進行跨鏈資產轉賬
到這所有的準備工作都準備好了,我們可以進行xcm消息傳遞了也就是說可以進行跨鏈資產轉移了。
1.鏈A向中繼鏈轉中繼鏈代幣
這裏需要註意的是 只能往中繼鏈轉中繼鏈代幣,因為我用的是rococo-local,所以中繼鏈代幣tokenSymbol為ROC。
2.鏈A向鏈B轉中繼鏈代幣
需要註意的是 這裏的AccountId32需要我們將ss58的地址hex一下(另外一提,這個hex的內容其實就是賬戶的公鑰)
轉換工具:Substrate Utilities「12」
同理也可以把CurrencyId切換成平行鏈代幣,比如鏈A的native token(AA)。
執行成功之後,我們可以去鏈B通過 Developer/ChainState 下的 tokens 模塊查看余額。
總結
在實踐之前需要先吃一些基礎的知識,可以囫圇吞棗但是不能不去了解。
配置的時候確實牽扯到需要的配置項,不過細心的理解每個選項的含義,也能把問題修復好。
參考鏈接
「1」Parachain Development · Polkadot Wiki:
https://wiki.polkadot.network/docs/build-pdk#how-to-make-cross-chain-transfers
「2」XCM: The Cross-Consensus Message Format:
https://medium.com/polkadot-network/xcm-the-cross-consensus-message-format-3b77b1373392
「3」xcm-format 參考資料:
https://wiki.polkadot.network/docs/learn-crosschain#overview-of-xcm-a-format-not-a-protocol
「4」XCM 不僅僅是跨鏈,而是 " 跨共識 " 消息格式!:
https://jishuin.proginn.com/p/763bfbd6d91c#:~:text=XCM是一種消息,表發消息的含義。
「5」Polkadot's Messaging Scheme:
https://medium.com/web3foundation/polkadots-messaging-scheme-b1ec560908b7
「6」Polkadot 的跨鏈消息傳遞方案(XCMP):
https://mp.weixin.qq.com/s?__biz=MzI3MzYxNzQ0Ng==&mid=2247485114&idx=1&sn=b85ed58daaa075c7c332c204b3749e20&chksm=eb21c1f3dc5648e588a01f860ae3cadd44c654270cc76a14b8b728e25a3b99ed03ccb97dc21f&scene=21
「7」Cross-Consensus Message Format (XCM) · Polkadot Wiki:
https://wiki.polkadot.network/docs/learn-crosschain
「8」How can I transfer assets using XCM?:
https://substrate.stackexchange.com/questions/37/how-can-i-transfer-assets-using-xcm/38#38
「9」parachain-system-pallet:
https://github.com/paritytech/cumulus/tree/master/pallets/parachain-system
「10」xcmp-queue-pallet:
https://github.com/paritytech/cumulus/tree/master/pallets/xcmp-queue
「11」Open HRMP Channel:
https://wiki.acala.network/build/development-guide/composable-chains/open-hrmp-channel
「12」Substrate Utilities:
https://www.shawntabrizi.com/substrate-js-utilities/
XCM Part II:
Versioning and Compatibilityhttps://medium.com/polkadot-network/xcm-part-ii-versioning-and-compatibility-b313fc257b83
XCM Part III:
Execution and Error Management:
https://medium.com/polkadot-network/xcm-part-iii-execution-and-error-management-ceb8155dd166
Polkadot Messaging Guide - HackMD:
https://hackmd.io/S4TZc1uTQH-vxEWv-QFapg
Sub0 Online:
Getting Started with XCM - Your First Cross Chain Messages:https://www.youtube.com/watch?v=5cgq5jOZx9g
Polkadot Launch Phases:
https://wiki.polkadot.network/docs/learn-launch
Acala & Karura Wiki:
https://wiki.acala.network/
Collator · Polkadot Wiki:
https://wiki.polkadot.network/docs/learn-collator