sp_messenger/
messages.rs

1#[cfg(not(feature = "std"))]
2extern crate alloc;
3
4use crate::endpoint::{
5    CollectedFee, Endpoint, EndpointRequest, EndpointRequestWithCollectedFee, EndpointResponse,
6};
7#[cfg(not(feature = "std"))]
8use alloc::vec::Vec;
9use parity_scale_codec::{Decode, Encode};
10use scale_info::TypeInfo;
11pub use sp_domains::{ChainId, ChannelId};
12use sp_runtime::app_crypto::sp_core::U256;
13use sp_runtime::DispatchError;
14use sp_subspace_mmr::ConsensusChainMmrLeafProof;
15use sp_trie::StorageProof;
16
17/// Nonce used as an identifier and ordering of messages within a channel.
18/// Nonce is always increasing.
19pub type Nonce = U256;
20
21/// Unique Id of a message between two chains.
22pub type MessageId = (ChannelId, Nonce);
23
24/// Unique message key for Outbox and Inbox responses
25pub type MessageKey = (ChainId, ChannelId, Nonce);
26
27/// Fee model to send a request and receive a response from another chain.
28#[derive(Default, Debug, Encode, Decode, Clone, Copy, Eq, PartialEq, TypeInfo)]
29pub struct FeeModel<Balance> {
30    /// Fee to relay message from one chain to another
31    pub relay_fee: Balance,
32}
33
34/// State of a channel.
35#[derive(Default, Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
36pub enum ChannelState {
37    /// Channel between chains is initiated but do not yet send or receive messages in this state.
38    #[default]
39    Initiated,
40    /// Channel is open and can send and receive messages.
41    Open,
42    /// Channel is closed and do not send or receive messages.
43    Closed,
44}
45
46/// Channel describes a bridge to exchange messages between two chains.
47#[derive(Default, Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
48pub struct Channel<Balance, AccountId> {
49    /// Channel identifier.
50    pub channel_id: ChannelId,
51    /// State of the channel.
52    pub state: ChannelState,
53    /// Next inbox nonce.
54    pub next_inbox_nonce: Nonce,
55    /// Next outbox nonce.
56    pub next_outbox_nonce: Nonce,
57    /// Latest outbox message nonce for which response was received from dst_chain.
58    pub latest_response_received_message_nonce: Option<Nonce>,
59    /// Maximum outgoing non-delivered messages.
60    pub max_outgoing_messages: u32,
61    /// Fee model for this channel between the chains.
62    pub fee: FeeModel<Balance>,
63    /// Owner of the channel
64    /// Owner maybe None if the channel was initiated on the other chain.
65    pub maybe_owner: Option<AccountId>,
66    /// The amount of funds put on hold by the owner account for this channel
67    pub channel_reserve_fee: Balance,
68}
69
70/// Channel open parameters
71#[derive(Default, Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy)]
72pub struct ChannelOpenParams<Balance> {
73    pub max_outgoing_messages: u32,
74    pub fee_model: FeeModel<Balance>,
75}
76
77/// Channel V1 open parameters
78#[derive(Debug, Encode, Decode, Eq, PartialEq, TypeInfo, Copy, Clone)]
79pub struct ChannelOpenParamsV1 {
80    pub max_outgoing_messages: u32,
81}
82
83/// Defines protocol requests performed on chains.
84#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
85pub enum ProtocolMessageRequest<ChannelOpenParams> {
86    /// Request to open a channel with foreign chain.
87    ChannelOpen(ChannelOpenParams),
88    /// Request to close an open channel with foreign chain.
89    ChannelClose,
90}
91
92impl<Balance: Default> From<ProtocolMessageRequest<ChannelOpenParamsV1>>
93    for ProtocolMessageRequest<ChannelOpenParams<Balance>>
94{
95    fn from(value: ProtocolMessageRequest<ChannelOpenParamsV1>) -> Self {
96        match value {
97            ProtocolMessageRequest::ChannelOpen(params) => {
98                ProtocolMessageRequest::ChannelOpen(ChannelOpenParams {
99                    max_outgoing_messages: params.max_outgoing_messages,
100                    // default is okay here as fee model is empty for V1
101                    fee_model: FeeModel::default(),
102                })
103            }
104            ProtocolMessageRequest::ChannelClose => ProtocolMessageRequest::ChannelClose,
105        }
106    }
107}
108
109impl<Balance: Default> From<ProtocolMessageRequest<ChannelOpenParams<Balance>>>
110    for ProtocolMessageRequest<ChannelOpenParamsV1>
111{
112    fn from(value: ProtocolMessageRequest<ChannelOpenParams<Balance>>) -> Self {
113        match value {
114            ProtocolMessageRequest::ChannelOpen(params) => {
115                ProtocolMessageRequest::ChannelOpen(ChannelOpenParamsV1 {
116                    max_outgoing_messages: params.max_outgoing_messages,
117                })
118            }
119            ProtocolMessageRequest::ChannelClose => ProtocolMessageRequest::ChannelClose,
120        }
121    }
122}
123
124/// Defines protocol requests performed on chains.
125pub type ProtocolMessageResponse = Result<(), DispatchError>;
126
127/// Protocol message that encompasses  request or its response.
128#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
129pub enum RequestResponse<Request, Response> {
130    Request(Request),
131    Response(Response),
132}
133
134/// Payload of the message
135#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
136pub enum Payload<Balance> {
137    /// Protocol message.
138    Protocol(
139        RequestResponse<
140            ProtocolMessageRequest<ChannelOpenParams<Balance>>,
141            ProtocolMessageResponse,
142        >,
143    ),
144    /// Endpoint message.
145    Endpoint(RequestResponse<EndpointRequest, EndpointResponse>),
146}
147
148impl<Balance: Default> From<PayloadV1<Balance>> for Payload<Balance> {
149    fn from(value: PayloadV1<Balance>) -> Self {
150        match value {
151            PayloadV1::Protocol(RequestResponse::Request(req)) => {
152                Payload::Protocol(RequestResponse::Request(req.into()))
153            }
154            PayloadV1::Protocol(RequestResponse::Response(resp)) => {
155                Payload::Protocol(RequestResponse::Response(resp))
156            }
157            PayloadV1::Endpoint(RequestResponse::Request(req)) => {
158                Payload::Endpoint(RequestResponse::Request(req.into()))
159            }
160            PayloadV1::Endpoint(RequestResponse::Response(resp)) => {
161                Payload::Endpoint(RequestResponse::Response(resp))
162            }
163        }
164    }
165}
166
167/// Payload v1 of the message
168#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
169pub enum PayloadV1<Balance> {
170    /// Protocol message.
171    Protocol(RequestResponse<ProtocolMessageRequest<ChannelOpenParamsV1>, ProtocolMessageResponse>),
172    /// Endpoint message.
173    Endpoint(RequestResponse<EndpointRequestWithCollectedFee<Balance>, EndpointResponse>),
174}
175
176impl<Balance: Default> From<Payload<Balance>> for PayloadV1<Balance> {
177    fn from(value: Payload<Balance>) -> Self {
178        match value {
179            Payload::Protocol(RequestResponse::Request(req)) => {
180                PayloadV1::Protocol(RequestResponse::Request(req.into()))
181            }
182            Payload::Protocol(RequestResponse::Response(resp)) => {
183                PayloadV1::Protocol(RequestResponse::Response(resp))
184            }
185            Payload::Endpoint(RequestResponse::Request(req)) => {
186                PayloadV1::Endpoint(RequestResponse::Request(req.into()))
187            }
188            Payload::Endpoint(RequestResponse::Response(resp)) => {
189                PayloadV1::Endpoint(RequestResponse::Response(resp))
190            }
191        }
192    }
193}
194
195/// Versioned message payload
196#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
197pub enum VersionedPayload<Balance> {
198    #[codec(index = 0)]
199    V0(Payload<Balance>),
200    #[codec(index = 1)]
201    V1(PayloadV1<Balance>),
202}
203
204#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
205pub struct ConvertedPayload<Balance> {
206    pub payload: Payload<Balance>,
207    pub is_v1: bool,
208}
209
210impl<Balance> From<Payload<Balance>> for ConvertedPayload<Balance> {
211    fn from(value: Payload<Balance>) -> Self {
212        ConvertedPayload {
213            payload: value,
214            is_v1: false,
215        }
216    }
217}
218
219impl<Balance: Default> From<PayloadV1<Balance>> for ConvertedPayload<Balance> {
220    fn from(value: PayloadV1<Balance>) -> Self {
221        ConvertedPayload {
222            payload: value.into(),
223            is_v1: true,
224        }
225    }
226}
227
228impl<Balance: Default + Clone> VersionedPayload<Balance> {
229    pub fn into_payload_v0(self) -> ConvertedPayload<Balance> {
230        match self {
231            VersionedPayload::V0(payload) => payload.into(),
232            VersionedPayload::V1(payload) => payload.into(),
233        }
234    }
235
236    pub fn maybe_collected_fee(&self) -> Option<CollectedFee<Balance>> {
237        match self {
238            // collected fee is only valid in endpoint v1 request
239            VersionedPayload::V1(PayloadV1::Endpoint(RequestResponse::Request(req))) => {
240                Some(req.collected_fee.clone())
241            }
242            _ => None,
243        }
244    }
245}
246
247/// Message weight tag used to indicate the consumed weight when handling the message
248#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Default)]
249pub enum MessageWeightTag {
250    ProtocolChannelOpen,
251    ProtocolChannelClose,
252    EndpointRequest(Endpoint),
253    EndpointResponse(Endpoint),
254    #[default]
255    None,
256}
257
258impl MessageWeightTag {
259    // Construct the weight tag for outbox message based on the outbox payload
260    pub fn outbox<Balance>(outbox_payload: &VersionedPayload<Balance>) -> Self {
261        match outbox_payload {
262            VersionedPayload::V0(Payload::Protocol(RequestResponse::Request(
263                ProtocolMessageRequest::ChannelOpen(_),
264            )))
265            | VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
266                ProtocolMessageRequest::ChannelOpen(_),
267            ))) => MessageWeightTag::ProtocolChannelOpen,
268            VersionedPayload::V0(Payload::Protocol(RequestResponse::Request(
269                ProtocolMessageRequest::ChannelClose,
270            )))
271            | VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Request(
272                ProtocolMessageRequest::ChannelClose,
273            ))) => MessageWeightTag::ProtocolChannelClose,
274            VersionedPayload::V0(Payload::Endpoint(RequestResponse::Request(req)))
275            | VersionedPayload::V1(PayloadV1::Endpoint(RequestResponse::Request(
276                EndpointRequestWithCollectedFee { req, .. },
277            ))) => MessageWeightTag::EndpointRequest(req.dst_endpoint.clone()),
278            _ => MessageWeightTag::None,
279        }
280    }
281
282    // Construct the weight tag for inbox response based on the weight tag of the request
283    // message and the response payload
284    pub fn inbox_response<Balance>(
285        req_type: MessageWeightTag,
286        resp_payload: &VersionedPayload<Balance>,
287    ) -> Self {
288        match (req_type, resp_payload) {
289            (
290                MessageWeightTag::ProtocolChannelOpen,
291                VersionedPayload::V0(Payload::Protocol(RequestResponse::Response(Ok(_)))),
292            ) => MessageWeightTag::ProtocolChannelOpen,
293            (
294                MessageWeightTag::EndpointRequest(endpoint),
295                VersionedPayload::V0(Payload::Endpoint(RequestResponse::Response(_))),
296            ) => MessageWeightTag::EndpointResponse(endpoint),
297            (
298                MessageWeightTag::ProtocolChannelOpen,
299                VersionedPayload::V1(PayloadV1::Protocol(RequestResponse::Response(Ok(_)))),
300            ) => MessageWeightTag::ProtocolChannelOpen,
301            (
302                MessageWeightTag::EndpointRequest(endpoint),
303                VersionedPayload::V1(PayloadV1::Endpoint(RequestResponse::Response(_))),
304            ) => MessageWeightTag::EndpointResponse(endpoint),
305            _ => MessageWeightTag::None,
306        }
307    }
308}
309
310/// Message contains information to be sent to or received from another chain.
311#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
312pub struct Message<Balance> {
313    /// Chain which initiated this message.
314    pub src_chain_id: ChainId,
315    /// Chain this message is intended for.
316    pub dst_chain_id: ChainId,
317    /// ChannelId the message was sent through.
318    pub channel_id: ChannelId,
319    /// Message nonce within the channel.
320    pub nonce: Nonce,
321    /// Payload of the message
322    pub payload: VersionedPayload<Balance>,
323    /// Last delivered message response nonce on src_chain.
324    pub last_delivered_message_response_nonce: Option<Nonce>,
325}
326
327#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
328pub enum Proof<CBlockNumber, CBlockHash, MmrHash> {
329    Consensus {
330        /// Consensus chain MMR leaf proof.
331        consensus_chain_mmr_proof: ConsensusChainMmrLeafProof<CBlockNumber, CBlockHash, MmrHash>,
332        /// Storage proof that message is processed on src_chain.
333        message_proof: StorageProof,
334    },
335    Domain {
336        /// Consensus chain MMR leaf proof.
337        consensus_chain_mmr_proof: ConsensusChainMmrLeafProof<CBlockNumber, CBlockHash, MmrHash>,
338        /// Storage proof that src domain chain's block is out of the challenge period on Consensus chain.
339        domain_proof: StorageProof,
340        /// Storage proof that message is processed on src_chain.
341        message_proof: StorageProof,
342    },
343}
344
345impl<CBlockNumber, CBlockHash, MmrHash> Proof<CBlockNumber, CBlockHash, MmrHash> {
346    pub fn message_proof(&self) -> StorageProof {
347        match self {
348            Proof::Consensus { message_proof, .. } => message_proof.clone(),
349            Proof::Domain { message_proof, .. } => message_proof.clone(),
350        }
351    }
352
353    pub fn consensus_mmr_proof(
354        &self,
355    ) -> ConsensusChainMmrLeafProof<CBlockNumber, CBlockHash, MmrHash>
356    where
357        CBlockNumber: Clone,
358        CBlockHash: Clone,
359        MmrHash: Clone,
360    {
361        match self {
362            Proof::Consensus {
363                consensus_chain_mmr_proof,
364                ..
365            } => consensus_chain_mmr_proof.clone(),
366            Proof::Domain {
367                consensus_chain_mmr_proof,
368                ..
369            } => consensus_chain_mmr_proof.clone(),
370        }
371    }
372
373    pub fn domain_proof(&self) -> Option<StorageProof> {
374        match self {
375            Proof::Consensus { .. } => None,
376            Proof::Domain { domain_proof, .. } => Some(domain_proof.clone()),
377        }
378    }
379}
380
381/// Cross Domain message contains Message and its proof on src_chain.
382#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
383pub struct CrossDomainMessage<CBlockNumber, CBlockHash, MmrHash> {
384    /// Chain which initiated this message.
385    pub src_chain_id: ChainId,
386    /// Chain this message is intended for.
387    pub dst_chain_id: ChainId,
388    /// ChannelId the message was sent through.
389    pub channel_id: ChannelId,
390    /// Message nonce within the channel.
391    pub nonce: Nonce,
392    /// Proof of message processed on src_chain.
393    pub proof: Proof<CBlockNumber, CBlockHash, MmrHash>,
394    /// The message weight tag
395    pub weight_tag: MessageWeightTag,
396}
397
398/// Message with storage key to generate storage proof using the backend.
399#[derive(Debug, Encode, Decode, TypeInfo, Clone, Eq, PartialEq)]
400pub struct BlockMessageWithStorageKey {
401    /// Chain which initiated this message.
402    pub src_chain_id: ChainId,
403    /// Chain this message is intended for.
404    pub dst_chain_id: ChainId,
405    /// ChannelId the message was sent through.
406    pub channel_id: ChannelId,
407    /// Message nonce within the channel.
408    pub nonce: Nonce,
409    /// Storage key to generate proof for using proof backend.
410    pub storage_key: Vec<u8>,
411    /// The message weight tag
412    pub weight_tag: MessageWeightTag,
413}
414
415impl BlockMessageWithStorageKey {
416    pub fn id(&self) -> (ChannelId, Nonce) {
417        (self.channel_id, self.nonce)
418    }
419}
420
421/// Set of messages with storage keys to be relayed in a given block..
422#[derive(Default, Debug, Encode, Decode, TypeInfo, Clone, Eq, PartialEq)]
423pub struct BlockMessagesWithStorageKey {
424    pub outbox: Vec<BlockMessageWithStorageKey>,
425    pub inbox_responses: Vec<BlockMessageWithStorageKey>,
426}
427
428impl BlockMessagesWithStorageKey {
429    pub fn is_empty(&self) -> bool {
430        self.outbox.is_empty() && self.inbox_responses.is_empty()
431    }
432}
433
434impl<BlockNumber, BlockHash, MmrHash> CrossDomainMessage<BlockNumber, BlockHash, MmrHash> {
435    pub fn from_relayer_msg_with_proof(
436        r_msg: BlockMessageWithStorageKey,
437        proof: Proof<BlockNumber, BlockHash, MmrHash>,
438    ) -> Self {
439        CrossDomainMessage {
440            src_chain_id: r_msg.src_chain_id,
441            dst_chain_id: r_msg.dst_chain_id,
442            channel_id: r_msg.channel_id,
443            nonce: r_msg.nonce,
444            proof,
445            weight_tag: r_msg.weight_tag,
446        }
447    }
448}