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_watermarkshould be strictly greater than the value ofHrmpWatermarksforP(if any).new_hrmp_watermarkmust not be greater than the context's block number.new_hrmp_watermarkshould be either- equal to the context's block number
- or in
HrmpChannelDigestsforPan 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_candidatemessages. - 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
Mwith the channelCidentified by(sender, M.recipient)check:- exists
M's payload size doesn't exceed a preconfigured limitC.max_message_sizeM's payload size summed with theC.total_sizedoesn't exceed a preconfigured limitC.max_total_size.C.msg_count + 1doesn'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
HMwith the channelCidentified by(sender, HM.recipient):- Append
HMintoHrmpChannelContentsthat corresponds toCwithsent_atequals to the current block number. - Locate or create an entry in
HrmpChannelDigestsforHM.recipientand appendsenderinto the entry's list. - Increment
C.msg_count - Increment
C.total_sizebyHM'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
HrmpChannelDigestsforrecipientremove 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
Cidentified by(sender, recipient)for eachsendercoming from the set, prune messages up to thenew_hrmp_watermark. - For each pruned message
Mfrom channelC:- Decrement
C.msg_count - Decrement
C.total_sizebyM's payload size.
- Decrement
- Set
HrmpWatermarksforPto 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
originis notrecipient. - Check that
proposed_max_capacityis less or equal toconfig.hrmp_channel_max_capacityand greater than zero. - Check that
proposed_max_message_sizeis less or equal toconfig.hrmp_channel_max_message_sizeand greater than zero. - Check that
recipientis 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 foundHrmpEgressChannelsIndexfororigin) and the number of open requests by theorigin(the value fromHrmpOpenChannelRequestCountfororigin) doesn't exceed the limit of channels (config.hrmp_max_parachain_outbound_channelsorconfig.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
originaccording toconfig.hrmp_sender_deposit - Increase
HrmpOpenChannelRequestCountby 1 fororigin. - Append
(origin, recipient)toHrmpOpenChannelRequestsList. - Add a new entry to
HrmpOpenChannelRequestsfor(origin, recipient)- Set
sender_deposittoconfig.hrmp_sender_deposit - Set
max_capacitytoproposed_max_capacity - Set
max_message_sizetoproposed_max_message_size - Set
max_total_sizetoconfig.hrmp_channel_max_total_size
- Set
- Send a downward message to
recipientnotifying about an inbound HRMP channel request.- The DM is sent using
queue_downward_message. - The DM is represented by the
HrmpNewChannelOpenRequestXCM message.senderis set toorigin,max_message_sizeis set toproposed_max_message_size,max_capacityis 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 inHrmpIngressChannelsIndexfororigin) and the number of accepted open requests by theorigin(the value fromHrmpAcceptedChannelRequestCountfororigin) doesn't exceed the limit of channels (config.hrmp_max_parachain_inbound_channelsorconfig.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
originaccording toconfig.hrmp_recipient_deposit - For the request in
HrmpOpenChannelRequestsidentified by(sender, P), setconfirmedflag totrue. - Increase
HrmpAcceptedChannelRequestCountby 1 fororigin. - Send a downward message to
sendernotifying that the channel request was accepted.- The DM is sent using
queue_downward_message. - The DM is represented by the
HrmpChannelAcceptedXCM message.recipientis set toorigin.
- The DM is sent using
- Check that there is an existing request between (
hrmp_cancel_open_request(ch):- Check that
originis eitherch.senderorch.recipient - Check that the open channel request
chexists. - Check that the open channel request for
chis not confirmed. - Remove
chfromHrmpOpenChannelRequestsandHrmpOpenChannelRequestsList - Decrement
HrmpAcceptedChannelRequestCountforch.recipientby 1. - Unreserve the deposit of
ch.sender.
- Check that
hrmp_close_channel(ch):- Check that
originis eitherch.senderorch.recipient - Check that
HrmpChannelsforchexists. - Check that
chis not in theHrmpCloseChannelRequestsset. - If not already there, insert a new entry
Some(())toHrmpCloseChannelRequestsforchand appendchtoHrmpCloseChannelRequestsList. - 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
HrmpChannelClosingXCM message with:initiatoris set toorigin,senderis set toch.sender,recipientis set toch.recipient.
- The opposite party is
ch.senderiforiginisch.recipientandch.recipientiforiginisch.sender.
- The DM is sent using
- Check that
Session Change
- For each
Pinoutgoing_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
HrmpOpenChannelRequestCountforP - Remove
HrmpAcceptedChannelRequestCountforP. - Remove
HrmpOpenChannelRequestsandHrmpOpenChannelRequestsListfor(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
Cis 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
DinHrmpOpenChannelRequestsListwe query the requestRfromHrmpOpenChannelRequests:- if
R.confirmed = true,- if both
D.senderandD.recipientare not offboarded. - create a new channel
Cbetween(D.sender, D.recipient).- Initialize the
C.sender_depositwithR.sender_depositandC.recipient_depositwith the value found in the configurationconfig.hrmp_recipient_deposit. - Insert
senderinto the setHrmpIngressChannelsIndexfor therecipient. - Insert
recipientinto the setHrmpEgressChannelsIndexfor thesender.
- Initialize the
- decrement
HrmpOpenChannelRequestCountforD.senderby 1. - decrement
HrmpAcceptedChannelRequestCountforD.recipientby 1. - remove
R - remove
D
- if both
- if
- For each HRMP channel designator
DinHrmpCloseChannelRequestsList- remove the channel identified by
D, if exists. - remove
DfromHrmpCloseChannelRequests. - remove
DfromHrmpCloseChannelRequestsList
- remove the channel identified by
To remove a HRMP channel C identified with a tuple (sender, recipient):
- Return
C.sender_depositto thesender. - Return
C.recipient_depositto therecipient. - Remove
CfromHrmpChannels. - Remove
CfromHrmpChannelContents. - Remove
recipientfrom the setHrmpEgressChannelsIndexforsender. - Remove
senderfrom the setHrmpIngressChannelsIndexforrecipient.