Skip to content

Commit df2b81e

Browse files
bkonturbkchr
authored andcommitted
Add support for versioned notification for HRMP pallet (paritytech#4281)
Closes: paritytech#4003 (please see for the problem description) ## TODO - [x] add more tests covering `WrapVersion` corner cases (e.g. para has lower version, ...) - [x] regenerate benchmarks `runtime_parachains::hrmp` (fix for Rococo is here: paritytech#4332) ## Questions / possible improvements - [ ] A `WrapVersion` implementation for `pallet_xcm` initiates version discovery with [note_unknown_version](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/xcm/pallet-xcm/src/lib.rs#L2527C5-L2527C25), there is possibility to avoid this overhead in this HRMP case to create new `WrapVersion` adapter for `pallet_xcm` which would not use `note_unknown_version`. Is it worth to do it or not? - [ ] There's a possibility to decouple XCM functionality from the HRMP pallet, allowing any relay chain to generate its own notifications. This approach wouldn't restrict notifications solely to the XCM. However, it's uncertain whether it's worthwhile or desirable to do so? It means making HRMP pallet more generic. E.g. hiding HRMP notifications behind some trait: ``` trait HrmpNotifications { fn on_channel_open_request( sender: ParaId, proposed_max_capacity: u32, proposed_max_message_size: u32) -> primitives::DownwardMessage; fn on_channel_accepted(recipient: ParaId) -> primitives::DownwardMessage; fn on_channel_closing(initiator: ParaId, sender: ParaId, recipient: ParaId) -> primitives::DownwardMessage; } ``` and then we could have whatever adapter, `impl HrmpNotifications for VersionedXcmHrmpNotifications {...}`, ``` impl parachains_hrmp::Config for Runtime { .. type HrmpNotifications = VersionedXcmHrmpNotifications; .. } ``` --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher <[email protected]>
1 parent 5c09c22 commit df2b81e

File tree

11 files changed

+406
-167
lines changed

11 files changed

+406
-167
lines changed

polkadot/runtime/parachains/src/hrmp.rs

+97-61
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,14 @@ pub mod pallet {
278278
/// parachain.
279279
type DefaultChannelSizeAndCapacityWithSystem: Get<(u32, u32)>;
280280

281+
/// Means of converting an `Xcm` into a `VersionedXcm`. This pallet sends HRMP XCM
282+
/// notifications to the channel-related parachains, while the `WrapVersion` implementation
283+
/// attempts to wrap them into the most suitable XCM version for the destination parachain.
284+
///
285+
/// NOTE: For example, `pallet_xcm` provides an accurate implementation (recommended), or
286+
/// the default `()` implementation uses the latest XCM version for all parachains.
287+
type VersionWrapper: xcm::WrapVersion;
288+
281289
/// Something that provides the weight of this pallet.
282290
type WeightInfo: WeightInfo;
283291
}
@@ -1499,28 +1507,19 @@ impl<T: Config> Pallet<T> {
14991507
);
15001508
HrmpOpenChannelRequestsList::<T>::append(channel_id);
15011509

1502-
let notification_bytes = {
1503-
use parity_scale_codec::Encode as _;
1504-
use xcm::opaque::{latest::prelude::*, VersionedXcm};
1505-
1506-
VersionedXcm::from(Xcm(vec![HrmpNewChannelOpenRequest {
1507-
sender: u32::from(origin),
1508-
max_capacity: proposed_max_capacity,
1509-
max_message_size: proposed_max_message_size,
1510-
}]))
1511-
.encode()
1512-
};
1513-
if let Err(dmp::QueueDownwardMessageError::ExceedsMaxMessageSize) =
1514-
dmp::Pallet::<T>::queue_downward_message(&config, recipient, notification_bytes)
1515-
{
1516-
// this should never happen unless the max downward message size is configured to a
1517-
// jokingly small number.
1518-
log::error!(
1519-
target: "runtime::hrmp",
1520-
"sending 'init_open_channel::notification_bytes' failed."
1521-
);
1522-
debug_assert!(false);
1523-
}
1510+
Self::send_to_para(
1511+
"init_open_channel",
1512+
&config,
1513+
recipient,
1514+
Self::wrap_notification(|| {
1515+
use xcm::opaque::latest::{prelude::*, Xcm};
1516+
Xcm(vec![HrmpNewChannelOpenRequest {
1517+
sender: origin.into(),
1518+
max_capacity: proposed_max_capacity,
1519+
max_message_size: proposed_max_message_size,
1520+
}])
1521+
}),
1522+
);
15241523

15251524
Ok(())
15261525
}
@@ -1562,23 +1561,15 @@ impl<T: Config> Pallet<T> {
15621561
HrmpOpenChannelRequests::<T>::insert(&channel_id, channel_req);
15631562
HrmpAcceptedChannelRequestCount::<T>::insert(&origin, accepted_cnt + 1);
15641563

1565-
let notification_bytes = {
1566-
use parity_scale_codec::Encode as _;
1567-
use xcm::opaque::{latest::prelude::*, VersionedXcm};
1568-
let xcm = Xcm(vec![HrmpChannelAccepted { recipient: u32::from(origin) }]);
1569-
VersionedXcm::from(xcm).encode()
1570-
};
1571-
if let Err(dmp::QueueDownwardMessageError::ExceedsMaxMessageSize) =
1572-
dmp::Pallet::<T>::queue_downward_message(&config, sender, notification_bytes)
1573-
{
1574-
// this should never happen unless the max downward message size is configured to an
1575-
// jokingly small number.
1576-
log::error!(
1577-
target: "runtime::hrmp",
1578-
"sending 'accept_open_channel::notification_bytes' failed."
1579-
);
1580-
debug_assert!(false);
1581-
}
1564+
Self::send_to_para(
1565+
"accept_open_channel",
1566+
&config,
1567+
sender,
1568+
Self::wrap_notification(|| {
1569+
use xcm::opaque::latest::{prelude::*, Xcm};
1570+
Xcm(vec![HrmpChannelAccepted { recipient: origin.into() }])
1571+
}),
1572+
);
15821573

15831574
Ok(())
15841575
}
@@ -1633,30 +1624,22 @@ impl<T: Config> Pallet<T> {
16331624
HrmpCloseChannelRequestsList::<T>::append(channel_id.clone());
16341625

16351626
let config = configuration::ActiveConfig::<T>::get();
1636-
let notification_bytes = {
1637-
use parity_scale_codec::Encode as _;
1638-
use xcm::opaque::{latest::prelude::*, VersionedXcm};
1639-
1640-
VersionedXcm::from(Xcm(vec![HrmpChannelClosing {
1641-
initiator: u32::from(origin),
1642-
sender: u32::from(channel_id.sender),
1643-
recipient: u32::from(channel_id.recipient),
1644-
}]))
1645-
.encode()
1646-
};
16471627
let opposite_party =
16481628
if origin == channel_id.sender { channel_id.recipient } else { channel_id.sender };
1649-
if let Err(dmp::QueueDownwardMessageError::ExceedsMaxMessageSize) =
1650-
dmp::Pallet::<T>::queue_downward_message(&config, opposite_party, notification_bytes)
1651-
{
1652-
// this should never happen unless the max downward message size is configured to an
1653-
// jokingly small number.
1654-
log::error!(
1655-
target: "runtime::hrmp",
1656-
"sending 'close_channel::notification_bytes' failed."
1657-
);
1658-
debug_assert!(false);
1659-
}
1629+
1630+
Self::send_to_para(
1631+
"close_channel",
1632+
&config,
1633+
opposite_party,
1634+
Self::wrap_notification(|| {
1635+
use xcm::opaque::latest::{prelude::*, Xcm};
1636+
Xcm(vec![HrmpChannelClosing {
1637+
initiator: origin.into(),
1638+
sender: channel_id.sender.into(),
1639+
recipient: channel_id.recipient.into(),
1640+
}])
1641+
}),
1642+
);
16601643

16611644
Ok(())
16621645
}
@@ -1875,3 +1858,56 @@ impl<T: Config> Pallet<T> {
18751858
}
18761859
}
18771860
}
1861+
1862+
impl<T: Config> Pallet<T> {
1863+
/// Wraps HRMP XCM notifications to the most suitable XCM version for the destination para.
1864+
/// If the XCM version is unknown, the latest XCM version is used as a best effort.
1865+
fn wrap_notification(
1866+
mut notification: impl FnMut() -> xcm::opaque::latest::opaque::Xcm,
1867+
) -> impl FnOnce(ParaId) -> primitives::DownwardMessage {
1868+
use xcm::{
1869+
opaque::VersionedXcm,
1870+
prelude::{Junction, Location},
1871+
WrapVersion,
1872+
};
1873+
1874+
// Return a closure that can prepare notifications.
1875+
move |dest| {
1876+
// Attempt to wrap the notification for the destination parachain.
1877+
T::VersionWrapper::wrap_version(
1878+
&Location::new(0, [Junction::Parachain(dest.into())]),
1879+
notification(),
1880+
)
1881+
.unwrap_or_else(|_| {
1882+
// As a best effort, if we cannot resolve the version, fallback to using the latest
1883+
// version.
1884+
VersionedXcm::from(notification())
1885+
})
1886+
.encode()
1887+
}
1888+
}
1889+
1890+
/// Sends/enqueues notification to the destination parachain.
1891+
fn send_to_para(
1892+
log_label: &str,
1893+
config: &HostConfiguration<BlockNumberFor<T>>,
1894+
dest: ParaId,
1895+
notification_bytes_for: impl FnOnce(ParaId) -> primitives::DownwardMessage,
1896+
) {
1897+
// prepare notification
1898+
let notification_bytes = notification_bytes_for(dest);
1899+
1900+
// try to enqueue
1901+
if let Err(dmp::QueueDownwardMessageError::ExceedsMaxMessageSize) =
1902+
dmp::Pallet::<T>::queue_downward_message(&config, dest, notification_bytes)
1903+
{
1904+
// this should never happen unless the max downward message size is configured to a
1905+
// jokingly small number.
1906+
log::error!(
1907+
target: "runtime::hrmp",
1908+
"sending '{log_label}::notification_bytes' failed."
1909+
);
1910+
debug_assert!(false);
1911+
}
1912+
}
1913+
}

polkadot/runtime/parachains/src/hrmp/tests.rs

+140-3
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ use super::*;
2222
use crate::{
2323
mock::{
2424
deregister_parachain, new_test_ext, register_parachain, register_parachain_with_balance,
25-
Hrmp, MockGenesisConfig, Paras, ParasShared, RuntimeEvent as MockEvent, RuntimeOrigin,
26-
System, Test,
25+
Dmp, Hrmp, MockGenesisConfig, Paras, ParasShared, RuntimeEvent as MockEvent, RuntimeOrigin,
26+
System, Test, TestUsesOnlyStoredVersionWrapper,
2727
},
2828
shared,
2929
};
3030
use frame_support::{assert_noop, assert_ok, error::BadOrigin};
31-
use primitives::BlockNumber;
31+
use primitives::{BlockNumber, InboundDownwardMessage};
3232
use std::collections::BTreeMap;
3333

3434
pub(crate) fn run_to_block(to: BlockNumber, new_session: Option<Vec<BlockNumber>>) {
@@ -1004,3 +1004,140 @@ fn establish_channel_with_system_with_invalid_args() {
10041004
Hrmp::assert_storage_consistency_exhaustive();
10051005
});
10061006
}
1007+
1008+
#[test]
1009+
fn hrmp_notifications_works() {
1010+
use xcm::{
1011+
opaque::{
1012+
latest::{prelude::*, Xcm},
1013+
VersionedXcm,
1014+
},
1015+
IntoVersion,
1016+
};
1017+
1018+
let para_a = 2001.into();
1019+
let para_a_origin: crate::Origin = 2001.into();
1020+
let para_b = 2003.into();
1021+
let para_b_origin: crate::Origin = 2003.into();
1022+
1023+
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
1024+
// We need both A & B to be registered and alive parachains.
1025+
register_parachain(para_a);
1026+
register_parachain(para_b);
1027+
run_to_block(5, Some(vec![4, 5]));
1028+
1029+
// set XCM versions for wrapper
1030+
1031+
// for para_a -> `None`, means we will use latest.
1032+
TestUsesOnlyStoredVersionWrapper::set_version(
1033+
Location::new(0, [Junction::Parachain(para_a.into())]),
1034+
None,
1035+
);
1036+
// for para_b -> `Some(latest - 1)`, means we will use latest-1 XCM version.
1037+
let previous_version = XCM_VERSION - 1;
1038+
TestUsesOnlyStoredVersionWrapper::set_version(
1039+
Location::new(0, [Junction::Parachain(para_b.into())]),
1040+
Some(previous_version),
1041+
);
1042+
1043+
let assert_notification_for = |sent_at, para_id, expected| {
1044+
assert_eq!(
1045+
Dmp::dmq_contents(para_id),
1046+
vec![InboundDownwardMessage { sent_at, msg: expected }]
1047+
);
1048+
};
1049+
1050+
// init open channel requests
1051+
assert_ok!(Hrmp::hrmp_init_open_channel(para_a_origin.clone().into(), para_b, 2, 8));
1052+
assert_ok!(Hrmp::hrmp_init_open_channel(para_b_origin.clone().into(), para_a, 2, 8));
1053+
Hrmp::assert_storage_consistency_exhaustive();
1054+
1055+
// check dmp notications
1056+
assert_notification_for(
1057+
5,
1058+
para_b,
1059+
VersionedXcm::from(Xcm(vec![HrmpNewChannelOpenRequest {
1060+
sender: u32::from(para_a),
1061+
max_capacity: 2,
1062+
max_message_size: 8,
1063+
}]))
1064+
.into_version(previous_version)
1065+
.expect("compatible")
1066+
.encode(),
1067+
);
1068+
assert_notification_for(
1069+
5,
1070+
para_a,
1071+
VersionedXcm::from(Xcm(vec![HrmpNewChannelOpenRequest {
1072+
sender: u32::from(para_b),
1073+
max_capacity: 2,
1074+
max_message_size: 8,
1075+
}]))
1076+
.encode(),
1077+
);
1078+
let _ = Dmp::prune_dmq(para_a, 1000);
1079+
let _ = Dmp::prune_dmq(para_b, 1000);
1080+
1081+
// accept open channel requests
1082+
assert_ok!(Hrmp::hrmp_accept_open_channel(para_a_origin.clone().into(), para_b));
1083+
assert_ok!(Hrmp::hrmp_accept_open_channel(para_b_origin.clone().into(), para_a));
1084+
Hrmp::assert_storage_consistency_exhaustive();
1085+
1086+
// check dmp notications
1087+
assert_notification_for(
1088+
5,
1089+
para_b,
1090+
VersionedXcm::from(Xcm(vec![HrmpChannelAccepted { recipient: u32::from(para_a) }]))
1091+
.into_version(previous_version)
1092+
.expect("compatible")
1093+
.encode(),
1094+
);
1095+
assert_notification_for(
1096+
5,
1097+
para_a,
1098+
VersionedXcm::from(Xcm(vec![HrmpChannelAccepted { recipient: u32::from(para_b) }]))
1099+
.encode(),
1100+
);
1101+
let _ = Dmp::prune_dmq(para_a, 1000);
1102+
let _ = Dmp::prune_dmq(para_b, 1000);
1103+
1104+
// On Block 6: session change - creates channel.
1105+
run_to_block(6, Some(vec![6]));
1106+
assert!(channel_exists(para_a, para_b));
1107+
1108+
// close channel requests
1109+
assert_ok!(Hrmp::hrmp_close_channel(
1110+
para_a_origin.into(),
1111+
HrmpChannelId { sender: para_a, recipient: para_b }
1112+
));
1113+
assert_ok!(Hrmp::hrmp_close_channel(
1114+
para_b_origin.into(),
1115+
HrmpChannelId { sender: para_b, recipient: para_a }
1116+
));
1117+
Hrmp::assert_storage_consistency_exhaustive();
1118+
1119+
// check dmp notications
1120+
assert_notification_for(
1121+
6,
1122+
para_b,
1123+
VersionedXcm::from(Xcm(vec![HrmpChannelClosing {
1124+
initiator: u32::from(para_a),
1125+
sender: u32::from(para_a),
1126+
recipient: u32::from(para_b),
1127+
}]))
1128+
.into_version(previous_version)
1129+
.expect("compatible")
1130+
.encode(),
1131+
);
1132+
assert_notification_for(
1133+
6,
1134+
para_a,
1135+
VersionedXcm::from(Xcm(vec![HrmpChannelClosing {
1136+
initiator: u32::from(para_b),
1137+
sender: u32::from(para_b),
1138+
recipient: u32::from(para_a),
1139+
}]))
1140+
.encode(),
1141+
);
1142+
});
1143+
}

0 commit comments

Comments
 (0)