Repository: paritytech/polkadot-sdk
Pull request #8409: check XCM size in VMP routing
Author: login: kianenigma
Labels: name: T6-XCM
description: This PR/Issue is related to XCM.
Created: 2025-05-02T12:36:44Z | Merged: 2025-05-14T21:29:25Z

Contxt: within AHM, AH and RC need to communicate large-ish payloads of data, such as 1000 validator key and corresponding reward points per session. While the current limits on Polkadot and Kusama allow for such payloads, I am putting the code in place such that we `SendXcm::validate` any message before sending, and based on that, if `MessageSizeExceeded` is returned, we recursively split the messages. This code itself will come in a sparate PR.

So far, it turned out that in the code bath of a UMP (para -> RC), `validate` actually didn't check the message size. This PR adds this. I am not super familiar yet with this part of the codebase to know where to add unit tests for this, but I can tell that in my ZN experiments, this fixes the issue.

I was expecting a similar hack to also be needed for DMP (RC -> para), but it seems like this is not needed and in this code-path (based on who is the router: `ChildParachainRouter` in my case).


Diff:
diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs
index 22f6514c8e36c..25e0fb3cfab6f 100644
--- a/cumulus/pallets/parachain-system/src/lib.rs
+++ b/cumulus/pallets/parachain-system/src/lib.rs
@@ -1630,6 +1630,42 @@ impl<T: Config> UpwardMessageSender for Pallet<T> {
 	fn send_upward_message(message: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError> {
 		Self::send_upward_message(message)
 	}
+
+	fn can_send_upward_message(message: &UpwardMessage) -> Result<(), MessageSendError> {
+		let max_upward_message_size = HostConfiguration::<T>::get()
+			.map(|cfg| cfg.max_upward_message_size)
+			.ok_or(MessageSendError::Other)?;
+		if message.len() > max_upward_message_size as usize {
+			Err(MessageSendError::TooBig)
+		} else {
+			Ok(())
+		}
+	}
+
+	#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
+	fn ensure_successful_delivery() {
+		const MAX_UPWARD_MESSAGE_SIZE: u32 = 65_531 * 3;
+		const MAX_CODE_SIZE: u32 = 3 * 1024 * 1024;
+		HostConfiguration::<T>::mutate(|cfg| match cfg {
+			Some(cfg) => cfg.max_upward_message_size = MAX_UPWARD_MESSAGE_SIZE,
+			None =>
+				*cfg = Some(AbridgedHostConfiguration {
+					max_code_size: MAX_CODE_SIZE,
+					max_head_data_size: 32 * 1024,
+					max_upward_queue_count: 8,
+					max_upward_queue_size: 1024 * 1024,
+					max_upward_message_size: MAX_UPWARD_MESSAGE_SIZE,
+					max_upward_message_num_per_candidate: 2,
+					hrmp_max_message_num_per_candidate: 2,
+					validation_upgrade_cooldown: 2,
+					validation_upgrade_delay: 2,
+					async_backing_params: relay_chain::AsyncBackingParams {
+						allowed_ancestry_len: 0,
+						max_candidate_depth: 0,
+					},
+				}),
+		})
+	}
 }
 
 impl<T: Config> InspectMessageQueues for Pallet<T> {
diff --git a/cumulus/pallets/parachain-system/src/tests.rs b/cumulus/pallets/parachain-system/src/tests.rs
index 1f9cb6d54de4f..fee6d7811b9d8 100755
--- a/cumulus/pallets/parachain-system/src/tests.rs
+++ b/cumulus/pallets/parachain-system/src/tests.rs
@@ -557,7 +557,7 @@ fn aborted_upgrade() {
 }
 
 #[test]
-fn checks_size() {
+fn checks_code_size() {
 	BlockTests::new()
 		.with_relay_sproof_builder(|_, _, builder| {
 			builder.host_config.max_code_size = 8;
@@ -706,6 +706,34 @@ fn send_upward_message_relay_bottleneck() {
 		);
 }
 
+#[test]
+fn send_upwards_message_checks_size_on_validate() {
+	BlockTests::new()
+		.with_relay_sproof_builder(|_, _, sproof| {
+			sproof.host_config.max_upward_message_size = 128;
+		})
+		.add(1, || {
+			assert_eq!(
+				ParachainSystem::can_send_upward_message(vec![0u8; 129].as_ref()),
+				Err(MessageSendError::TooBig)
+			);
+		});
+}
+
+#[test]
+fn send_upward_message_check_size() {
+	BlockTests::new()
+		.with_relay_sproof_builder(|_, _, sproof| {
+			sproof.host_config.max_upward_message_size = 128;
+		})
+		.add(1, || {
+			assert_eq!(
+				ParachainSystem::send_upward_message(vec![0u8; 129]),
+				Err(MessageSendError::TooBig)
+			);
+		});
+}
+
 #[test]
 fn send_hrmp_message_buffer_channel_close() {
 	BlockTests::new()
diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs
index 3279aba88d5cc..36494098edd87 100644
--- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs
+++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs
@@ -19,7 +19,7 @@ use super::xcm_helpers;
 use crate::{assert_matches_reserve_asset_deposited_instructions, get_fungible_delivery_fees};
 use codec::Encode;
 use core::ops::Mul;
-use cumulus_primitives_core::XcmpMessageSource;
+use cumulus_primitives_core::{UpwardMessageSender, XcmpMessageSource};
 use frame_support::{
 	assert_noop, assert_ok,
 	traits::{
@@ -171,6 +171,8 @@ pub fn teleports_for_native_asset_works<
 
 			// 2. try to teleport asset back to the relaychain
 			{
+				<cumulus_pallet_parachain_system::Pallet<Runtime> as UpwardMessageSender>::ensure_successful_delivery();
+
 				let dest = Location::parent();
 				let mut dest_beneficiary = Location::parent()
 					.appended_with(AccountId32 {
diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/xcm_helpers.rs b/cumulus/parachains/runtimes/assets/test-utils/src/xcm_helpers.rs
index 36c99449500ad..b81785fec6906 100644
--- a/cumulus/parachains/runtimes/assets/test-utils/src/xcm_helpers.rs
+++ b/cumulus/parachains/runtimes/assets/test-utils/src/xcm_helpers.rs
@@ -97,8 +97,9 @@ fn teleport_assets_dummy_message(
 
 /// Given a message, a sender, and a destination, it returns the delivery fees
 fn get_fungible_delivery_fees<S: SendXcm>(destination: Location, message: Xcm<()>) -> u128 {
-	let Ok((_, delivery_fees)) = validate_send::<S>(destination, message) else {
-		unreachable!("message can be sent; qed")
+	let delivery_fees = match validate_send::<S>(destination, message) {
+		Ok((_, delivery_fees)) => delivery_fees,
+		Err(e) => unreachable!("message can be sent - {:?}; qed", e),
 	};
 	if let Some(delivery_fee) = delivery_fees.inner().first() {
 		let Fungible(delivery_fee_amount) = delivery_fee.fun else {
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs
index 26a44104bb98d..071380993693e 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs
@@ -140,6 +140,7 @@ mod bridge_hub_westend_tests {
 		BridgeHubWestendLocation, WestendGlobalConsensusNetwork,
 		WithBridgeHubWestendMessagesInstance, XcmOverBridgeHubWestendInstance,
 	};
+	use cumulus_primitives_core::UpwardMessageSender;
 
 	// Random para id of sibling chain used in tests.
 	pub const SIBLING_PARACHAIN_ID: u32 = 2053;
@@ -386,7 +387,7 @@ mod bridge_hub_westend_tests {
 					_ => None,
 				}
 			}),
-			|| (),
+			|| <ParachainSystem as UpwardMessageSender>::ensure_successful_delivery(),
 		)
 	}
 
@@ -533,6 +534,7 @@ mod bridge_hub_bulletin_tests {
 		RococoBulletinGlobalConsensusNetwork, RococoBulletinGlobalConsensusNetworkLocation,
 		WithRococoBulletinMessagesInstance, XcmOverPolkadotBulletinInstance,
 	};
+	use cumulus_primitives_core::UpwardMessageSender;
 
 	// Random para id of sibling chain used in tests.
 	pub const SIBLING_PEOPLE_PARACHAIN_ID: u32 =
@@ -668,7 +670,7 @@ mod bridge_hub_bulletin_tests {
 					_ => None,
 				}
 			}),
-			|| (),
+			|| <ParachainSystem as UpwardMessageSender>::ensure_successful_delivery(),
 		)
 	}
 
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs
index 704a20b58401f..7f5efbf295de0 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs
@@ -40,6 +40,7 @@ use bridge_to_rococo_config::{
 	DeliveryRewardInBalance, WithBridgeHubRococoMessagesInstance, XcmOverBridgeHubRococoInstance,
 };
 use codec::{Decode, Encode};
+use cumulus_primitives_core::UpwardMessageSender;
 use frame_support::{
 	assert_err, assert_ok,
 	dispatch::GetDispatchInfo,
@@ -315,7 +316,7 @@ fn message_dispatch_routing_works() {
 				_ => None,
 			}
 		}),
-		|| (),
+		|| <ParachainSystem as UpwardMessageSender>::ensure_successful_delivery(),
 	)
 }
 
diff --git a/cumulus/primitives/core/src/lib.rs b/cumulus/primitives/core/src/lib.rs
index bc707bc251c87..3866cabed0257 100644
--- a/cumulus/primitives/core/src/lib.rs
+++ b/cumulus/primitives/core/src/lib.rs
@@ -154,12 +154,24 @@ pub trait UpwardMessageSender {
 	/// Send the given UMP message; return the expected number of blocks before the message will
 	/// be dispatched or an error if the message cannot be sent.
 	/// return the hash of the message sent
-	fn send_upward_message(msg: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError>;
+	fn send_upward_message(message: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError>;
+
+	/// Pre-check the given UMP message.
+	fn can_send_upward_message(message: &UpwardMessage) -> Result<(), MessageSendError>;
+
+	/// Ensure `[Self::send_upward_message]` is successful when called in benchmarks/tests.
+	#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
+	fn ensure_successful_delivery() {}
 }
+
 impl UpwardMessageSender for () {
-	fn send_upward_message(_msg: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError> {
+	fn send_upward_message(_message: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError> {
 		Err(MessageSendError::NoChannel)
 	}
+
+	fn can_send_upward_message(_message: &UpwardMessage) -> Result<(), MessageSendError> {
+		Err(MessageSendError::Other)
+	}
 }
 
 /// The status of a channel.
diff --git a/cumulus/primitives/utility/src/lib.rs b/cumulus/primitives/utility/src/lib.rs
index dd80335caaaab..60b925c69453b 100644
--- a/cumulus/primitives/utility/src/lib.rs
+++ b/cumulus/primitives/utility/src/lib.rs
@@ -77,6 +77,9 @@ where
 				.map_err(|()| SendError::ExceedsMaxMessageSize)?;
 			let data = versioned_xcm.encode();
 
+			// Pre-check with our message sender if everything else is okay.
+			T::can_send_upward_message(&data).map_err(Self::map_upward_sender_err)?;
+
 			Ok((data, price))
 		} else {
 			// Anything else is unhandled. This includes a message that is not meant for us.
@@ -87,12 +90,24 @@ where
 	}
 
 	fn deliver(data: Vec<u8>) -> Result<XcmHash, SendError> {
-		let (_, hash) = T::send_upward_message(data).map_err(|e| match e {
+		let (_, hash) = T::send_upward_message(data).map_err(Self::map_upward_sender_err)?;
+		Ok(hash)
+	}
+
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_successful_delivery(location: Option<Location>) {
+		if location.as_ref().map_or(false, |l| l.contains_parents_only(1)) {
+			T::ensure_successful_delivery();
+		}
+	}
+}
+
+impl<T, W, P> ParentAsUmp<T, W, P> {
+	fn map_upward_sender_err(message_send_error: MessageSendError) -> SendError {
+		match message_send_error {
 			MessageSendError::TooBig => SendError::ExceedsMaxMessageSize,
 			e => SendError::Transport(e.into()),
-		})?;
-
-		Ok(hash)
+		}
 	}
 }
 
@@ -596,12 +611,16 @@ mod test_xcm_router {
 		}
 	}
 
-	/// Impl [`UpwardMessageSender`] that return `Other` error
-	struct OtherErrorUpwardMessageSender;
-	impl UpwardMessageSender for OtherErrorUpwardMessageSender {
+	/// Impl [`UpwardMessageSender`] that return `Ok` for `can_send_upward_message`.
+	struct CanSendUpwardMessageSender;
+	impl UpwardMessageSender for CanSendUpwardMessageSender {
 		fn send_upward_message(_: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError> {
 			Err(MessageSendError::Other)
 		}
+
+		fn can_send_upward_message(_: &UpwardMessage) -> Result<(), MessageSendError> {
+			Ok(())
+		}
 	}
 
 	#[test]
@@ -641,7 +660,7 @@ mod test_xcm_router {
 		let dest = (Parent, Here);
 		let mut dest_wrapper = Some(dest.clone().into());
 		let mut msg_wrapper = Some(message.clone());
-		assert!(<ParentAsUmp<(), (), ()> as SendXcm>::validate(
+		assert!(<ParentAsUmp<CanSendUpwardMessageSender, (), ()> as SendXcm>::validate(
 			&mut dest_wrapper,
 			&mut msg_wrapper
 		)
@@ -655,7 +674,7 @@ mod test_xcm_router {
 		assert_eq!(
 			Err(SendError::Transport("Other")),
 			send_xcm::<(
-				ParentAsUmp<OtherErrorUpwardMessageSender, (), ()>,
+				ParentAsUmp<CanSendUpwardMessageSender, (), ()>,
 				OkFixedXcmHashWithAssertingRequiredInputsSender
 			)>(dest.into(), message)
 		);
@@ -665,7 +684,7 @@ mod test_xcm_router {
 	fn parent_as_ump_validate_nested_xcm_works() {
 		let dest = Parent;
 
-		type Router = ParentAsUmp<(), (), ()>;
+		type Router = ParentAsUmp<CanSendUpwardMessageSender, (), ()>;
 
 		// Message that is not too deeply nested:
 		let mut good = Xcm(vec![ClearOrigin]);
@@ -853,6 +872,9 @@ impl<
 			return (None, None);
 		}
 
+		// Ensure routers
+		XcmConfig::XcmSender::ensure_successful_delivery(Some(Location::parent()));
+
 		let mut fees_mode = None;
 		if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) {
 			// if not waived, we need to set up accounts for paying and receiving fees
diff --git a/prdoc/pr_8409.prdoc b/prdoc/pr_8409.prdoc
new file mode 100644
index 0000000000000..272f3a52eabb0
--- /dev/null
+++ b/prdoc/pr_8409.prdoc
@@ -0,0 +1,14 @@
+title: check XCM size in VMP routing
+doc:
+- audience: Runtime Dev
+  description: |-
+    This PR adds the ability for a UMP (para -> relay), when using `UpwardMessageSender` to also
+    check the size of the message within validate. This is exposed into the existing `validate` of
+    `ParentAsUmp`.
+crates:
+- name: cumulus-pallet-parachain-system
+  bump: minor
+- name: cumulus-primitives-core
+  bump: minor
+- name: cumulus-primitives-utility
+  bump: minor


Reviews:
user: login: bkontur
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T09:02:24Z
user: login: kianenigma
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T09:41:38Z
user: login: kianenigma
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T09:51:00Z
user: login: bkontur
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T13:36:01Z
user: login: kianenigma
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T15:27:43Z
user: login: kianenigma
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T15:37:48Z
user: login: acatangiu
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T08:07:10Z
user: login: bkontur
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T08:54:52Z
user: login: bkontur
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T10:25:20Z
user: login: franciscoaguirre
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-11T16:05:37Z

Review comments:
diff_hunk: @@ -155,11 +155,19 @@ pub trait UpwardMessageSender {
 	/// be dispatched or an error if the message cannot be sent.
 	/// return the hash of the message sent
 	fn send_upward_message(msg: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError>;
+
+	/// Check whether the message size is acceptable for the channel.
+	fn check_size(size: usize) -> Result<(), ()>;
path: cumulus/primitives/core/src/lib.rs
user: login: bkontur
body: Mentioned `ChildParachainRouter` does something like that: `SendXcm::validate` use `dmp::Pallet::<T>::can_queue_downward_message` and `SendXcm::deliver` then triggers `dmp::Pallet::<T>::queue_downward_message`.

So maybe more general like:
```suggestion
	fn can_send_upward_message(msg: &UpwardMessage) -> Result<(), ()> {
```

---

In an ideal world (and according to the `SendXcm` trait definition), `SendXcm::validate` should validate everything and return a validated `Self::Ticket` containing all the necessary data for a successful `SendXcm::deliver`.

I believe there's a common hidden "issue" shared by all our base routers - `XcmpQueue`, `ChildParachainRouter`, and `ParentAsUmp`. Typically, they all trigger the same validation twice: once during `SendXcm::validate`, and again during `SendXcm::deliver`. For example, `ChildParachainRouter` calls `can_queue_downward_message`, which performs multiple storage reads (e.g., `paras::Heads::<T>::contains_key`, `DownwardMessageQueues::<T>::decode_len`) and `queue_downward_message` also checks again `can_queue_downward_message`.

So, I think there's room for **some very light optimization** here. However, I'm not sure whether it's truly worth it or what the actual impact would be (except to be aligned with `SendXcm` trait definition) - perhaps just some minor weight savings? depending on storage caching?.

The general idea would be to align all these routers with the philosophy: "validate once, return validated data for delivery, and perform delivery without redundant validations."

If we agree on this, we can create a follow-up issue (good first issue).






created_at: 2025-05-05T09:02:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -155,11 +155,19 @@ pub trait UpwardMessageSender {
 	/// be dispatched or an error if the message cannot be sent.
 	/// return the hash of the message sent
 	fn send_upward_message(msg: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError>;
+
+	/// Check whether the message size is acceptable for the channel.
+	fn check_size(size: usize) -> Result<(), ()>;
path: cumulus/primitives/core/src/lib.rs
user: login: kianenigma
body: That's a great insight @bkontur thanks for sharing! 

I would split it into: 

1. Let's first make sure all `validate`s do the same thing (and as you said, all of the checks)
2. Afterwards tackle the optimization

I do think the optimization is probably a negligible gain though. 
created_at: 2025-05-05T09:41:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -155,11 +155,19 @@ pub trait UpwardMessageSender {
 	/// be dispatched or an error if the message cannot be sent.
 	/// return the hash of the message sent
 	fn send_upward_message(msg: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError>;
+
+	/// Check whether the message size is acceptable for the channel.
+	fn check_size(size: usize) -> Result<(), ()>;
path: cumulus/primitives/core/src/lib.rs
user: login: kianenigma
body: I've added the tests and renmaed the method as per your suggestion in https://github.com/paritytech/polkadot-sdk/pull/8409/commits/29495a54a98c4dac37885b57cb89c1743db75778

I have made sure that indeed `ChildParachainRouter` already does the right behavior. 

I have not checked yet if the `XcmpQueue` also checks for the message size or not.
created_at: 2025-05-05T09:51:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -155,11 +155,19 @@ pub trait UpwardMessageSender {
 	/// be dispatched or an error if the message cannot be sent.
 	/// return the hash of the message sent
 	fn send_upward_message(msg: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError>;
+
+	/// Check whether the message size is acceptable for the channel.
+	fn can_send_upward_message(size: usize) -> Result<(), MessageSendError>;
 }
+
 impl UpwardMessageSender for () {
 	fn send_upward_message(_msg: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError> {
 		Err(MessageSendError::NoChannel)
 	}
+
+	fn can_send_upward_message(_size: usize) -> Result<(), MessageSendError> {
path: cumulus/primitives/core/src/lib.rs
user: login: bkontur
body: What about changing `usize` to `&UpwardMessage`?
```suggestion
	fn can_send_upward_message(_msg: &UpwardMessage) -> Result<(), MessageSendError> {
```
created_at: 2025-05-05T13:36:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -155,11 +155,19 @@ pub trait UpwardMessageSender {
 	/// be dispatched or an error if the message cannot be sent.
 	/// return the hash of the message sent
 	fn send_upward_message(msg: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError>;
+
+	/// Check whether the message size is acceptable for the channel.
+	fn can_send_upward_message(size: usize) -> Result<(), MessageSendError>;
 }
+
 impl UpwardMessageSender for () {
 	fn send_upward_message(_msg: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError> {
 		Err(MessageSendError::NoChannel)
 	}
+
+	fn can_send_upward_message(_size: usize) -> Result<(), MessageSendError> {
path: cumulus/primitives/core/src/lib.rs
user: login: kianenigma
body: you are right, it makes a lot more sense like this! 
created_at: 2025-05-05T15:27:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -155,11 +155,19 @@ pub trait UpwardMessageSender {
 	/// be dispatched or an error if the message cannot be sent.
 	/// return the hash of the message sent
 	fn send_upward_message(msg: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError>;
+
+	/// Check whether the message size is acceptable for the channel.
+	fn can_send_upward_message(size: usize) -> Result<(), MessageSendError>;
 }
+
 impl UpwardMessageSender for () {
 	fn send_upward_message(_msg: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError> {
 		Err(MessageSendError::NoChannel)
 	}
+
+	fn can_send_upward_message(_size: usize) -> Result<(), MessageSendError> {
path: cumulus/primitives/core/src/lib.rs
user: login: kianenigma
body: https://github.com/paritytech/polkadot-sdk/pull/8409/commits/6c39e811ac58f3c5ad46c3b07630ae43259002b0
created_at: 2025-05-05T15:37:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1648,6 +1648,23 @@ impl<T: Config> UpwardMessageSender for Pallet<T> {
 	fn send_upward_message(message: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError> {
 		Self::send_upward_message(message)
 	}
+
+	fn can_send_upward_message(message: &UpwardMessage) -> Result<(), MessageSendError> {
+		#[cfg(not(feature = "runtime-benchmarks"))]
+		let max_upward_message_size = HostConfiguration::<T>::get()
+			.map(|cfg| cfg.max_upward_message_size)
+			.ok_or(MessageSendError::Other)?;
+		// for benchmarks, if host cfg is not set, we assume no limit on the size.
path: cumulus/pallets/parachain-system/src/lib.rs
user: login: bkontur
body: @kianenigma this is not enough, because several tests fail now,
probably some more tuning needed for feature flags here, e.g.:
```
#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]

and also cover `not(any)`
```

Btw. I fixed tests also here: https://github.com/paritytech/polkadot-sdk/pull/8458 with approach 2
created_at: 2025-05-07T10:25:19Z
author_association: CONTRIBUTOR

PR comments:
user: login: franciscoaguirre
created_at: 2025-05-02T12:55:24Z
body: The approach looks good to me. Why didn't you have to add it to the `ChildParachainRouter`? Does it already validate the message size?

There are unit tests for the `ParentAsUmp` router in the file where it's defined: cumulus/primitives/utility/src/lib.rs. I would add a unit test for validating a message that's too big there.
author_association: CONTRIBUTOR
user: login: acatangiu
created_at: 2025-05-02T14:00:55Z
body: cc @serban300 
author_association: CONTRIBUTOR
user: login: kianenigma
created_at: 2025-05-05T09:41:59Z
body: \cmd prdoc --bump minor
author_association: CONTRIBUTOR
user: login: kianenigma
created_at: 2025-05-05T09:47:57Z
body: \cmd prdoc --bump minor
author_association: CONTRIBUTOR
user: login: kianenigma
created_at: 2025-05-05T13:03:30Z
body: /cmd prdoc --bump minor
author_association: CONTRIBUTOR
user: login: kianenigma
created_at: 2025-05-06T16:42:47Z
body: I looked a bit into why the benches are failing. It is because `HostConfigurations` is not set, and at the moment we fail if it doesn't exist. It seems to me the right behavior.

I have 2 options, and would like advice on which to take, based on the "standards" and conventions of XCM related code that I don't know very well, but would like to respect: 

1. `#[cfg(feature = runtime-bencharks)] let max_size = HostConfigurations.unwrap_or(usize::max)` directly in `UpwardMessageSender`.
2. Add an impl of `EnsureDelivery` for `parachains_system::Pallet`, which sets up `HostConfigurations` (and potentially any other configuration that might be needed in the future)

Latter is clearly more elegant to me, but a bit more work
author_association: CONTRIBUTOR
user: login: kianenigma
created_at: 2025-05-07T08:57:26Z
body: I pushed approach 1 in https://github.com/paritytech/polkadot-sdk/pull/8409/commits/550619387ed51430a6d84e029ebd89fa26f48591 for now.
author_association: CONTRIBUTOR
user: login: bkontur
created_at: 2025-05-07T09:29:44Z
body: > I pushed approach 1 in [5506193](https://github.com/paritytech/polkadot-sdk/commit/550619387ed51430a6d84e029ebd89fa26f48591) for now.

nice quick hack :), something like this for approach 2 should also work: https://github.com/paritytech/polkadot-sdk/compare/kiz-vmp-message-size-check...bko-on-kiz-vmp-message-size-check?expand=1
author_association: CONTRIBUTOR
user: login: paritytech-workflow-stopper[bot]
created_at: 2025-05-13T08:52:36Z
body: 
All GitHub workflows were cancelled due to failure one of the required jobs.
**Failed workflow url**: https://github.com/paritytech/polkadot-sdk/actions/runs/14991655906
**Failed job name**: test-linux-stable-no-try-runtime

author_association: NONE
user: login: kianenigma
created_at: 2025-05-14T20:26:15Z
body: /cmd prdoc --bump minor
author_association: CONTRIBUTOR
user: login: kianenigma
created_at: 2025-05-14T20:26:25Z
body: \cmd prdoc --bump minor


author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-14T20:28:40Z
body: Command "prdoc --bump minor" has failed ❌! [See logs here](https://github.com/paritytech/polkadot-sdk/actions/runs/15030388595/job/42241314056)
author_association: CONTRIBUTOR
user: login: kianenigma
created_at: 2025-05-14T20:34:32Z
body: /cmd prdoc --bump minor --force
author_association: CONTRIBUTOR