sp_messenger/
lib.rs

1// Copyright (C) 2021 Subspace Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Primitives for Messenger.
17
18#![cfg_attr(not(feature = "std"), no_std)]
19
20pub mod endpoint;
21pub mod messages;
22
23#[cfg(not(feature = "std"))]
24extern crate alloc;
25
26use crate::messages::{ChannelStateWithNonce, MessageKey, MessagesWithStorageKey, Nonce};
27#[cfg(not(feature = "std"))]
28use alloc::collections::BTreeMap;
29#[cfg(not(feature = "std"))]
30use alloc::collections::BTreeSet;
31#[cfg(not(feature = "std"))]
32use alloc::vec::Vec;
33#[cfg(feature = "std")]
34use frame_support::inherent::InherentData;
35use frame_support::inherent::{InherentIdentifier, IsFatalError};
36#[cfg(feature = "runtime-benchmarks")]
37use frame_support::storage::storage_prefix;
38#[cfg(feature = "runtime-benchmarks")]
39use frame_support::{Identity, StorageHasher};
40use messages::{BlockMessagesQuery, ChannelId, CrossDomainMessage};
41use parity_scale_codec::{Decode, Encode};
42use scale_info::TypeInfo;
43use sp_domains::{ChainId, DomainAllowlistUpdates, DomainId};
44use sp_subspace_mmr::ConsensusChainMmrLeafProof;
45#[cfg(feature = "std")]
46use std::collections::BTreeMap;
47#[cfg(feature = "std")]
48use std::collections::BTreeSet;
49
50/// Messenger inherent identifier.
51pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"messengr";
52
53/// Maximum number of XDMs per domain/channel with future nonces that are allowed to be validated.
54/// Any XDM comes with a nonce above Maximum future nonce will be rejected.
55// TODO: We need to benchmark how many XDMs can fit in to a
56//  - Single consensus block
57//  - Single domain block(includes all bundles filled with XDMs)
58//  Once we have that info, we can make a better judgement on how many XDMs
59//  we want to include per block while allowing other extrinsics to be included as well.
60//  Note: Currently XDM takes priority over other extrinsics unless they come with priority fee
61pub const MAX_FUTURE_ALLOWED_NONCES: u32 = 256;
62
63/// Trait to handle XDM rewards.
64pub trait OnXDMRewards<Balance> {
65    fn on_xdm_rewards(rewards: Balance);
66    fn on_chain_protocol_fees(chain_id: ChainId, fees: Balance);
67}
68
69/// Trait to note cross chain transfer
70pub trait NoteChainTransfer<Balance> {
71    fn note_transfer_in(amount: Balance, from_chain_id: ChainId) -> bool;
72    fn note_transfer_out(amount: Balance, to_chain_id: ChainId) -> bool;
73}
74
75impl<Balance> NoteChainTransfer<Balance> for () {
76    fn note_transfer_in(_amount: Balance, _from_chain_id: ChainId) -> bool {
77        true
78    }
79    fn note_transfer_out(_amount: Balance, _to_chain_id: ChainId) -> bool {
80        true
81    }
82}
83
84impl<Balance> OnXDMRewards<Balance> for () {
85    fn on_xdm_rewards(_: Balance) {}
86
87    fn on_chain_protocol_fees(_chain_id: ChainId, _fees: Balance) {}
88}
89
90/// Trait to check if the domain is registered.
91pub trait DomainRegistration {
92    fn is_domain_registered(domain_id: DomainId) -> bool;
93}
94
95impl DomainRegistration for () {
96    fn is_domain_registered(_domain_id: DomainId) -> bool {
97        false
98    }
99}
100
101/// Trait that return various storage keys for storages on Consensus chain and domains
102pub trait StorageKeys {
103    /// Returns the storage key for confirmed domain block on conensus chain
104    fn confirmed_domain_block_storage_key(domain_id: DomainId) -> Option<Vec<u8>>;
105
106    /// Returns the outbox storage key for given chain.
107    fn outbox_storage_key(chain_id: ChainId, message_key: MessageKey) -> Option<Vec<u8>>;
108
109    /// Returns the inbox responses storage key for given chain.
110    fn inbox_responses_storage_key(chain_id: ChainId, message_key: MessageKey) -> Option<Vec<u8>>;
111}
112
113impl StorageKeys for () {
114    fn confirmed_domain_block_storage_key(_domain_id: DomainId) -> Option<Vec<u8>> {
115        None
116    }
117
118    fn outbox_storage_key(_chain_id: ChainId, _message_key: MessageKey) -> Option<Vec<u8>> {
119        None
120    }
121
122    fn inbox_responses_storage_key(
123        _chain_id: ChainId,
124        _message_key: MessageKey,
125    ) -> Option<Vec<u8>> {
126        None
127    }
128}
129
130#[cfg(feature = "runtime-benchmarks")]
131pub struct BenchmarkStorageKeys;
132
133#[cfg(feature = "runtime-benchmarks")]
134impl StorageKeys for BenchmarkStorageKeys {
135    fn confirmed_domain_block_storage_key(domain_id: DomainId) -> Option<Vec<u8>> {
136        let storage_prefix = storage_prefix(
137            "Domains".as_bytes(),
138            "LatestConfirmedDomainExecutionReceipt".as_bytes(),
139        );
140        let key_hashed = domain_id.using_encoded(Identity::hash);
141
142        let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.len());
143
144        final_key.extend_from_slice(&storage_prefix);
145        final_key.extend_from_slice(key_hashed.as_ref());
146
147        Some(final_key)
148    }
149
150    fn outbox_storage_key(_chain_id: ChainId, message_key: MessageKey) -> Option<Vec<u8>> {
151        let storage_prefix = storage_prefix("Messenger".as_bytes(), "Outbox".as_bytes());
152        let key_hashed = message_key.using_encoded(Identity::hash);
153
154        let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.len());
155
156        final_key.extend_from_slice(&storage_prefix);
157        final_key.extend_from_slice(key_hashed.as_ref());
158
159        Some(final_key)
160    }
161
162    fn inbox_responses_storage_key(_chain_id: ChainId, message_key: MessageKey) -> Option<Vec<u8>> {
163        let storage_prefix = storage_prefix("Messenger".as_bytes(), "InboxResponses".as_bytes());
164        let key_hashed = message_key.using_encoded(Identity::hash);
165
166        let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.len());
167
168        final_key.extend_from_slice(&storage_prefix);
169        final_key.extend_from_slice(key_hashed.as_ref());
170
171        Some(final_key)
172    }
173}
174
175/// The type of the messenger inherent data.
176#[derive(Debug, Encode, Decode)]
177pub struct InherentType {
178    pub maybe_updates: Option<DomainAllowlistUpdates>,
179}
180
181/// Inherent specific errors
182#[derive(Debug, Encode)]
183#[cfg_attr(feature = "std", derive(Decode))]
184pub enum InherentError {
185    MissingAllowlistUpdates,
186    IncorrectAllowlistUpdates,
187}
188
189impl IsFatalError for InherentError {
190    fn is_fatal_error(&self) -> bool {
191        true
192    }
193}
194
195/// Provides the set code inherent data.
196#[cfg(feature = "std")]
197pub struct InherentDataProvider {
198    data: InherentType,
199}
200
201#[cfg(feature = "std")]
202impl InherentDataProvider {
203    /// Create new inherent data provider from the given `data`.
204    pub fn new(data: InherentType) -> Self {
205        Self { data }
206    }
207
208    /// Returns the `data` of this inherent data provider.
209    pub fn data(&self) -> &InherentType {
210        &self.data
211    }
212}
213
214#[cfg(feature = "std")]
215#[async_trait::async_trait]
216impl sp_inherents::InherentDataProvider for InherentDataProvider {
217    async fn provide_inherent_data(
218        &self,
219        inherent_data: &mut InherentData,
220    ) -> Result<(), sp_inherents::Error> {
221        inherent_data.put_data(INHERENT_IDENTIFIER, &self.data)
222    }
223
224    async fn try_handle_error(
225        &self,
226        identifier: &InherentIdentifier,
227        error: &[u8],
228    ) -> Option<Result<(), sp_inherents::Error>> {
229        if *identifier != INHERENT_IDENTIFIER {
230            return None;
231        }
232
233        let error = InherentError::decode(&mut &*error).ok()?;
234
235        Some(Err(sp_inherents::Error::Application(Box::from(format!(
236            "{error:?}"
237        )))))
238    }
239}
240
241/// Represent a union of XDM types with their message ID
242#[derive(Debug, Encode, Decode, TypeInfo, Copy, Clone)]
243pub enum XdmId {
244    RelayMessage(MessageKey),
245    RelayResponseMessage(MessageKey),
246}
247
248impl XdmId {
249    pub fn get_chain_id_and_channel_id(&self) -> (ChainId, ChannelId) {
250        match self {
251            XdmId::RelayMessage(key) => (key.0, key.1),
252            XdmId::RelayResponseMessage(key) => (key.0, key.1),
253        }
254    }
255}
256
257#[derive(Debug, Encode, Decode, TypeInfo, Copy, Clone)]
258pub struct ChannelNonce {
259    /// Last processed relay message nonce.
260    /// Could be None if there is no relay message yet.
261    pub relay_msg_nonce: Option<Nonce>,
262    /// Last processed relay response message nonce.
263    /// Could be None if there is no first response yet
264    pub relay_response_msg_nonce: Option<Nonce>,
265}
266
267sp_api::decl_runtime_apis! {
268    /// Api useful for relayers to fetch messages and submit transactions.
269    #[api_version(3)]
270    pub trait RelayerApi<BlockNumber, CNumber, CHash>
271    where
272        BlockNumber: Encode + Decode,
273        CNumber: Encode + Decode,
274        CHash: Encode + Decode,
275    {
276        /// Constructs an outbox message to the dst_chain as an unsigned extrinsic.
277        fn outbox_message_unsigned(
278            msg: CrossDomainMessage<CNumber, CHash, sp_core::H256>,
279        ) -> Option<Block::Extrinsic>;
280
281        /// Constructs an inbox response message to the dst_chain as an unsigned extrinsic.
282        fn inbox_response_message_unsigned(
283            msg: CrossDomainMessage<CNumber, CHash, sp_core::H256>,
284        ) -> Option<Block::Extrinsic>;
285
286        /// Returns the list of channels updated in the given block.
287        fn updated_channels() -> BTreeSet<(ChainId, ChannelId)>;
288
289        /// Returns storage key for channels for given chain and channel id.
290        fn channel_storage_key(chain_id: ChainId, channel_id: ChannelId) -> Vec<u8>;
291
292        /// Returns all the open channels to other chains.
293        fn open_channels() -> BTreeSet<(ChainId, ChannelId)>;
294
295        /// Returns outbox and inbox responses from given nonce to maximum allowed nonce per block
296        /// Storage key is used to generate the storage proof for the message.
297        fn block_messages_with_query(query: BlockMessagesQuery) -> MessagesWithStorageKey;
298
299        /// Returns all the channels to other chains and their local Channel state.
300        fn channels_and_state() -> Vec<(ChainId, ChannelId, ChannelStateWithNonce)>;
301
302        /// Returns the first outbox message nonce that should be relayed to the dst_chain.
303        fn first_outbox_message_nonce_to_relay(dst_chain_id: ChainId, channel_id: ChannelId, from_nonce: Nonce) -> Option<Nonce>;
304
305        /// Returns the first inbox response message nonce that should be relayed to the dst_chain.
306        fn first_inbox_message_response_nonce_to_relay(dst_chain_id: ChainId,channel_id: ChannelId, from_nonce: Nonce) -> Option<Nonce>;
307    }
308
309    /// Api to provide XDM extraction from Runtime Calls.
310    #[api_version(3)]
311    pub trait MessengerApi<CNumber, CHash>
312    where
313        CNumber: Encode + Decode,
314        CHash: Encode + Decode,
315    {
316        /// Returns `Some(true)` if valid XDM or `Some(false)` if not
317        /// Returns None if this is not an XDM
318        fn is_xdm_mmr_proof_valid(
319            ext: &Block::Extrinsic
320        ) -> Option<bool>;
321
322        // Extract the MMR proof from the XDM
323        fn extract_xdm_mmr_proof(ext: &Block::Extrinsic) -> Option<ConsensusChainMmrLeafProof<CNumber, CHash, sp_core::H256>>;
324
325        // Extract the MMR proofs a the given batch of XDM
326        // `allow(clippy::ptr_arg` is needed because Clippy complains to replace `&Vec<T>` with `&[T]`
327        // but the latter fails to compile.
328        #[allow(clippy::ptr_arg)]
329        fn batch_extract_xdm_mmr_proof(ext: &Vec<Block::Extrinsic>) -> BTreeMap<u32, ConsensusChainMmrLeafProof<CNumber, CHash, sp_core::H256>>;
330
331        /// Returns the confirmed domain block storage for given domain.
332        fn confirmed_domain_block_storage_key(domain_id: DomainId) -> Vec<u8>;
333
334        /// Returns storage key for outbox for a given message_id.
335        fn outbox_storage_key(message_key: MessageKey) -> Vec<u8>;
336
337        /// Returns storage key for inbox response for a given message_id.
338        fn inbox_response_storage_key(message_key: MessageKey) -> Vec<u8>;
339
340        /// Returns any domain's chains allowlist updates on consensus chain.
341        fn domain_chains_allowlist_update(domain_id: DomainId) -> Option<DomainAllowlistUpdates>;
342
343        /// Returns XDM message ID
344        fn xdm_id(ext: &Block::Extrinsic) -> Option<XdmId>;
345
346        /// Get Channel nonce for given chain and channel id.
347        fn channel_nonce(chain_id: ChainId, channel_id: ChannelId) -> Option<ChannelNonce>;
348    }
349}