From 83a35874eecddaa8cc7007f11ad7eafc8443201d Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Thu, 9 Sep 2021 14:06:13 +0200 Subject: [PATCH] Do not expire HRMP open channel requests (#3543) * Do not expire HRMP open channel requests * Fix the build and update the docs * Implement canceling requests and do not remove them automatically * Fix a borked merge * Fix fmt * Please spellchecker * Apply suggestions from code review Co-authored-by: Amar Singh * Use `mutate_exists` for maintaining request counts * Apply `rustfmt` * Move newly introduced entrypoint to end to preserve ordering Co-authored-by: Amar Singh --- polkadot/node/service/src/chain_spec.rs | 2 +- polkadot/parachain/src/primitives.rs | 10 +- .../implementers-guide/src/runtime/hrmp.md | 26 +- .../implementers-guide/src/types/runtime.md | 6 +- .../runtime/parachains/src/configuration.rs | 25 +- polkadot/runtime/parachains/src/hrmp.rs | 296 +++++++++++++----- polkadot/scripts/gitlab/lingua.dic | 3 + 7 files changed, 250 insertions(+), 118 deletions(-) diff --git a/polkadot/node/service/src/chain_spec.rs b/polkadot/node/service/src/chain_spec.rs index c0e225666e..e2ed37334a 100644 --- a/polkadot/node/service/src/chain_spec.rs +++ b/polkadot/node/service/src/chain_spec.rs @@ -174,7 +174,7 @@ fn default_parachains_host_configuration( ump_service_total_weight: 4 * 1_000_000_000, max_upward_message_size: 1024 * 1024, max_upward_message_num_per_candidate: 5, - hrmp_open_request_ttl: 5, + _hrmp_open_request_ttl: 5, hrmp_sender_deposit: 0, hrmp_recipient_deposit: 0, hrmp_channel_max_capacity: 8, diff --git a/polkadot/parachain/src/primitives.rs b/polkadot/parachain/src/primitives.rs index c6920a6a95..900c75c6cd 100644 --- a/polkadot/parachain/src/primitives.rs +++ b/polkadot/parachain/src/primitives.rs @@ -328,7 +328,8 @@ impl AccountIdConversion for Id { /// unidirectional, meaning that `(A, B)` and `(B, A)` refer to different channels. The convention is /// that we use the first item tuple for the sender and the second for the recipient. Only one channel /// is allowed between two participants in one direction, i.e. there cannot be 2 different channels -/// identified by `(A, B)`. +/// identified by `(A, B)`. A channel with the same para id in sender and recipient is invalid. That +/// is, however, not enforced. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode, RuntimeDebug)] #[cfg_attr(feature = "std", derive(Hash))] pub struct HrmpChannelId { @@ -338,6 +339,13 @@ pub struct HrmpChannelId { pub recipient: Id, } +impl HrmpChannelId { + /// Returns true if the given id corresponds to either the sender or the recipient. + pub fn is_participant(&self, id: Id) -> bool { + id == self.sender || id == self.recipient + } +} + /// A message from a parachain to its Relay Chain. pub type UpwardMessage = Vec; diff --git a/polkadot/roadmap/implementers-guide/src/runtime/hrmp.md b/polkadot/roadmap/implementers-guide/src/runtime/hrmp.md index a19e89b0b3..2b0b4751e3 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/hrmp.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/hrmp.md @@ -11,8 +11,6 @@ HRMP related structs: struct HrmpOpenChannelRequest { /// Indicates if this request was confirmed by the recipient. confirmed: bool, - /// How many session boundaries ago this request was seen. - age: SessionIndex, /// 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. @@ -158,8 +156,8 @@ Candidate Enactment: 1. Decrement `C.msg_count` 1. Decrement `C.total_size` by `M`'s payload size. 1. Set `HrmpWatermarks` for `P` to be equal to `new_hrmp_watermark` - > NOTE: That collecting digests can be inefficient and the time it takes grows very fast. Thanks to the aggresive - > parametrization this shouldn't be a big of a deal. + > 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. @@ -212,6 +210,13 @@ the parachain executed the message. - The DM is sent using `queue_downward_message`. - The DM is represented by the `HrmpChannelAccepted` XCM message. - `recipient` is set to `origin`. +* `hrmp_cancel_open_request(ch)`: + 1. Check that `origin` is either `ch.sender` or `ch.recipient` + 1. Check that the open channel request `ch` exists. + 1. Check that the open channel request for `ch` is not confirmed. + 1. Remove `ch` from `HrmpOpenChannelRequests` and `HrmpOpenChannelRequestsList` + 1. Decrement `HrmpAcceptedChannelRequestCount` for `ch.recipient` by 1. + 1. Unreserve the deposit of `ch.sender`. * `hrmp_close_channel(ch)`: 1. Check that `origin` is either `ch.sender` or `ch.recipient` 1. Check that `HrmpChannels` for `ch` exists. @@ -233,15 +238,12 @@ the parachain executed the message. 1. Remove all outbound channels of `P`, i.e. `(P, _)`, 1. Remove `HrmpOpenChannelRequestCount` for `P` 1. Remove `HrmpAcceptedChannelRequestCount` for `P`. + 1. Remove `HrmpOpenChannelRequests` and `HrmpOpenChannelRequestsList` for `(P, _)` and `(_, P)`. + 1. For each removed channel request `C`: + 1. Unreserve the sender's deposit if the sender is not present in `outgoing_paras` + 1. Unreserve the recipient's deposit if `C` is confirmed and the recipient is not present in `outgoing_paras` 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 `D.sender` by 1. - 1. remove `R` - 1. remove `D` - 2. if `R.confirmed = true`, + 1. if `R.confirmed = true`, 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` diff --git a/polkadot/roadmap/implementers-guide/src/types/runtime.md b/polkadot/roadmap/implementers-guide/src/types/runtime.md index 8f52bf8bc7..5749aeca86 100644 --- a/polkadot/roadmap/implementers-guide/src/types/runtime.md +++ b/polkadot/roadmap/implementers-guide/src/types/runtime.md @@ -42,7 +42,7 @@ struct HostConfiguration { pub dispute_period: SessionIndex, /// How long after dispute conclusion to accept statements. pub dispute_post_conclusion_acceptance_period: BlockNumber, - /// The maximum number of dispute spam slots + /// The maximum number of dispute spam slots pub dispute_max_spam_slots: u32, /// How long it takes for a dispute to conclude by time-out, if no supermajority is reached. pub dispute_conclusion_by_time_out_period: BlockNumber, @@ -85,8 +85,6 @@ struct HostConfiguration { /// decide to do with its PoV so this value in practice will be picked as a fraction of the PoV /// size. pub max_downward_message_size: u32, - /// Number of sessions after which an HRMP open channel request expires. - pub hrmp_open_request_ttl: u32, /// The deposit that the sender should provide for opening an HRMP channel. pub hrmp_sender_deposit: u32, /// The deposit that the recipient should provide for accepting opening an HRMP channel. @@ -119,7 +117,7 @@ struct HostConfiguration { Inherent data passed to a runtime entry-point for the advancement of parachain consensus. This contains 3 pieces of data: -1. [`Bitfields`](availability.md#signed-availability-bitfield) +1. [`Bitfields`](availability.md#signed-availability-bitfield) 2. [`BackedCandidates`](backing.md#backed-candidate) 3. [`MultiDisputeStatementSet`](disputes.md#multidisputestatementset) diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index b675bf9034..a6cadd158f 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -91,8 +91,10 @@ pub struct HostConfiguration { pub hrmp_max_parachain_outbound_channels: u32, /// The maximum number of outbound HRMP channels a parathread is allowed to open. pub hrmp_max_parathread_outbound_channels: u32, - /// Number of sessions after which an HRMP open channel request expires. - pub hrmp_open_request_ttl: u32, + /// NOTE: this field is deprecated. Channel open requests became non-expiring. Changing this value + /// doesn't have any effect. This field doesn't have a `deprecated` attribute because that would + /// trigger warnings coming from macros. + pub _hrmp_open_request_ttl: u32, /// The deposit that the sender should provide for opening an HRMP channel. pub hrmp_sender_deposit: Balance, /// The deposit that the recipient should provide for accepting opening an HRMP channel. @@ -202,7 +204,7 @@ impl> Default for HostConfiguration, new: u32) -> DispatchResult { - ensure_root(origin)?; - Self::update_config_member(|config| { - sp_std::mem::replace(&mut config.hrmp_open_request_ttl, new) != new - }); - Ok(()) + // Deprecated, but is not marked as such, because that would trigger warnings coming from + // the macro. + pub fn set_hrmp_open_request_ttl(_origin: OriginFor, _new: u32) -> DispatchResult { + Err("this doesn't have any effect".into()) } /// Sets the amount of funds that the sender should provide for opening an HRMP channel. @@ -888,7 +888,7 @@ mod tests { ump_service_total_weight: 20000, max_upward_message_size: 448, max_upward_message_num_per_candidate: 5, - hrmp_open_request_ttl: 1312, + _hrmp_open_request_ttl: 0, hrmp_sender_deposit: 22, hrmp_recipient_deposit: 4905, hrmp_channel_max_capacity: 3921, @@ -1013,11 +1013,6 @@ mod tests { new_config.max_upward_message_num_per_candidate, ) .unwrap(); - Configuration::set_hrmp_open_request_ttl( - Origin::root(), - new_config.hrmp_open_request_ttl, - ) - .unwrap(); Configuration::set_hrmp_sender_deposit(Origin::root(), new_config.hrmp_sender_deposit) .unwrap(); Configuration::set_hrmp_recipient_deposit( diff --git a/polkadot/runtime/parachains/src/hrmp.rs b/polkadot/runtime/parachains/src/hrmp.rs index c31f658faf..bb200900d5 100644 --- a/polkadot/runtime/parachains/src/hrmp.rs +++ b/polkadot/runtime/parachains/src/hrmp.rs @@ -39,8 +39,9 @@ pub use pallet::*; pub struct HrmpOpenChannelRequest { /// Indicates if this request was confirmed by the recipient. pub confirmed: bool, - /// How many session boundaries ago this request was seen. - pub age: SessionIndex, + /// NOTE: this field is deprecated. Channel open requests became non-expiring and this value + /// became unused. + pub _age: SessionIndex, /// The amount that the sender supplied at the time of creation of this request. pub sender_deposit: Balance, /// The maximum message size that could be put into the channel. @@ -200,6 +201,9 @@ pub mod pallet { /// Open HRMP channel requested. /// `[sender, recipient, proposed_max_capacity, proposed_max_message_size]` OpenChannelRequested(ParaId, ParaId, u32, u32), + /// An HRMP channel request sent by the receiver was canceled by either party. + /// `[by_parachain, channel_id]` + OpenChannelCanceled(ParaId, HrmpChannelId), /// Open HRMP channel accepted. `[sender, recipient]` OpenChannelAccepted(ParaId, ParaId), /// HRMP channel closed. `[by_parachain, channel_id]` @@ -238,6 +242,12 @@ pub mod pallet { CloseHrmpChannelDoesntExist, /// The channel close request is already requested. CloseHrmpChannelAlreadyUnderway, + /// Canceling is requested by neither the sender nor recipient of the open channel request. + CancelHrmpOpenChannelUnauthorized, + /// The open request doesn't exist. + OpenHrmpChannelDoesntExist, + /// Cannot cancel an HRMP open channel request because it is already confirmed. + OpenHrmpChannelAlreadyConfirmed, } /// The set of pending HRMP open channel requests. @@ -464,6 +474,22 @@ pub mod pallet { Self::process_hrmp_close_channel_requests(); Ok(()) } + + /// This cancels a pending open channel request. It can be canceled be either of the sender + /// or the recipient for that request. The origin must be either of those. + /// + /// The cancelling happens immediately. It is not possible to cancel the request if it is + /// already accepted. + #[pallet::weight(0)] + pub fn hrmp_cancel_open_request( + origin: OriginFor, + channel_id: HrmpChannelId, + ) -> DispatchResult { + let origin = ensure_parachain(::Origin::from(origin))?; + Self::cancel_open_request(origin, channel_id.clone())?; + Self::deposit_event(Event::OpenChannelCanceled(origin, channel_id)); + Ok(()) + } } } @@ -507,19 +533,77 @@ impl Pallet { notification: &initializer::SessionChangeNotification, outgoing_paras: &[ParaId], ) { - Self::perform_outgoing_para_cleanup(outgoing_paras); + Self::perform_outgoing_para_cleanup(¬ification.prev_config, outgoing_paras); Self::process_hrmp_open_channel_requests(¬ification.prev_config); Self::process_hrmp_close_channel_requests(); } /// Iterate over all paras that were noted for offboarding and remove all the data /// associated with them. - fn perform_outgoing_para_cleanup(outgoing: &[ParaId]) { + fn perform_outgoing_para_cleanup( + config: &HostConfiguration, + outgoing: &[ParaId], + ) { + Self::clean_open_channel_requests(config, outgoing); for outgoing_para in outgoing { Self::clean_hrmp_after_outgoing(outgoing_para); } } + // Go over the HRMP open channel requests and remove all in which offboarding paras participate. + // + // This will also perform the refunds for the counterparty if it doesn't offboard. + fn clean_open_channel_requests( + config: &HostConfiguration, + outgoing: &[ParaId], + ) { + // First collect all the channel ids of the open requests in which there is at least one + // party presents in the outgoing list. + // + // Both the open channel request list and outgoing list are expected to be small enough. + // In the most common case there will be only single outgoing para. + let open_channel_reqs = ::HrmpOpenChannelRequestsList::get(); + let (go, stay): (Vec, Vec) = open_channel_reqs + .into_iter() + .partition(|req_id| outgoing.iter().any(|id| req_id.is_participant(*id))); + ::HrmpOpenChannelRequestsList::put(stay); + + // Then iterate over all open requests to be removed, pull them out of the set and perform + // the refunds if applicable. + for req_id in go { + let req_data = match ::HrmpOpenChannelRequests::take(&req_id) { + Some(req_data) => req_data, + None => { + // Can't normally happen but no need to panic. + continue + }, + }; + + // Return the deposit of the sender, but only if it is not the para being offboarded. + if !outgoing.contains(&req_id.sender) { + T::Currency::unreserve( + &req_id.sender.into_account(), + req_data.sender_deposit.unique_saturated_into(), + ); + } + + // If the request was confirmed, then it means it was confirmed in the finished session. + // Therefore, the config's hrmp_recipient_deposit represents the actual value of the + // deposit. + // + // We still want to refund the deposit only if the para is not being offboarded. + if req_data.confirmed { + if !outgoing.contains(&req_id.recipient) { + T::Currency::unreserve( + &req_id.recipient.into_account(), + config.hrmp_recipient_deposit.unique_saturated_into(), + ); + } + Self::decrease_accepted_channel_request_count(req_id.recipient); + } + } + } + /// Remove all storage entries associated with the given para. fn clean_hrmp_after_outgoing(outgoing_para: &ParaId) { ::HrmpOpenChannelRequestCount::remove(outgoing_para); @@ -561,7 +645,7 @@ impl Pallet { idx -= 1; let channel_id = open_req_channels[idx].clone(); - let mut request = ::HrmpOpenChannelRequests::get(&channel_id).expect( + let request = ::HrmpOpenChannelRequests::get(&channel_id).expect( "can't be `None` due to the invariant that the list contains the same items as the set; qed", ); @@ -595,52 +679,11 @@ impl Pallet { }); } - let new_open_channel_req_cnt = - ::HrmpOpenChannelRequestCount::get(&channel_id.sender) - .saturating_sub(1); - if new_open_channel_req_cnt != 0 { - ::HrmpOpenChannelRequestCount::insert( - &channel_id.sender, - new_open_channel_req_cnt, - ); - } else { - ::HrmpOpenChannelRequestCount::remove(&channel_id.sender); - } - - let new_accepted_channel_req_cnt = - ::HrmpAcceptedChannelRequestCount::get(&channel_id.recipient) - .saturating_sub(1); - if new_accepted_channel_req_cnt != 0 { - ::HrmpAcceptedChannelRequestCount::insert( - &channel_id.recipient, - new_accepted_channel_req_cnt, - ); - } else { - ::HrmpAcceptedChannelRequestCount::remove(&channel_id.recipient); - } + Self::decrease_open_channel_request_count(channel_id.sender); + Self::decrease_accepted_channel_request_count(channel_id.recipient); let _ = open_req_channels.swap_remove(idx); ::HrmpOpenChannelRequests::remove(&channel_id); - } else { - request.age += 1; - if request.age == config.hrmp_open_request_ttl { - // got stale - ::HrmpOpenChannelRequestCount::mutate(&channel_id.sender, |v| { - *v -= 1; - }); - - let _ = open_req_channels.swap_remove(idx); - if let Some(HrmpOpenChannelRequest { sender_deposit, .. }) = - ::HrmpOpenChannelRequests::take(&channel_id) - { - T::Currency::unreserve( - &channel_id.sender.into_account(), - sender_deposit.unique_saturated_into(), - ); - } - } else { - ::HrmpOpenChannelRequests::insert(&channel_id, request); - } } } @@ -996,7 +1039,7 @@ impl Pallet { &channel_id, HrmpOpenChannelRequest { confirmed: false, - age: 0, + _age: 0, sender_deposit: config.hrmp_sender_deposit, max_capacity: proposed_max_capacity, max_message_size: proposed_max_message_size, @@ -1081,12 +1124,39 @@ impl Pallet { Ok(()) } + fn cancel_open_request(origin: ParaId, channel_id: HrmpChannelId) -> DispatchResult { + // check if the origin is allowed to close the channel. + ensure!(channel_id.is_participant(origin), Error::::CancelHrmpOpenChannelUnauthorized); + + let open_channel_req = ::HrmpOpenChannelRequests::get(&channel_id) + .ok_or(Error::::OpenHrmpChannelDoesntExist)?; + ensure!(!open_channel_req.confirmed, Error::::OpenHrmpChannelAlreadyConfirmed); + + // Remove the request by the channel id and sync the accompanying list with the set. + ::HrmpOpenChannelRequests::remove(&channel_id); + ::HrmpOpenChannelRequestsList::mutate(|open_req_channels| { + if let Some(pos) = open_req_channels.iter().position(|x| x == &channel_id) { + open_req_channels.swap_remove(pos); + } + }); + + Self::decrease_open_channel_request_count(channel_id.sender); + // Don't decrease `HrmpAcceptedChannelRequestCount` because we don't consider confirmed + // requests here. + + // Unreserve the sender's deposit. The recipient could not have left their deposit because + // we ensured that the request is not confirmed. + T::Currency::unreserve( + &channel_id.sender.into_account(), + open_channel_req.sender_deposit.unique_saturated_into(), + ); + + Ok(()) + } + fn close_channel(origin: ParaId, channel_id: HrmpChannelId) -> Result<(), Error> { // check if the origin is allowed to close the channel. - ensure!( - origin == channel_id.sender || origin == channel_id.recipient, - Error::::CloseHrmpChannelUnauthorized, - ); + ensure!(channel_id.is_participant(origin), Error::::CloseHrmpChannelUnauthorized); // check if the channel requested to close does exist. ensure!( @@ -1167,6 +1237,30 @@ impl Pallet { } } +impl Pallet { + /// Decreases the open channel request count for the given sender. If the value reaches zero + /// it is removed completely. + fn decrease_open_channel_request_count(sender: ParaId) { + ::HrmpOpenChannelRequestCount::mutate_exists(&sender, |opt_rc| { + *opt_rc = opt_rc.and_then(|rc| match rc.saturating_sub(1) { + 0 => None, + n => Some(n), + }); + }); + } + + /// Decreases the accepted channel request count for the given sender. If the value reaches + /// zero it is removed completely. + fn decrease_accepted_channel_request_count(recipient: ParaId) { + ::HrmpAcceptedChannelRequestCount::mutate_exists(&recipient, |opt_rc| { + *opt_rc = opt_rc.and_then(|rc| match rc.saturating_sub(1) { + 0 => None, + n => Some(n), + }); + }); + } +} + #[cfg(test)] mod tests { use super::*; @@ -1231,7 +1325,6 @@ mod tests { hrmp_channel_max_total_size: u32, hrmp_sender_deposit: Balance, hrmp_recipient_deposit: Balance, - hrmp_open_request_ttl: u32, } impl Default for GenesisConfigBuilder { @@ -1247,7 +1340,6 @@ mod tests { hrmp_channel_max_total_size: 16, hrmp_sender_deposit: 100, hrmp_recipient_deposit: 100, - hrmp_open_request_ttl: 3, } } } @@ -1267,7 +1359,6 @@ mod tests { config.hrmp_channel_max_total_size = self.hrmp_channel_max_total_size; config.hrmp_sender_deposit = self.hrmp_sender_deposit; config.hrmp_recipient_deposit = self.hrmp_recipient_deposit; - config.hrmp_open_request_ttl = self.hrmp_open_request_ttl; genesis } } @@ -1818,35 +1909,6 @@ mod tests { }); } - #[test] - fn refund_deposit_on_request_expiry() { - let para_a = 32.into(); - let para_b = 64.into(); - - let mut genesis = GenesisConfigBuilder::default(); - genesis.hrmp_sender_deposit = 20; - genesis.hrmp_recipient_deposit = 15; - genesis.hrmp_open_request_ttl = 2; - new_test_ext(genesis.build()).execute_with(|| { - // Register two parachains funded with different amounts of funds, send an open channel - // request but do not accept it. - register_parachain_with_balance(para_a, 100); - register_parachain_with_balance(para_b, 110); - run_to_block(5, Some(vec![4, 5])); - Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap(); - assert_eq!(::Currency::free_balance(¶_a.into_account()), 80); - assert_eq!(::Currency::free_balance(¶_b.into_account()), 110); - - // Request age is 1 out of 2 - run_to_block(10, Some(vec![10])); - assert_eq!(::Currency::free_balance(¶_a.into_account()), 80); - - // Request age is 2 out of 2. The request should expire. - run_to_block(20, Some(vec![20])); - assert_eq!(::Currency::free_balance(¶_a.into_account()), 100); - }); - } - #[test] fn refund_deposit_on_offboarding() { let para_a = 32.into(); @@ -1880,4 +1942,68 @@ mod tests { assert_eq!(::Currency::free_balance(¶_b.into_account()), 110); }); } + + #[test] + fn no_dangling_open_requests() { + let para_a = 32.into(); + let para_b = 64.into(); + + let mut genesis = GenesisConfigBuilder::default(); + genesis.hrmp_sender_deposit = 20; + genesis.hrmp_recipient_deposit = 15; + new_test_ext(genesis.build()).execute_with(|| { + // Register two parachains and open a channel between them. + register_parachain_with_balance(para_a, 100); + register_parachain_with_balance(para_b, 110); + run_to_block(5, Some(vec![4, 5])); + + // Start opening a channel a->b + Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap(); + assert_eq!(::Currency::free_balance(¶_a.into_account()), 80); + + // Then deregister one parachain, but don't wait two sessions until it takes effect. + // Instead, para_b will confirm the request, which will take place the same time + // the offboarding should happen. + deregister_parachain(para_a); + run_to_block(9, Some(vec![9])); + Hrmp::accept_open_channel(para_b, para_a).unwrap(); + assert_eq!(::Currency::free_balance(¶_b.into_account()), 95); + assert!(!channel_exists(para_a, para_b)); + run_to_block(10, Some(vec![10])); + + // The outcome we expect is para_b should receive the refund. + assert_eq!(::Currency::free_balance(¶_b.into_account()), 110); + assert!(!channel_exists(para_a, para_b)); + assert_storage_consistency_exhaustive(); + }); + } + + #[test] + fn cancel_pending_open_channel_request() { + let para_a = 32.into(); + let para_b = 64.into(); + + let mut genesis = GenesisConfigBuilder::default(); + genesis.hrmp_sender_deposit = 20; + genesis.hrmp_recipient_deposit = 15; + new_test_ext(genesis.build()).execute_with(|| { + // Register two parachains and open a channel between them. + register_parachain_with_balance(para_a, 100); + register_parachain_with_balance(para_b, 110); + run_to_block(5, Some(vec![4, 5])); + + // Start opening a channel a->b + Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap(); + assert_eq!(::Currency::free_balance(¶_a.into_account()), 80); + + // Cancel opening the channel + Hrmp::cancel_open_request(para_a, HrmpChannelId { sender: para_a, recipient: para_b }) + .unwrap(); + assert_eq!(::Currency::free_balance(¶_a.into_account()), 100); + + run_to_block(10, Some(vec![10])); + assert!(!channel_exists(para_a, para_b)); + assert_storage_consistency_exhaustive(); + }); + } } diff --git a/polkadot/scripts/gitlab/lingua.dic b/polkadot/scripts/gitlab/lingua.dic index 0dda104ca7..dc50c5d8ef 100644 --- a/polkadot/scripts/gitlab/lingua.dic +++ b/polkadot/scripts/gitlab/lingua.dic @@ -292,3 +292,6 @@ WND/S Wococo XCM/S XCMP/M +decrement +DM +ParaId \ No newline at end of file