mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-09 01:18:00 +00:00
Implement HRMP (#1900)
* HRMP: Update the impl guide * HRMP: Incorporate the channel notifications into the guide * HRMP: Renaming in the impl guide * HRMP: Constrain the maximum number of HRMP messages per candidate This commit addresses the HRMP part of https://github.com/paritytech/polkadot/issues/1869 * XCM: Introduce HRMP related message types * HRMP: Data structures and plumbing * HRMP: Configuration * HRMP: Data layout * HRMP: Acceptance & Enactment * HRMP: Test base logic * Update adder collator * HRMP: Runtime API for accessing inbound messages Also, removing some redundant fully-qualified names. * HRMP: Add diagnostic logging in acceptance criteria * HRMP: Additional tests * Self-review fixes * save test refactorings for the next time * Missed a return statement. * a formatting blip * Add missing logic for appending HRMP digests * Remove the channel contents vectors which became empty * Tighten HRMP channel digests invariants. * Apply suggestions from code review Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com> * Remove a note about sorting for channel id * Add missing rustdocs to the configuration * Clarify and update the invariant for HrmpChannelDigests * Make the onboarding invariant less sloppy Namely, introduce `Paras::is_valid_para` (in fact, it already is present in the implementation) and hook up the invariant to that. Note that this says "within a session" because I don't want to make it super strict on the session boundary. The logic on the session boundary should be extremely careful. * Make `CandidateCheckContext` use T::BlockNumber for hrmp_watermark Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>
This commit is contained in:
Generated
+1
@@ -5478,6 +5478,7 @@ dependencies = [
|
||||
"sp-std",
|
||||
"sp-trie",
|
||||
"sp-version",
|
||||
"xcm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -102,6 +102,26 @@ pub struct InboundDownwardMessage<BlockNumber = crate::BlockNumber> {
|
||||
pub msg: DownwardMessage,
|
||||
}
|
||||
|
||||
/// An HRMP message seen from the perspective of a recipient.
|
||||
#[derive(codec::Encode, codec::Decode, Clone, sp_runtime::RuntimeDebug, PartialEq)]
|
||||
pub struct InboundHrmpMessage<BlockNumber = crate::BlockNumber> {
|
||||
/// The block number at which this message was sent.
|
||||
/// Specifically, it is the block number at which the candidate that sends this message was
|
||||
/// enacted.
|
||||
pub sent_at: BlockNumber,
|
||||
/// The message payload.
|
||||
pub data: sp_std::vec::Vec<u8>,
|
||||
}
|
||||
|
||||
/// An HRMP message seen from the perspective of a sender.
|
||||
#[derive(codec::Encode, codec::Decode, Clone, sp_runtime::RuntimeDebug, PartialEq, Eq, Hash)]
|
||||
pub struct OutboundHrmpMessage<Id> {
|
||||
/// The para that will get this message in its downward message queue.
|
||||
pub recipient: Id,
|
||||
/// The message payload.
|
||||
pub data: sp_std::vec::Vec<u8>,
|
||||
}
|
||||
|
||||
/// V1 primitives.
|
||||
pub mod v1 {
|
||||
pub use super::*;
|
||||
|
||||
@@ -274,10 +274,12 @@ async fn handle_new_activations<Context: SubsystemContext>(
|
||||
|
||||
let commitments = CandidateCommitments {
|
||||
upward_messages: collation.upward_messages,
|
||||
horizontal_messages: collation.horizontal_messages,
|
||||
new_validation_code: collation.new_validation_code,
|
||||
head_data: collation.head_data,
|
||||
erasure_root,
|
||||
processed_downward_messages: collation.processed_downward_messages,
|
||||
hrmp_watermark: collation.hrmp_watermark,
|
||||
};
|
||||
|
||||
let ccr = CandidateReceipt {
|
||||
@@ -382,12 +384,14 @@ mod tests {
|
||||
fn test_collation() -> Collation {
|
||||
Collation {
|
||||
upward_messages: Default::default(),
|
||||
horizontal_messages: Default::default(),
|
||||
new_validation_code: Default::default(),
|
||||
head_data: Default::default(),
|
||||
proof_of_validity: PoV {
|
||||
block_data: BlockData(Vec::new()),
|
||||
},
|
||||
processed_downward_messages: Default::default(),
|
||||
hrmp_watermark: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -726,10 +726,12 @@ impl CandidateBackingJob {
|
||||
|
||||
let commitments = CandidateCommitments {
|
||||
upward_messages: outputs.upward_messages,
|
||||
horizontal_messages: outputs.horizontal_messages,
|
||||
erasure_root,
|
||||
new_validation_code: outputs.new_validation_code,
|
||||
head_data: outputs.head_data,
|
||||
processed_downward_messages: outputs.processed_downward_messages,
|
||||
hrmp_watermark: outputs.hrmp_watermark,
|
||||
};
|
||||
|
||||
let res = match with_commitments(commitments) {
|
||||
@@ -1209,9 +1211,11 @@ mod tests {
|
||||
tx.send(Ok(
|
||||
ValidationResult::Valid(ValidationOutputs {
|
||||
head_data: expected_head_data.clone(),
|
||||
horizontal_messages: Vec::new(),
|
||||
upward_messages: Vec::new(),
|
||||
new_validation_code: None,
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
}, test_state.validation_data.persisted),
|
||||
)).unwrap();
|
||||
}
|
||||
@@ -1346,8 +1350,10 @@ mod tests {
|
||||
ValidationResult::Valid(ValidationOutputs {
|
||||
head_data: expected_head_data.clone(),
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
new_validation_code: None,
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
}, test_state.validation_data.persisted),
|
||||
)).unwrap();
|
||||
}
|
||||
@@ -1495,8 +1501,10 @@ mod tests {
|
||||
ValidationResult::Valid(ValidationOutputs {
|
||||
head_data: expected_head_data.clone(),
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
new_validation_code: None,
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
}, test_state.validation_data.persisted),
|
||||
)).unwrap();
|
||||
}
|
||||
@@ -1678,8 +1686,10 @@ mod tests {
|
||||
ValidationResult::Valid(ValidationOutputs {
|
||||
head_data: expected_head_data.clone(),
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
new_validation_code: None,
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
}, test_state.validation_data.persisted),
|
||||
)).unwrap();
|
||||
}
|
||||
|
||||
@@ -489,8 +489,10 @@ fn validate_candidate_exhaustive<B: ValidationBackend, S: SpawnNamed + 'static>(
|
||||
let outputs = ValidationOutputs {
|
||||
head_data: res.head_data,
|
||||
upward_messages: res.upward_messages,
|
||||
horizontal_messages: res.horizontal_messages,
|
||||
new_validation_code: res.new_validation_code,
|
||||
processed_downward_messages: res.processed_downward_messages,
|
||||
hrmp_watermark: res.hrmp_watermark,
|
||||
};
|
||||
Ok(ValidationResult::Valid(outputs, persisted_validation_data))
|
||||
}
|
||||
@@ -833,7 +835,9 @@ mod tests {
|
||||
head_data: HeadData(vec![1, 1, 1]),
|
||||
new_validation_code: Some(vec![2, 2, 2].into()),
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
|
||||
@@ -848,7 +852,9 @@ mod tests {
|
||||
assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => {
|
||||
assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1]));
|
||||
assert_eq!(outputs.upward_messages, Vec::<UpwardMessage>::new());
|
||||
assert_eq!(outputs.horizontal_messages, Vec::new());
|
||||
assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into()));
|
||||
assert_eq!(outputs.hrmp_watermark, 0);
|
||||
assert_eq!(used_validation_data, validation_data);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -130,6 +130,7 @@ fn make_runtime_api_request<Client>(
|
||||
Request::CandidateEvents(sender) => query!(candidate_events(), sender),
|
||||
Request::ValidatorDiscovery(ids, sender) => query!(validator_discovery(ids), sender),
|
||||
Request::DmqContents(id, sender) => query!(dmq_contents(id), sender),
|
||||
Request::InboundHrmpChannelsContents(id, sender) => query!(inbound_hrmp_channels_contents(id), sender),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,12 +181,11 @@ mod tests {
|
||||
ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, PersistedValidationData,
|
||||
Id as ParaId, OccupiedCoreAssumption, ValidationData, SessionIndex, ValidationCode,
|
||||
CommittedCandidateReceipt, CandidateEvent, AuthorityDiscoveryId, InboundDownwardMessage,
|
||||
BlockNumber,
|
||||
BlockNumber, InboundHrmpMessage,
|
||||
};
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use sp_core::testing::TaskExecutor;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, BTreeMap};
|
||||
use futures::channel::oneshot;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
@@ -201,6 +201,7 @@ mod tests {
|
||||
candidate_pending_availability: HashMap<ParaId, CommittedCandidateReceipt>,
|
||||
candidate_events: Vec<CandidateEvent>,
|
||||
dmq_contents: HashMap<ParaId, Vec<InboundDownwardMessage>>,
|
||||
hrmp_channels: HashMap<ParaId, BTreeMap<ParaId, Vec<InboundHrmpMessage>>>,
|
||||
}
|
||||
|
||||
impl ProvideRuntimeApi<Block> for MockRuntimeApi {
|
||||
@@ -306,9 +307,16 @@ mod tests {
|
||||
fn dmq_contents(
|
||||
&self,
|
||||
recipient: ParaId,
|
||||
) -> Vec<polkadot_primitives::v1::InboundDownwardMessage> {
|
||||
) -> Vec<InboundDownwardMessage> {
|
||||
self.dmq_contents.get(&recipient).map(|q| q.clone()).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn inbound_hrmp_channels_contents(
|
||||
&self,
|
||||
recipient: ParaId
|
||||
) -> BTreeMap<ParaId, Vec<InboundHrmpMessage>> {
|
||||
self.hrmp_channels.get(&recipient).map(|q| q.clone()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,6 +709,72 @@ mod tests {
|
||||
futures::executor::block_on(future::join(subsystem_task, test_task));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requests_inbound_hrmp_channels_contents() {
|
||||
let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new());
|
||||
|
||||
let relay_parent = [1; 32].into();
|
||||
let para_a = 99.into();
|
||||
let para_b = 66.into();
|
||||
let para_c = 33.into();
|
||||
|
||||
let para_b_inbound_channels = [
|
||||
(para_a, vec![]),
|
||||
(
|
||||
para_c,
|
||||
vec![InboundHrmpMessage {
|
||||
sent_at: 1,
|
||||
data: "𝙀=𝙈𝘾²".as_bytes().to_owned(),
|
||||
}],
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
let runtime_api = Arc::new({
|
||||
let mut runtime_api = MockRuntimeApi::default();
|
||||
|
||||
runtime_api.hrmp_channels.insert(para_a, BTreeMap::new());
|
||||
runtime_api
|
||||
.hrmp_channels
|
||||
.insert(para_b, para_b_inbound_channels.clone());
|
||||
|
||||
runtime_api
|
||||
});
|
||||
|
||||
let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None));
|
||||
let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::InboundHrmpChannelsContents(para_a, tx),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
assert_eq!(rx.await.unwrap().unwrap(), BTreeMap::new());
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::InboundHrmpChannelsContents(para_b, tx),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
assert_eq!(rx.await.unwrap().unwrap(), para_b_inbound_channels,);
|
||||
|
||||
ctx_handle
|
||||
.send(FromOverseer::Signal(OverseerSignal::Conclude))
|
||||
.await;
|
||||
};
|
||||
futures::executor::block_on(future::join(subsystem_task, test_task));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requests_historical_code() {
|
||||
let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new());
|
||||
|
||||
@@ -28,7 +28,7 @@ use polkadot_primitives::v1::{
|
||||
Hash, CommittedCandidateReceipt, CandidateReceipt, CompactStatement,
|
||||
EncodeAs, Signed, SigningContext, ValidatorIndex, ValidatorId,
|
||||
UpwardMessage, ValidationCode, PersistedValidationData, ValidationData,
|
||||
HeadData, PoV, CollatorPair, Id as ParaId, ValidationOutputs, CandidateHash,
|
||||
HeadData, PoV, CollatorPair, Id as ParaId, OutboundHrmpMessage, ValidationOutputs, CandidateHash,
|
||||
};
|
||||
use polkadot_statement_table::{
|
||||
generic::{
|
||||
@@ -252,9 +252,11 @@ impl std::convert::TryFrom<FromTableMisbehavior> for MisbehaviorReport {
|
||||
/// - does not contain the erasure root; that's computed at the Polkadot level, not at Cumulus
|
||||
/// - contains a proof of validity.
|
||||
#[derive(Clone, Encode, Decode)]
|
||||
pub struct Collation {
|
||||
pub struct Collation<BlockNumber = polkadot_primitives::v1::BlockNumber> {
|
||||
/// Messages destined to be interpreted by the Relay chain itself.
|
||||
pub upward_messages: Vec<UpwardMessage>,
|
||||
/// The horizontal messages sent by the parachain.
|
||||
pub horizontal_messages: Vec<OutboundHrmpMessage<ParaId>>,
|
||||
/// New validation code.
|
||||
pub new_validation_code: Option<ValidationCode>,
|
||||
/// The head-data produced as a result of execution.
|
||||
@@ -263,6 +265,8 @@ pub struct Collation {
|
||||
pub proof_of_validity: PoV,
|
||||
/// The number of messages processed from the DMQ.
|
||||
pub processed_downward_messages: u32,
|
||||
/// The mark which specifies the block number up to which all inbound HRMP messages are processed.
|
||||
pub hrmp_watermark: BlockNumber,
|
||||
}
|
||||
|
||||
/// Configuration for the collation generator
|
||||
|
||||
@@ -37,9 +37,10 @@ use polkadot_primitives::v1::{
|
||||
GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption,
|
||||
PersistedValidationData, PoV, SessionIndex, SignedAvailabilityBitfield,
|
||||
ValidationCode, ValidatorId, ValidationData, CandidateHash,
|
||||
ValidatorIndex, ValidatorSignature, InboundDownwardMessage,
|
||||
ValidatorIndex, ValidatorSignature, InboundDownwardMessage, InboundHrmpMessage,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::collections::btree_map::BTreeMap;
|
||||
|
||||
/// A notification of a new backed candidate.
|
||||
#[derive(Debug)]
|
||||
@@ -452,6 +453,12 @@ pub enum RuntimeApiRequest {
|
||||
ParaId,
|
||||
RuntimeApiSender<Vec<InboundDownwardMessage<BlockNumber>>>,
|
||||
),
|
||||
/// Get the contents of all channels addressed to the given recipient. Channels that have no
|
||||
/// messages in them are also included.
|
||||
InboundHrmpChannelsContents(
|
||||
ParaId,
|
||||
RuntimeApiSender<BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>>,
|
||||
),
|
||||
}
|
||||
|
||||
/// A message to the Runtime API subsystem.
|
||||
|
||||
@@ -28,7 +28,7 @@ use serde::{Serialize, Deserialize};
|
||||
#[cfg(feature = "std")]
|
||||
use sp_core::bytes;
|
||||
|
||||
use polkadot_core_primitives::Hash;
|
||||
use polkadot_core_primitives::{Hash, OutboundHrmpMessage};
|
||||
|
||||
/// Block number type used by the relay chain.
|
||||
pub use polkadot_core_primitives::BlockNumber as RelayChainBlockNumber;
|
||||
@@ -186,6 +186,21 @@ impl<T: Encode + Decode + Default> AccountIdConversion<T> for Id {
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that uniquely identifies an HRMP channel. An HRMP channel is established between two paras.
|
||||
/// In text, we use the notation `(A, B)` to specify a channel between A and B. The channels are
|
||||
/// 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)`.
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(Hash))]
|
||||
pub struct HrmpChannelId {
|
||||
/// The para that acts as the sender in this channel.
|
||||
pub sender: Id,
|
||||
/// The para that acts as the recipient in this channel.
|
||||
pub recipient: Id,
|
||||
}
|
||||
|
||||
/// A message from a parachain to its Relay Chain.
|
||||
pub type UpwardMessage = Vec<u8>;
|
||||
|
||||
@@ -212,7 +227,7 @@ pub struct ValidationParams {
|
||||
}
|
||||
|
||||
/// The result of parachain validation.
|
||||
// TODO: egress and balance uploads (https://github.com/paritytech/polkadot/issues/220)
|
||||
// TODO: balance uploads (https://github.com/paritytech/polkadot/issues/220)
|
||||
#[derive(PartialEq, Eq, Encode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug, Decode))]
|
||||
pub struct ValidationResult {
|
||||
@@ -222,8 +237,12 @@ pub struct ValidationResult {
|
||||
pub new_validation_code: Option<ValidationCode>,
|
||||
/// Upward messages send by the Parachain.
|
||||
pub upward_messages: Vec<UpwardMessage>,
|
||||
/// Outbound horizontal messages sent by the parachain.
|
||||
pub horizontal_messages: Vec<OutboundHrmpMessage<Id>>,
|
||||
/// Number of downward messages that were processed by the Parachain.
|
||||
///
|
||||
/// It is expected that the Parachain processes them from first to last.
|
||||
pub processed_downward_messages: u32,
|
||||
/// The mark which specifies the block number up to which all inbound HRMP messages are processed.
|
||||
pub hrmp_watermark: RelayChainBlockNumber,
|
||||
}
|
||||
|
||||
@@ -114,10 +114,12 @@ impl Collator {
|
||||
|
||||
let collation = Collation {
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
new_validation_code: None,
|
||||
head_data: head_data.encode().into(),
|
||||
proof_of_validity: PoV { block_data: block_data.encode().into() },
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: validation_data.persisted.block_number,
|
||||
};
|
||||
|
||||
async move { Some(collation) }.boxed()
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
//! WASM validation for adder parachain.
|
||||
|
||||
use crate::{HeadData, BlockData};
|
||||
use core::{intrinsics, panic};
|
||||
use core::panic;
|
||||
use sp_std::vec::Vec;
|
||||
use parachain::primitives::{ValidationResult, HeadData as GenericHeadData};
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn validate_block(params: *const u8, len: usize) -> u64 {
|
||||
pub extern "C" fn validate_block(params: *const u8, len: usize) -> u64 {
|
||||
let params = unsafe { parachain::load_params(params, len) };
|
||||
let parent_head = HeadData::decode(&mut ¶ms.parent_head.0[..])
|
||||
.expect("invalid parent head format.");
|
||||
@@ -38,7 +39,9 @@ pub extern fn validate_block(params: *const u8, len: usize) -> u64 {
|
||||
head_data: GenericHeadData(new_head.encode()),
|
||||
new_validation_code: None,
|
||||
upward_messages: sp_std::vec::Vec::new(),
|
||||
horizontal_messages: sp_std::vec::Vec::new(),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: params.relay_chain_height,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
//! V1 Primitives.
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
use parity_scale_codec::{Encode, Decode};
|
||||
use bitvec::vec::BitVec;
|
||||
|
||||
@@ -29,14 +30,14 @@ pub use runtime_primitives::traits::{BlakeTwo256, Hash as HashT};
|
||||
|
||||
// Export some core primitives.
|
||||
pub use polkadot_core_primitives::v1::{
|
||||
BlockNumber, Moment, Signature, AccountPublic, AccountId, AccountIndex,
|
||||
ChainId, Hash, Nonce, Balance, Header, Block, BlockId, UncheckedExtrinsic,
|
||||
Remark, DownwardMessage, InboundDownwardMessage, CandidateHash,
|
||||
BlockNumber, Moment, Signature, AccountPublic, AccountId, AccountIndex, ChainId, Hash, Nonce,
|
||||
Balance, Header, Block, BlockId, UncheckedExtrinsic, Remark, DownwardMessage,
|
||||
InboundDownwardMessage, CandidateHash, InboundHrmpMessage, OutboundHrmpMessage,
|
||||
};
|
||||
|
||||
// Export some polkadot-parachain primitives
|
||||
pub use polkadot_parachain::primitives::{
|
||||
Id, LOWEST_USER_ID, UpwardMessage, HeadData, BlockData, ValidationCode,
|
||||
Id, LOWEST_USER_ID, HrmpChannelId, UpwardMessage, HeadData, BlockData, ValidationCode,
|
||||
};
|
||||
|
||||
// Export some basic parachain primitives from v0.
|
||||
@@ -317,18 +318,24 @@ pub struct ValidationOutputs {
|
||||
pub head_data: HeadData,
|
||||
/// Upward messages to the relay chain.
|
||||
pub upward_messages: Vec<UpwardMessage>,
|
||||
/// The horizontal messages sent by the parachain.
|
||||
pub horizontal_messages: Vec<OutboundHrmpMessage<Id>>,
|
||||
/// The new validation code submitted by the execution, if any.
|
||||
pub new_validation_code: Option<ValidationCode>,
|
||||
/// The number of messages processed from the DMQ.
|
||||
pub processed_downward_messages: u32,
|
||||
/// The mark which specifies the block number up to which all inbound HRMP messages are processed.
|
||||
pub hrmp_watermark: BlockNumber,
|
||||
}
|
||||
|
||||
/// Commitments made in a `CandidateReceipt`. Many of these are outputs of validation.
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug, Default, Hash))]
|
||||
pub struct CandidateCommitments {
|
||||
pub struct CandidateCommitments<N = BlockNumber> {
|
||||
/// Messages destined to be interpreted by the Relay chain itself.
|
||||
pub upward_messages: Vec<UpwardMessage>,
|
||||
/// Horizontal messages sent by the parachain.
|
||||
pub horizontal_messages: Vec<OutboundHrmpMessage<Id>>,
|
||||
/// The root of a block's erasure encoding Merkle tree.
|
||||
pub erasure_root: Hash,
|
||||
/// New validation code.
|
||||
@@ -337,6 +344,8 @@ pub struct CandidateCommitments {
|
||||
pub head_data: HeadData,
|
||||
/// The number of messages processed from the DMQ.
|
||||
pub processed_downward_messages: u32,
|
||||
/// The mark which specifies the block number up to which all inbound HRMP messages are processed.
|
||||
pub hrmp_watermark: N,
|
||||
}
|
||||
|
||||
impl CandidateCommitments {
|
||||
@@ -735,6 +744,10 @@ sp_api::decl_runtime_apis! {
|
||||
fn dmq_contents(
|
||||
recipient: Id,
|
||||
) -> Vec<InboundDownwardMessage<N>>;
|
||||
|
||||
/// Get the contents of all channels addressed to the given recipient. Channels that have no
|
||||
/// messages in them are also included.
|
||||
fn inbound_hrmp_channels_contents(recipient: Id) -> BTreeMap<Id, Vec<InboundHrmpMessage<N>>>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,8 +70,7 @@ All failed checks should lead to an unrecoverable error making the block invalid
|
||||
1. call `Router::check_upward_messages(para, commitments.upward_messages)` to check that the upward messages are valid.
|
||||
1. call `Router::check_processed_downward_messages(para, commitments.processed_downward_messages)` to check that the DMQ is properly drained.
|
||||
1. call `Router::check_hrmp_watermark(para, commitments.hrmp_watermark)` for each candidate to check rules of processing the HRMP watermark.
|
||||
1. check that in the commitments of each candidate the horizontal messages are sorted by ascending recipient ParaId and there is no two horizontal messages have the same recipient.
|
||||
1. using `Router::verify_outbound_hrmp(sender, commitments.horizontal_messages)` ensure that the each candidate send a valid set of horizontal messages
|
||||
1. using `Router::check_outbound_hrmp(sender, commitments.horizontal_messages)` ensure that the each candidate sent a valid set of horizontal messages
|
||||
1. create an entry in the `PendingAvailability` map for each backed candidate with a blank `availability_votes` bitfield.
|
||||
1. create a corresponding entry in the `PendingAvailabilityCommitments` with the commitments.
|
||||
1. Return a `Vec<CoreIndex>` of all scheduled cores of the list of passed assignments that a candidate was successfully backed for, sorted ascending by CoreIndex.
|
||||
@@ -79,9 +78,9 @@ All failed checks should lead to an unrecoverable error making the block invalid
|
||||
1. If the receipt contains a code upgrade, Call `Paras::schedule_code_upgrade(para_id, code, relay_parent_number + config.validationl_upgrade_delay)`.
|
||||
> TODO: Note that this is safe as long as we never enact candidates where the relay parent is across a session boundary. In that case, which we should be careful to avoid with contextual execution, the configuration might have changed and the para may de-sync from the host's understanding of it.
|
||||
1. call `Router::enact_upward_messages` for each backed candidate, using the [`UpwardMessage`s](../types/messages.md#upward-message) from the [`CandidateCommitments`](../types/candidate.md#candidate-commitments).
|
||||
1. call `Router::queue_outbound_hrmp` with the para id of the candidate and the list of horizontal messages taken from the commitment,
|
||||
1. call `Router::prune_hrmp` with the para id of the candiate and the candidate's `hrmp_watermark`.
|
||||
1. call `Router::prune_dmq` with the para id of the candidate and the candidate's `processed_downward_messages`.
|
||||
1. call `Router::prune_hrmp` with the para id of the candiate and the candidate's `hrmp_watermark`.
|
||||
1. call `Router::queue_outbound_hrmp` with the para id of the candidate and the list of horizontal messages taken from the commitment,
|
||||
1. Call `Paras::note_new_head` using the `HeadData` from the receipt and `relay_parent_number`.
|
||||
* `collect_pending`:
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ OutgoingParas: Vec<ParaId>;
|
||||
* `note_new_head(ParaId, HeadData, BlockNumber)`: note that a para has progressed to a new head, where the new head was executed in the context of a relay-chain block with given number. This will apply pending code upgrades based on the block number provided.
|
||||
* `validation_code_at(ParaId, at: BlockNumber, assume_intermediate: Option<BlockNumber>)`: Fetches the validation code to be used when validating a block in the context of the given relay-chain height. A second block number parameter may be used to tell the lookup to proceed as if an intermediate parablock has been included at the given relay-chain height. This may return past, current, or (with certain choices of `assume_intermediate`) future code. `assume_intermediate`, if provided, must be before `at`. If the validation code has been pruned, this will return `None`.
|
||||
* `is_parathread(ParaId) -> bool`: Returns true if the para ID references any live parathread.
|
||||
* `is_valid_para(ParaId) -> bool`: Returns true if the para ID references either a live parathread or live parachain.
|
||||
|
||||
* `last_code_upgrade(id: ParaId, include_future: bool) -> Option<BlockNumber>`: The block number of the last scheduled upgrade of the requested para. Includes future upgrades if the flag is set. This is the `expected_at` number, not the `activated_at` number.
|
||||
* `persisted_validation_data(id: ParaId) -> Option<PersistedValidationData>`: Get the PersistedValidationData of the given para, assuming the context is the parent block. Returns `None` if the para is not known.
|
||||
|
||||
@@ -78,10 +78,12 @@ struct HrmpOpenChannelRequest {
|
||||
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.
|
||||
max_message_size: u32,
|
||||
/// The maximum number of messages that can be pending in the channel at once.
|
||||
limit_used_places: u32,
|
||||
max_capacity: u32,
|
||||
/// The maximum total size of the messages that can be pending in the channel at once.
|
||||
limit_used_bytes: u32,
|
||||
max_total_size: u32,
|
||||
}
|
||||
|
||||
/// A metadata of an HRMP channel.
|
||||
@@ -91,25 +93,25 @@ struct HrmpChannel {
|
||||
/// 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.
|
||||
limit_used_places: u32,
|
||||
max_capacity: u32,
|
||||
/// The maximum total size of the messages that can be pending in the channel at once.
|
||||
limit_used_bytes: u32,
|
||||
max_total_size: u32,
|
||||
/// The maximum message size that could be put into the channel.
|
||||
limit_message_size: u32,
|
||||
max_message_size: u32,
|
||||
/// The current number of messages pending in the channel.
|
||||
/// Invariant: should be less or equal to `limit_used_places`.
|
||||
used_places: u32,
|
||||
/// 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 `limit_used_bytes`.
|
||||
used_bytes: u32,
|
||||
/// 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`.
|
||||
/// - `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: Hash,
|
||||
mqc_head: Option<Hash>,
|
||||
}
|
||||
```
|
||||
HRMP related storage layout
|
||||
@@ -144,14 +146,26 @@ 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>;
|
||||
/// The indexes that map all senders to their recievers and vise versa.
|
||||
/// 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)`.
|
||||
/// - 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.
|
||||
@@ -159,7 +173,11 @@ HrmpEgressChannelsIndex: map ParaId => Vec<ParaId>;
|
||||
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 reciever.
|
||||
/// Invariant: The para ids vector is never empty.
|
||||
/// 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>)>;
|
||||
```
|
||||
|
||||
@@ -184,12 +202,14 @@ Candidate Acceptance Function:
|
||||
1. `new_hrmp_watermark` should be either
|
||||
1. equal to the context's block number
|
||||
1. or in `HrmpChannelDigests` for `P` an entry with the block number should exist
|
||||
* `verify_outbound_hrmp(sender: ParaId, Vec<OutboundHrmpMessage>)`:
|
||||
* `check_outbound_hrmp(sender: ParaId, Vec<OutboundHrmpMessage>)`:
|
||||
1. Checks that there are at most `config.hrmp_max_message_num_per_candidate` messages.
|
||||
1. Checks that horizontal messages are sorted by ascending recipient ParaId and there is no two horizontal messages have the same recipient.
|
||||
1. For each horizontal message `M` with the channel `C` identified by `(sender, M.recipient)` check:
|
||||
1. exists
|
||||
1. `M`'s payload size doesn't exceed a preconfigured limit `C.limit_message_size`
|
||||
1. `M`'s payload size summed with the `C.used_bytes` doesn't exceed a preconfigured limit `C.limit_used_bytes`.
|
||||
1. `C.used_places + 1` doesn't exceed a preconfigured limit `C.limit_used_places`.
|
||||
1. `M`'s payload size doesn't exceed a preconfigured limit `C.max_message_size`
|
||||
1. `M`'s payload size summed with the `C.total_size` doesn't exceed a preconfigured limit `C.max_total_size`.
|
||||
1. `C.msg_count + 1` doesn't exceed a preconfigured limit `C.max_capacity`.
|
||||
|
||||
Candidate Enactment:
|
||||
|
||||
@@ -197,17 +217,21 @@ Candidate Enactment:
|
||||
1. For each horizontal message `HM` with the channel `C` identified by `(sender, HM.recipient)`:
|
||||
1. Append `HM` into `HrmpChannelContents` that corresponds to `C` with `sent_at` equals to the current block number.
|
||||
1. Locate or create an entry in `HrmpChannelDigests` for `HM.recipient` and append `sender` into the entry's list.
|
||||
1. Increment `C.used_places`
|
||||
1. Increment `C.used_bytes` by `HM`'s payload size
|
||||
1. Increment `C.msg_count`
|
||||
1. Increment `C.total_size` by `HM`'s payload size
|
||||
1. 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.
|
||||
* `prune_hrmp(recipient, new_hrmp_watermark)`:
|
||||
1. From `HrmpChannelDigests` for `recipient` remove all entries up to an entry with block number equal to `new_hrmp_watermark`.
|
||||
1. From the removed digests construct a set of paras that sent new messages within the interval between the old and new watermarks.
|
||||
1. For each channel `C` identified by `(sender, recipient)` for each `sender` coming from the set, prune messages up to the `new_hrmp_watermark`.
|
||||
1. For each pruned message `M` from channel `C`:
|
||||
1. Decrement `C.used_places`
|
||||
1. Decrement `C.used_bytes` by `M`'s payload size.
|
||||
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.
|
||||
> If that becomes a problem consider introducing an extra dictionary which says at what block the given sender
|
||||
> sent a message to the recipient.
|
||||
* `prune_dmq(P: ParaId, processed_downward_messages)`:
|
||||
1. Remove the first `processed_downward_messages` from the `DownwardMessageQueues` of `P`.
|
||||
* `enact_upward_messages(P: ParaId, Vec<UpwardMessage>)`:
|
||||
@@ -251,10 +275,10 @@ 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, max_places, max_message_size)`:
|
||||
* `hrmp_init_open_channel(recipient, proposed_max_capacity, proposed_max_message_size)`:
|
||||
1. Check that the `origin` is not `recipient`.
|
||||
1. Check that `max_places` is less or equal to `config.hrmp_channel_max_places` and greater than zero.
|
||||
1. Check that `max_message_size` is less or equal to `config.hrmp_channel_max_message_size` and greater than zero.
|
||||
1. Check that `proposed_max_capacity` is less or equal to `config.hrmp_channel_max_capacity` and greater than zero.
|
||||
1. Check that `proposed_max_message_size` is less or equal to `config.hrmp_channel_max_message_size` and greater than zero.
|
||||
1. Check that `recipient` is a valid para.
|
||||
1. Check that there is no existing channel for `(origin, recipient)` in `HrmpChannels`.
|
||||
1. Check that there is no existing open channel request (`origin`, `recipient`) in `HrmpOpenChannelRequests`.
|
||||
@@ -268,9 +292,15 @@ the parachain executed the message.
|
||||
1. Append `(origin, recipient)` to `HrmpOpenChannelRequestsList`.
|
||||
1. Add a new entry to `HrmpOpenChannelRequests` for `(origin, recipient)`
|
||||
1. Set `sender_deposit` to `config.hrmp_sender_deposit`
|
||||
1. Set `limit_used_places` to `max_places`
|
||||
1. Set `limit_message_size` to `max_message_size`
|
||||
1. Set `limit_used_bytes` to `config.hrmp_channel_max_size`
|
||||
1. Set `max_capacity` to `proposed_max_capacity`
|
||||
1. Set `max_message_size` to `proposed_max_message_size`
|
||||
1. Set `max_total_size` to `config.hrmp_channel_max_total_size`
|
||||
1. 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 to `origin`,
|
||||
- `max_message_size` is set to `proposed_max_message_size`,
|
||||
- `max_capacity` is set to `proposed_max_capacity`.
|
||||
* `hrmp_accept_open_channel(sender)`:
|
||||
1. Check that there is an existing request between (`sender`, `origin`) in `HrmpOpenChannelRequests`
|
||||
1. Check that it is not confirmed.
|
||||
@@ -283,12 +313,23 @@ the parachain executed the message.
|
||||
1. Reserve the deposit for the `origin` according to `config.hrmp_recipient_deposit`
|
||||
1. For the request in `HrmpOpenChannelRequests` identified by `(sender, P)`, set `confirmed` flag to `true`.
|
||||
1. Increase `HrmpAcceptedChannelRequestCount` by 1 for `origin`.
|
||||
1. 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 to `origin`.
|
||||
* `hrmp_close_channel(ch)`:
|
||||
1. Check that `origin` is either `ch.sender` or `ch.recipient`
|
||||
1. Check that `HrmpChannels` for `ch` exists.
|
||||
1. Check that `ch` is not in the `HrmpCloseChannelRequests` set.
|
||||
1. If not already there, insert a new entry `Some(())` to `HrmpCloseChannelRequests` for `ch`
|
||||
and append `ch` to `HrmpCloseChannelRequestsList`.
|
||||
1. 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:
|
||||
- `initator` is set to `origin`,
|
||||
- `sender` is set to `ch.sender`,
|
||||
- `recipient` is set to `ch.recipient`.
|
||||
- The opposite party is `ch.sender` if `origin` is `ch.recipient` and `ch.recipient` if `origin` is `ch.sender`.
|
||||
|
||||
## Session Change
|
||||
|
||||
|
||||
@@ -42,8 +42,6 @@ that we use the first item tuple for the sender and the second for the recipient
|
||||
is allowed between two participants in one direction, i.e. there cannot be 2 different channels
|
||||
identified by `(A, B)`.
|
||||
|
||||
`HrmpChannelId` has a defined ordering: first `sender` and tie is resolved by `recipient`.
|
||||
|
||||
```rust,ignore
|
||||
struct HrmpChannelId {
|
||||
sender: ParaId,
|
||||
|
||||
@@ -77,18 +77,24 @@ struct HostConfiguration {
|
||||
/// The deposit that the recipient should provide for accepting opening an HRMP channel.
|
||||
pub hrmp_recipient_deposit: u32,
|
||||
/// The maximum number of messages allowed in an HRMP channel at once.
|
||||
pub hrmp_channel_max_places: u32,
|
||||
pub hrmp_channel_max_capacity: u32,
|
||||
/// The maximum total size of messages in bytes allowed in an HRMP channel at once.
|
||||
pub hrmp_channel_max_size: u32,
|
||||
pub hrmp_channel_max_total_size: u32,
|
||||
/// The maximum number of inbound HRMP channels a parachain is allowed to accept.
|
||||
pub hrmp_max_parachain_inbound_channels: u32,
|
||||
/// The maximum number of inbound HRMP channels a parathread is allowed to accept.
|
||||
pub hrmp_max_parathread_inbound_channels: u32,
|
||||
/// The maximum size of a message that could ever be put into an HRMP channel.
|
||||
///
|
||||
/// This parameter affects the upper bound of size of `CandidateCommitments`.
|
||||
pub hrmp_channel_max_message_size: u32,
|
||||
/// The maximum number of outbound HRMP channels a parachain is allowed to open.
|
||||
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,
|
||||
/// The maximum number of outbound HRMP messages can be sent by a candidate.
|
||||
///
|
||||
/// This parameter affects the upper bound of size of `CandidateCommitments`.
|
||||
pub hrmp_max_message_num_per_candidate: u32,
|
||||
}
|
||||
```
|
||||
|
||||
@@ -427,6 +427,7 @@ mod tests {
|
||||
|
||||
impl router::Trait for Test {
|
||||
type UmpSink = ();
|
||||
type Origin = Origin;
|
||||
}
|
||||
|
||||
impl pallet_session::historical::Trait for Test {
|
||||
|
||||
@@ -22,12 +22,14 @@
|
||||
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
use sp_core::u32_trait::{_1, _2, _3, _5};
|
||||
use codec::{Encode, Decode};
|
||||
use primitives::v1::{
|
||||
AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt,
|
||||
CoreState, GroupRotationInfo, Hash, Id, Moment, Nonce, OccupiedCoreAssumption,
|
||||
PersistedValidationData, Signature, ValidationCode, ValidationData, ValidatorId, ValidatorIndex,
|
||||
InboundDownwardMessage, InboundHrmpMessage,
|
||||
};
|
||||
use runtime_common::{
|
||||
claims, SlowAdjustingFeeUpdate, CurrencyToVote,
|
||||
@@ -1112,9 +1114,15 @@ sp_api::impl_runtime_apis! {
|
||||
|
||||
fn dmq_contents(
|
||||
_recipient: Id,
|
||||
) -> Vec<primitives::v1::InboundDownwardMessage<BlockNumber>> {
|
||||
) -> Vec<InboundDownwardMessage<BlockNumber>> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn inbound_hrmp_channels_contents(
|
||||
_recipient: Id
|
||||
) -> BTreeMap<Id, Vec<InboundHrmpMessage<BlockNumber>>> {
|
||||
BTreeMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl fg_primitives::GrandpaApi<Block> for Runtime {
|
||||
|
||||
@@ -33,6 +33,7 @@ pallet-vesting = { git = "https://github.com/paritytech/substrate", branch = "ma
|
||||
pallet-offences = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
|
||||
|
||||
xcm = { package = "xcm", path = "../../xcm", default-features = false }
|
||||
primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false }
|
||||
libsecp256k1 = { version = "0.3.2", default-features = false, optional = true }
|
||||
|
||||
@@ -81,6 +82,7 @@ std = [
|
||||
"frame-system/std",
|
||||
"pallet-timestamp/std",
|
||||
"pallet-vesting/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"libsecp256k1/hmac",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
//! Configuration can change only at session boundaries and is buffered until then.
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use primitives::v1::ValidatorId;
|
||||
use primitives::v1::{Balance, ValidatorId};
|
||||
use frame_support::{
|
||||
decl_storage, decl_module, decl_error,
|
||||
dispatch::DispatchResult,
|
||||
@@ -84,6 +84,32 @@ pub struct HostConfiguration<BlockNumber> {
|
||||
///
|
||||
/// This parameter affects the size upper bound of the `CandidateCommitments`.
|
||||
pub max_upward_message_num_per_candidate: 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: Balance,
|
||||
/// The deposit that the recipient should provide for accepting opening an HRMP channel.
|
||||
pub hrmp_recipient_deposit: Balance,
|
||||
/// The maximum number of messages allowed in an HRMP channel at once.
|
||||
pub hrmp_channel_max_capacity: u32,
|
||||
/// The maximum total size of messages in bytes allowed in an HRMP channel at once.
|
||||
pub hrmp_channel_max_total_size: u32,
|
||||
/// The maximum number of inbound HRMP channels a parachain is allowed to accept.
|
||||
pub hrmp_max_parachain_inbound_channels: u32,
|
||||
/// The maximum number of inbound HRMP channels a parathread is allowed to accept.
|
||||
pub hrmp_max_parathread_inbound_channels: u32,
|
||||
/// The maximum size of a message that could ever be put into an HRMP channel.
|
||||
///
|
||||
/// This parameter affects the upper bound of size of `CandidateCommitments`.
|
||||
pub hrmp_channel_max_message_size: u32,
|
||||
/// The maximum number of outbound HRMP channels a parachain is allowed to open.
|
||||
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,
|
||||
/// The maximum number of outbound HRMP messages can be sent by a candidate.
|
||||
///
|
||||
/// This parameter affects the upper bound of size of `CandidateCommitments`.
|
||||
pub hrmp_max_message_num_per_candidate: u32,
|
||||
}
|
||||
|
||||
pub trait Trait: frame_system::Trait { }
|
||||
@@ -276,6 +302,117 @@ decl_module! {
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the number of sessions after which an HRMP open channel request expires.
|
||||
#[weight = (1_000, DispatchClass::Operational)]
|
||||
pub fn set_hrmp_open_request_ttl(origin, new: u32) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
Self::update_config_member(|config| {
|
||||
sp_std::mem::replace(&mut config.hrmp_open_request_ttl, new) != new
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the amount of funds that the sender should provide for opening an HRMP channel.
|
||||
#[weight = (1_000, DispatchClass::Operational)]
|
||||
pub fn set_hrmp_sender_deposit(origin, new: Balance) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
Self::update_config_member(|config| {
|
||||
sp_std::mem::replace(&mut config.hrmp_sender_deposit, new) != new
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the amount of funds that the recipient should provide for accepting opening an HRMP
|
||||
/// channel.
|
||||
#[weight = (1_000, DispatchClass::Operational)]
|
||||
pub fn set_hrmp_recipient_deposit(origin, new: Balance) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
Self::update_config_member(|config| {
|
||||
sp_std::mem::replace(&mut config.hrmp_recipient_deposit, new) != new
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the maximum number of messages allowed in an HRMP channel at once.
|
||||
#[weight = (1_000, DispatchClass::Operational)]
|
||||
pub fn set_hrmp_channel_max_capacity(origin, new: u32) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
Self::update_config_member(|config| {
|
||||
sp_std::mem::replace(&mut config.hrmp_channel_max_capacity, new) != new
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the maximum total size of messages in bytes allowed in an HRMP channel at once.
|
||||
#[weight = (1_000, DispatchClass::Operational)]
|
||||
pub fn set_hrmp_channel_max_total_size(origin, new: u32) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
Self::update_config_member(|config| {
|
||||
sp_std::mem::replace(&mut config.hrmp_channel_max_total_size, new) != new
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the maximum number of inbound HRMP channels a parachain is allowed to accept.
|
||||
#[weight = (1_000, DispatchClass::Operational)]
|
||||
pub fn set_hrmp_max_parachain_inbound_channels(origin, new: u32) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
Self::update_config_member(|config| {
|
||||
sp_std::mem::replace(&mut config.hrmp_max_parachain_inbound_channels, new) != new
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the maximum number of inbound HRMP channels a parathread is allowed to accept.
|
||||
#[weight = (1_000, DispatchClass::Operational)]
|
||||
pub fn set_hrmp_max_parathread_inbound_channels(origin, new: u32) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
Self::update_config_member(|config| {
|
||||
sp_std::mem::replace(&mut config.hrmp_max_parathread_inbound_channels, new) != new
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the maximum size of a message that could ever be put into an HRMP channel.
|
||||
#[weight = (1_000, DispatchClass::Operational)]
|
||||
pub fn set_hrmp_channel_max_message_size(origin, new: u32) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
Self::update_config_member(|config| {
|
||||
sp_std::mem::replace(&mut config.hrmp_channel_max_message_size, new) != new
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the maximum number of outbound HRMP channels a parachain is allowed to open.
|
||||
#[weight = (1_000, DispatchClass::Operational)]
|
||||
pub fn set_hrmp_max_parachain_outbound_channels(origin, new: u32) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
Self::update_config_member(|config| {
|
||||
sp_std::mem::replace(&mut config.hrmp_max_parachain_outbound_channels, new) != new
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the maximum number of outbound HRMP channels a parathread is allowed to open.
|
||||
#[weight = (1_000, DispatchClass::Operational)]
|
||||
pub fn set_hrmp_max_parathread_outbound_channels(origin, new: u32) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
Self::update_config_member(|config| {
|
||||
sp_std::mem::replace(&mut config.hrmp_max_parathread_outbound_channels, new) != new
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the maximum number of outbound HRMP messages can be sent by a candidate.
|
||||
#[weight = (1_000, DispatchClass::Operational)]
|
||||
pub fn set_hrmp_max_message_num_per_candidate(origin, new: u32) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
Self::update_config_member(|config| {
|
||||
sp_std::mem::replace(&mut config.hrmp_max_message_num_per_candidate, new) != new
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,6 +497,17 @@ mod tests {
|
||||
preferred_dispatchable_upward_messages_step_weight: 20000,
|
||||
max_upward_message_size: 448,
|
||||
max_upward_message_num_per_candidate: 5,
|
||||
hrmp_open_request_ttl: 1312,
|
||||
hrmp_sender_deposit: 22,
|
||||
hrmp_recipient_deposit: 4905,
|
||||
hrmp_channel_max_capacity: 3921,
|
||||
hrmp_channel_max_total_size: 7687,
|
||||
hrmp_max_parachain_inbound_channels: 3722,
|
||||
hrmp_max_parathread_inbound_channels: 1967,
|
||||
hrmp_channel_max_message_size: 8192,
|
||||
hrmp_max_parachain_outbound_channels: 100,
|
||||
hrmp_max_parathread_outbound_channels: 200,
|
||||
hrmp_max_message_num_per_candidate: 20,
|
||||
};
|
||||
|
||||
assert!(<Configuration as Store>::PendingConfig::get().is_none());
|
||||
@@ -415,6 +563,50 @@ mod tests {
|
||||
Configuration::set_max_upward_message_num_per_candidate(
|
||||
Origin::root(), 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(
|
||||
Origin::root(),
|
||||
new_config.hrmp_recipient_deposit,
|
||||
).unwrap();
|
||||
Configuration::set_hrmp_channel_max_capacity(
|
||||
Origin::root(),
|
||||
new_config.hrmp_channel_max_capacity,
|
||||
).unwrap();
|
||||
Configuration::set_hrmp_channel_max_total_size(
|
||||
Origin::root(),
|
||||
new_config.hrmp_channel_max_total_size,
|
||||
).unwrap();
|
||||
Configuration::set_hrmp_max_parachain_inbound_channels(
|
||||
Origin::root(),
|
||||
new_config.hrmp_max_parachain_inbound_channels,
|
||||
).unwrap();
|
||||
Configuration::set_hrmp_max_parathread_inbound_channels(
|
||||
Origin::root(),
|
||||
new_config.hrmp_max_parathread_inbound_channels,
|
||||
).unwrap();
|
||||
Configuration::set_hrmp_channel_max_message_size(
|
||||
Origin::root(),
|
||||
new_config.hrmp_channel_max_message_size,
|
||||
).unwrap();
|
||||
Configuration::set_hrmp_max_parachain_outbound_channels(
|
||||
Origin::root(),
|
||||
new_config.hrmp_max_parachain_outbound_channels,
|
||||
).unwrap();
|
||||
Configuration::set_hrmp_max_parathread_outbound_channels(
|
||||
Origin::root(),
|
||||
new_config.hrmp_max_parathread_outbound_channels,
|
||||
).unwrap();
|
||||
Configuration::set_hrmp_max_message_num_per_candidate(
|
||||
Origin::root(),
|
||||
new_config.hrmp_max_message_num_per_candidate,
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(<Configuration as Store>::PendingConfig::get(), Some(new_config));
|
||||
})
|
||||
|
||||
@@ -157,6 +157,10 @@ decl_error! {
|
||||
IncorrectDownwardMessageHandling,
|
||||
/// At least one upward message sent does not pass the acceptance criteria.
|
||||
InvalidUpwardMessages,
|
||||
/// The candidate didn't follow the rules of HRMP watermark advancement.
|
||||
HrmpWatermarkMishandling,
|
||||
/// The HRMP messages sent by the candidate is not valid.
|
||||
InvalidOutboundHrmp,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,6 +419,8 @@ impl<T: Trait> Module<T> {
|
||||
&candidate.candidate.commitments.new_validation_code,
|
||||
candidate.candidate.commitments.processed_downward_messages,
|
||||
&candidate.candidate.commitments.upward_messages,
|
||||
T::BlockNumber::from(candidate.candidate.commitments.hrmp_watermark),
|
||||
&candidate.candidate.commitments.horizontal_messages,
|
||||
)?;
|
||||
|
||||
for (i, assignment) in scheduled[skip..].iter().enumerate() {
|
||||
@@ -548,6 +554,8 @@ impl<T: Trait> Module<T> {
|
||||
&validation_outputs.new_validation_code,
|
||||
validation_outputs.processed_downward_messages,
|
||||
&validation_outputs.upward_messages,
|
||||
T::BlockNumber::from(validation_outputs.hrmp_watermark),
|
||||
&validation_outputs.horizontal_messages,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -578,6 +586,14 @@ impl<T: Trait> Module<T> {
|
||||
receipt.descriptor.para_id,
|
||||
commitments.upward_messages,
|
||||
);
|
||||
weight += <router::Module<T>>::prune_hrmp(
|
||||
receipt.descriptor.para_id,
|
||||
T::BlockNumber::from(commitments.hrmp_watermark),
|
||||
);
|
||||
weight += <router::Module<T>>::queue_outbound_hrmp(
|
||||
receipt.descriptor.para_id,
|
||||
commitments.horizontal_messages,
|
||||
);
|
||||
|
||||
Self::deposit_event(
|
||||
Event::<T>::CandidateIncluded(plain, commitments.head_data.clone())
|
||||
@@ -702,6 +718,8 @@ impl<T: Trait> CandidateCheckContext<T> {
|
||||
new_validation_code: &Option<primitives::v1::ValidationCode>,
|
||||
processed_downward_messages: u32,
|
||||
upward_messages: &[primitives::v1::UpwardMessage],
|
||||
hrmp_watermark: T::BlockNumber,
|
||||
horizontal_messages: &[primitives::v1::OutboundHrmpMessage<ParaId>],
|
||||
) -> Result<(), DispatchError> {
|
||||
ensure!(
|
||||
head_data.0.len() <= self.config.max_head_data_size as _,
|
||||
@@ -739,6 +757,22 @@ impl<T: Trait> CandidateCheckContext<T> {
|
||||
),
|
||||
Error::<T>::InvalidUpwardMessages,
|
||||
);
|
||||
ensure!(
|
||||
<router::Module<T>>::check_hrmp_watermark(
|
||||
para_id,
|
||||
self.relay_parent_number,
|
||||
hrmp_watermark,
|
||||
),
|
||||
Error::<T>::HrmpWatermarkMishandling,
|
||||
);
|
||||
ensure!(
|
||||
<router::Module<T>>::check_outbound_hrmp(
|
||||
&self.config,
|
||||
para_id,
|
||||
horizontal_messages,
|
||||
),
|
||||
Error::<T>::InvalidOutboundHrmp,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -946,6 +980,7 @@ mod tests {
|
||||
relay_parent: Hash,
|
||||
persisted_validation_data_hash: Hash,
|
||||
new_validation_code: Option<ValidationCode>,
|
||||
hrmp_watermark: BlockNumber,
|
||||
}
|
||||
|
||||
impl TestCandidateBuilder {
|
||||
@@ -961,6 +996,7 @@ mod tests {
|
||||
commitments: CandidateCommitments {
|
||||
head_data: self.head_data,
|
||||
new_validation_code: self.new_validation_code,
|
||||
hrmp_watermark: self.hrmp_watermark,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
@@ -1359,6 +1395,9 @@ mod tests {
|
||||
let chain_b = ParaId::from(2);
|
||||
let thread_a = ParaId::from(3);
|
||||
|
||||
// The block number of the relay-parent for testing.
|
||||
const RELAY_PARENT_NUM: BlockNumber = 4;
|
||||
|
||||
let paras = vec![(chain_a, true), (chain_b, true), (thread_a, false)];
|
||||
let validators = vec![
|
||||
Sr25519Keyring::Alice,
|
||||
@@ -1421,6 +1460,7 @@ mod tests {
|
||||
relay_parent: System::parent_hash(),
|
||||
pov_hash: Hash::from([1; 32]),
|
||||
persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
collator_sign_candidate(
|
||||
@@ -1454,6 +1494,7 @@ mod tests {
|
||||
relay_parent: System::parent_hash(),
|
||||
pov_hash: Hash::from([1; 32]),
|
||||
persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
let mut candidate_b = TestCandidateBuilder {
|
||||
@@ -1461,6 +1502,7 @@ mod tests {
|
||||
relay_parent: System::parent_hash(),
|
||||
pov_hash: Hash::from([2; 32]),
|
||||
persisted_validation_data_hash: make_vdata_hash(chain_b).unwrap(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
|
||||
@@ -1510,6 +1552,7 @@ mod tests {
|
||||
relay_parent: System::parent_hash(),
|
||||
pov_hash: Hash::from([1; 32]),
|
||||
persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
collator_sign_candidate(
|
||||
@@ -1579,6 +1622,7 @@ mod tests {
|
||||
relay_parent: System::parent_hash(),
|
||||
pov_hash: Hash::from([1; 32]),
|
||||
persisted_validation_data_hash: make_vdata_hash(thread_a).unwrap(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
|
||||
@@ -1618,6 +1662,7 @@ mod tests {
|
||||
relay_parent: System::parent_hash(),
|
||||
pov_hash: Hash::from([1; 32]),
|
||||
persisted_validation_data_hash: make_vdata_hash(thread_a).unwrap(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
|
||||
@@ -1656,6 +1701,7 @@ mod tests {
|
||||
relay_parent: System::parent_hash(),
|
||||
pov_hash: Hash::from([1; 32]),
|
||||
persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
|
||||
@@ -1703,6 +1749,7 @@ mod tests {
|
||||
relay_parent: System::parent_hash(),
|
||||
pov_hash: Hash::from([1; 32]),
|
||||
persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
|
||||
@@ -1743,6 +1790,7 @@ mod tests {
|
||||
pov_hash: Hash::from([1; 32]),
|
||||
new_validation_code: Some(vec![5, 6, 7, 8].into()),
|
||||
persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
|
||||
@@ -1785,6 +1833,7 @@ mod tests {
|
||||
relay_parent: System::parent_hash(),
|
||||
pov_hash: Hash::from([1; 32]),
|
||||
persisted_validation_data_hash: [42u8; 32].into(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
|
||||
@@ -1820,6 +1869,9 @@ mod tests {
|
||||
let chain_b = ParaId::from(2);
|
||||
let thread_a = ParaId::from(3);
|
||||
|
||||
// The block number of the relay-parent for testing.
|
||||
const RELAY_PARENT_NUM: BlockNumber = 4;
|
||||
|
||||
let paras = vec![(chain_a, true), (chain_b, true), (thread_a, false)];
|
||||
let validators = vec![
|
||||
Sr25519Keyring::Alice,
|
||||
@@ -1880,6 +1932,7 @@ mod tests {
|
||||
relay_parent: System::parent_hash(),
|
||||
pov_hash: Hash::from([1; 32]),
|
||||
persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
collator_sign_candidate(
|
||||
@@ -1892,6 +1945,7 @@ mod tests {
|
||||
relay_parent: System::parent_hash(),
|
||||
pov_hash: Hash::from([2; 32]),
|
||||
persisted_validation_data_hash: make_vdata_hash(chain_b).unwrap(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
collator_sign_candidate(
|
||||
@@ -1904,6 +1958,7 @@ mod tests {
|
||||
relay_parent: System::parent_hash(),
|
||||
pov_hash: Hash::from([3; 32]),
|
||||
persisted_validation_data_hash: make_vdata_hash(thread_a).unwrap(),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
collator_sign_candidate(
|
||||
@@ -2001,6 +2056,9 @@ mod tests {
|
||||
fn can_include_candidate_with_ok_code_upgrade() {
|
||||
let chain_a = ParaId::from(1);
|
||||
|
||||
// The block number of the relay-parent for testing.
|
||||
const RELAY_PARENT_NUM: BlockNumber = 4;
|
||||
|
||||
let paras = vec![(chain_a, true)];
|
||||
let validators = vec![
|
||||
Sr25519Keyring::Alice,
|
||||
@@ -2044,6 +2102,7 @@ mod tests {
|
||||
pov_hash: Hash::from([1; 32]),
|
||||
persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
|
||||
new_validation_code: Some(vec![1, 2, 3].into()),
|
||||
hrmp_watermark: RELAY_PARENT_NUM,
|
||||
..Default::default()
|
||||
}.build();
|
||||
collator_sign_candidate(
|
||||
|
||||
@@ -109,6 +109,7 @@ impl crate::paras::Trait for Test {
|
||||
}
|
||||
|
||||
impl crate::router::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type UmpSink = crate::router::MockUmpSink;
|
||||
}
|
||||
|
||||
|
||||
@@ -541,6 +541,12 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the given ID refers to a valid para.
|
||||
pub(crate) fn is_valid_para(id: ParaId) -> bool {
|
||||
Self::parachains().binary_search(&id).is_ok()
|
||||
|| Self::is_parathread(id)
|
||||
}
|
||||
|
||||
/// Whether a para ID corresponds to any live parathread.
|
||||
pub(crate) fn is_parathread(id: ParaId) -> bool {
|
||||
Parathreads::get(&id).is_some()
|
||||
|
||||
@@ -20,25 +20,30 @@
|
||||
//! routing the messages at their destinations and informing the parachains about the incoming
|
||||
//! messages.
|
||||
|
||||
use crate::{
|
||||
configuration,
|
||||
initializer,
|
||||
};
|
||||
use crate::{configuration, paras, initializer, ensure_parachain};
|
||||
use sp_std::prelude::*;
|
||||
use frame_support::{decl_error, decl_module, decl_storage, weights::Weight};
|
||||
use frame_support::{decl_error, decl_module, decl_storage, dispatch::DispatchResult, weights::Weight};
|
||||
use sp_std::collections::vec_deque::VecDeque;
|
||||
use primitives::v1::{Id as ParaId, InboundDownwardMessage, Hash, UpwardMessage};
|
||||
use primitives::v1::{
|
||||
Id as ParaId, InboundDownwardMessage, Hash, UpwardMessage, HrmpChannelId, InboundHrmpMessage,
|
||||
};
|
||||
|
||||
mod dmp;
|
||||
mod hrmp;
|
||||
mod ump;
|
||||
|
||||
use hrmp::{HrmpOpenChannelRequest, HrmpChannel};
|
||||
pub use dmp::QueueDownwardMessageError;
|
||||
pub use ump::UmpSink;
|
||||
|
||||
#[cfg(test)]
|
||||
pub use ump::mock_sink::MockUmpSink;
|
||||
|
||||
pub trait Trait: frame_system::Trait + configuration::Trait {
|
||||
pub trait Trait: frame_system::Trait + configuration::Trait + paras::Trait {
|
||||
type Origin: From<crate::Origin>
|
||||
+ From<<Self as frame_system::Trait>::Origin>
|
||||
+ Into<Result<crate::Origin, <Self as Trait>::Origin>>;
|
||||
|
||||
/// A place where all received upward messages are funneled.
|
||||
type UmpSink: UmpSink;
|
||||
}
|
||||
@@ -103,17 +108,148 @@ decl_storage! {
|
||||
/// Invariant:
|
||||
/// - If `Some(para)`, then `para` must be present in `NeedsDispatch`.
|
||||
NextDispatchRoundStartWith: Option<ParaId>;
|
||||
|
||||
/*
|
||||
* Horizontally Relay-routed Message Passing (HRMP)
|
||||
*
|
||||
* HRMP related storage layout
|
||||
*/
|
||||
|
||||
/// 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 hasher(twox_64_concat) HrmpChannelId => Option<HrmpOpenChannelRequest>;
|
||||
HrmpOpenChannelRequestsList: Vec<HrmpChannelId>;
|
||||
|
||||
/// 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 `(X, _)`
|
||||
/// as the number of `HrmpOpenChannelRequestCount` for `X`.
|
||||
HrmpOpenChannelRequestCount: map hasher(twox_64_concat) 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 hasher(twox_64_concat) 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 hasher(twox_64_concat) 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 hasher(twox_64_concat) ParaId => Option<T::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 hasher(twox_64_concat) 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 hasher(twox_64_concat) ParaId => Vec<ParaId>;
|
||||
HrmpEgressChannelsIndex: map hasher(twox_64_concat) 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 hasher(twox_64_concat) HrmpChannelId => Vec<InboundHrmpMessage<T::BlockNumber>>;
|
||||
/// Maintains a mapping that can be used to answer the question:
|
||||
/// What paras sent a message at the given block number for a given reciever.
|
||||
/// 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 hasher(twox_64_concat) ParaId => Vec<(T::BlockNumber, Vec<ParaId>)>;
|
||||
}
|
||||
}
|
||||
|
||||
decl_error! {
|
||||
pub enum Error for Module<T: Trait> { }
|
||||
pub enum Error for Module<T: Trait> {
|
||||
/// The sender tried to open a channel to themselves.
|
||||
OpenHrmpChannelToSelf,
|
||||
/// The recipient is not a valid para.
|
||||
OpenHrmpChannelInvalidRecipient,
|
||||
/// The requested capacity is zero.
|
||||
OpenHrmpChannelZeroCapacity,
|
||||
/// The requested capacity exceeds the global limit.
|
||||
OpenHrmpChannelCapacityExceedsLimit,
|
||||
/// The requested maximum message size is 0.
|
||||
OpenHrmpChannelZeroMessageSize,
|
||||
/// The open request requested the message size that exceeds the global limit.
|
||||
OpenHrmpChannelMessageSizeExceedsLimit,
|
||||
/// The channel already exists
|
||||
OpenHrmpChannelAlreadyExists,
|
||||
/// There is already a request to open the same channel.
|
||||
OpenHrmpChannelAlreadyRequested,
|
||||
/// The sender already has the maximum number of allowed outbound channels.
|
||||
OpenHrmpChannelLimitExceeded,
|
||||
/// The channel from the sender to the origin doesn't exist.
|
||||
AcceptHrmpChannelDoesntExist,
|
||||
/// The channel is already confirmed.
|
||||
AcceptHrmpChannelAlreadyConfirmed,
|
||||
/// The recipient already has the maximum number of allowed inbound channels.
|
||||
AcceptHrmpChannelLimitExceeded,
|
||||
/// The origin tries to close a channel where it is neither the sender nor the recipient.
|
||||
CloseHrmpChannelUnauthorized,
|
||||
/// The channel to be closed doesn't exist.
|
||||
CloseHrmpChannelDoesntExist,
|
||||
/// The channel close request is already requested.
|
||||
CloseHrmpChannelAlreadyUnderway,
|
||||
}
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
/// The router module.
|
||||
pub struct Module<T: Trait> for enum Call where origin: <T as frame_system::Trait>::Origin {
|
||||
type Error = Error<T>;
|
||||
|
||||
#[weight = 0]
|
||||
fn hrmp_init_open_channel(
|
||||
origin,
|
||||
recipient: ParaId,
|
||||
proposed_max_capacity: u32,
|
||||
proposed_max_message_size: u32,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_parachain(<T as Trait>::Origin::from(origin))?;
|
||||
Self::init_open_channel(
|
||||
origin,
|
||||
recipient,
|
||||
proposed_max_capacity,
|
||||
proposed_max_message_size
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[weight = 0]
|
||||
fn hrmp_accept_open_channel(origin, sender: ParaId) -> DispatchResult {
|
||||
let origin = ensure_parachain(<T as Trait>::Origin::from(origin))?;
|
||||
Self::accept_open_channel(origin, sender)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[weight = 0]
|
||||
fn hrmp_close_channel(origin, channel_id: HrmpChannelId) -> DispatchResult {
|
||||
let origin = ensure_parachain(<T as Trait>::Origin::from(origin))?;
|
||||
Self::close_channel(origin, channel_id)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,12 +264,21 @@ impl<T: Trait> Module<T> {
|
||||
|
||||
/// Called by the initializer to note that a new session has started.
|
||||
pub(crate) fn initializer_on_new_session(
|
||||
_notification: &initializer::SessionChangeNotification<T::BlockNumber>,
|
||||
notification: &initializer::SessionChangeNotification<T::BlockNumber>,
|
||||
) {
|
||||
Self::perform_outgoing_para_cleanup();
|
||||
Self::process_hrmp_open_channel_requests(¬ification.prev_config);
|
||||
Self::process_hrmp_close_channel_requests();
|
||||
}
|
||||
|
||||
/// Iterate over all paras that were registered for offboarding and remove all the data
|
||||
/// associated with them.
|
||||
fn perform_outgoing_para_cleanup() {
|
||||
let outgoing = OutgoingParas::take();
|
||||
for outgoing_para in outgoing {
|
||||
Self::clean_dmp_after_outgoing(outgoing_para);
|
||||
Self::clean_ump_after_outgoing(outgoing_para);
|
||||
Self::clean_hrmp_after_outgoing(outgoing_para);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,12 +18,13 @@
|
||||
//! functions.
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
use primitives::v1::{
|
||||
ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, ValidationData,
|
||||
Id as ParaId, OccupiedCoreAssumption, SessionIndex, ValidationCode,
|
||||
CommittedCandidateReceipt, ScheduledCore, OccupiedCore, CoreOccupied, CoreIndex,
|
||||
GroupIndex, CandidateEvent, PersistedValidationData, AuthorityDiscoveryId,
|
||||
InboundDownwardMessage,
|
||||
InboundDownwardMessage, InboundHrmpMessage,
|
||||
};
|
||||
use sp_runtime::traits::Zero;
|
||||
use frame_support::debug;
|
||||
@@ -328,3 +329,10 @@ pub fn dmq_contents<T: router::Trait>(
|
||||
) -> Vec<InboundDownwardMessage<T::BlockNumber>> {
|
||||
<router::Module<T>>::dmq_contents(recipient)
|
||||
}
|
||||
|
||||
/// Implementation for the `inbound_hrmp_channels_contents` function of the runtime API.
|
||||
pub fn inbound_hrmp_channels_contents<T: router::Trait>(
|
||||
recipient: ParaId,
|
||||
) -> BTreeMap<ParaId, Vec<InboundHrmpMessage<T::BlockNumber>>> {
|
||||
<router::Module<T>>::inbound_hrmp_channels_contents(recipient)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
use sp_runtime::traits::{One, Saturating};
|
||||
use primitives::v1::{Id as ParaId, PersistedValidationData, TransientValidationData};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
use crate::{configuration, paras, router};
|
||||
|
||||
@@ -34,7 +33,7 @@ pub fn make_persisted_validation_data<T: paras::Trait + router::Trait>(
|
||||
Some(PersistedValidationData {
|
||||
parent_head: <paras::Module<T>>::para_head(¶_id)?,
|
||||
block_number: relay_parent_number,
|
||||
hrmp_mqc_heads: Vec::new(),
|
||||
hrmp_mqc_heads: <router::Module<T>>::hrmp_mqc_heads(para_id),
|
||||
dmq_mqc_head: <router::Module<T>>::dmq_mqc_head(para_id),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -30,12 +30,14 @@ use runtime_common::{
|
||||
};
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
use sp_core::u32_trait::{_1, _2, _3, _4, _5};
|
||||
use codec::{Encode, Decode};
|
||||
use primitives::v1::{
|
||||
AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt,
|
||||
CoreState, GroupRotationInfo, Hash, Id, Moment, Nonce, OccupiedCoreAssumption,
|
||||
PersistedValidationData, Signature, ValidationCode, ValidationData, ValidatorId, ValidatorIndex,
|
||||
InboundDownwardMessage, InboundHrmpMessage,
|
||||
};
|
||||
use sp_runtime::{
|
||||
create_runtime_str, generic, impl_opaque_keys, ModuleId, ApplyExtrinsicResult,
|
||||
@@ -1106,9 +1108,16 @@ sp_api::impl_runtime_apis! {
|
||||
|
||||
fn dmq_contents(
|
||||
_recipient: Id,
|
||||
) -> Vec<primitives::v1::InboundDownwardMessage<BlockNumber>> {
|
||||
) -> Vec<InboundDownwardMessage<BlockNumber>> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn inbound_hrmp_channels_contents(
|
||||
_recipient: Id
|
||||
) -> BTreeMap<Id, Vec<InboundHrmpMessage<BlockNumber>>> {
|
||||
BTreeMap::new()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl fg_primitives::GrandpaApi<Block> for Runtime {
|
||||
|
||||
@@ -22,12 +22,13 @@
|
||||
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
use codec::Encode;
|
||||
use primitives::v1::{
|
||||
AccountId, AccountIndex, Balance, BlockNumber, Hash, Nonce, Signature, Moment,
|
||||
GroupRotationInfo, CoreState, Id, ValidationData, ValidationCode, CandidateEvent,
|
||||
ValidatorId, ValidatorIndex, CommittedCandidateReceipt, OccupiedCoreAssumption,
|
||||
PersistedValidationData,
|
||||
PersistedValidationData, InboundDownwardMessage, InboundHrmpMessage,
|
||||
};
|
||||
use runtime_common::{
|
||||
SlowAdjustingFeeUpdate,
|
||||
@@ -532,6 +533,7 @@ impl parachains_paras::Trait for Runtime {
|
||||
}
|
||||
|
||||
impl parachains_router::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
type UmpSink = (); // TODO: #1873 To be handled by the XCM receiver.
|
||||
}
|
||||
|
||||
@@ -681,9 +683,15 @@ sp_api::impl_runtime_apis! {
|
||||
runtime_api_impl::validator_discovery::<Runtime>(validators)
|
||||
}
|
||||
|
||||
fn dmq_contents(recipient: Id) -> Vec<primitives::v1::InboundDownwardMessage<BlockNumber>> {
|
||||
fn dmq_contents(recipient: Id) -> Vec<InboundDownwardMessage<BlockNumber>> {
|
||||
runtime_api_impl::dmq_contents::<Runtime>(recipient)
|
||||
}
|
||||
|
||||
fn inbound_hrmp_channels_contents(
|
||||
recipient: Id
|
||||
) -> BTreeMap<Id, Vec<InboundHrmpMessage<BlockNumber>>> {
|
||||
runtime_api_impl::inbound_hrmp_channels_contents::<Runtime>(recipient)
|
||||
}
|
||||
}
|
||||
|
||||
impl fg_primitives::GrandpaApi<Block> for Runtime {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
use codec::Encode;
|
||||
use polkadot_runtime_parachains::{
|
||||
configuration,
|
||||
@@ -36,6 +37,7 @@ use primitives::v1::{
|
||||
AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt,
|
||||
CoreState, GroupRotationInfo, Hash as HashT, Id as ParaId, Moment, Nonce, OccupiedCoreAssumption,
|
||||
PersistedValidationData, Signature, ValidationCode, ValidationData, ValidatorId, ValidatorIndex,
|
||||
InboundDownwardMessage, InboundHrmpMessage,
|
||||
};
|
||||
use runtime_common::{
|
||||
claims, SlowAdjustingFeeUpdate, paras_sudo_wrapper,
|
||||
@@ -453,6 +455,7 @@ impl paras::Trait for Runtime {
|
||||
}
|
||||
|
||||
impl router::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
type UmpSink = ();
|
||||
}
|
||||
|
||||
@@ -668,9 +671,15 @@ sp_api::impl_runtime_apis! {
|
||||
|
||||
fn dmq_contents(
|
||||
recipient: ParaId,
|
||||
) -> Vec<primitives::v1::InboundDownwardMessage<BlockNumber>> {
|
||||
) -> Vec<InboundDownwardMessage<BlockNumber>> {
|
||||
runtime_impl::dmq_contents::<Runtime>(recipient)
|
||||
}
|
||||
|
||||
fn inbound_hrmp_channels_contents(
|
||||
recipient: ParaId,
|
||||
) -> BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>> {
|
||||
runtime_impl::inbound_hrmp_channels_contents::<Runtime>(recipient)
|
||||
}
|
||||
}
|
||||
|
||||
impl fg_primitives::GrandpaApi<Block> for Runtime {
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
use codec::{Encode, Decode};
|
||||
use primitives::v1::{
|
||||
AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt,
|
||||
CoreState, GroupRotationInfo, Hash, Id, Moment, Nonce, OccupiedCoreAssumption,
|
||||
PersistedValidationData, Signature, ValidationCode, ValidationData, ValidatorId, ValidatorIndex,
|
||||
InboundDownwardMessage, InboundHrmpMessage,
|
||||
};
|
||||
use runtime_common::{
|
||||
SlowAdjustingFeeUpdate, CurrencyToVote,
|
||||
@@ -856,9 +858,15 @@ sp_api::impl_runtime_apis! {
|
||||
|
||||
fn dmq_contents(
|
||||
_recipient: Id,
|
||||
) -> Vec<primitives::v1::InboundDownwardMessage<BlockNumber>> {
|
||||
) -> Vec<InboundDownwardMessage<BlockNumber>> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn inbound_hrmp_channels_contents(
|
||||
_recipient: Id
|
||||
) -> BTreeMap<Id, Vec<InboundHrmpMessage<BlockNumber>>> {
|
||||
BTreeMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl fg_primitives::GrandpaApi<Block> for Runtime {
|
||||
|
||||
@@ -158,6 +158,51 @@ pub enum Xcm {
|
||||
///
|
||||
/// Errors:
|
||||
RelayedFrom { superorigin: MultiLocation, inner: Box<VersionedXcm> },
|
||||
|
||||
/// A message to notify about a new incoming HRMP channel. This message is meant to be sent by the
|
||||
/// relay-chain to a para.
|
||||
///
|
||||
/// - `sender`: The sender in the to-be opened channel. Also, the initiator of the channel opening.
|
||||
/// - `max_message_size`: The maximum size of a message proposed by the sender.
|
||||
/// - `max_capacity`: The maximum number of messages that can be queued in the channel.
|
||||
///
|
||||
/// Safety: The message should originate directly from the relay-chain.
|
||||
///
|
||||
/// Kind: *System Notification*
|
||||
HrmpNewChannelOpenRequest {
|
||||
#[codec(compact)] sender: u32,
|
||||
#[codec(compact)] max_message_size: u32,
|
||||
#[codec(compact)] max_capacity: u32,
|
||||
},
|
||||
|
||||
/// A message to notify about that a previously sent open channel request has been accepted by
|
||||
/// the recipient. That means that the channel will be opened during the next relay-chain session
|
||||
/// change. This message is meant to be sent by the relay-chain to a para.
|
||||
///
|
||||
/// Safety: The message should originate directly from the relay-chain.
|
||||
///
|
||||
/// Kind: *System Notification*
|
||||
///
|
||||
/// Errors:
|
||||
HrmpChannelAccepted {
|
||||
#[codec(compact)] recipient: u32,
|
||||
},
|
||||
|
||||
/// A message to notify that the other party in an open channel decided to close it. In particular,
|
||||
/// `inititator` is going to close the channel opened from `sender` to the `recipient`. The close
|
||||
/// will be enacted at the next relay-chain session change. This message is meant to be sent by
|
||||
/// the relay-chain to a para.
|
||||
///
|
||||
/// Safety: The message should originate directly from the relay-chain.
|
||||
///
|
||||
/// Kind: *System Notification*
|
||||
///
|
||||
/// Errors:
|
||||
HrmpChannelClosing {
|
||||
#[codec(compact)] initiator: u32,
|
||||
#[codec(compact)] sender: u32,
|
||||
#[codec(compact)] recipient: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<Xcm> for VersionedXcm {
|
||||
|
||||
Reference in New Issue
Block a user