HRMP Pallet
A module responsible for Horizontally Relay-routed Message Passing (HRMP). See Messaging Overview for more details.
Storage
HRMP related structs:
#![allow(unused)] fn main() { /// A description of a request to open an HRMP channel. struct HrmpOpenChannelRequest { /// Indicates if this request was confirmed by the recipient. confirmed: bool, /// The amount that the sender supplied at the time of creation of this request. sender_deposit: Balance, /// The maximum message size that could be put into the channel. max_message_size: u32, /// The maximum number of messages that can be pending in the channel at once. max_capacity: u32, /// The maximum total size of the messages that can be pending in the channel at once. max_total_size: u32, } /// A metadata of an HRMP channel. struct HrmpChannel { /// The amount that the sender supplied as a deposit when opening this channel. sender_deposit: Balance, /// The amount that the recipient supplied as a deposit when accepting opening this channel. recipient_deposit: Balance, /// The maximum number of messages that can be pending in the channel at once. max_capacity: u32, /// The maximum total size of the messages that can be pending in the channel at once. max_total_size: u32, /// The maximum message size that could be put into the channel. max_message_size: u32, /// The current number of messages pending in the channel. /// Invariant: should be less or equal to `max_capacity`. msg_count: u32, /// The total size in bytes of all message payloads in the channel. /// Invariant: should be less or equal to `max_total_size`. total_size: u32, /// A head of the Message Queue Chain for this channel. Each link in this chain has a form: /// `(prev_head, B, H(M))`, where /// - `prev_head`: is the previous value of `mqc_head` or zero if none. /// - `B`: is the [relay-chain] block number in which a message was appended /// - `H(M)`: is the hash of the message being appended. /// This value is initialized to a special value that consists of all zeroes which indicates /// that no messages were previously added. mqc_head: Option<Hash>, } }
HRMP related storage layout
#![allow(unused)] fn main() { /// The set of pending HRMP open channel requests. /// /// The set is accompanied by a list for iteration. /// /// Invariant: /// - There are no channels that exists in list but not in the set and vice versa. HrmpOpenChannelRequests: map HrmpChannelId => Option<HrmpOpenChannelRequest>; HrmpOpenChannelRequestsList: Vec<HrmpChannelId>; /// This mapping tracks how many open channel requests are initiated by a given sender para. /// Invariant: `HrmpOpenChannelRequests` should contain the same number of items that has `(X, _)` /// as the number of `HrmpOpenChannelRequestCount` for `X`. HrmpOpenChannelRequestCount: map ParaId => u32; /// This mapping tracks how many open channel requests were accepted by a given recipient para. /// Invariant: `HrmpOpenChannelRequests` should contain the same number of items `(_, X)` with /// `confirmed` set to true, as the number of `HrmpAcceptedChannelRequestCount` for `X`. HrmpAcceptedChannelRequestCount: map ParaId => u32; /// A set of pending HRMP close channel requests that are going to be closed during the session change. /// Used for checking if a given channel is registered for closure. /// /// The set is accompanied by a list for iteration. /// /// Invariant: /// - There are no channels that exists in list but not in the set and vice versa. HrmpCloseChannelRequests: map HrmpChannelId => Option<()>; HrmpCloseChannelRequestsList: Vec<HrmpChannelId>; /// The HRMP watermark associated with each para. /// Invariant: /// - each para `P` used here as a key should satisfy `Paras::is_valid_para(P)` within a session. HrmpWatermarks: map ParaId => Option<BlockNumber>; /// HRMP channel data associated with each para. /// Invariant: /// - each participant in the channel should satisfy `Paras::is_valid_para(P)` within a session. HrmpChannels: map HrmpChannelId => Option<HrmpChannel>; /// Ingress/egress indexes allow to find all the senders and receivers given the opposite /// side. I.e. /// /// (a) ingress index allows to find all the senders for a given recipient. /// (b) egress index allows to find all the recipients for a given sender. /// /// Invariants: /// - for each ingress index entry for `P` each item `I` in the index should present in `HrmpChannels` /// as `(I, P)`. /// - for each egress index entry for `P` each item `E` in the index should present in `HrmpChannels` /// as `(P, E)`. /// - there should be no other dangling channels in `HrmpChannels`. /// - the vectors are sorted. HrmpIngressChannelsIndex: map ParaId => Vec<ParaId>; HrmpEgressChannelsIndex: map ParaId => Vec<ParaId>; /// Storage for the messages for each channel. /// Invariant: cannot be non-empty if the corresponding channel in `HrmpChannels` is `None`. HrmpChannelContents: map HrmpChannelId => Vec<InboundHrmpMessage>; /// Maintains a mapping that can be used to answer the question: /// What paras sent a message at the given block number for a given receiver. /// Invariants: /// - The inner `Vec<ParaId>` is never empty. /// - The inner `Vec<ParaId>` cannot store two same `ParaId`. /// - The outer vector is sorted ascending by block number and cannot store two items with the same /// block number. HrmpChannelDigests: map ParaId => Vec<(BlockNumber, Vec<ParaId>)>; }
Initialization
No initialization routine runs for this module.
Routines
Candidate Acceptance Function:
check_hrmp_watermark(P: ParaId, new_hrmp_watermark)
:new_hrmp_watermark
should be strictly greater than the value ofHrmpWatermarks
forP
(if any).new_hrmp_watermark
must not be greater than the context's block number.new_hrmp_watermark
should be either- equal to the context's block number
- or in
HrmpChannelDigests
forP
an entry with the block number should exist
check_outbound_hrmp(sender: ParaId, Vec<OutboundHrmpMessage>)
:- Checks that there are at most
config.hrmp_max_message_num_per_candidate
messages. - Checks that horizontal messages are sorted by ascending recipient ParaId and there is no two horizontal messages have the same recipient.
- For each horizontal message
M
with the channelC
identified by(sender, M.recipient)
check:- exists
M
's payload size doesn't exceed a preconfigured limitC.max_message_size
M
's payload size summed with theC.total_size
doesn't exceed a preconfigured limitC.max_total_size
.C.msg_count + 1
doesn't exceed a preconfigured limitC.max_capacity
.
- Checks that there are at most
Candidate Enactment:
queue_outbound_hrmp(sender: ParaId, Vec<OutboundHrmpMessage>)
:- For each horizontal message
HM
with the channelC
identified by(sender, HM.recipient)
:- Append
HM
intoHrmpChannelContents
that corresponds toC
withsent_at
equals to the current block number. - Locate or create an entry in
HrmpChannelDigests
forHM.recipient
and appendsender
into the entry's list. - Increment
C.msg_count
- Increment
C.total_size
byHM
's payload size - Append a new link to the MQC and save the new head in
C.mqc_head
. Note that the current block number as of enactment is used for the link.
- Append
- For each horizontal message
prune_hrmp(recipient, new_hrmp_watermark)
:- From
HrmpChannelDigests
forrecipient
remove all entries up to an entry with block number equal tonew_hrmp_watermark
. - From the removed digests construct a set of paras that sent new messages within the interval between the old and new watermarks.
- For each channel
C
identified by(sender, recipient)
for eachsender
coming from the set, prune messages up to thenew_hrmp_watermark
. - For each pruned message
M
from channelC
:- Decrement
C.msg_count
- Decrement
C.total_size
byM
's payload size.
- Decrement
- Set
HrmpWatermarks
forP
to be equal tonew_hrmp_watermark
NOTE: That collecting digests can be inefficient and the time it takes grows very fast. Thanks to the aggressive parameterization this shouldn't be a big of a deal. If that becomes a problem consider introducing an extra dictionary which says at what block the given sender sent a message to the recipient.
- From
Entry-points
The following entry-points are meant to be used for HRMP channel management.
Those entry-points are meant to be called from a parachain. origin
is defined as the ParaId
of the parachain
executed the message.
hrmp_init_open_channel(recipient, proposed_max_capacity, proposed_max_message_size)
:- Check that the
origin
is notrecipient
. - Check that
proposed_max_capacity
is less or equal toconfig.hrmp_channel_max_capacity
and greater than zero. - Check that
proposed_max_message_size
is less or equal toconfig.hrmp_channel_max_message_size
and greater than zero. - Check that
recipient
is a valid para. - Check that there is no existing channel for
(origin, recipient)
inHrmpChannels
. - Check that there is no existing open channel request (
origin
,recipient
) inHrmpOpenChannelRequests
. - Check that the sum of the number of already opened HRMP channels by the
origin
(the size of the set foundHrmpEgressChannelsIndex
fororigin
) and the number of open requests by theorigin
(the value fromHrmpOpenChannelRequestCount
fororigin
) doesn't exceed the limit of channels (config.hrmp_max_parachain_outbound_channels
orconfig.hrmp_max_parathread_outbound_channels
) minus 1. - Check that
origin
's balance is more or equal toconfig.hrmp_sender_deposit
- Reserve the deposit for the
origin
according toconfig.hrmp_sender_deposit
- Increase
HrmpOpenChannelRequestCount
by 1 fororigin
. - Append
(origin, recipient)
toHrmpOpenChannelRequestsList
. - Add a new entry to
HrmpOpenChannelRequests
for(origin, recipient)
- Set
sender_deposit
toconfig.hrmp_sender_deposit
- Set
max_capacity
toproposed_max_capacity
- Set
max_message_size
toproposed_max_message_size
- Set
max_total_size
toconfig.hrmp_channel_max_total_size
- Set
- Send a downward message to
recipient
notifying about an inbound HRMP channel request.- The DM is sent using
queue_downward_message
. - The DM is represented by the
HrmpNewChannelOpenRequest
XCM message.sender
is set toorigin
,max_message_size
is set toproposed_max_message_size
,max_capacity
is set toproposed_max_capacity
.
- The DM is sent using
- Check that the
hrmp_accept_open_channel(sender)
:- Check that there is an existing request between (
sender
,origin
) inHrmpOpenChannelRequests
- Check that it is not confirmed.
- Check that the sum of the number of inbound HRMP channels opened to
origin
(the size of the set found inHrmpIngressChannelsIndex
fororigin
) and the number of accepted open requests by theorigin
(the value fromHrmpAcceptedChannelRequestCount
fororigin
) doesn't exceed the limit of channels (config.hrmp_max_parachain_inbound_channels
orconfig.hrmp_max_parathread_inbound_channels
) minus 1. - Check that
origin
's balance is more or equal toconfig.hrmp_recipient_deposit
. - Reserve the deposit for the
origin
according toconfig.hrmp_recipient_deposit
- For the request in
HrmpOpenChannelRequests
identified by(sender, P)
, setconfirmed
flag totrue
. - Increase
HrmpAcceptedChannelRequestCount
by 1 fororigin
. - Send a downward message to
sender
notifying that the channel request was accepted.- The DM is sent using
queue_downward_message
. - The DM is represented by the
HrmpChannelAccepted
XCM message.recipient
is set toorigin
.
- The DM is sent using
- Check that there is an existing request between (
hrmp_cancel_open_request(ch)
:- Check that
origin
is eitherch.sender
orch.recipient
- Check that the open channel request
ch
exists. - Check that the open channel request for
ch
is not confirmed. - Remove
ch
fromHrmpOpenChannelRequests
andHrmpOpenChannelRequestsList
- Decrement
HrmpAcceptedChannelRequestCount
forch.recipient
by 1. - Unreserve the deposit of
ch.sender
.
- Check that
hrmp_close_channel(ch)
:- Check that
origin
is eitherch.sender
orch.recipient
- Check that
HrmpChannels
forch
exists. - Check that
ch
is not in theHrmpCloseChannelRequests
set. - If not already there, insert a new entry
Some(())
toHrmpCloseChannelRequests
forch
and appendch
toHrmpCloseChannelRequestsList
. - Send a downward message to the opposite party notifying about the channel closing.
- The DM is sent using
queue_downward_message
. - The DM is represented by the
HrmpChannelClosing
XCM message with:initiator
is set toorigin
,sender
is set toch.sender
,recipient
is set toch.recipient
.
- The opposite party is
ch.sender
iforigin
isch.recipient
andch.recipient
iforigin
isch.sender
.
- The DM is sent using
- Check that
Session Change
- For each
P
inoutgoing_paras
(generated byParas::on_new_session
):- Remove all inbound channels of
P
, i.e.(_, P)
, - Remove all outbound channels of
P
, i.e.(P, _)
, - Remove
HrmpOpenChannelRequestCount
forP
- Remove
HrmpAcceptedChannelRequestCount
forP
. - Remove
HrmpOpenChannelRequests
andHrmpOpenChannelRequestsList
for(P, _)
and(_, P)
.- For each removed channel request
C
:- Unreserve the sender's deposit if the sender is not present in
outgoing_paras
- Unreserve the recipient's deposit if
C
is confirmed and the recipient is not present inoutgoing_paras
- Unreserve the sender's deposit if the sender is not present in
- For each removed channel request
- Remove all inbound channels of
- For each channel designator
D
inHrmpOpenChannelRequestsList
we query the requestR
fromHrmpOpenChannelRequests
:- if
R.confirmed = true
,- if both
D.sender
andD.recipient
are not offboarded. - create a new channel
C
between(D.sender, D.recipient)
.- Initialize the
C.sender_deposit
withR.sender_deposit
andC.recipient_deposit
with the value found in the configurationconfig.hrmp_recipient_deposit
. - Insert
sender
into the setHrmpIngressChannelsIndex
for therecipient
. - Insert
recipient
into the setHrmpEgressChannelsIndex
for thesender
.
- Initialize the
- decrement
HrmpOpenChannelRequestCount
forD.sender
by 1. - decrement
HrmpAcceptedChannelRequestCount
forD.recipient
by 1. - remove
R
- remove
D
- if both
- if
- For each HRMP channel designator
D
inHrmpCloseChannelRequestsList
- remove the channel identified by
D
, if exists. - remove
D
fromHrmpCloseChannelRequests
. - remove
D
fromHrmpCloseChannelRequestsList
- remove the channel identified by
To remove a HRMP channel C
identified with a tuple (sender, recipient)
:
- Return
C.sender_deposit
to thesender
. - Return
C.recipient_deposit
to therecipient
. - Remove
C
fromHrmpChannels
. - Remove
C
fromHrmpChannelContents
. - Remove
recipient
from the setHrmpEgressChannelsIndex
forsender
. - Remove
sender
from the setHrmpIngressChannelsIndex
forrecipient
.