平行链上线技术系列四:Polkadot XCM从基础到实践

avatar
DAOrayaki
2年前
本文约5427字,阅读全文需要约7分钟
XCM是一种消息格式,而不是消息传递协议, 因为它无法在系统之间发送任何消息,仅仅是一种应如何执行消息传输的格式,也就是表达接收者应该做什么。

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})

平行链上线技术系列四:Polkadot XCM从基础到实践

消息路由

Cross-Consensus Protocols

随着 XCM Format 的正式建立,这些 messages 需要协议的通用模式,Polkadot实现了两个,用于在其组成的 parachain 之间处理XCM messages。

(分别是VMP和XCMP)

注:下文中的“消息” 意指上面提到的messages。

VMP(垂直消息传递)

实际上,有两种垂直消息传递传输协议。

  • UMP (向上消息传递):允许平行链向他的中继链发送消息

  • DMP (向下消息传递):允许中继链将消息向下传递到他们的平行链之一。

注意,

DMP 的消息也可能来自平行链,例如:平行链A转给平行链B的情况,这个过程是这样子的。

首先平行链A将使用

UMP,将消息传递给中继链,其次中继链再根据

DMP将消息向下传递给平行链B

平行链上线技术系列四:Polkadot XCM从基础到实践

XCMP(跨链消息传递)

XCMP 允许平行链与其中继链上其他的平行链交换消息。

跨链交易使用基于 Merkle 树的简单排队机制来解决,以确保fidelity(保真度)。中继链验证人的任务是将一个平行链的输出队列上的交易移动到目标平行链的输入队列中。但是,只有关联的 metadata 作为 hash 存储在中继链的存储中。

输入和输出队列有时在 Polkadot 代码库和相关文档中分别称为ingress(输入队列)和egress(输出队列)消息。(这里队列的概念下文会提到。)

XCMP-精简版(HRMP)

水平中继路由消息传递协议 (HRMP) 协议是未完全完成的功能的临时替代品。虽然 XCMP 本身仍在开发中,但 HRMP 是一个有效的替代品。

HRMP 具有与 XCMP 相同的接口和功能,但对资源的要求更高,因为它将所有消息存储在中继链存储中。当 XCMP 实施后,HRMP 计划被弃用并逐步淘汰以支持它。

XCMP

(思想:中继链只保存相关元数据的hash值,只做关于xcm的相关验证。)

平行链上线技术系列四:Polkadot XCM从基础到实践

由于现在XCMP还没有被完全开发出来,现在主要使用的是HRMP,上文也提到了,HRMP需要用到许多的资源,现在吞吐量 可能会是42条平行链上下。不过现在parity的开发者已经在把其他模块交易移到其他平行链 比如国库财政部分来提高吞吐能力和承载链数量。

小问题:

xcm消息执行失败了,怎么办呢(因为中继链最后敲定区块,所以能够解决回滚的问题)题外话:波卡网络上平行链只负责出块,所以没有grandpa共识,最后敲定区块上交给中继链决定的。

消息分发

Polkadots Messaging Scheme「5」

Polkadot 的跨链消息传递方案(XCMP)「6」

平⾏链阶段中,收集⼈打包区块的同时,也会将跨链交易放到平⾏链的出队列中。跨链交易通过

XCMP 协议进⾏传输,根据收集⼈和验证⼈的⽹络连接情况,

具体的传输⽅法有三种:

  • 发起链收集⼈直接发送给⽬标链收集⼈;

  • 发起链验证⼈发送给⽬标链收集⼈;发起链收集⼈发送给发起链验证⼈,发起链验证⼈传递给⽬标链验证⼈,⽬标链验证⼈再传递给⽬ 标链收集⼈。

  • 跨链交易传递到⽬标链后,会把跨链交易放到平⾏链⼊队列中。通过以上步骤,就完成了跨链交易在 链间的传递。

如果A和B不共享全节点,则需要依靠中继链上的验证⼈来传递消息。

队列

中继链验证者应负责将某⼀平⾏链上输出队列中的交易移⾄⽬标平⾏链上的输⼊队列中。

  • 收集⼈节点负责把平⾏链之间的信息传递。

  • 收集⼈产⽣出⼝列表信息并会在⼊⼝接收到其它平⾏ 链信息。

  • 当收集⼈产⽣了区块并提交给验证⼈,它会收集最新⼊⼝队列信息并且处理它(构造新区块 时会把以⾃⼰为⽬的地、还未被处理的跨链消息都处理下)。

  • 验证⼈将会对收集⼈提交的平⾏链区块进⾏验证,包括处理到该平⾏链预期⼊⼝的信息(看看跨链消息是否真的被处理过了,因此消息的⼀ 些元数据还是会上中继链的)。

cumulus 的 pallets 中两种队列:

平行链上线技术系列四:Polkadot XCM从基础到实践

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)进中继链中。

平行链上线技术系列四:Polkadot XCM从基础到实践

跨链资产转账详解:从基础到实践

上方都是关于XCM的基础部分,有了上面的知识,我们就进一步扩展讲讲其中的一些应用实现,例如跨链资产转账。

关于平行链之间进行资产转账会有一些细节。

跨链资产转账方式

XCM其实定义了两种转账的方式,一种是Asset Teleportation一种是Reserve Asset Transfer。

参考:How can I transfer assets using XCM?「8」

Asset Teleportation

平行链上线技术系列四:Polkadot XCM从基础到实践

这个转账模型只有两个参与者,源(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

平行链上线技术系列四:Polkadot XCM从基础到实践

过程: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

平行链的跨链转账一共有两种场景:

  • 平行链转中继链(向上转账)
    平行链转中继链都是转的中继链代币,如果想让自己的平行链能支持向中继链跨链转账的功能其实只需要配置XcmConfig就行。

  • 平行链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,
  // ..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;
  type Barrier = Barrier;
  type Weigher = FixedWeightBounds;
  type Trader = UsingComponents, 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> for CurrencyIdConvert {
  fn convert(id: CurrencyId) -> Option {
    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> for CurrencyIdConvert {
  fn convert(l: MultiLocation) -> Option {
    let aa: Vec = AA.into();
    let bb: Vec = 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> for CurrencyIdConvert {
  fn convert(asset: MultiAsset) -> Option {
    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;
  type GetNativeCurrencyId = GetNativeCurrencyId;
  type WeightInfo = ();
}
pub struct AccountIdToMultiLocation;
impl Convert 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(*baa/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;
  type Weigher = FixedWeightBounds;
  type BaseXcmWeight = BaseXcmWeight;
  type LocationInverter = LocationInverter;
  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;
  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;
}
// =====================================
// =====================================
// =====================================
// 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, Config},
    XTokens: orml_xtokens::{Pallet, Storage, Call, Event},
    UnknownTokens: orml_unknown_tokens::{Pallet, Storage, Event},
    Currencies: orml_currencies::{Pallet, Call, Event},
    OrmlXcm: orml_xcm::{Pallet, Call, Event},
    // ......
  }
);

3. 配置XcmConfig

Rust
// ==================================
// Barrier 起到过滤xcm消息的作用,如果不满足要求就会报 `Error: Barrier` 的错误
// ps: 如果在执行交易的时候,destweight太小会导致 `Error: Barrier`.
// ==================================
/// 配置parachain1000和parachain2000之间可以进行消息传递
match_type! {
  pub type SpecParachain: impl Contains = {
    // 当前上一级中继链下的parachain 1000
    MultiLocation {parents: 1, interior: X1(Parachain(1000))} |
    // 当前上一级中继链下的parachain 2000
    MultiLocation {parents: 1, interior: X1(Parachain(2000))}
  };
}
pub type Barrier = (
  TakeWeightCredit,
  AllowTopLevelPaidExecutionFrom,
  AllowUnpaidExecutionFrom,
  // ^^^ Parent and its exec plurality get free execution
  AllowUnpaidExecutionFrom,
);
// ==================================
// AssetTransactor 设置支持的资产类型
// ==================================
pub type LocalAssetTransactor = MultiCurrencyAdapter<
  Currencies,
  UnknownTokens,
  IsNativeConcrete,
  AccountId,
  LocationToAccountId,
  CurrencyId,
  CurrencyIdConvert,
  DepositToAlternative,
>;
// ==================================
// 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 wont know how to charge fees for a transfer of said token.
pub type Trader = (
  FixedRateOfFungible,
  FixedRateOfFungible,
  FixedRateOfFungible,
  FixedRateOfFungible,
);
parameter_types! {
  pub RocPerSecond: (AssetId, u128) = (MultiLocation::parent().into(), roc_per_second());
  pub NativePerSecond: (AssetId, u128) = (
    MultiLocation::new(
      1,
      X2(Parachain(1000), GeneralKey(bAA.to_vec()))
    ).into(),
    // AA:ROC = 80:1
    roc_per_second() * 80
  );
  pub NativeNewPerSecond: (AssetId, u128) = (
    MultiLocation::new(
      0,
      X1(GeneralKey(bAA.to_vec()))
    ).into(),
    // AA:ROC = 80:1
    roc_per_second() * 80
  );
  pub BbPerSecond: (AssetId, u128) = (
    MultiLocation::new(
      1,
      X2(Parachain(2000), GeneralKey(bBB.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;
  type Barrier = Barrier;
  type Weigher = FixedWeightBounds;
  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不指定任何账户,

平行链上线技术系列四:Polkadot XCM从基础到实践

类似这样子的写法,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

平行链上线技术系列四:Polkadot XCM从基础到实践

2.打开 2000 -> 1000

平行链上线技术系列四:Polkadot XCM从基础到实践

进行跨链资产转账

到这所有的准备工作都准备好了,我们可以进行xcm消息传递了也就是说可以进行跨链资产转移了。

1.链A向中继链转中继链代币

平行链上线技术系列四:Polkadot XCM从基础到实践

这里需要注意的是 只能往中继链转中继链代币,因为我用的是rococo-local,所以中继链代币tokenSymbol为ROC。

2.链A向链B转中继链代币

需要注意的是 这里的AccountId32需要我们将ss58的地址hex一下(另外一提,这个hex的内容其实就是账户的公钥)

转换工具:Substrate Utilities「12」

平行链上线技术系列四:Polkadot XCM从基础到实践

同理也可以把CurrencyId切换成平行链代币,比如链A的native token(AA)。

执行成功之后,我们可以去链B通过 Developer/ChainState 下的 tokens 模块查看余额。

平行链上线技术系列四:Polkadot XCM从基础到实践

总结

在实践之前需要先吃一些基础的知识,可以囫囵吞枣但是不能不去了解。

配置的时候确实牵扯到需要的配置项,不过细心的理解每个选项的含义,也能把问题修复好。

参考链接

「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」Polkadots Messaging Scheme:

https://medium.com/web3foundation/polkadots-messaging-scheme-b1ec560908b7

「6」Polkadot 的跨链消息传递方案(XCMP):

https://mp.weixin.qq.com/s?__biz=MzI3MzYxNzQ0Ng==mid=2247485114idx=1sn=b85ed58daaa075c7c332c204b3749e20chksm=eb21c1f3dc5648e588a01f860ae3cadd44c654270cc76a14b8b728e25a3b99ed03ccb97dc21fscene=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

原创文章,作者:DAOrayaki。转载/内容合作/寻求报道请联系 report@odaily.email;违规转载法律必究。

ODAILY提醒,请广大读者树立正确的货币观念和投资理念,理性看待区块链,切实提高风险意识;对发现的违法犯罪线索,可积极向有关部门举报反映。

推荐阅读
星球精选