Add Method to Establish HRMP Channels Among System Parachains (#1473)

Solution to establish HRMP channels between system parachains.

---------

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>
This commit is contained in:
joe petrowski
2023-09-19 13:46:28 +02:00
committed by GitHub
parent f3061c14bf
commit 2d96c8d27c
9 changed files with 592 additions and 83 deletions
+194 -18
View File
@@ -21,13 +21,16 @@ use crate::{
use frame_support::{pallet_prelude::*, traits::ReservableCurrency, DefaultNoBound};
use frame_system::pallet_prelude::*;
use parity_scale_codec::{Decode, Encode};
use polkadot_parachain_primitives::primitives::HorizontalMessages;
use polkadot_parachain_primitives::primitives::{HorizontalMessages, IsSystem};
use primitives::{
Balance, Hash, HrmpChannelId, Id as ParaId, InboundHrmpMessage, OutboundHrmpMessage,
SessionIndex,
};
use scale_info::TypeInfo;
use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash as HashT, UniqueSaturatedInto};
use sp_runtime::{
traits::{AccountIdConversion, BlakeTwo256, Hash as HashT, UniqueSaturatedInto, Zero},
ArithmeticError,
};
use sp_std::{
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
fmt, mem,
@@ -60,6 +63,8 @@ pub trait WeightInfo {
fn hrmp_cancel_open_request(c: u32) -> Weight;
fn clean_open_channel_requests(c: u32) -> Weight;
fn force_open_hrmp_channel(c: u32) -> Weight;
fn establish_system_channel() -> Weight;
fn poke_channel_deposits() -> Weight;
}
/// A weight info that is only suitable for testing.
@@ -93,6 +98,12 @@ impl WeightInfo for TestWeightInfo {
fn force_open_hrmp_channel(_: u32) -> Weight {
Weight::MAX
}
fn establish_system_channel() -> Weight {
Weight::MAX
}
fn poke_channel_deposits() -> Weight {
Weight::MAX
}
}
/// A description of a request to open an HRMP channel.
@@ -279,6 +290,12 @@ pub mod pallet {
/// An HRMP channel was opened via Root origin.
/// `[sender, recipient, proposed_max_capacity, proposed_max_message_size]`
HrmpChannelForceOpened(ParaId, ParaId, u32, u32),
/// An HRMP channel was opened between two system chains.
/// `[sender, recipient, proposed_max_capacity, proposed_max_message_size]`
HrmpSystemChannelOpened(ParaId, ParaId, u32, u32),
/// An HRMP channel's deposits were updated.
/// `[sender, recipient]`
OpenChannelDepositsUpdated(ParaId, ParaId),
}
#[pallet::error]
@@ -321,6 +338,8 @@ pub mod pallet {
OpenHrmpChannelAlreadyConfirmed,
/// The provided witness data is wrong.
WrongWitness,
/// The channel between these two chains cannot be authorized.
ChannelCreationNotAuthorized,
}
/// The set of pending HRMP open channel requests.
@@ -600,8 +619,8 @@ pub mod pallet {
/// the `max_capacity` and `max_message_size` are still subject to the Relay Chain's
/// configured limits.
///
/// Expected use is when one of the `ParaId`s involved in the channel is governed by the
/// Relay Chain, e.g. a system parachain.
/// Expected use is when one (and only one) of the `ParaId`s involved in the channel is
/// governed by the system, e.g. a system parachain.
///
/// Origin must be the `ChannelManager`.
#[pallet::call_index(7)]
@@ -628,7 +647,8 @@ pub mod pallet {
0
};
// Now we proceed with normal init/accept.
// Now we proceed with normal init/accept, except that we set `no_deposit` to true such
// that it will not require deposits from either member.
Self::init_open_channel(sender, recipient, max_capacity, max_message_size)?;
Self::accept_open_channel(recipient, sender)?;
Self::deposit_event(Event::HrmpChannelForceOpened(
@@ -640,6 +660,146 @@ pub mod pallet {
Ok(Some(<T as Config>::WeightInfo::force_open_hrmp_channel(cancel_request)).into())
}
/// Establish an HRMP channel between two system chains. If the channel does not already
/// exist, the transaction fees will be refunded to the caller. The system does not take
/// deposits for channels between system chains, and automatically sets the message number
/// and size limits to the maximum allowed by the network's configuration.
///
/// Arguments:
///
/// - `sender`: A system chain, `ParaId`.
/// - `recipient`: A system chain, `ParaId`.
///
/// Any signed origin can call this function, but _both_ inputs MUST be system chains. If
/// the channel does not exist yet, there is no fee.
#[pallet::call_index(8)]
#[pallet::weight(<T as Config>::WeightInfo::establish_system_channel())]
pub fn establish_system_channel(
origin: OriginFor<T>,
sender: ParaId,
recipient: ParaId,
) -> DispatchResultWithPostInfo {
let _caller = ensure_signed(origin)?;
// both chains must be system
ensure!(
sender.is_system() && recipient.is_system(),
Error::<T>::ChannelCreationNotAuthorized
);
let config = <configuration::Pallet<T>>::config();
let max_message_size = config.hrmp_channel_max_message_size;
let max_capacity = config.hrmp_channel_max_capacity;
Self::init_open_channel(sender, recipient, max_capacity, max_message_size)?;
Self::accept_open_channel(recipient, sender)?;
Self::deposit_event(Event::HrmpSystemChannelOpened(
sender,
recipient,
max_capacity,
max_message_size,
));
Ok(Pays::No.into())
}
/// Update the deposits held for an HRMP channel to the latest `Configuration`. Channels
/// with system chains do not require a deposit.
///
/// Arguments:
///
/// - `sender`: A chain, `ParaId`.
/// - `recipient`: A chain, `ParaId`.
///
/// Any signed origin can call this function.
#[pallet::call_index(9)]
#[pallet::weight(<T as Config>::WeightInfo::poke_channel_deposits())]
pub fn poke_channel_deposits(
origin: OriginFor<T>,
sender: ParaId,
recipient: ParaId,
) -> DispatchResult {
let _caller = ensure_signed(origin)?;
let channel_id = HrmpChannelId { sender, recipient };
let is_system = sender.is_system() || recipient.is_system();
let config = <configuration::Pallet<T>>::config();
// Channels with and amongst the system do not require a deposit.
let (new_sender_deposit, new_recipient_deposit) = if is_system {
(0, 0)
} else {
(config.hrmp_sender_deposit, config.hrmp_recipient_deposit)
};
let _ = HrmpChannels::<T>::mutate(&channel_id, |channel| -> DispatchResult {
if let Some(ref mut channel) = channel {
let current_sender_deposit = channel.sender_deposit;
let current_recipient_deposit = channel.recipient_deposit;
// nothing to update
if current_sender_deposit == new_sender_deposit &&
current_recipient_deposit == new_recipient_deposit
{
return Ok(())
}
// sender
if current_sender_deposit > new_sender_deposit {
// Can never underflow, but be paranoid.
let amount = current_sender_deposit
.checked_sub(new_sender_deposit)
.ok_or(ArithmeticError::Underflow)?;
T::Currency::unreserve(
&channel_id.sender.into_account_truncating(),
// The difference should always be convertable into `Balance`, but be
// paranoid and do nothing in case.
amount.try_into().unwrap_or(Zero::zero()),
);
} else if current_sender_deposit < new_sender_deposit {
let amount = new_sender_deposit
.checked_sub(current_sender_deposit)
.ok_or(ArithmeticError::Underflow)?;
T::Currency::reserve(
&channel_id.sender.into_account_truncating(),
amount.try_into().unwrap_or(Zero::zero()),
)?;
}
// recipient
if current_recipient_deposit > new_recipient_deposit {
let amount = current_recipient_deposit
.checked_sub(new_recipient_deposit)
.ok_or(ArithmeticError::Underflow)?;
T::Currency::unreserve(
&channel_id.recipient.into_account_truncating(),
amount.try_into().unwrap_or(Zero::zero()),
);
} else if current_recipient_deposit < new_recipient_deposit {
let amount = new_recipient_deposit
.checked_sub(current_recipient_deposit)
.ok_or(ArithmeticError::Underflow)?;
T::Currency::reserve(
&channel_id.recipient.into_account_truncating(),
amount.try_into().unwrap_or(Zero::zero()),
)?;
}
// update storage
channel.sender_deposit = new_sender_deposit;
channel.recipient_deposit = new_recipient_deposit;
} else {
return Err(Error::<T>::OpenHrmpChannelDoesntExist.into())
}
Ok(())
})?;
Self::deposit_event(Event::OpenChannelDepositsUpdated(sender, recipient));
Ok(())
}
}
}
@@ -817,6 +977,10 @@ impl<T: Config> Pallet<T> {
"can't be `None` due to the invariant that the list contains the same items as the set; qed",
);
let system_channel = channel_id.sender.is_system() || channel_id.recipient.is_system();
let sender_deposit = request.sender_deposit;
let recipient_deposit = if system_channel { 0 } else { config.hrmp_recipient_deposit };
if request.confirmed {
if <paras::Pallet<T>>::is_valid_para(channel_id.sender) &&
<paras::Pallet<T>>::is_valid_para(channel_id.recipient)
@@ -824,8 +988,8 @@ impl<T: Config> Pallet<T> {
HrmpChannels::<T>::insert(
&channel_id,
HrmpChannel {
sender_deposit: request.sender_deposit,
recipient_deposit: config.hrmp_recipient_deposit,
sender_deposit,
recipient_deposit,
max_capacity: request.max_capacity,
max_total_size: request.max_total_size,
max_message_size: request.max_message_size,
@@ -1173,7 +1337,9 @@ impl<T: Config> Pallet<T> {
}
/// Initiate opening a channel from a parachain to a given recipient with given channel
/// parameters.
/// parameters. If neither chain is part of the system, then a deposit from the `Configuration`
/// will be required for `origin` (the sender) upon opening the request and the `recipient` upon
/// accepting it.
///
/// Basically the same as [`hrmp_init_open_channel`](Pallet::hrmp_init_open_channel) but
/// intended for calling directly from other pallets rather than dispatched.
@@ -1219,10 +1385,15 @@ impl<T: Config> Pallet<T> {
Error::<T>::OpenHrmpChannelLimitExceeded,
);
T::Currency::reserve(
&origin.into_account_truncating(),
config.hrmp_sender_deposit.unique_saturated_into(),
)?;
// Do not require deposits for channels with or amongst the system.
let is_system = origin.is_system() || recipient.is_system();
let deposit = if is_system { 0 } else { config.hrmp_sender_deposit };
if !deposit.is_zero() {
T::Currency::reserve(
&origin.into_account_truncating(),
deposit.unique_saturated_into(),
)?;
}
// mutating storage directly now -- shall not bail henceforth.
@@ -1232,7 +1403,7 @@ impl<T: Config> Pallet<T> {
HrmpOpenChannelRequest {
confirmed: false,
_age: 0,
sender_deposit: config.hrmp_sender_deposit,
sender_deposit: deposit,
max_capacity: proposed_max_capacity,
max_message_size: proposed_max_message_size,
max_total_size: config.hrmp_channel_max_total_size,
@@ -1254,7 +1425,7 @@ impl<T: Config> Pallet<T> {
if let Err(dmp::QueueDownwardMessageError::ExceedsMaxMessageSize) =
<dmp::Pallet<T>>::queue_downward_message(&config, recipient, notification_bytes)
{
// this should never happen unless the max downward message size is configured to an
// this should never happen unless the max downward message size is configured to a
// jokingly small number.
log::error!(
target: "runtime::hrmp",
@@ -1287,10 +1458,15 @@ impl<T: Config> Pallet<T> {
Error::<T>::AcceptHrmpChannelLimitExceeded,
);
T::Currency::reserve(
&origin.into_account_truncating(),
config.hrmp_recipient_deposit.unique_saturated_into(),
)?;
// Do not require deposits for channels with or amongst the system.
let is_system = origin.is_system() || sender.is_system();
let deposit = if is_system { 0 } else { config.hrmp_recipient_deposit };
if !deposit.is_zero() {
T::Currency::reserve(
&origin.into_account_truncating(),
deposit.unique_saturated_into(),
)?;
}
// persist the updated open channel request and then increment the number of accepted
// channels.