Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic message channel protocol #41

Merged
merged 54 commits into from
Jul 8, 2021
Merged

Basic message channel protocol #41

merged 54 commits into from
Jul 8, 2021

Conversation

hujw77
Copy link
Collaborator

@hujw77 hujw77 commented Jun 10, 2021

Message

A message is used by an application on one chain to call a function with parameters on another chain. You may be familiar with that concept as a remote procedure call or RPC. A message contains data to identify the source application, target application and a payload which the target application is expected to understand.

Channel

A channel is a concept used as part of the bridge which facilitates the delivery of messages in a single direction. A channel consists of a sender and a receiver, each being a piece of business logic that runs on opposite chains, as well as a feature set as described below. Any user or system wanting to send a message across the bridge must submit it to the channel. Different channels may exist that provide varying deliverability guarantees to a message, such as replay protection across multiple messages.

For more ideas around channels, see Channels

Basic Inbound Message Channel (Polkadot → Ethereum)

This basic channel provides a mechanism for sending messages out from Polkadot to Ethereum via darwinia message commitments. It consists of Commitments pallet, which operates a basic channel that provides replay protection.

The Commitments pallet is responsible for accepting requests from other pallets or parachains via XCMP for Ethereum RPCs to be sent over to Ethereum. After accepting a request, it adds a nonce and puts the message into a queue. At a fixed interval (initially once a minute) it produces a commitment to that queue in the form of a single hash of all the messages in that queue that is added to the darwinia header.

The corresponding receiving Darwinia Light Client smart contract accepts as input this commitment as well as the set of messages in that commitment. It validates the inclusion of the commitment in the MMR as described here and then verifies that those messages are included in the commitment by hashing them and then processes them in order by calling out to the Ethereum smart contracts for each Ethereum RPC.

Basic Outbound Message Channel (Ethereum → Polkadot)

This basic channel provides a mechanism for sending messages out from Ethereum to Polkadot via events. It consists of a smart contract on the Ethereum side and a corresponding pallet on the parachain side.

The smart contract is responsible for accepting requests from other smart contracts for Polkadot RPCs to be sent over to Polkadot. It uses Ethereum Events as the medium for sending messages, so after accepting a request, it adds a nonce and emits an event that the corresponding pallet will receive on the Polkadot side.

The corresponsing receiving pallet accepts as input a set of events and a transaction receipt proof, calls out to the Ethereum Light Client Verifier to confirm that the containing block is considered final by having a predefined number of confirmations and to verify that each Ethereum event is in fact valid and included in the Ethereum chain, and then it processes those events by forwarding them to their destination pallet.

Mapping token dapp

Mapping token dapp is a cross-chain dApp that facilitates the registration, creation and movement of pegged ERC20 assets. It is based on the security of the basic channel and focuses on its own cross-chain transfer logic.

@hujw77
Copy link
Collaborator Author

hujw77 commented Jun 11, 2021

MMRLeaf Spec

struct BeefyMMRLeaf {
    bytes32 parentHash;
    bytes32 messagesRoot;
    uint32 blockNumber;
}

MMR Leaf Encode

keccak256(
    abi.encodePacked(
        leaf.parentHash,
        leaf.messagesRoot,
        leaf.blockNumber
    )
);

Message commitment

keccak256(abi.encode(messages)) == commitment

Inbound Message Spec

struct Message {
    address target;
    uint64 nonce;
    bytes payload;
}
event MessageDispatched(uint64 nonce, bool result);
  1. MMRLeaf should be verify by Light client bridge The light client 2-phase interactive verification protocol #38 .
  2. keccak256(abi.encode(messages)) == commitment
    Ref: https://github.com/Snowfork/polkadot-ethereum/blob/f28dc43a7267ab0e152cfd79e67244b0da0d34ed/parachain/pallets/basic-channel/src/outbound/mod.rs#L204

OutBound Message Spec

event Message(
    address source,
    uint64 nonce,
    bytes payload
);

Dapps only needs to define its own payload and which basic channel they use.

Mapping Token Dapp Spec

Inbound message

bytes payload =  abi.encodeWithSignature("mappingToken(address,address)",
                                    backing,
                                    source
                              );

Outbound message

bytes memory call = encodeCall(sig, params);
outbound_channel.submit(msg.sender, call);

@hackfisher
Copy link
Contributor

@hujw77 hujw77 linked an issue Jul 6, 2021 that may be closed by this pull request
@xiaoch05
Copy link
Contributor

xiaoch05 commented Jul 7, 2021

https://github.com/darwinia-network/darwinia-bridge-sol/blob/master/contracts/utils/contracts/Bitfield.sol#L94-L114
this function called so many times. the following change can save some gas
index / 256 ==> index >> 8
index % 256 ==> index & 255

@xiaoch05
Copy link
Contributor

xiaoch05 commented Jul 8, 2021

还是觉得这里的设计有问题
https://github.com/darwinia-network/darwinia-bridge-sol/blob/channel/contracts/bridge/contracts/BasicInboundChannel.sol#L93
如果这里的call出现了异常revert
第一种场景:忽略返回值success, 使用这种方式忽略应用层的revert,那么消息已经被执行,但是结果不是预期的,且无法重试。
第二种场景:要求success返回值必须成功,否则该处也revert,这时nonce不会增加,会阻塞后续的所有消息。

@hujw77
Copy link
Collaborator Author

hujw77 commented Jul 8, 2021

还是觉得这里的设计有问题
https://github.com/darwinia-network/darwinia-bridge-sol/blob/channel/contracts/bridge/contracts/BasicInboundChannel.sol#L93
如果这里的call出现了异常revert
第一种场景:忽略返回值success, 使用这种方式忽略应用层的revert,那么消息已经被执行,但是结果不是预期的,且无法重试。
第二种场景:要求success返回值必须成功,否则该处也revert,这时nonce不会增加,会阻塞后续的所有消息。

首先, 这里这个作为基础的通道, 它的职责是保证消息的可达性, 并不保证消息的正确性, 所以这里可以是允许消息revert, 且它提供消息的回执, event MessageDispatched(uint64 nonce, bool result);. 所以不会要求返回值必须是success. 如果应用层需要重试, 可以再发一条新的消息, 或者对错误的原因分析后再发一条正确的消息来保证应用层的逻辑.
需要重试的逻辑在于, 消息没有回执的情况才会去重试, 如果知道了回执还去重试, 是没有必要的

@xiaoch05
Copy link
Contributor

xiaoch05 commented Jul 8, 2021

还是觉得这里的设计有问题
https://github.com/darwinia-network/darwinia-bridge-sol/blob/channel/contracts/bridge/contracts/BasicInboundChannel.sol#L93
如果这里的call出现了异常revert
第一种场景:忽略返回值success, 使用这种方式忽略应用层的revert,那么消息已经被执行,但是结果不是预期的,且无法重试。
第二种场景:要求success返回值必须成功,否则该处也revert,这时nonce不会增加,会阻塞后续的所有消息。

首先, 这里这个作为基础的通道, 它的职责是保证消息的可达性, 并不保证消息的正确性, 所以这里可以是允许消息revert, 且它提供消息的回执, event MessageDispatched(uint64 nonce, bool result);. 所以不会要求返回值必须是success. 如果应用层需要重试, 可以再发一条新的消息, 或者对错误的原因分析后再发一条正确的消息来保证应用层的逻辑.
需要重试的逻辑在于, 消息没有回执的情况才会去重试, 如果知道了回执还去重试, 是没有必要的

我理解协议是要求不需要回执的,https://www.notion.so/itering/Basic-Message-Channel-c41f0c9e453c478abb68e93f6a067c52 ,“它们是异步的、单向的调用,不需要任何回应”,意思是说正确的消息一定会被对方验证、接收并得到执行。一般来说我们没有从源链上面重试的机会,只能重试该消息。比如用户在源链上锁定一笔资产,在Ethereum上面发行,发行失败,如果在源链上面重试相当于重新锁定一笔资产。

@hujw77
Copy link
Collaborator Author

hujw77 commented Jul 8, 2021

正确的消息一定会被对方验证、接收并得到执行。

这里确实存在一定的歧义, 但是在实践中, 如果我们保证APP消息执行的正确性, 是不需要回执的, 但是往往会出现意料之外的错误, 这时候即使重试也是无济于事的, 只能在保证APP层协议也正确的情况下, 消息才能正确执行.

@hackfisher
Copy link
Contributor

hackfisher commented Jul 8, 2021

还是觉得这里的设计有问题
https://github.com/darwinia-network/darwinia-bridge-sol/blob/channel/contracts/bridge/contracts/BasicInboundChannel.sol#L93
如果这里的call出现了异常revert
第一种场景:忽略返回值success, 使用这种方式忽略应用层的revert,那么消息已经被执行,但是结果不是预期的,且无法重试。
第二种场景:要求success返回值必须成功,否则该处也revert,这时nonce不会增加,会阻塞后续的所有消息。

首先, 这里这个作为基础的通道, 它的职责是保证消息的可达性, 并不保证消息的正确性, 所以这里可以是允许消息revert, 且它提供消息的回执, event MessageDispatched(uint64 nonce, bool result);. 所以不会要求返回值必须是success. 如果应用层需要重试, 可以再发一条新的消息, 或者对错误的原因分析后再发一条正确的消息来保证应用层的逻辑.
需要重试的逻辑在于, 消息没有回执的情况才会去重试, 如果知道了回执还去重试, 是没有必要的

我理解协议是要求不需要回执的,https://www.notion.so/itering/Basic-Message-Channel-c41f0c9e453c478abb68e93f6a067c52 ,“它们是异步的、单向的调用,不需要任何回应”,意思是说正确的消息一定会被对方验证、接收并得到执行。一般来说我们没有从源链上面重试的机会,只能重试该消息。比如用户在源链上锁定一笔资产,在Ethereum上面发行,发行失败,如果在源链上面重试相当于重新锁定一笔资产。

The document need to be update, and changed to:

The message protocol will require delivery process(provide delivery proof) to source chain, in addition, the delivery event (emit MessageDispatched(messages[i].nonce, success);) require to include the execution result of the call.

This delivery process and remote call execution result will help application level with necessary primitives to build atomic transactional ability. This will resolve the issues mentioned here.

The message must be verified in source chain being delivered, but the call could fail and its result will be included in delivery so source chain can receive remote call result and how to handle it. (e.g. unlock the assets back to origin user in the lock_and_remote_issue protocol, or confirm the lock)

Current implementation is compatible with this design and require no change.

Refer: darwinia-network/darwinia-common#700

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Put message root directly in the mmr leaf.
3 participants