3个版本

2.0.0-pre.22020年11月29日
2.0.0-pre.12020年10月19日
2.0.0-pre.02020年10月18日

#51 in #ibc

Apache-2.0

110KB
2K SLoC

Substrate IBC Pallet(进行中)

crates.io Released API docs

该项目由Interchain Foundation资助。

目的

此模块实现了标准的IBC协议

此模块的目标是允许基于Substrate构建的区块链通过IBC协议以无需信任的方式与其他链进行交互,无论对方链使用何种共识机制。

该项目目前处于早期阶段,最终将被提交到上游。

ICS规范中的一些组件已实现以支持工作演示(https://github.com/cdot-network/ibc-demo),但尚未完全按照规范实现

  • ics-002-client-semantics
  • ics-003-connection-semantics
  • ics-004-channel-and-packet-semantics
  • ics-005-port-allocation
  • ics-010-grandpa-client
  • ics-018-relayer-algorithms
  • ics-025-handler-interface
  • ics-026-routing-module

以下是演示,展示了如何使用此模块,它初始化了一系列跨链通信步骤,从创建客户端到发送数据包。

依赖项

特质

此模块不依赖于任何外部定义的特质。

模块

此模块不依赖于任何其他FRAME模块或外部开发的模块。

安装

运行时 Cargo.toml

要将此模块添加到您的运行时,请将以下内容添加到您的运行时的Cargo.toml文件中

[dependencies.pallet-ibc]
default_features = false
git = 'https://github.com/cdot-network/substrate-ibc.git'

并更新您的运行时的std功能以包括此模块

std = [
    # --snip--
        'pallet-ibc/std',
	]

运行时 lib.rs

必须定义一个自定义结构,该结构实现pallet_ibc::ModuleCallbacks,以将消息分派到接收模块。

pub struct ModuleCallbacksImpl;

impl pallet_ibc::ModuleCallbacks for ModuleCallbacksImpl {
    # --snip--
}

您应该像这样实现它

/// Used for test_module
impl pallet_ibc::Trait for Runtime {
	type Event = Event;
	type ModuleCallbacks = ModuleCallbacksImpl;
}

并将其包含在您的construct_runtime!宏中

Ibc: pallet_ibc::{Module, Call, Storage, Event<T>},

创世配置

此托盘没有任何创世配置。

如何与托盘交互

运行时

在 ibc-demo 仓库中,substrate-subxt 通过宏 substrate_subxt_proc_macro::Call 调用托盘的可调用函数。

以函数 test_create_client 为例。 Client 扩展了该函数

// in https://github.com/cdot-network/ibc-demo/blob/master/pallets/template/src/lib.rs
pub fn test_create_client(
    origin,
    identifier: H256,
    height: u32,
    set_id: SetId,
    authorities: AuthorityList,
    root: H256
) -> dispatch::DispatchResult {
...
}

通过

// https://github.com/cdot-network/ibc-demo/blob/master/calls/src/template.rs
#[derive(Encode, Call)]
pub struct TestCreateClientCall<T: TemplateModule> {
    pub _runtime: PhantomData<T>,
    pub identifier: H256,
    pub height: u32,
    pub set_id: SetId,
    pub authority_list: AuthorityList,
    pub root: H256,
}

因此,

//  https://github.com/cdot-network/ibc-demo/blob/master/cli/src/main.rs
client
.test_create_client(...)

可以调用 test_create_client 函数。

请参阅文档 substrate_subxt_proc_macro::Call 获取详细信息。

单元测试

在单元测试中,我们遵循 substrate 的文档 Runtime Tests

模拟环境在 mock.rs 中构建;在 tests.rs 中,测试了托盘的可调用函数。

源代码中的实现逻辑

同步其他链的区块头

  • 中继通过调用 Datagram::ClientUpdate 挂钩将其他链的最新区块头发送到 ibc 托盘
// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
pub fn handle_datagram(datagram: Datagram) -> dispatch::DispatchResult {
    match datagram {
        Datagram::ClientUpdate { identifier, header } => {   // <--- "Datagram::ClientUpdate" will be matached
  • 如果经过验证,则将传入的区块头的 commitment_root 和区块高度插入到存储 ConsensusStates 中。
// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
ConsensusStates::insert((identifier, header.height), new_consensus_state);

连接打开握手 - ICS-003

Opening Handshake 中的表格所示,两个链(A & B)之间的握手包含 4 个步骤。

发起者 数据报 受影响的链 初始状态(A,B) 后续状态(A,B)
行为者 ConnOpenInit A (none, none) (INIT, none)
中继 ConnOpenTry B (INIT, none) (INIT, TRYOPEN)
中继 ConnOpenAck A (INIT, TRYOPEN) (OPEN, TRYOPEN)
中继 ConnOpenConfirm B (OPEN, TRYOPEN) (OPEN, OPEN)

(none, none) -> (INIT, none)

这是通过一个行为者完成的,该行为者在链 A 中调用函数 conn_open_init

// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
pub fn conn_open_init(
    identifier: H256,
    desired_counterparty_connection_identifier: H256,
    client_id: H256,
    counterparty_client_id: H256,
) -> dispatch::DispatchResult {
...
}

(INIT, none) -> (INIT, TRYOPEN)

中继检测到链 A 连接的 INIT 状态,然后通过调用链 B 的函数 pub fn handle_datagram(datagram: Datagram) 尝试将链 B 的连接状态设置为 TRYOPEN,其中 Datagram::ConnOpenTry 将被匹配。

// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
pub fn handle_datagram(datagram: Datagram) -> dispatch::DispatchResult {
    match datagram {
        ...
        Datagram::ConnOpenTry {
            ...
        }

(INIT, TRYOPEN) -> (OPEN, TRYOPEN)

中继检测到链 B 连接的 TRYOPEN,然后通过调用链 A 的函数 pub fn handle_datagram(datagram: Datagram) 尝试将链 A 的连接状态设置为 OPEN,其中 Datagram::ConnOpenAck 将被匹配。

// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
pub fn handle_datagram(datagram: Datagram) -> dispatch::DispatchResult {
    match datagram {
        ...
        Datagram::ConnOpenAck {
            ...
        }

(OPEN, TRYOPEN) -> (OPEN, OPEN)

中继器检测到链A连接的OPEN状态,然后尝试通过调用链B的函数pub fn handle_datagram(datagram: Datagram)将链B连接的状态设置为OPEN,其分支Datagram::ConnOpenConfirm将被匹配。

// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
pub fn handle_datagram(datagram: Datagram) -> dispatch::DispatchResult {
    match datagram {
        ...
        Datagram::ConnOpenConfirm {
            ...
        }

通道开启握手 - ICS-004

在两条链(A和B)完成连接握手后,它们能够通过连接握手建立通道。

根据通道生命周期管理中的表格,两条链(A和B)之间的握手包括4个步骤。

发起者 数据报 受影响的链 初始状态(A,B) 后续状态(A,B)
行为者 ChanOpenInit A (none, none) (INIT, none)
中继 ChanOpenTry B (INIT, none) (INIT, TRYOPEN)
中继 ChanOpenAck A (INIT, TRYOPEN) (OPEN, TRYOPEN)
中继 ChanOpenConfirm B (OPEN, TRYOPEN) (OPEN, OPEN)

(none, none) -> (INIT, none)

这由一个操作者完成,该操作者在链A中调用函数chan_open_init

// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
pub fn chan_open_init(
    ...
) -> dispatch::DispatchResult {
...
}

(INIT, none) -> (INIT, TRYOPEN)

中继器检测到链A通道的INIT状态,然后尝试通过调用链B的函数pub fn handle_datagram(datagram: Datagram)将链B通道的状态设置为TRYOPEN,其分支Datagram::ChanOpenTry将被匹配。

// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
pub fn handle_datagram(datagram: Datagram) -> dispatch::DispatchResult {
    match datagram {
        ...
        Datagram::ChanOpenTry {
            ...
        }

(INIT, TRYOPEN) -> (OPEN, TRYOPEN)

中继器检测到链B通道的TRYOPEN状态,然后尝试通过调用链A的函数pub fn handle_datagram(datagram: Datagram)将链A通道的状态设置为OPEN,其分支Datagram::ChanOpenAck将被匹配。

// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
pub fn handle_datagram(datagram: Datagram) -> dispatch::DispatchResult {
    match datagram {
        ...
        Datagram::ChanOpenAck {
            ...
        }

(OPEN, TRYOPEN) -> (OPEN, OPEN)

中继器检测到链A通道的OPEN状态,然后尝试通过调用链B的函数pub fn handle_datagram(datagram: Datagram)将链B通道的状态设置为OPEN,其分支Datagram::ChanOpenConfirm将被匹配。

// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
pub fn handle_datagram(datagram: Datagram) -> dispatch::DispatchResult {
    match datagram {
        ...
        Datagram::ChanOpenConfirm {
            ...
        }

数据包流与处理 - ICS-004

在两条链(A和B)完成通道握手后,它们能够在通道上相互发送数据包。

根据数据包流与处理中的流程图,从链A向链B发送数据包的标准流程包括3个步骤。

发送数据包

链A的ibc pallet中的可调用函数send_packet通过记录一个RawEvent::SendPacket事件来发送数据包。

// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
pub fn send_packet(packet: Packet) -> dispatch::DispatchResult {
    ...
    Self::deposit_event(RawEvent::SendPacket(
        ...
    ));
}

接收数据包并写入确认

中继器检测到链A的RawEvent::SendPacket事件,然后尝试调用链B的函数pub fn handle_datagram(datagram: Datagram),并匹配其分支Datagram::PacketRecv,以便链B接收数据包。

在接收到数据包后,链B将事件RawEvent::RecvPacket作为确认。

// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
pub fn handle_datagram(datagram: Datagram) -> dispatch::DispatchResult {
    match datagram {
        ...
        Datagram::PacketRecv {
            ...

            Self::deposit_event(RawEvent::RecvPacket(
                ...
            ));
        }

处理确认

中继器检测到链B的RawEvent::RecvPacket事件,然后尝试调用链A的函数pub fn handle_datagram(datagram: Datagram),并匹配其分支Datagram::PacketAcknowledgement,以便链A处理确认。

// https://github.com/cdot-network/substrate-ibc/blob/master/src/lib.rs
pub fn handle_datagram(datagram: Datagram) -> dispatch::DispatchResult {
    match datagram {
        ...
        Datagram::PacketAcknowledgement {
            ...
        }

参考文档

您可以通过运行以下命令查看此组件的参考文档:

cargo doc --open

或访问此网站:https://docs.rs/pallet-ibc

依赖项

~8–18MB
~242K SLoC