Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Implementer's Guide: Make HRMP use upward message kinds #1591

Merged
merged 9 commits into from
Aug 18, 2020
7 changes: 4 additions & 3 deletions roadmap/implementers-guide/src/messaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ The weight that processing of the dispatchables can consume is limited by a prec
that some dispatchables will be left for later blocks. To make the dispatching more fair, the queues are processed turn-by-turn
in a round robin fashion.

Other kinds of upward messages can be introduced in the future as well. Potential candidates are channel management for
horizontal message passing (XCMP and HRMP, both are to be described below), new validation code signalling, or other
requests to the relay chain.
Upward messages are also used by a parachain to request opening and closing HRMP channels (HRMP will be described below).

Other kinds of upward messages can be introduced in the future as well. Potential candidates are
new validation code signalling, or other requests to the relay chain.

## Horizontal Message Passing

Expand Down
129 changes: 68 additions & 61 deletions roadmap/implementers-guide/src/runtime/router.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ struct HrmpChannelId {

/// A description of a request to open an HRMP channel.
struct HrmpOpenChannelRequest {
/// The sender and the initiator of this request.
sender: ParaId,
/// The recipient of the opened request.
recipient: ParaId,
/// Indicates if this request was confirmed by the recipient.
confirmed: bool,
/// How many session boundaries ago this request was seen.
Expand All @@ -56,19 +52,6 @@ struct HrmpOpenChannelRequest {
limit_used_bytes: u32,
}

/// A description of a request to close an opened HRMP channel.
struct HrmpCloseChannelRequest {
/// The para which initiated closing an existing channel.
///
/// Invariant: it should be equal to one of the following
/// - `Some(sender)`
/// - `Some(recipient)`
/// - `None` in case the request initiated by the runtime (including governance, migration, etc)
initiator: Option<ParaId>,
/// The identifier of an HRMP channel to be closed.
id: HrmpChannelId,
}

/// A metadata of an HRMP channel.
struct HrmpChannel {
/// The amount that the sender supplied as a deposit when opening this channel.
Expand Down Expand Up @@ -98,14 +81,30 @@ struct HrmpChannel {
HRMP related storage layout

```rust,ignore
/// Pending HRMP open channel requests.
HrmpOpenChannelRequests: Vec<HrmpOpenChannelRequest>;
/// 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>;
Copy link
Contributor

Choose a reason for hiding this comment

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

frame maps support iteration already, i believe.


/// This mapping tracks how many open channel requests are inititated by a given sender para.
/// Invariant: `HrmpOpenChannelRequests` should contain the same number of items that has `_.sender == X`
/// Invariant: `HrmpOpenChannelRequestsList` should contain the same number of items that has `(X, _)`
/// as the number of `HrmpOpenChannelRequestCount` for `X`.
HrmpOpenChannelRequestCount: map ParaId => u32;
/// Pending HRMP close channel requests.
HrmpCloseChannelRequests: Vec<HrmpCloseChannelRequest>;

/// 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.
///
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 explicitness

/// 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.
HrmpWatermarks: map ParaId => Option<BlockNumber>;
/// HRMP channel data associated with each para.
Expand All @@ -129,44 +128,32 @@ HrmpChannelDigests: map ParaId => Vec<(BlockNumber, Vec<ParaId>)>;

No initialization routine runs for this module.

## Entry points

The following routines are intended to be invoked by paras' upward messages.

* `hrmp_init_open_channel(recipient)`:
1. Check that the origin of this message is a para. We say that the origin of this message is the `sender`.
1. Check that the `sender` is not `recipient`.
1. Check that the `recipient` para exists.
1. Check that there is no existing open channel request from sender to recipient.
1. Check that the sum of the number of already opened HRMP channels by the `sender` (the size of the set found `HrmpEgressChannelsIndex` for `sender`) and the number of open requests by the `sender` (the value from `HrmpOpenChannelRequestCount` for `sender`) doesn't exceed the limit of channels (`config.hrmp_max_parachain_outbound_channels` or `config.hrmp_max_parathread_outbound_channels`) minus 1.
1. Reserve the deposit for the `sender` according to `config.hrmp_sender_deposit`
1. Add a new entry to `HrmpOpenChannelRequests` and increase `HrmpOpenChannelRequestCount` by 1 for the `sender`.
1. Set `sender_deposit` to `config.hrmp_sender_deposit`
1. Set `limit_used_places` to `config.hrmp_channel_max_places`
1. Set `limit_limit_used_bytes` to `config.hrmp_channel_max_size`

* `hrmp_accept_open_channel(i)`, `i` - is the index of open channel request:
1. Check that the designated open channel request exists
1. Check that the request's `recipient` corresponds to the origin of this message.
1. Reserve the deposit for the `recipient` according to `config.hrmp_recipient_deposit`
1. Set the request's `confirmed` flag to `true`.

* `hrmp_close_channel(sender, recipient)`:
1. Check that the channel between `sender` and `recipient` exists
1. Check that the origin of the message is either `sender` or `recipient`
1. Check that there is no existing intention to close the channel between `sender` and `recipient`.
1. Add a new entry to `HrmpCloseChannelRequests` with initiator set to the `Some` variant with the origin of this message.

## Routines

Candidate Acceptance Function:

* `check_upward_messages(P: ParaId, Vec<UpwardMessage>`:
1. Checks that there are at most `config.max_upward_message_num_per_candidate` messages.
1. Checks each upward message individually depending on its kind:
1. Checks each upward message `M` individually depending on its kind:
1. If the message kind is `Dispatchable`:
1. Verify that `RelayDispatchQueueSize` for `P` has enough capacity for the message (NOTE that should include all processed
upward messages of the `Dispatchable` kind up to this point!)
1. If the message kind is `HrmpInitOpenChannel(recipient)`:
1. Check that the `P` is not `recipient`.
1. Check that `recipient` is a valid para.
1. Check that there is no existing open channel request (`P`, `recipient`) in `HrmpOpenChannelRequests`.
1. Check that the sum of the number of already opened HRMP channels by the `sender` (the size
of the set found `HrmpEgressChannelsIndex` for `sender`) and the number of open requests by the
`sender` (the value from `HrmpOpenChannelRequestCount` for `sender`) doesn't exceed the limit of
channels (`config.hrmp_max_parachain_outbound_channels` or `config.hrmp_max_parathread_outbound_channels`) minus 1.
1. Check that `P`'s balance is more or equal to `config.hrmp_sender_deposit`
1. If the message kind is `HrmpAcceptOpenChannel(sender)`:
1. Check that there is exsting request between (`sender`, `P`) in `HrmpOpenChannelRequests`
pepyakin marked this conversation as resolved.
Show resolved Hide resolved
1. Check that `P`'s balance is more or equal to `config.hrmp_recipient_deposit`.
1. If the message kind is `HrmpCloseChannel(ch)`:
1. Check that `P` is either `ch.sender` or `ch.recipient`
1. Check that `HrmpChannels` for `ch` exists.
1. Check that `ch` is not in the `HrmpCloseChannelRequests` set.
* `check_processed_downward_messages(P: ParaId, processed_downward_messages)`:
1. Checks that `DownwardMessageQueues` for `P` is at least `processed_downward_messages` long.
1. Checks that `processed_downward_messages` is at least 1 if `DownwardMessageQueues` for `P` is not empty.
Expand Down Expand Up @@ -205,6 +192,20 @@ Candidate Enactment:
1. Append the message to `RelayDispatchQueues` for `P`
1. Increment the size and the count in `RelayDispatchQueueSize` for `P`.
1. Ensure that `P` is present in `NeedsDispatch`.
1. If the message kind is `HrmpInitOpenChannel(recipient)`:
1. Increase `HrmpOpenChannelRequestCount` by 1 for the `P`.
1. Append `(P, recipient)` to `HrmpOpenChannelRequestsList`.
1. Add a new entry to `HrmpOpenChannelRequests` for `(sender, recipient)`
1. Set `sender_deposit` to `config.hrmp_sender_deposit`
1. Set `limit_used_places` to `config.hrmp_channel_max_places`
1. Set `limit_limit_used_bytes` to `config.hrmp_channel_max_size`
1. Reserve the deposit for the `P` according to `config.hrmp_sender_deposit`
1. If the message kind is `HrmpAcceptOpenChannel(sender)`:
1. Reserve the deposit for the `P` according to `config.hrmp_recipient_deposit`
1. For the request in `HrmpOpenChannelRequests` identified by `(sender, P)`, set `confirmed` flag to `true`.
1. If the message kind is `HrmpCloseChannel(ch)`:
1. Insert a new entry `Some(())` to `HrmpCloseChannelRequests` for `ch`.
1. Append `ch` to `HrmpCloseChannelRequestsList`.

The following routine is intended to be called in the same time when `Paras::schedule_para_cleanup` is called.

Expand Down Expand Up @@ -242,23 +243,29 @@ any of dispatchables return an error.
1. Remove `RelayDispatchQueues` of `P`.
1. Remove `P` if it exists in `NeedsDispatch`.
1. If `P` is in `NextDispatchRoundStartWith`, then reset it to `None`
- Note that we don't remove the open/close requests since they are gon die out naturally.
1. For each request `R` in `HrmpOpenChannelRequests`:
- Note that if we don't remove the open/close requests since they are goning die out naturally at the end of the session.
pepyakin marked this conversation as resolved.
Show resolved Hide resolved
1. For each channel designator `D` in `HrmpOpenChannelRequestsList` we query the request `R` from `HrmpOpenChannelRequests`:
1. if `R.confirmed = false`:
1. increment `R.age` by 1.
1. if `R.age` reached a preconfigured time-to-live limit `config.hrmp_open_request_ttl`, then:
1. refund `R.sender_deposit` to the sender
1. decrement `HrmpOpenChannelRequestCount` for `R.sender` by 1.
1. decrement `HrmpOpenChannelRequestCount` for `D.sender` by 1.
1. remove `R`
1. remove `D`
2. if `R.confirmed = true`,
1. check that `R.sender` and `R.recipient` are not offboarded.
1. create a new channel `C` between `(R.sender, R.recipient)`.
1. Initialize the `C.sender_deposit` with `R.sender_deposit` and `C.recipient_deposit` with the value found in the configuration `config.hrmp_recipient_deposit`.
1. Insert `sender` into the set `HrmpIngressChannelsIndex` for the `recipient`.
1. Insert `recipient` into the set `HrmpEgressChannelsIndex` for the `sender`.
1. decrement `HrmpOpenChannelRequestCount` for `R.sender` by 1.
1. if both `D.sender` and `D.recipient` are not offboarded.
1. create a new channel `C` between `(D.sender, D.recipient)`.
1. Initialize the `C.sender_deposit` with `R.sender_deposit` and `C.recipient_deposit`
with the value found in the configuration `config.hrmp_recipient_deposit`.
1. Insert `sender` into the set `HrmpIngressChannelsIndex` for the `recipient`.
1. Insert `recipient` into the set `HrmpEgressChannelsIndex` for the `sender`.
1. decrement `HrmpOpenChannelRequestCount` for `D.sender` by 1.
1. remove `R`
1. For each request `R` in `HrmpCloseChannelRequests` remove the channel identified by `R.id`, if exists.
1. remove `D`
1. For each channel designator `D` in `HrmpCloseChannelRequestsList`
1. remove the channel identified by `D`, if exists.
1. remove `D` from `HrmpCloseChannelRequests`.
1. remove `D` from `HrmpCloseChannelRequestsList`

To remove a channel `C` identified with a tuple `(sender, recipient)`:

Expand Down
20 changes: 17 additions & 3 deletions roadmap/implementers-guide/src/types/messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,23 @@ enum UpwardMessage {
/// The dispatchable to be executed in its raw form.
dispatchable: RawDispatchable,
},
// Examples:
// HrmpOpenChannel { .. },
// HrmpCloseChannel { .. },
/// A message for initiation of opening a new HRMP channel between the origin para and the
/// given `recipient`.
///
/// Let `origin` be the parachain that sent this upward message. In that case the channel
/// to be opened is (`origin` -> `recipient`).
HrmpInitOpenChannel(ParaId),
/// A message that is meant to confirm the HRMP open channel request initiated earlier by the
/// `HrmpInitOpenChannel` by the given `sender`.
///
/// Let `origin` be the parachain that sent this upward message. In that case the channel
/// (`origin` -> `sender`) will be opened during the session change.
HrmpAcceptOpenChannel(ParaId),
/// A message for closing the specified existing channel `ch`.
///
/// The channel to be closed is `(ch.sender -> ch.recipient)`. The parachain that sent this
/// upward message must be either `ch.sender` or `ch.recipient`.
HrmpCloseChannel(HrmpChannelId),
}
```

Expand Down