Skip to content

Make HRMP advancement rule more restrictive#9086

Merged
serban300 merged 6 commits intoparitytech:masterfrom
serban300:hrmp-advancement-rule
Jan 20, 2026
Merged

Make HRMP advancement rule more restrictive#9086
serban300 merged 6 commits intoparitytech:masterfrom
serban300:hrmp-advancement-rule

Conversation

@serban300
Copy link
Copy Markdown
Contributor

Related to: #9021
Followup for #8860

Looked some more into this and from what I understand the HRMP max message size can't be changed dynamically. In order to change it we would need to close the channel and than open it again which would lead to clearing all the pending messages. So it's safe to use the current hrmp max message size in the advancement rule check.

@serban300 serban300 self-assigned this Jul 3, 2025
@serban300 serban300 added T9-cumulus This PR/Issue is related to cumulus. T18-zombienet_tests Trigger zombienet CI tests. labels Jul 3, 2025
@serban300 serban300 force-pushed the hrmp-advancement-rule branch from 077f83f to 69609db Compare July 3, 2025 10:34
@serban300
Copy link
Copy Markdown
Contributor Author

/cmd prdoc --bump patch

@serban300 serban300 force-pushed the hrmp-advancement-rule branch 2 times, most recently from 5a51d66 to eeb9161 Compare July 3, 2025 11:08
@serban300 serban300 requested review from bkchr and ggwpez July 3, 2025 11:59
@serban300 serban300 force-pushed the hrmp-advancement-rule branch 3 times, most recently from 77f14a0 to 0ca86b6 Compare September 12, 2025 11:08
@serban300 serban300 changed the title Improve HRMP advancement rule Make HRMP advancement rule more restrictive Sep 12, 2025
@serban300 serban300 force-pushed the hrmp-advancement-rule branch from 0ca86b6 to 408a035 Compare September 12, 2025 11:12
min_size
);
},
None => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is None, it means there are no hashed messages and we left the function already.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if I understand. This should mean that no size constraints were provided in which case we should apply the most basic constraint. Which is the case now for DMP. There might be hashed messages.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if in line 203. We can never end up here, because the if should resolve to true if the hashed messages are empty, which also means that the message_info is not set.

Maybe the entire function should only be called when we have message_size_info.

Copy link
Copy Markdown
Contributor Author

@serban300 serban300 Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, but for DMP we need the logic in the None branch. So I split this in 2 functions.

size = size.saturating_add(msg.data().len());
}

// Let's take a margin of safety.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can remove it, but I think it's safer to have it. It can save us from a parachain stall in very ugly corner cases while it doesn't impact the min size very much normally. I don't know.

For example if there have been some miscalculations in other places and we end up with a message that is off by a couple of bytes for example. Off by 1 errors are very common in my experience (there was one in this PR as well :D ).

let first_hashed_msg_channel = hashed_messages
.first()
.map(|(sender, _msg)| *sender)
.map(|sender| Self::get_ingress_channel_or_panic(ingress_channels, sender));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should take the maximum from all the max_message_size. Otherwise you may take 100bytes while some other channel has a max size of 1MIB.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I think only the max size of the first hashed message should be important. For example:

Let's say that we are connected to parachains:

  • 1000 -> max_message_size = 1
  • 2000 -> max_message_size = 50

And:

  • XCMP full messages total size limit = 100
  • full_messages: [{len: 50, sender: 2000}, {len: 1, sender: 1000}] => total length = 51

If we have:
hashed_messages: [{sender: 2000}, ...]
It's ok, because in the worst case scenario we had a maximal message from parachain 2000 and this would have exceeded the max full messages limit: 51 + 50 = 101 > 100

If we have:
hashed_messages: [{sender: 1000}, ...]
We should panic because the first message could have been added as a full message: 51 + 1 = 52 < 100 . Regardless of the fact that sender 2000 has a max size of 50.

Am I missing something ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see where my confusion is coming from. I assumed we took messages from the hrmp messages until we hit the maximum. But this is not correct, as we directly stop when one message doesn't fit anymore. Maybe another improvement 🙈

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, would be nice. We can consider doing this in the future. But I think it would require changing the MQC head logic ? which wouldn't be trivial

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would need to keep the order per parachain. That is fine. But we should be able to skip parachain A and continue with B etc.

min_size
);
},
None => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if in line 203. We can never end up here, because the if should resolve to true if the hashed messages are empty, which also means that the message_info is not set.

Maybe the entire function should only be called when we have message_size_info.

let first_hashed_msg_channel = hashed_messages
.first()
.map(|(sender, _msg)| *sender)
.map(|sender| Self::get_ingress_channel_or_panic(ingress_channels, sender));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see where my confusion is coming from. I assumed we took messages from the hrmp messages until we hit the maximum. But this is not correct, as we directly stop when one message doesn't fit anymore. Maybe another improvement 🙈

Comment on lines +227 to +228
assert!(
full_messages_size > min_full_messages_size,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just doing full_message_size + first_hashed_msg_max_size > max_full_messages_size should be easier? Then we don't need to do leave any extra room or whatever?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would still have no margin of safety in the worst case scenario. Anyway, normally it's more correct like this. Done.

ingress_channels
.get(channel_idx)
.map(|(_, channel)| channel)
.expect("Channel index was just calculated and is correct; qed")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably use some and, map or then functions on the option to directly check both in the if above.
Two possible pancis for the same thing is a bit odd.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Copy Markdown
Member

@ggwpez ggwpez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay so you make the HRMP rule greedy now? So that it forces the collator to include all possible messages and not just one? good


// Here we just check that there is at least 1 full message.
assert!(
self.full_messages.len() >= 1,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the case that already the first message is too big can never happen, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point !

With the current Polkadot and Kusama configs it won't happen. But with a misconfiguration it probably could. Anyway this check is only used for DMP and the problem there is that we don't know the max message size in order to do a proper check. This will be fixed when we migrate DMP to check_enough_messages_included_advanced as well. But in order to do this we need to also forward the max dmp message size to the parachain in the metadata. It would be great if we could do that in a separate PR and merge this one as it is. WDYT ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea we can do it separate

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the case that already the first message is too big can never happen, right?

If this would happen, we could never move forward. I see this not as an issue. Yes, could be a misconfiguration, but then you can not receive messages anymore.

@redzsina redzsina moved this from Backlog to Scheduled in Security Audit (PRs) - SRLabs Nov 17, 2025
@redzsina redzsina moved this from Scheduled to In progress in Security Audit (PRs) - SRLabs Nov 24, 2025
@rachsrl rachsrl moved this from In progress to Audited in Security Audit (PRs) - SRLabs Dec 2, 2025
@serban300 serban300 added this pull request to the merge queue Jan 20, 2026
Merged via the queue into paritytech:master with commit e30ff0d Jan 20, 2026
218 of 220 checks passed
@serban300 serban300 deleted the hrmp-advancement-rule branch January 20, 2026 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T9-cumulus This PR/Issue is related to cumulus. T18-zombienet_tests Trigger zombienet CI tests.

Projects

Status: Audited
Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants