diff --git a/Cargo.lock b/Cargo.lock
index 5ea406367..f3b8b8b93 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -7532,6 +7532,19 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dd856d451cc0da70e2ef2ce95a18e39a93b7558bedf10201ad28503f918568"
+[[package]]
+name = "manual-randomness-rpc"
+version = "0.1.0"
+dependencies = [
+ "cumulus-primitives-core",
+ "flume 0.10.14",
+ "hex-literal 0.3.4",
+ "jsonrpsee",
+ "parity-scale-codec",
+ "sp-core",
+ "staging-xcm",
+]
+
[[package]]
name = "manual-xcm-rpc"
version = "0.1.0"
@@ -17846,6 +17859,7 @@ dependencies = [
"hex-literal 0.3.4",
"jsonrpsee",
"log",
+ "manual-randomness-rpc",
"manual-xcm-rpc",
"nimbus-consensus",
"nimbus-primitives",
diff --git a/Cargo.toml b/Cargo.toml
index 8624cbd7e..1b58c1bda 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -87,6 +87,7 @@ flashbox-runtime = { path = "runtime/flashbox", default-features = false }
dancelight-runtime = { path = "solo-chains/runtime/dancelight", default-features = false }
dancelight-runtime-constants = { path = "solo-chains/runtime/dancelight/constants", default-features = false }
ethabi = { package = "ethabi-decode", version = "1.0.0", default-features = false }
+manual-randomness-rpc = { path = "client/manual-randomness" }
manual-xcm-rpc = { path = "client/manual-xcm" }
node-common = { path = "client/node-common" }
services-payment-rpc = { path = "client/services-payment" }
diff --git a/client/manual-randomness/Cargo.toml b/client/manual-randomness/Cargo.toml
new file mode 100644
index 000000000..e0651a77f
--- /dev/null
+++ b/client/manual-randomness/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "manual-randomness-rpc"
+authors = { workspace = true }
+edition = "2021"
+license = "GPL-3.0-only"
+repository = { workspace = true }
+version = "0.1.0"
+
+[lints]
+workspace = true
+
+[dependencies]
+cumulus-primitives-core = { workspace = true, features = [ "std" ] }
+flume = { workspace = true }
+hex-literal = { workspace = true }
+jsonrpsee = { workspace = true, features = [ "macros", "server" ] }
+parity-scale-codec = { workspace = true, features = [ "std" ] }
+sp-core = { workspace = true, features = [ "std" ] }
+staging-xcm = { workspace = true }
diff --git a/client/manual-randomness/src/lib.rs b/client/manual-randomness/src/lib.rs
new file mode 100644
index 000000000..b08e4d705
--- /dev/null
+++ b/client/manual-randomness/src/lib.rs
@@ -0,0 +1,70 @@
+// Copyright (C) Moondance Labs Ltd.
+// This file is part of Tanssi.
+
+// Tanssi is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Tanssi is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Tanssi. If not, see .
+use jsonrpsee::{core::RpcResult, proc_macros::rpc};
+use sp_core::H256;
+
+#[rpc(server)]
+#[jsonrpsee::core::async_trait]
+pub trait ManualRandomnessApi {
+ /// Inject randomness
+ #[method(name = "mock_activateRandomness")]
+ async fn activate_randomness(&self, seed: Option) -> RpcResult<()>;
+ #[method(name = "mock_deactivateRandomness")]
+ async fn deactivate_randomness(&self) -> RpcResult<()>;
+}
+
+pub struct ManualRandomness {
+ pub randomness_message_channel: flume::Sender<(bool, Option<[u8; 32]>)>,
+}
+
+#[jsonrpsee::core::async_trait]
+impl ManualRandomnessApiServer for ManualRandomness {
+ async fn activate_randomness(&self, seed: Option) -> RpcResult<()> {
+ let randomness_message_channel = self.randomness_message_channel.clone();
+
+ // Push the message to the shared channel where it will be queued up
+ // to be injected in to an upcoming block.
+ randomness_message_channel
+ .send_async((true, seed.map(|x| x.into())))
+ .await
+ .map_err(|err| internal_err(err.to_string()))?;
+
+ Ok(())
+ }
+
+ async fn deactivate_randomness(&self) -> RpcResult<()> {
+ let randomness_message_channel = self.randomness_message_channel.clone();
+
+ // Push the message to the shared channel where it will be queued up
+ // to be injected in to an upcoming block.
+ randomness_message_channel
+ .send_async((false, None))
+ .await
+ .map_err(|err| internal_err(err.to_string()))?;
+
+ Ok(())
+ }
+}
+
+// This bit cribbed from frontier.
+pub fn internal_err>(message: T) -> jsonrpsee::types::ErrorObjectOwned {
+ jsonrpsee::types::error::ErrorObject::borrowed(
+ jsonrpsee::types::error::INTERNAL_ERROR_CODE,
+ message.as_ref(),
+ None,
+ )
+ .into_owned()
+}
diff --git a/node/Cargo.toml b/node/Cargo.toml
index a083d571f..20e8930a4 100644
--- a/node/Cargo.toml
+++ b/node/Cargo.toml
@@ -36,6 +36,7 @@ dancebox-runtime = { workspace = true, features = [ "std" ] }
dp-container-chain-genesis-data = { workspace = true, features = [ "json", "std" ] }
dp-slot-duration-runtime-api = { workspace = true }
flashbox-runtime = { workspace = true, features = [ "std" ] }
+manual-randomness-rpc = { workspace = true }
manual-xcm-rpc = { workspace = true }
node-common = { workspace = true }
pallet-author-noting-runtime-api = { workspace = true, features = [ "std" ] }
diff --git a/node/src/chain_spec/dancebox.rs b/node/src/chain_spec/dancebox.rs
index 709fe17e3..0a38acbc5 100644
--- a/node/src/chain_spec/dancebox.rs
+++ b/node/src/chain_spec/dancebox.rs
@@ -98,6 +98,7 @@ pub fn development_config(
parathreads_per_collator: 1,
target_container_chain_fullness: Perbill::from_percent(80),
max_parachain_cores_percentage: None,
+ full_rotation_mode: Default::default(),
},
..Default::default()
},
@@ -161,6 +162,7 @@ pub fn local_dancebox_config(
parathreads_per_collator: 1,
target_container_chain_fullness: Perbill::from_percent(80),
max_parachain_cores_percentage: None,
+ full_rotation_mode: Default::default(),
},
..Default::default()
},
diff --git a/node/src/chain_spec/flashbox.rs b/node/src/chain_spec/flashbox.rs
index 555cd6ac4..06e3583fe 100644
--- a/node/src/chain_spec/flashbox.rs
+++ b/node/src/chain_spec/flashbox.rs
@@ -98,6 +98,7 @@ pub fn development_config(
parathreads_per_collator: 1,
target_container_chain_fullness: Perbill::from_percent(80),
max_parachain_cores_percentage: None,
+ full_rotation_mode: Default::default(),
},
..Default::default()
},
@@ -161,6 +162,7 @@ pub fn local_flashbox_config(
parathreads_per_collator: 1,
target_container_chain_fullness: Perbill::from_percent(80),
max_parachain_cores_percentage: None,
+ full_rotation_mode: Default::default(),
},
..Default::default()
},
diff --git a/node/src/rpc.rs b/node/src/rpc.rs
index 26317acdd..bf0d75361 100644
--- a/node/src/rpc.rs
+++ b/node/src/rpc.rs
@@ -24,6 +24,7 @@
use {
cumulus_primitives_core::ParaId,
dancebox_runtime::{opaque::Block, AccountId, Index as Nonce},
+ manual_randomness_rpc::{ManualRandomness, ManualRandomnessApiServer},
manual_xcm_rpc::{ManualXcm, ManualXcmApiServer},
polkadot_primitives::Hash,
sc_client_api::{AuxStore, UsageProvider},
@@ -55,6 +56,8 @@ pub struct FullDeps {
pub command_sink: Option>>,
/// Channels for manual xcm messages (downward, hrmp)
pub xcm_senders: Option<(flume::Sender>, flume::Sender<(ParaId, Vec)>)>,
+ /// Channels for manually activating the randomness
+ pub randomness_sender: Option)>>,
}
/// Instantiate all RPC extensions.
@@ -84,6 +87,7 @@ where
pool,
command_sink,
xcm_senders,
+ randomness_sender,
} = deps;
module.merge(System::new(client.clone(), pool).into_rpc())?;
@@ -108,5 +112,14 @@ where
)?;
}
+ if let Some(randomness_message_channel) = randomness_sender {
+ module.merge(
+ ManualRandomness {
+ randomness_message_channel,
+ }
+ .into_rpc(),
+ )?;
+ }
+
Ok(module)
}
diff --git a/node/src/service.rs b/node/src/service.rs
index 68780f9c8..d0d5e90b6 100644
--- a/node/src/service.rs
+++ b/node/src/service.rs
@@ -51,7 +51,7 @@ use {
pallet_author_noting_runtime_api::AuthorNotingApi,
pallet_data_preservers_runtime_api::DataPreserversApi,
pallet_registrar_runtime_api::RegistrarApi,
- parity_scale_codec::Encode,
+ parity_scale_codec::{Decode, Encode},
polkadot_cli::ProvideRuntimeApi,
polkadot_parachain_primitives::primitives::HeadData,
polkadot_service::Handle,
@@ -94,6 +94,9 @@ use {
mod mocked_relay_keys;
+// We use this to detect whether randomness is activated
+const RANDOMNESS_ACTIVATED_AUX_KEY: &[u8] = b"__DEV_RANDOMNESS_ACTIVATED";
+
type FullBackend = TFullBackend;
pub struct NodeConfig;
@@ -287,6 +290,7 @@ async fn start_node_impl(
pool: transaction_pool.clone(),
command_sink: None,
xcm_senders: None,
+ randomness_sender: None,
};
crate::rpc::create_full(deps).map_err(Into::into)
@@ -893,11 +897,18 @@ pub fn start_dev_node(
// production.
let mut command_sink = None;
let mut xcm_senders = None;
+ let mut randomness_sender = None;
if parachain_config.role.is_authority() {
let client = node_builder.client.clone();
let (downward_xcm_sender, downward_xcm_receiver) = flume::bounded::>(100);
+
let (hrmp_xcm_sender, hrmp_xcm_receiver) = flume::bounded::<(ParaId, Vec)>(100);
+ // Create channels for mocked parachain candidates.
+ let (mock_randomness_sender, mock_randomness_receiver) =
+ flume::bounded::<(bool, Option<[u8; 32]>)>(100);
+
xcm_senders = Some((downward_xcm_sender, hrmp_xcm_sender));
+ randomness_sender = Some(mock_randomness_sender);
command_sink = node_builder.install_manual_seal(ManualSealConfiguration {
block_import,
@@ -958,6 +969,34 @@ pub fn start_dev_node(
let downward_xcm_receiver = downward_xcm_receiver.clone();
let hrmp_xcm_receiver = hrmp_xcm_receiver.clone();
+ let randomness_enabler_messages: Vec<(bool, Option<[u8; 32]>)> = mock_randomness_receiver.drain().collect();
+
+ // If there is a value to be updated, we update it
+ if let Some((enable_randomness, new_seed)) = randomness_enabler_messages.last() {
+ let value = client
+ .get_aux(RANDOMNESS_ACTIVATED_AUX_KEY)
+ .expect("Should be able to query aux storage; qed").unwrap_or((false, Option::<[u8; 32]>::None).encode());
+ let (_mock_additional_randomness, mut mock_randomness_seed): (bool, Option<[u8; 32]>) = Decode::decode(&mut value.as_slice()).expect("Boolean non-decodable");
+
+ if let Some(new_seed) = new_seed {
+ mock_randomness_seed = Some(*new_seed);
+ }
+
+ client
+ .insert_aux(
+ &[(RANDOMNESS_ACTIVATED_AUX_KEY, (enable_randomness, mock_randomness_seed).encode().as_slice())],
+ &[],
+ )
+ .expect("Should be able to write to aux storage; qed");
+ }
+
+ // We read the value
+ // If error when reading, we simply put false
+ let value = client
+ .get_aux(RANDOMNESS_ACTIVATED_AUX_KEY)
+ .expect("Should be able to query aux storage; qed").unwrap_or((false, Option::<[u8; 32]>::None).encode());
+ let (mock_additional_randomness, mock_randomness_seed): (bool, Option<[u8; 32]>) = Decode::decode(&mut value.as_slice()).expect("Boolean non-decodable");
+
let client_for_xcm = client.clone();
async move {
let mocked_author_noting =
@@ -974,6 +1013,18 @@ pub fn start_dev_node(
let (registrar_paras_key_2002, para_info_2002) = mocked_relay_keys::get_mocked_registrar_paras(2002.into());
additional_keys.extend([(para_head_key, para_head_data), (relay_slot_key, Slot::from(relay_slot).encode()), (registrar_paras_key_2002, para_info_2002)]);
+ if mock_additional_randomness {
+ let mut mock_randomness: [u8; 32] = [0u8; 32];
+ mock_randomness[..4].copy_from_slice(¤t_para_block.to_be_bytes());
+ if let Some(seed) = mock_randomness_seed {
+ for i in 0..32 {
+ mock_randomness[i] ^= seed[i];
+ }
+ }
+ additional_keys.extend([(RelayWellKnownKeys::CURRENT_BLOCK_RANDOMNESS.to_vec(), Some(mock_randomness).encode())]);
+ log::info!("mokcing randomnessss!!! {}", current_para_block);
+ }
+
let time = MockTimestampInherentDataProvider;
let mocked_parachain = MockValidationDataInherentDataProvider {
current_para_block,
@@ -1011,6 +1062,7 @@ pub fn start_dev_node(
pool: transaction_pool.clone(),
command_sink: command_sink.clone(),
xcm_senders: xcm_senders.clone(),
+ randomness_sender: randomness_sender.clone(),
};
crate::rpc::create_full(deps).map_err(Into::into)
diff --git a/pallets/collator-assignment/src/assignment.rs b/pallets/collator-assignment/src/assignment.rs
index d5f52e685..29139318f 100644
--- a/pallets/collator-assignment/src/assignment.rs
+++ b/pallets/collator-assignment/src/assignment.rs
@@ -24,7 +24,9 @@ use {
mem,
vec::Vec,
},
- tp_traits::{ParaId, RemoveInvulnerables as RemoveInvulnerablesT},
+ tp_traits::{
+ FullRotationMode, FullRotationModes, ParaId, RemoveInvulnerables as RemoveInvulnerablesT,
+ },
};
// Separate import of `sp_std::vec!` macro, which cause issues with rustfmt if grouped
@@ -38,29 +40,6 @@ impl Assignment
where
T: crate::Config,
{
- /// Recompute collator assignment from scratch. If the list of collators and the list of
- /// container chains are shuffled, this returns a random assignment.
- pub fn assign_collators_rotate_all(
- collators: Vec,
- orchestrator_chain: ChainNumCollators,
- chains: Vec,
- shuffle: Option,
- ) -> Result, AssignmentError>
- where
- TShuffle: FnOnce(&mut Vec),
- {
- // This is just the "always_keep_old" algorithm but with an empty "old"
- let old_assigned = Default::default();
-
- Self::assign_collators_always_keep_old(
- collators,
- orchestrator_chain,
- chains,
- old_assigned,
- shuffle,
- )
- }
-
/// Assign new collators to missing container_chains.
/// Old collators always have preference to remain on the same chain.
/// If there are no missing collators, nothing is changed.
@@ -80,10 +59,11 @@ where
orchestrator_chain: ChainNumCollators,
mut chains: Vec,
mut old_assigned: AssignedCollators,
- shuffle: Option,
+ mut shuffle: Option,
+ full_rotation_mode: FullRotationModes,
) -> Result, AssignmentError>
where
- TShuffle: FnOnce(&mut Vec),
+ TShuffle: FnMut(&mut Vec),
{
if collators.is_empty() && !T::ForceEmptyOrchestrator::get() {
return Err(AssignmentError::ZeroCollators);
@@ -111,7 +91,24 @@ where
&collators_set,
);
+ // Remove some previously assigned collators to allow new collators to take their place.
+ // Based on full_rotation_mode. In regular sessions this is FullRotationMode::KeepAll, a no-op.
+ for chain in chains.iter() {
+ let mode = if chain.para_id == orchestrator_chain.para_id {
+ full_rotation_mode.orchestrator.clone()
+ } else if chain.parathread {
+ full_rotation_mode.parathread.clone()
+ } else {
+ full_rotation_mode.parachain.clone()
+ };
+
+ let collators = old_assigned.get_mut(&chain.para_id);
+ Self::keep_collator_subset(collators, mode, chain.max_collators, shuffle.as_mut());
+ }
+
// Ensure the first `min_orchestrator_collators` of orchestrator chain are invulnerables
+ // Invulnerables can be unassigned by `keep_collator_subset`, but here we will assign other
+ // invulnerables again. The downside is that the new invulnerables can be different.
Self::prioritize_invulnerables(&collators, orchestrator_chain, &mut old_assigned);
let new_assigned_chains =
@@ -142,6 +139,48 @@ where
Ok(new_assigned)
}
+ /// Keep a subset of collators instead of rotating all of them.
+ pub fn keep_collator_subset(
+ collators: Option<&mut Vec>,
+ full_rotation_mode: FullRotationMode,
+ max_collators: u32,
+ shuffle: Option<&mut TShuffle>,
+ ) where
+ TShuffle: FnMut(&mut Vec),
+ {
+ let collators = match collators {
+ Some(x) => x,
+ None => return,
+ };
+
+ let num_to_keep = match full_rotation_mode {
+ FullRotationMode::RotateAll => 0,
+ FullRotationMode::KeepAll => {
+ return;
+ }
+ FullRotationMode::KeepCollators { keep } => keep,
+ FullRotationMode::KeepPerbill { percentage: keep } => keep * max_collators,
+ };
+
+ if num_to_keep == 0 {
+ // Remove all
+ collators.clear();
+ return;
+ }
+
+ // Less than N collators, no need to shuffle
+ if collators.len() as u32 <= num_to_keep {
+ return;
+ }
+
+ // Shuffle and keep first N
+ if let Some(shuffle) = shuffle {
+ shuffle(collators);
+ }
+
+ collators.truncate(num_to_keep as usize);
+ }
+
/// Select which container chains will be assigned collators and how many collators, but do not specify which
/// collator goes to which chain.
///
@@ -488,8 +527,12 @@ pub enum AssignmentError {
/// This can be a container chain, a parathread, or the orchestrator chain.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ChainNumCollators {
+ /// Para id.
pub para_id: ParaId,
+ /// Min collators.
pub min_collators: u32,
- // This will only be filled if all the other min have been reached
+ /// Max collators. This will only be filled if all the other chains have reached min_collators
pub max_collators: u32,
+ /// True if this a parathread. False means parachain or orchestrator.
+ pub parathread: bool,
}
diff --git a/pallets/collator-assignment/src/benchmarking.rs b/pallets/collator-assignment/src/benchmarking.rs
index 10b67e07e..fa3c54177 100644
--- a/pallets/collator-assignment/src/benchmarking.rs
+++ b/pallets/collator-assignment/src/benchmarking.rs
@@ -110,6 +110,7 @@ mod benchmarks {
random_seed,
full_rotation: false,
target_session: T::SessionIndex::from(1u32),
+ full_rotation_mode: FullRotationModes::keep_all(),
}
.into(),
);
diff --git a/pallets/collator-assignment/src/lib.rs b/pallets/collator-assignment/src/lib.rs
index 227b5e7b3..c8360cc43 100644
--- a/pallets/collator-assignment/src/lib.rs
+++ b/pallets/collator-assignment/src/lib.rs
@@ -54,7 +54,7 @@ use {
},
sp_std::{collections::btree_set::BTreeSet, fmt::Debug, prelude::*, vec},
tp_traits::{
- CollatorAssignmentTip, GetContainerChainAuthor, GetHostConfiguration,
+ CollatorAssignmentTip, FullRotationModes, GetContainerChainAuthor, GetHostConfiguration,
GetSessionContainerChains, ParaId, ParaIdAssignmentHooks, RemoveInvulnerables,
ShouldRotateAllCollators, Slot,
},
@@ -121,6 +121,7 @@ pub mod pallet {
random_seed: [u8; 32],
full_rotation: bool,
target_session: T::SessionIndex,
+ full_rotation_mode: FullRotationModes,
},
}
@@ -334,6 +335,7 @@ pub mod pallet {
para_id: T::SelfParaId::get(),
min_collators: 0u32,
max_collators: 0u32,
+ parathread: false,
}
} else {
ChainNumCollators {
@@ -344,6 +346,7 @@ pub mod pallet {
max_collators: T::HostConfiguration::max_collators_for_orchestrator(
target_session_index,
),
+ parathread: false,
}
};
@@ -360,6 +363,7 @@ pub mod pallet {
para_id: *para_id,
min_collators: collators_per_container,
max_collators: collators_per_container,
+ parathread: false,
});
}
for para_id in ¶threads {
@@ -367,6 +371,7 @@ pub mod pallet {
para_id: *para_id,
min_collators: collators_per_parathread,
max_collators: collators_per_parathread,
+ parathread: true,
});
}
@@ -394,47 +399,44 @@ pub mod pallet {
// We assign new collators
// we use the config scheduled at the target_session_index
- let new_assigned =
- if T::ShouldRotateAllCollators::should_rotate_all_collators(target_session_index) {
- log::debug!(
- "Collator assignment: rotating collators. Session {:?}, Seed: {:?}",
- current_session_index.encode(),
- random_seed
- );
+ let full_rotation =
+ T::ShouldRotateAllCollators::should_rotate_all_collators(target_session_index);
+ if full_rotation {
+ log::info!(
+ "Collator assignment: rotating collators. Session {:?}, Seed: {:?}",
+ current_session_index.encode(),
+ random_seed
+ );
+ } else {
+ log::info!(
+ "Collator assignment: keep old assigned. Session {:?}, Seed: {:?}",
+ current_session_index.encode(),
+ random_seed
+ );
+ }
- Self::deposit_event(Event::NewPendingAssignment {
- random_seed,
- full_rotation: true,
- target_session: target_session_index,
- });
-
- Assignment::::assign_collators_rotate_all(
- collators,
- orchestrator_chain,
- chains,
- shuffle_collators,
- )
- } else {
- log::debug!(
- "Collator assignment: keep old assigned. Session {:?}, Seed: {:?}",
- current_session_index.encode(),
- random_seed
- );
+ let full_rotation_mode = if full_rotation {
+ T::HostConfiguration::full_rotation_mode(target_session_index)
+ } else {
+ // On sessions where there is no rotation, we try to keep all collators assigned to the same chains
+ FullRotationModes::keep_all()
+ };
- Self::deposit_event(Event::NewPendingAssignment {
- random_seed,
- full_rotation: false,
- target_session: target_session_index,
- });
-
- Assignment::::assign_collators_always_keep_old(
- collators,
- orchestrator_chain,
- chains,
- old_assigned.clone(),
- shuffle_collators,
- )
- };
+ Self::deposit_event(Event::NewPendingAssignment {
+ random_seed,
+ full_rotation,
+ target_session: target_session_index,
+ full_rotation_mode: full_rotation_mode.clone(),
+ });
+
+ let new_assigned = Assignment::::assign_collators_always_keep_old(
+ collators,
+ orchestrator_chain,
+ chains,
+ old_assigned.clone(),
+ shuffle_collators,
+ full_rotation_mode,
+ );
let mut new_assigned = match new_assigned {
Ok(x) => x,
diff --git a/pallets/collator-assignment/src/mock.rs b/pallets/collator-assignment/src/mock.rs
index f59e3ae4c..04136d76e 100644
--- a/pallets/collator-assignment/src/mock.rs
+++ b/pallets/collator-assignment/src/mock.rs
@@ -19,6 +19,7 @@ use {
self as pallet_collator_assignment, pallet::CollatorContainerChain,
CoreAllocationConfiguration, GetRandomnessForNextBlock, RotateCollatorsEveryNSessions,
},
+ dp_collator_assignment::AssignedCollators,
frame_support::{
parameter_types,
traits::{ConstBool, ConstU16, ConstU64, Hooks},
@@ -31,9 +32,12 @@ use {
traits::{BlakeTwo256, IdentityLookup},
BuildStorage, Perbill,
},
- sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet},
+ sp_std::{
+ collections::{btree_map::BTreeMap, btree_set::BTreeSet},
+ ops::Range,
+ },
tp_traits::{
- CollatorAssignmentTip, ParaId, ParaIdAssignmentHooks, ParathreadParams,
+ CollatorAssignmentTip, FullRotationModes, ParaId, ParaIdAssignmentHooks, ParathreadParams,
RemoveInvulnerables, SessionContainerChains,
},
tracing_subscriber::{layer::SubscriberExt, FmtSubscriber},
@@ -150,6 +154,7 @@ pub struct Mocks {
pub chains_that_are_tipping: Vec,
// None means 5
pub full_rotation_period: Option,
+ pub full_rotation_mode: FullRotationModes,
pub apply_tip: bool,
pub assignment_hook_errors: bool,
}
@@ -170,6 +175,7 @@ impl Default for Mocks {
random_seed: Default::default(),
chains_that_are_tipping: vec![1003.into(), 1004.into()],
full_rotation_period: Default::default(),
+ full_rotation_mode: Default::default(),
apply_tip: Default::default(),
assignment_hook_errors: Default::default(),
}
@@ -215,6 +221,10 @@ impl pallet_collator_assignment::GetHostConfiguration for HostConfiguration
None
}
+ fn full_rotation_mode(_session_index: u32) -> FullRotationModes {
+ MockData::mock().full_rotation_mode
+ }
+
#[cfg(feature = "runtime-benchmarks")]
fn set_host_configuration(_session_index: u32) {
MockData::mutate(|mocks| {
@@ -449,3 +459,88 @@ pub fn silence_logs R, R>(f: F) -> R {
tracing::subscriber::with_default(no_logging_subscriber, f)
}
+
+pub fn collect_assignment_history(
+ end_block: u64,
+ mut update_fn: F,
+) -> BTreeMap>
+where
+ F: FnMut(u64),
+{
+ let mut assignment_history = BTreeMap::new();
+
+ let start_block = System::block_number();
+
+ for n in start_block..end_block {
+ let block_number = System::block_number();
+
+ update_fn(block_number);
+
+ let session_len = 5;
+ let session_index = (block_number / session_len) as u32;
+ assignment_history.insert(session_index, CollatorContainerChain::::get());
+
+ run_to_block(n);
+ }
+
+ assignment_history
+}
+
+/// Given an assignment history (map of session => AssignedCollators), computes the maximum number
+/// of collators that rotate from one session to the next one.
+///
+/// Examples:
+/// * If collators never rotate, this will return 0
+/// * If all collators always rotate, this will return the number of collators
+///
+/// Because of randomness, if is possible that the returned value if lower than expected, e.g. we
+/// expect all collators to rotate but not all of them do.
+pub fn compute_max_rotation(
+ assignment_history: &BTreeMap>,
+ extract_collators_fn: F,
+) -> usize
+where
+ F: Fn(&AssignedCollators) -> BTreeMap>,
+{
+ let mut max_rotation = 0;
+ let mut prev_assignments: BTreeMap> = BTreeMap::new();
+
+ if let Some(first_assignment) = assignment_history.values().next() {
+ prev_assignments = extract_collators_fn(first_assignment);
+ }
+
+ for assignment in assignment_history.values().skip(1) {
+ let current_assignments = extract_collators_fn(assignment);
+
+ for (chain_id, current_collators) in ¤t_assignments {
+ let empty_btreeset = BTreeSet::new();
+ let prev_collators = prev_assignments.get(chain_id).unwrap_or(&empty_btreeset);
+ let new_collators = current_collators.difference(prev_collators).count();
+ max_rotation = max_rotation.max(new_collators);
+ }
+
+ prev_assignments = current_assignments;
+ }
+
+ max_rotation
+}
+
+/// Get the collator assignment for all parachains or all parathreads.
+/// Use range 2000..3000 for parachains and 3000..4000 for parathreads.
+pub fn extract_assignments_in_range(
+ assignment: &AssignedCollators,
+ range: Range,
+) -> BTreeMap> {
+ assignment
+ .container_chains
+ .iter()
+ .filter_map(|(chain_id, collators)| {
+ let id = u32::from(*chain_id);
+ if range.contains(&id) {
+ Some((id, collators.iter().cloned().collect()))
+ } else {
+ None
+ }
+ })
+ .collect()
+}
diff --git a/pallets/collator-assignment/src/tests.rs b/pallets/collator-assignment/src/tests.rs
index e7dd4aa2f..7711c2750 100644
--- a/pallets/collator-assignment/src/tests.rs
+++ b/pallets/collator-assignment/src/tests.rs
@@ -17,10 +17,13 @@
use {
crate::{mock::*, CollatorContainerChain, Event, PendingCollatorContainerChain},
dp_collator_assignment::AssignedCollators,
+ sp_runtime::Perbill,
std::collections::BTreeMap,
+ tp_traits::{FullRotationMode, FullRotationModes},
};
mod assign_full;
+mod keep_collator_subset;
mod prioritize_invulnerables;
mod select_chains;
mod with_core_config;
@@ -1078,6 +1081,7 @@ fn rotation_events() {
random_seed: [0; 32],
full_rotation: false,
target_session: 1,
+ full_rotation_mode: FullRotationModes::keep_all(),
}
.into(),
);
@@ -1091,6 +1095,7 @@ fn rotation_events() {
random_seed: [0; 32],
full_rotation: false,
target_session: (i / 5) as u32 + 1,
+ full_rotation_mode: FullRotationModes::keep_all(),
}
.into(),
);
@@ -1122,6 +1127,7 @@ fn rotation_events() {
random_seed: [1; 32],
full_rotation: false,
target_session: (i / 5) as u32 + 1,
+ full_rotation_mode: FullRotationModes::keep_all(),
}
.into(),
);
@@ -1132,6 +1138,7 @@ fn rotation_events() {
random_seed: [1; 32],
full_rotation: true,
target_session: (i / 5) as u32 + 1,
+ full_rotation_mode: FullRotationModes::default(),
}
.into(),
);
@@ -1370,3 +1377,63 @@ fn assign_collators_truncates_before_shuffling() {
);
});
}
+
+#[test]
+fn keep_subset_uses_correct_config() {
+ new_test_ext().execute_with(|| {
+ run_to_block(1);
+
+ MockData::mutate(|m| {
+ m.collators_per_container = 2;
+ m.collators_per_parathread = 2;
+ m.min_orchestrator_chain_collators = 2;
+ m.max_orchestrator_chain_collators = 5;
+ // Add randomness to test shuffle
+ m.random_seed = [1; 32];
+ // Rotate every session
+ m.full_rotation_period = Some(1);
+ m.full_rotation_mode = FullRotationModes {
+ orchestrator: FullRotationMode::RotateAll,
+ parachain: FullRotationMode::KeepCollators { keep: 2 },
+ parathread: FullRotationMode::KeepPerbill {
+ percentage: Perbill::from_percent(50),
+ },
+ };
+
+ m.collators = (1..50).collect();
+ m.container_chains = (2001..2010).collect();
+ m.parathreads = (3001..3010).collect();
+ });
+ assert_eq!(assigned_collators(), initial_collators(),);
+ run_to_block(11);
+
+ // Collect assignment history
+ let assignment_history = collect_assignment_history(50, |n| {
+ // Update randomness for each block
+ MockData::mutate(|m| {
+ m.random_seed = [n as u8; 32];
+ });
+ });
+
+ // Check: there is at least one session in which all orchestrator collators rotate
+ let max_orchestrator_rotate = compute_max_rotation(&assignment_history, |assignment| {
+ let collators = assignment.orchestrator_chain.clone();
+ let mut map = BTreeMap::new();
+ map.insert(1000, collators.into_iter().collect());
+ map
+ });
+ assert_eq!(max_orchestrator_rotate, 5);
+
+ // Check: parachain collators never rotate
+ let max_parachain_rotate = compute_max_rotation(&assignment_history, |assignment| {
+ extract_assignments_in_range(assignment, 2000..3000)
+ });
+ assert_eq!(max_parachain_rotate, 0);
+
+ // Check: at most one collator will rotate out of each parathread, never all 2
+ let max_parathread_rotate = compute_max_rotation(&assignment_history, |assignment| {
+ extract_assignments_in_range(assignment, 3000..4000)
+ });
+ assert_eq!(max_parathread_rotate, 1);
+ });
+}
diff --git a/pallets/collator-assignment/src/tests/keep_collator_subset.rs b/pallets/collator-assignment/src/tests/keep_collator_subset.rs
new file mode 100644
index 000000000..9f7fd4a29
--- /dev/null
+++ b/pallets/collator-assignment/src/tests/keep_collator_subset.rs
@@ -0,0 +1,163 @@
+// Copyright (C) Moondance Labs Ltd.
+// This file is part of Tanssi.
+
+// Tanssi is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Tanssi is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Tanssi. If not, see
+
+use {
+ crate::{assignment::Assignment, tests::Test},
+ sp_runtime::Perbill,
+ tp_traits::FullRotationMode,
+};
+
+#[test]
+fn keep_subset_keep_50_percent() {
+ let mut collators = vec![1, 2, 3, 4, 5];
+ let mut shuffle_count = 0;
+
+ let mut shuffle = |_collators: &mut Vec| {
+ shuffle_count += 1;
+ };
+
+ let full_rotation_mode = FullRotationMode::KeepPerbill {
+ percentage: Perbill::from_percent(50),
+ };
+ let max_collators = 5;
+ Assignment::::keep_collator_subset(
+ Some(&mut collators),
+ full_rotation_mode,
+ max_collators,
+ Some(&mut shuffle),
+ );
+
+ // 50% of 5 is 2
+ assert_eq!(collators.len(), 2);
+ assert_eq!(shuffle_count, 1);
+}
+
+#[test]
+fn keep_subset_keep_2_collators() {
+ let mut collators = vec![1, 2, 3, 4, 5];
+ let mut shuffle_count = 0;
+
+ let mut shuffle = |_collators: &mut Vec| {
+ shuffle_count += 1;
+ };
+
+ let full_rotation_mode = FullRotationMode::KeepCollators { keep: 2 };
+ let max_collators = 5;
+ Assignment::::keep_collator_subset(
+ Some(&mut collators),
+ full_rotation_mode,
+ max_collators,
+ Some(&mut shuffle),
+ );
+
+ assert_eq!(collators.len(), 2);
+ assert_eq!(shuffle_count, 1);
+}
+
+#[test]
+fn keep_subset_rotate_all() {
+ let mut collators = vec![1, 2, 3, 4, 5];
+ let mut shuffle_count = 0;
+
+ let mut shuffle = |_collators: &mut Vec| {
+ shuffle_count += 1;
+ };
+
+ let full_rotation_mode = FullRotationMode::RotateAll;
+ let max_collators = 5;
+ Assignment::::keep_collator_subset(
+ Some(&mut collators),
+ full_rotation_mode,
+ max_collators,
+ Some(&mut shuffle),
+ );
+
+ assert_eq!(collators.len(), 0);
+ assert_eq!(shuffle_count, 0);
+}
+
+#[test]
+fn keep_subset_keep_all() {
+ let mut collators = vec![1, 2, 3, 4, 5];
+ let mut shuffle_count = 0;
+
+ let mut shuffle = |_collators: &mut Vec| {
+ shuffle_count += 1;
+ };
+
+ let full_rotation_mode = FullRotationMode::KeepAll;
+ let max_collators = 5;
+ Assignment::::keep_collator_subset(
+ Some(&mut collators),
+ full_rotation_mode,
+ max_collators,
+ Some(&mut shuffle),
+ );
+
+ assert_eq!(collators.len(), 5);
+ assert_eq!(shuffle_count, 0);
+}
+
+#[test]
+fn keep_subset_empty_collators() {
+ let mut collators = vec![];
+ let mut shuffle_count = 0;
+
+ let mut shuffle = |_collators: &mut Vec| {
+ shuffle_count += 1;
+ };
+
+ let full_rotation_mode = FullRotationMode::KeepCollators { keep: 2 };
+ let max_collators = 5;
+ Assignment::::keep_collator_subset(
+ Some(&mut collators),
+ full_rotation_mode.clone(),
+ max_collators,
+ Some(&mut shuffle),
+ );
+ assert_eq!(collators.len(), 0);
+
+ // Calling this with None does not panic
+ Assignment::::keep_collator_subset(
+ None,
+ full_rotation_mode,
+ max_collators,
+ Some(&mut shuffle),
+ );
+ assert_eq!(shuffle_count, 0);
+}
+
+#[test]
+fn keep_subset_keep_more_than_max() {
+ // Trying to keep more collators than the max keeps all of them and does not panic
+ let mut collators = vec![1, 2, 3, 4, 5];
+ let mut shuffle_count = 0;
+
+ let mut shuffle = |_collators: &mut Vec| {
+ shuffle_count += 1;
+ };
+
+ let full_rotation_mode = FullRotationMode::KeepCollators { keep: 200 };
+ let max_collators = 5;
+ Assignment::::keep_collator_subset(
+ Some(&mut collators),
+ full_rotation_mode.clone(),
+ max_collators,
+ Some(&mut shuffle),
+ );
+ assert_eq!(collators.len(), 5);
+ assert_eq!(shuffle_count, 0);
+}
diff --git a/pallets/collator-assignment/src/tests/prioritize_invulnerables.rs b/pallets/collator-assignment/src/tests/prioritize_invulnerables.rs
index 4e13c69d4..ad009b440 100644
--- a/pallets/collator-assignment/src/tests/prioritize_invulnerables.rs
+++ b/pallets/collator-assignment/src/tests/prioritize_invulnerables.rs
@@ -29,6 +29,7 @@ fn invulnerable_priority_0_collators() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
};
let mut old_assigned = BTreeMap::new();
@@ -48,6 +49,7 @@ fn invulnerable_priority_0_invulnerables() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
};
let mut old_assigned = BTreeMap::from_iter(vec![(1000.into(), vec![1, 2])]);
@@ -67,6 +69,7 @@ fn invulnerable_priority_1_invulnerable_orchestrator() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
};
let mut old_assigned = BTreeMap::from_iter(vec![(1000.into(), vec![101])]);
@@ -86,6 +89,7 @@ fn invulnerable_priority_1_invulnerable_not_assigned() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
};
let mut old_assigned = BTreeMap::new();
@@ -105,6 +109,7 @@ fn invulnerable_priority_1_invulnerable_assigned_to_another_chain() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
};
let mut old_assigned =
BTreeMap::from_iter(vec![(1000.into(), vec![]), (2000.into(), vec![101])]);
@@ -125,6 +130,7 @@ fn bug_same_invulnerable_selected_twice() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
};
let mut old_assigned = BTreeMap::from_iter(vec![(1000.into(), vec![100])]);
@@ -148,16 +154,19 @@ fn bug_not_using_assigned_invulnerables() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
},
ChainNumCollators {
para_id: 2000.into(),
min_collators: 2,
max_collators: 2,
+ parathread: false,
},
ChainNumCollators {
para_id: 2001.into(),
min_collators: 2,
max_collators: 2,
+ parathread: false,
},
];
let orchestrator_chain = container_chains[0];
diff --git a/pallets/collator-assignment/src/tests/select_chains.rs b/pallets/collator-assignment/src/tests/select_chains.rs
index 24487dbc5..e2212fd39 100644
--- a/pallets/collator-assignment/src/tests/select_chains.rs
+++ b/pallets/collator-assignment/src/tests/select_chains.rs
@@ -27,16 +27,19 @@ fn select_chains_not_enough_to_reach_min_container() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
},
ChainNumCollators {
para_id: 2000.into(),
min_collators: 10,
max_collators: 10,
+ parathread: false,
},
ChainNumCollators {
para_id: 2001.into(),
min_collators: 10,
max_collators: 10,
+ parathread: false,
},
];
let new_assigned = Assignment::::select_chains_with_collators(10, &container_chains);
@@ -50,6 +53,7 @@ fn select_chains_not_enough_to_reach_min_orchestrator() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
}];
let new_assigned = Assignment::::select_chains_with_collators(1, &container_chains);
assert_eq!(new_assigned, vec![(1000.into(), 1),]);
@@ -64,16 +68,19 @@ fn select_chains_not_enough_for_all_min() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
},
ChainNumCollators {
para_id: 2000.into(),
min_collators: 2,
max_collators: 2,
+ parathread: false,
},
ChainNumCollators {
para_id: 2001.into(),
min_collators: 2,
max_collators: 2,
+ parathread: false,
},
];
let new_assigned = Assignment::::select_chains_with_collators(5, &container_chains);
@@ -89,16 +96,19 @@ fn select_chains_not_enough_for_all_max() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
},
ChainNumCollators {
para_id: 2000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
},
ChainNumCollators {
para_id: 2001.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
},
];
let new_assigned = Assignment::::select_chains_with_collators(7, &container_chains);
@@ -131,16 +141,19 @@ fn select_chains_more_than_max() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
},
ChainNumCollators {
para_id: 2000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
},
ChainNumCollators {
para_id: 2001.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
},
];
let new_assigned = Assignment::::select_chains_with_collators(20, &container_chains);
@@ -159,16 +172,19 @@ fn select_chains_not_enough_to_reach_min_container_but_enough_for_parathread() {
para_id: 1000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
},
ChainNumCollators {
para_id: 2000.into(),
min_collators: 2,
max_collators: 2,
+ parathread: false,
},
ChainNumCollators {
para_id: 3000.into(),
min_collators: 1,
max_collators: 1,
+ parathread: true,
},
];
let new_assigned = Assignment::::select_chains_with_collators(3, &container_chains);
@@ -183,16 +199,19 @@ fn select_chains_solochain() {
para_id: 1000.into(),
min_collators: 0,
max_collators: 0,
+ parathread: false,
},
ChainNumCollators {
para_id: 2000.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
},
ChainNumCollators {
para_id: 2001.into(),
min_collators: 2,
max_collators: 5,
+ parathread: false,
},
];
let new_assigned = Assignment::::select_chains_with_collators(5, &container_chains);
@@ -209,6 +228,7 @@ fn select_chains_solochain_zero_collators() {
para_id: 1000.into(),
min_collators: 0,
max_collators: 0,
+ parathread: false,
}];
let new_assigned = Assignment::::select_chains_with_collators(0, &container_chains);
assert_eq!(new_assigned, vec![(1000.into(), 0)]);
diff --git a/pallets/collator-assignment/src/tests/with_core_config.rs b/pallets/collator-assignment/src/tests/with_core_config.rs
index 59f73afb2..6b07bfb19 100644
--- a/pallets/collator-assignment/src/tests/with_core_config.rs
+++ b/pallets/collator-assignment/src/tests/with_core_config.rs
@@ -25,6 +25,7 @@ fn create_blank_chain_num_collator(id: u32) -> ChainNumCollators {
para_id: ParaId::new(id),
min_collators: 0,
max_collators: 0,
+ parathread: false,
}
}
diff --git a/pallets/configuration/src/lib.rs b/pallets/configuration/src/lib.rs
index 6e79da558..8b25a4ff8 100644
--- a/pallets/configuration/src/lib.rs
+++ b/pallets/configuration/src/lib.rs
@@ -42,6 +42,7 @@ pub use weights::WeightInfo;
mod benchmarks;
pub use pallet::*;
+use tp_traits::FullRotationModes;
use {
frame_support::pallet_prelude::*,
frame_system::pallet_prelude::*,
@@ -88,6 +89,8 @@ pub struct HostConfiguration {
pub target_container_chain_fullness: Perbill,
/// Maximum number of cores that can be allocated to parachains (only applicable for solo chain)
pub max_parachain_cores_percentage: Option,
+ /// Full rotation mode
+ pub full_rotation_mode: FullRotationModes,
}
impl Default for HostConfiguration {
@@ -102,6 +105,7 @@ impl Default for HostConfiguration {
parathreads_per_collator: 1,
target_container_chain_fullness: Perbill::from_percent(80),
max_parachain_cores_percentage: None,
+ full_rotation_mode: Default::default(),
}
}
}
@@ -161,7 +165,7 @@ impl HostConfiguration {
#[frame_support::pallet]
pub mod pallet {
- use tp_traits::GetHostConfiguration;
+ use tp_traits::{FullRotationMode, GetHostConfiguration};
use super::*;
@@ -195,7 +199,7 @@ pub mod pallet {
/// The active configuration for the current session.
#[pallet::storage]
- pub(crate) type ActiveConfig = StorageValue<_, HostConfiguration, ValueQuery>;
+ pub type ActiveConfig = StorageValue<_, HostConfiguration, ValueQuery>;
/// Pending configuration changes.
///
@@ -209,7 +213,7 @@ pub mod pallet {
// since it can have at most 2 items anyway. But the upstream pallet doesn't do that so low
// priority.
#[pallet::unbounded]
- pub(crate) type PendingConfigs =
+ pub type PendingConfigs =
StorageValue<_, Vec<(T::SessionIndex, HostConfiguration)>, ValueQuery>;
/// If this is set, then the configuration setters will bypass the consistency checks. This
@@ -356,6 +360,31 @@ pub mod pallet {
})
}
+ #[pallet::call_index(9)]
+ #[pallet::weight((
+ T::WeightInfo::set_config_with_u32(),
+ DispatchClass::Operational,
+ ))]
+ pub fn set_full_rotation_mode(
+ origin: OriginFor,
+ orchestrator: Option,
+ parachain: Option,
+ parathread: Option,
+ ) -> DispatchResult {
+ ensure_root(origin)?;
+ Self::schedule_config_update(|config| {
+ if let Some(orchestrator) = orchestrator {
+ config.full_rotation_mode.orchestrator = orchestrator;
+ }
+ if let Some(parachain) = parachain {
+ config.full_rotation_mode.parachain = parachain;
+ }
+ if let Some(parathread) = parathread {
+ config.full_rotation_mode.parathread = parathread;
+ }
+ })
+ }
+
/// Setting this to true will disable consistency checks for the configuration setters.
/// Use with caution.
#[pallet::call_index(44)]
@@ -599,5 +628,9 @@ pub mod pallet {
fn max_parachain_cores_percentage(session_index: T::SessionIndex) -> Option {
Self::config_at_session(session_index).max_parachain_cores_percentage
}
+
+ fn full_rotation_mode(session_index: T::SessionIndex) -> FullRotationModes {
+ Self::config_at_session(session_index).full_rotation_mode
+ }
}
}
diff --git a/primitives/traits/src/lib.rs b/primitives/traits/src/lib.rs
index 3be563c6d..d0c8315dd 100644
--- a/primitives/traits/src/lib.rs
+++ b/primitives/traits/src/lib.rs
@@ -239,6 +239,7 @@ pub trait GetHostConfiguration {
fn collators_per_parathread(session_index: SessionIndex) -> u32;
fn target_container_chain_fullness(session_index: SessionIndex) -> Perbill;
fn max_parachain_cores_percentage(session_index: SessionIndex) -> Option;
+ fn full_rotation_mode(session_index: SessionIndex) -> FullRotationModes;
#[cfg(feature = "runtime-benchmarks")]
fn set_host_configuration(_session_index: SessionIndex) {}
}
@@ -515,3 +516,63 @@ impl OnEraEnd for Tuple {
for_tuples!( #( Tuple::on_era_end(era_index); )* );
}
}
+
+/// Strategy to use when rotating collators. Default: rotate all of them. Allows to rotate only a random subset.
+#[derive(
+ Clone,
+ Debug,
+ Default,
+ Encode,
+ Decode,
+ scale_info::TypeInfo,
+ PartialEq,
+ Eq,
+ Serialize,
+ Deserialize,
+ MaxEncodedLen,
+)]
+pub enum FullRotationMode {
+ #[default]
+ RotateAll,
+ KeepAll,
+ /// Keep this many collators
+ KeepCollators {
+ keep: u32,
+ },
+ /// Keep a ratio of collators wrt to max collators.
+ /// If max collators changes, the number of collators kept also changes.
+ KeepPerbill {
+ percentage: Perbill,
+ },
+}
+
+/// Allow to set a different [FullRotationMode] for each kind of chain. Default: rotate all.
+#[derive(
+ Clone,
+ Debug,
+ Default,
+ Encode,
+ Decode,
+ scale_info::TypeInfo,
+ PartialEq,
+ Eq,
+ Serialize,
+ Deserialize,
+ MaxEncodedLen,
+)]
+pub struct FullRotationModes {
+ pub orchestrator: FullRotationMode,
+ pub parachain: FullRotationMode,
+ pub parathread: FullRotationMode,
+}
+
+impl FullRotationModes {
+ /// Keep all collators assigned to their current chain if possible. This is equivalent to disabling rotation.
+ pub fn keep_all() -> Self {
+ Self {
+ orchestrator: FullRotationMode::KeepAll,
+ parachain: FullRotationMode::KeepAll,
+ parathread: FullRotationMode::KeepAll,
+ }
+ }
+}
diff --git a/runtime/common/src/migrations.rs b/runtime/common/src/migrations.rs
index 1eb5138b1..940be3532 100644
--- a/runtime/common/src/migrations.rs
+++ b/runtime/common/src/migrations.rs
@@ -34,13 +34,10 @@
//! This module acts as a registry where each migration is defined. Each migration should implement
//! the "Migration" trait declared in the pallet-migrations crate.
-#[cfg(feature = "try-runtime")]
-use frame_support::ensure;
-use frame_support::migration::move_pallet;
use {
cumulus_primitives_core::ParaId,
frame_support::{
- migration::{clear_storage_prefix, storage_key_iter},
+ migration::{clear_storage_prefix, move_pallet, storage_key_iter},
pallet_prelude::GetStorageVersion,
traits::{
fungible::MutateHold, OnRuntimeUpgrade, PalletInfoAccess, ReservableCurrency,
@@ -57,6 +54,8 @@ use {
sp_runtime::Perbill,
sp_std::{collections::btree_set::BTreeSet, marker::PhantomData, prelude::*},
};
+#[cfg(feature = "try-runtime")]
+use {frame_support::ensure, parity_scale_codec::DecodeAll};
#[derive(
Default,
@@ -67,7 +66,7 @@ use {
sp_core::RuntimeDebug,
scale_info::TypeInfo,
)]
-pub struct HostConfigurationV2 {
+pub struct HostConfigurationV3 {
pub max_collators: u32,
pub min_orchestrator_collators: u32,
pub max_orchestrator_collators: u32,
@@ -76,28 +75,26 @@ pub struct HostConfigurationV2 {
pub collators_per_parathread: u32,
pub parathreads_per_collator: u32,
pub target_container_chain_fullness: Perbill,
+ pub max_parachain_cores_percentage: Option,
}
-pub struct MigrateConfigurationAddParachainPercentage(pub PhantomData);
-impl Migration for MigrateConfigurationAddParachainPercentage
+pub struct MigrateConfigurationAddFullRotationMode(pub PhantomData);
+impl Migration for MigrateConfigurationAddFullRotationMode
where
T: pallet_configuration::Config,
{
fn friendly_name(&self) -> &str {
- "TM_MigrateConfigurationAddParachainPercentage"
+ "TM_MigrateConfigurationAddFullRotationMode"
}
fn migrate(&self, _available_weight: Weight) -> Weight {
- const CONFIGURATION_ACTIVE_CONFIG_KEY: &[u8] =
- &hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385");
- const CONFIGURATION_PENDING_CONFIGS_KEY: &[u8] =
- &hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22d53b4123b2e186e07fb7bad5dda5f55c0");
let default_config = HostConfiguration::default();
// Modify active config
- let old_config: HostConfigurationV2 =
- frame_support::storage::unhashed::get(CONFIGURATION_ACTIVE_CONFIG_KEY)
- .expect("configuration.activeConfig should have value");
+ let old_config: HostConfigurationV3 = frame_support::storage::unhashed::get(
+ &pallet_configuration::ActiveConfig::::hashed_key(),
+ )
+ .expect("configuration.activeConfig should have value");
let new_config = HostConfiguration {
max_collators: old_config.max_collators,
min_orchestrator_collators: old_config.min_orchestrator_collators,
@@ -107,14 +104,20 @@ where
collators_per_parathread: old_config.collators_per_parathread,
parathreads_per_collator: old_config.parathreads_per_collator,
target_container_chain_fullness: old_config.target_container_chain_fullness,
- max_parachain_cores_percentage: default_config.max_parachain_cores_percentage,
+ max_parachain_cores_percentage: old_config.max_parachain_cores_percentage,
+ full_rotation_mode: default_config.full_rotation_mode.clone(),
};
- frame_support::storage::unhashed::put(CONFIGURATION_ACTIVE_CONFIG_KEY, &new_config);
+ frame_support::storage::unhashed::put(
+ &pallet_configuration::ActiveConfig::::hashed_key(),
+ &new_config,
+ );
// Modify pending configs, if any
- let old_pending_configs: Vec<(u32, HostConfigurationV2)> =
- frame_support::storage::unhashed::get(CONFIGURATION_PENDING_CONFIGS_KEY)
- .unwrap_or_default();
+ let old_pending_configs: Vec<(u32, HostConfigurationV3)> =
+ frame_support::storage::unhashed::get(
+ &pallet_configuration::PendingConfigs::::hashed_key(),
+ )
+ .unwrap_or_default();
let mut new_pending_configs: Vec<(u32, HostConfiguration)> = vec![];
for (session_index, old_config) in old_pending_configs {
@@ -127,14 +130,15 @@ where
collators_per_parathread: old_config.collators_per_parathread,
parathreads_per_collator: old_config.parathreads_per_collator,
target_container_chain_fullness: old_config.target_container_chain_fullness,
- max_parachain_cores_percentage: default_config.max_parachain_cores_percentage,
+ max_parachain_cores_percentage: old_config.max_parachain_cores_percentage,
+ full_rotation_mode: default_config.full_rotation_mode.clone(),
};
new_pending_configs.push((session_index, new_config));
}
if !new_pending_configs.is_empty() {
frame_support::storage::unhashed::put(
- CONFIGURATION_PENDING_CONFIGS_KEY,
+ &pallet_configuration::PendingConfigs::::hashed_key(),
&new_pending_configs,
);
}
@@ -145,20 +149,19 @@ where
/// Run a standard pre-runtime test. This works the same way as in a normal runtime upgrade.
#[cfg(feature = "try-runtime")]
fn pre_upgrade(&self) -> Result, sp_runtime::DispatchError> {
- const CONFIGURATION_ACTIVE_CONFIG_KEY: &[u8] =
- &hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385");
+ let old_config_bytes = frame_support::storage::unhashed::get_raw(
+ &pallet_configuration::ActiveConfig::::hashed_key(),
+ )
+ .unwrap();
+ let old_config: Result =
+ DecodeAll::decode_all(&mut old_config_bytes.as_ref());
+ let new_config: Result =
+ DecodeAll::decode_all(&mut old_config_bytes.as_ref());
- let old_config_bytes =
- frame_support::storage::unhashed::get_raw(CONFIGURATION_ACTIVE_CONFIG_KEY)
- .expect("configuration.activeConfig should have value");
- // This works because there is no enum in the v2
- assert_eq!(
- old_config_bytes.len(),
- HostConfigurationV2::default().encoded_size()
- );
+ assert!(old_config.is_ok());
+ assert!(new_config.is_err());
- use parity_scale_codec::Encode;
- Ok((old_config_bytes).encode())
+ Ok(vec![])
}
/// Run a standard post-runtime test. This works the same way as in a normal runtime upgrade.
@@ -167,132 +170,24 @@ where
&self,
_number_of_invulnerables: Vec,
) -> Result<(), sp_runtime::DispatchError> {
- let new_config = pallet_configuration::Pallet::::config();
- let default_config = HostConfiguration::default();
- assert_eq!(
- new_config.max_parachain_cores_percentage,
- default_config.max_parachain_cores_percentage
- );
- Ok(())
- }
-}
-
-#[derive(
- Clone,
- parity_scale_codec::Encode,
- parity_scale_codec::Decode,
- PartialEq,
- sp_core::RuntimeDebug,
- scale_info::TypeInfo,
-)]
-struct HostConfigurationV1 {
- pub max_collators: u32,
- pub min_orchestrator_collators: u32,
- pub max_orchestrator_collators: u32,
- pub collators_per_container: u32,
- pub full_rotation_period: u32,
-}
-
-pub struct MigrateConfigurationParathreads(pub PhantomData);
-impl Migration for MigrateConfigurationParathreads
-where
- T: pallet_configuration::Config,
-{
- fn friendly_name(&self) -> &str {
- "TM_MigrateConfigurationParathreads"
- }
-
- fn migrate(&self, _available_weight: Weight) -> Weight {
- const CONFIGURATION_ACTIVE_CONFIG_KEY: &[u8] =
- &hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385");
- const CONFIGURATION_PENDING_CONFIGS_KEY: &[u8] =
- &hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22d53b4123b2e186e07fb7bad5dda5f55c0");
- let default_config = HostConfiguration::default();
-
- // Modify active config
- let old_config: HostConfigurationV1 =
- frame_support::storage::unhashed::get(CONFIGURATION_ACTIVE_CONFIG_KEY)
- .expect("configuration.activeConfig should have value");
- let new_config = HostConfiguration {
- max_collators: old_config.max_collators,
- min_orchestrator_collators: old_config.min_orchestrator_collators,
- max_orchestrator_collators: old_config.max_orchestrator_collators,
- collators_per_container: old_config.collators_per_container,
- full_rotation_period: old_config.full_rotation_period,
- collators_per_parathread: default_config.collators_per_parathread,
- parathreads_per_collator: default_config.parathreads_per_collator,
- target_container_chain_fullness: default_config.target_container_chain_fullness,
- max_parachain_cores_percentage: default_config.max_parachain_cores_percentage,
- };
- frame_support::storage::unhashed::put(CONFIGURATION_ACTIVE_CONFIG_KEY, &new_config);
-
- // Modify pending configs, if any
- let old_pending_configs: Vec<(u32, HostConfigurationV1)> =
- frame_support::storage::unhashed::get(CONFIGURATION_PENDING_CONFIGS_KEY)
- .unwrap_or_default();
- let mut new_pending_configs: Vec<(u32, HostConfiguration)> = vec![];
-
- for (session_index, old_config) in old_pending_configs {
- let new_config = HostConfiguration {
- max_collators: old_config.max_collators,
- min_orchestrator_collators: old_config.min_orchestrator_collators,
- max_orchestrator_collators: old_config.max_orchestrator_collators,
- collators_per_container: old_config.collators_per_container,
- full_rotation_period: old_config.full_rotation_period,
- collators_per_parathread: default_config.collators_per_parathread,
- parathreads_per_collator: default_config.parathreads_per_collator,
- target_container_chain_fullness: default_config.target_container_chain_fullness,
- max_parachain_cores_percentage: default_config.max_parachain_cores_percentage,
- };
- new_pending_configs.push((session_index, new_config));
- }
-
- if !new_pending_configs.is_empty() {
- frame_support::storage::unhashed::put(
- CONFIGURATION_PENDING_CONFIGS_KEY,
- &new_pending_configs,
- );
- }
-
- ::WeightInfo::set_config_with_u32()
- }
-
- /// Run a standard pre-runtime test. This works the same way as in a normal runtime upgrade.
- #[cfg(feature = "try-runtime")]
- fn pre_upgrade(&self) -> Result, sp_runtime::DispatchError> {
- const CONFIGURATION_ACTIVE_CONFIG_KEY: &[u8] =
- &hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385");
-
- let old_config_bytes =
- frame_support::storage::unhashed::get_raw(CONFIGURATION_ACTIVE_CONFIG_KEY)
- .expect("configuration.activeConfig should have value");
- assert_eq!(old_config_bytes.len(), 20);
+ let new_config_bytes = frame_support::storage::unhashed::get_raw(
+ &pallet_configuration::ActiveConfig::::hashed_key(),
+ )
+ .unwrap();
+ let old_config: Result =
+ DecodeAll::decode_all(&mut new_config_bytes.as_ref());
+ let new_config: Result =
+ DecodeAll::decode_all(&mut new_config_bytes.as_ref());
- use parity_scale_codec::Encode;
- Ok((old_config_bytes).encode())
- }
+ assert!(old_config.is_err());
+ assert!(new_config.is_ok());
- /// Run a standard post-runtime test. This works the same way as in a normal runtime upgrade.
- #[cfg(feature = "try-runtime")]
- fn post_upgrade(
- &self,
- _number_of_invulnerables: Vec,
- ) -> Result<(), sp_runtime::DispatchError> {
let new_config = pallet_configuration::Pallet::::config();
let default_config = HostConfiguration::default();
assert_eq!(
- new_config.collators_per_parathread,
- default_config.collators_per_parathread
+ new_config.full_rotation_mode,
+ default_config.full_rotation_mode
);
- assert_eq!(
- new_config.parathreads_per_collator,
- default_config.collators_per_parathread
- );
- assert_eq!(
- new_config.target_container_chain_fullness,
- default_config.target_container_chain_fullness
- );
-
Ok(())
}
}
@@ -961,19 +856,20 @@ where
//let migrate_services_payment =
// MigrateServicesPaymentAddCredits::(Default::default());
//let migrate_boot_nodes = MigrateBootNodes::(Default::default());
- let migrate_config_parathread_params =
- MigrateConfigurationParathreads::(Default::default());
-
- let migrate_add_collator_assignment_credits =
- MigrateServicesPaymentAddCollatorAssignmentCredits::(Default::default());
- let migrate_registrar_pending_verification =
- RegistrarPendingVerificationValueToMap::(Default::default());
- let migrate_registrar_manager =
- RegistrarParaManagerMigration::(Default::default());
- let migrate_data_preservers_assignments =
- DataPreserversAssignmentsMigration::(Default::default());
- let migrate_registrar_reserves = RegistrarReserveToHoldMigration::(Default::default());
- let migrate_config_max_parachain_percentage = MigrateConfigurationAddParachainPercentage::(Default::default());
+ //let migrate_config_parathread_params =
+ // MigrateConfigurationParathreads::(Default::default());
+
+ //let migrate_add_collator_assignment_credits =
+ // MigrateServicesPaymentAddCollatorAssignmentCredits::(Default::default());
+ //let migrate_registrar_pending_verification =
+ // RegistrarPendingVerificationValueToMap::(Default::default());
+ //let migrate_registrar_manager =
+ // RegistrarParaManagerMigration::(Default::default());
+ //let migrate_data_preservers_assignments =
+ // DataPreserversAssignmentsMigration::(Default::default());
+ //let migrate_registrar_reserves = RegistrarReserveToHoldMigration::(Default::default());
+ //let migrate_config_max_parachain_percentage = MigrateConfigurationAddParachainPercentage::(Default::default());
+ let migrate_config_full_rotation_mode = MigrateConfigurationAddFullRotationMode::(Default::default());
vec![
// Applied in runtime 400
@@ -981,13 +877,20 @@ where
// Applied in runtime 400
//Box::new(migrate_boot_nodes),
// Applied in runtime 400
- Box::new(migrate_config_parathread_params),
- Box::new(migrate_add_collator_assignment_credits),
- Box::new(migrate_registrar_pending_verification),
- Box::new(migrate_registrar_manager),
- Box::new(migrate_data_preservers_assignments),
- Box::new(migrate_registrar_reserves),
- Box::new(migrate_config_max_parachain_percentage),
+ //Box::new(migrate_config_parathread_params),
+ // Applied in runtime 500
+ //Box::new(migrate_add_collator_assignment_credits),
+ // Applied in runtime 700
+ //Box::new(migrate_registrar_pending_verification),
+ // Applied in runtime 700
+ //Box::new(migrate_registrar_manager),
+ // Applied in runtime 700
+ //Box::new(migrate_data_preservers_assignments),
+ // Applied in runtime 800
+ //Box::new(migrate_registrar_reserves),
+ // Applied in runtime 900
+ //Box::new(migrate_config_max_parachain_percentage),
+ Box::new(migrate_config_full_rotation_mode),
]
}
}
@@ -1027,23 +930,23 @@ where
// let migrate_hold_reason_runtime_enum =
// MigrateHoldReasonRuntimeEnum::(Default::default());
- let migrate_config_parathread_params =
- MigrateConfigurationParathreads::(Default::default());
- let migrate_add_collator_assignment_credits =
- MigrateServicesPaymentAddCollatorAssignmentCredits::(Default::default());
- let migrate_xcmp_queue_v4 = XcmpQueueMigrationV4::(Default::default());
- let migrate_registrar_pending_verification =
- RegistrarPendingVerificationValueToMap::(Default::default());
- let migrate_registrar_manager =
- RegistrarParaManagerMigration::(Default::default());
- let migrate_data_preservers_assignments =
- DataPreserversAssignmentsMigration::(Default::default());
-
- let migrate_pallet_xcm_v4 = MigrateToLatestXcmVersion::(Default::default());
- let foreign_asset_creator_migration =
- ForeignAssetCreatorMigration::(Default::default());
- let migrate_registrar_reserves = RegistrarReserveToHoldMigration::(Default::default());
- let migrate_config_max_parachain_percentage = MigrateConfigurationAddParachainPercentage::(Default::default());
+ //let migrate_config_parathread_params =
+ // MigrateConfigurationParathreads::(Default::default());
+ //let migrate_add_collator_assignment_credits =
+ // MigrateServicesPaymentAddCollatorAssignmentCredits::(Default::default());
+ //let migrate_xcmp_queue_v4 = XcmpQueueMigrationV4::(Default::default());
+ //let migrate_registrar_pending_verification =
+ // RegistrarPendingVerificationValueToMap::(Default::default());
+ //let migrate_registrar_manager =
+ // RegistrarParaManagerMigration::(Default::default());
+ //let migrate_data_preservers_assignments =
+ // DataPreserversAssignmentsMigration::(Default::default());
+
+ //let migrate_pallet_xcm_v4 = MigrateToLatestXcmVersion::(Default::default());
+ //let foreign_asset_creator_migration =
+ // ForeignAssetCreatorMigration::(Default::default());
+ //let migrate_registrar_reserves = RegistrarReserveToHoldMigration::(Default::default());
+ let migrate_config_full_rotation_mode = MigrateConfigurationAddFullRotationMode::(Default::default());
vec![
// Applied in runtime 200
@@ -1062,16 +965,27 @@ where
//Box::new(migrate_hold_reason_runtime_enum),
// Applied in runtime 400
//Box::new(migrate_boot_nodes),
- Box::new(migrate_config_parathread_params),
- Box::new(migrate_add_collator_assignment_credits),
- Box::new(migrate_xcmp_queue_v4),
- Box::new(migrate_registrar_pending_verification),
- Box::new(migrate_registrar_manager),
- Box::new(migrate_pallet_xcm_v4),
- Box::new(foreign_asset_creator_migration),
- Box::new(migrate_data_preservers_assignments),
- Box::new(migrate_registrar_reserves),
- Box::new(migrate_config_max_parachain_percentage)
+ // Applied in runtime 500
+ //Box::new(migrate_config_parathread_params),
+ // Applied in runtime 500
+ //Box::new(migrate_add_collator_assignment_credits),
+ // Applied in runtime 500
+ //Box::new(migrate_xcmp_queue_v4),
+ // Applied in runtime 700
+ //Box::new(migrate_registrar_pending_verification),
+ // Applied in runtime 700
+ //Box::new(migrate_registrar_manager),
+ // Applied in runtime 700
+ //Box::new(migrate_pallet_xcm_v4),
+ // Applied in runtime 700
+ //Box::new(foreign_asset_creator_migration),
+ // Applied in runtime 700
+ //Box::new(migrate_data_preservers_assignments),
+ // Applied in runtime 800
+ //Box::new(migrate_registrar_reserves),
+ // Applied in runtime 900
+ //Box::new(migrate_config_max_parachain_percentage),
+ Box::new(migrate_config_full_rotation_mode),
]
}
}
@@ -1116,6 +1030,7 @@ impl GetMigrations for DancelightMigrations
where
Runtime: frame_system::Config,
Runtime: pallet_external_validators::Config,
+ Runtime: pallet_configuration::Config,
Runtime: pallet_session::Config<
ValidatorId = ::ValidatorId,
>,
@@ -1124,10 +1039,13 @@ where
let migrate_mmr_leaf_pallet = MigrateMMRLeafPallet::(Default::default());
let migrate_external_validators =
ExternalValidatorsInitialMigration::(Default::default());
+ let migrate_config_full_rotation_mode =
+ MigrateConfigurationAddFullRotationMode::(Default::default());
vec![
Box::new(migrate_mmr_leaf_pallet),
Box::new(migrate_external_validators),
+ Box::new(migrate_config_full_rotation_mode),
]
}
}
diff --git a/runtime/dancebox/src/tests/integration_test.rs b/runtime/dancebox/src/tests/integration_test.rs
index 4083a6963..64e43b3dc 100644
--- a/runtime/dancebox/src/tests/integration_test.rs
+++ b/runtime/dancebox/src/tests/integration_test.rs
@@ -60,8 +60,7 @@ use {
},
std::marker::PhantomData,
tanssi_runtime_common::migrations::{
- ForeignAssetCreatorMigration, HostConfigurationV2,
- MigrateConfigurationAddParachainPercentage, MigrateConfigurationParathreads,
+ ForeignAssetCreatorMigration, HostConfigurationV3, MigrateConfigurationAddFullRotationMode,
MigrateServicesPaymentAddCollatorAssignmentCredits, RegistrarPendingVerificationValueToMap,
},
test_relay_sproof_builder::{HeaderAs, ParaHeaderSproofBuilder, ParaHeaderSproofBuilderItem},
@@ -3898,7 +3897,7 @@ fn test_reward_to_invulnerable_with_key_change() {
}
#[test]
-fn test_migration_config_add_parachain_percentage() {
+fn test_migration_config_add_full_rotation_mode() {
ExtBuilder::default().build().execute_with(|| {
const CONFIGURATION_ACTIVE_CONFIG_KEY: &[u8] =
&hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385");
@@ -3908,7 +3907,7 @@ fn test_migration_config_add_parachain_percentage() {
// Modify active config
frame_support::storage::unhashed::put_raw(
CONFIGURATION_ACTIVE_CONFIG_KEY,
- &HostConfigurationV2 {
+ &HostConfigurationV3 {
max_collators: 5,
min_orchestrator_collators: 2,
max_orchestrator_collators: 1,
@@ -3917,6 +3916,7 @@ fn test_migration_config_add_parachain_percentage() {
collators_per_parathread: 2,
parathreads_per_collator: 1,
target_container_chain_fullness: Perbill::from_percent(45),
+ max_parachain_cores_percentage: Some(Perbill::from_percent(75)),
}
.encode(),
);
@@ -3926,7 +3926,7 @@ fn test_migration_config_add_parachain_percentage() {
&vec![
(
1234u32,
- HostConfigurationV2 {
+ HostConfigurationV3 {
max_collators: 1,
min_orchestrator_collators: 4,
max_orchestrator_collators: 45,
@@ -3935,11 +3935,12 @@ fn test_migration_config_add_parachain_percentage() {
collators_per_parathread: 1,
parathreads_per_collator: 1,
target_container_chain_fullness: Perbill::from_percent(65),
+ max_parachain_cores_percentage: Some(Perbill::from_percent(75)),
},
),
(
5678u32,
- HostConfigurationV2 {
+ HostConfigurationV3 {
max_collators: 1,
min_orchestrator_collators: 4,
max_orchestrator_collators: 45,
@@ -3948,13 +3949,14 @@ fn test_migration_config_add_parachain_percentage() {
collators_per_parathread: 1,
parathreads_per_collator: 1,
target_container_chain_fullness: Perbill::from_percent(65),
+ max_parachain_cores_percentage: Some(Perbill::from_percent(75)),
},
),
]
.encode(),
);
- let migration = MigrateConfigurationAddParachainPercentage::(Default::default());
+ let migration = MigrateConfigurationAddFullRotationMode::(Default::default());
migration.migrate(Default::default());
let expected_active = pallet_configuration::HostConfiguration {
@@ -3966,6 +3968,7 @@ fn test_migration_config_add_parachain_percentage() {
collators_per_parathread: 2,
parathreads_per_collator: 1,
target_container_chain_fullness: Perbill::from_percent(45),
+ max_parachain_cores_percentage: Some(Perbill::from_percent(75)),
..Default::default()
};
assert_eq!(Configuration::config(), expected_active);
@@ -3982,6 +3985,7 @@ fn test_migration_config_add_parachain_percentage() {
collators_per_parathread: 1,
parathreads_per_collator: 1,
target_container_chain_fullness: Perbill::from_percent(65),
+ max_parachain_cores_percentage: Some(Perbill::from_percent(75)),
..Default::default()
},
),
@@ -3996,6 +4000,7 @@ fn test_migration_config_add_parachain_percentage() {
collators_per_parathread: 1,
parathreads_per_collator: 1,
target_container_chain_fullness: Perbill::from_percent(65),
+ max_parachain_cores_percentage: Some(Perbill::from_percent(75)),
..Default::default()
},
),
@@ -4004,62 +4009,6 @@ fn test_migration_config_add_parachain_percentage() {
});
}
-#[test]
-fn test_migration_config_full_rotation_period() {
- ExtBuilder::default()
- .build()
- .execute_with(|| {
- const CONFIGURATION_ACTIVE_CONFIG_KEY: &[u8] =
- &hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385");
- const CONFIGURATION_PENDING_CONFIGS_KEY: &[u8] =
- &hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22d53b4123b2e186e07fb7bad5dda5f55c0");
-
- // Modify active config
- frame_support::storage::unhashed::put_raw(CONFIGURATION_ACTIVE_CONFIG_KEY, &hex_literal::hex!("6300000002000000050000000200000000000000"));
- // Modify pending configs
- frame_support::storage::unhashed::put_raw(CONFIGURATION_PENDING_CONFIGS_KEY, &hex_literal::hex!("08b10800006300000002000000050000000200000000000000b20800006400000002000000050000000200000000000000"));
-
- let migration = MigrateConfigurationParathreads::(Default::default());
- migration.migrate(Default::default());
-
- let expected_active = pallet_configuration::HostConfiguration {
- max_collators: 99,
- min_orchestrator_collators: 2,
- max_orchestrator_collators: 5,
- collators_per_container: 2,
- full_rotation_period: 0,
- ..Default::default()
- };
- assert_eq!(Configuration::config(), expected_active);
-
- let expected_pending = vec![
- (
- 2225,
- pallet_configuration::HostConfiguration {
- max_collators: 99,
- min_orchestrator_collators: 2,
- max_orchestrator_collators: 5,
- collators_per_container: 2,
- full_rotation_period: 0,
- ..Default::default()
- },
- ),
- (
- 2226,
- pallet_configuration::HostConfiguration {
- max_collators: 100,
- min_orchestrator_collators: 2,
- max_orchestrator_collators: 5,
- collators_per_container: 2,
- full_rotation_period: 0,
- ..Default::default()
- },
- ),
- ];
- assert_eq!(Configuration::pending_configs(), expected_pending);
- });
-}
-
#[test]
fn test_migration_registrar_pending_verification() {
ExtBuilder::default().build().execute_with(|| {
diff --git a/solo-chains/runtime/dancelight/src/tests/integration_test.rs b/solo-chains/runtime/dancelight/src/tests/integration_test.rs
index 5018a8de3..cdab01507 100644
--- a/solo-chains/runtime/dancelight/src/tests/integration_test.rs
+++ b/solo-chains/runtime/dancelight/src/tests/integration_test.rs
@@ -24,10 +24,16 @@ use {
cumulus_primitives_core::{relay_chain::HeadData, ParaId},
dancelight_runtime_constants::currency::EXISTENTIAL_DEPOSIT,
frame_support::{assert_noop, assert_ok, BoundedVec},
+ pallet_migrations::Migration,
pallet_registrar_runtime_api::{
runtime_decl_for_registrar_api::RegistrarApi, ContainerChainGenesisData,
},
+ sp_arithmetic::Perbill,
+ sp_core::Encode,
sp_std::vec,
+ tanssi_runtime_common::migrations::{
+ HostConfigurationV3, MigrateConfigurationAddFullRotationMode,
+ },
};
#[test]
@@ -354,3 +360,116 @@ fn test_container_deregister_unassign_data_preserver() {
assert!(pallet_data_preservers::Assignments::::get(para_id).is_empty());
});
}
+
+#[test]
+fn test_migration_config_add_full_rotation_mode() {
+ ExtBuilder::default().build().execute_with(|| {
+ const CONFIGURATION_ACTIVE_CONFIG_KEY: &[u8] =
+ &hex_literal::hex!("86e86c1d728ee2b18f76dd0e04d96cdbb4b49d95320d9021994c850f25b8e385");
+ const CONFIGURATION_PENDING_CONFIGS_KEY: &[u8] =
+ &hex_literal::hex!("86e86c1d728ee2b18f76dd0e04d96cdb53b4123b2e186e07fb7bad5dda5f55c0");
+
+ // Modify active config
+ frame_support::storage::unhashed::put_raw(
+ CONFIGURATION_ACTIVE_CONFIG_KEY,
+ &HostConfigurationV3 {
+ max_collators: 5,
+ min_orchestrator_collators: 2,
+ max_orchestrator_collators: 1,
+ collators_per_container: 3,
+ full_rotation_period: 4,
+ collators_per_parathread: 2,
+ parathreads_per_collator: 1,
+ target_container_chain_fullness: Perbill::from_percent(45),
+ max_parachain_cores_percentage: Some(Perbill::from_percent(75)),
+ }
+ .encode(),
+ );
+ // Modify pending configs
+ frame_support::storage::unhashed::put_raw(
+ CONFIGURATION_PENDING_CONFIGS_KEY,
+ &vec![
+ (
+ 1234u32,
+ HostConfigurationV3 {
+ max_collators: 1,
+ min_orchestrator_collators: 4,
+ max_orchestrator_collators: 45,
+ collators_per_container: 5,
+ full_rotation_period: 1,
+ collators_per_parathread: 1,
+ parathreads_per_collator: 1,
+ target_container_chain_fullness: Perbill::from_percent(65),
+ max_parachain_cores_percentage: Some(Perbill::from_percent(75)),
+ },
+ ),
+ (
+ 5678u32,
+ HostConfigurationV3 {
+ max_collators: 1,
+ min_orchestrator_collators: 4,
+ max_orchestrator_collators: 45,
+ collators_per_container: 5,
+ full_rotation_period: 1,
+ collators_per_parathread: 1,
+ parathreads_per_collator: 1,
+ target_container_chain_fullness: Perbill::from_percent(65),
+ max_parachain_cores_percentage: Some(Perbill::from_percent(75)),
+ },
+ ),
+ ]
+ .encode(),
+ );
+
+ let migration = MigrateConfigurationAddFullRotationMode::(Default::default());
+ migration.migrate(Default::default());
+
+ let expected_active = pallet_configuration::HostConfiguration {
+ max_collators: 5,
+ min_orchestrator_collators: 2,
+ max_orchestrator_collators: 1,
+ collators_per_container: 3,
+ full_rotation_period: 4,
+ collators_per_parathread: 2,
+ parathreads_per_collator: 1,
+ target_container_chain_fullness: Perbill::from_percent(45),
+ max_parachain_cores_percentage: Some(Perbill::from_percent(75)),
+ ..Default::default()
+ };
+ assert_eq!(CollatorConfiguration::config(), expected_active);
+
+ let expected_pending = vec![
+ (
+ 1234u32,
+ pallet_configuration::HostConfiguration {
+ max_collators: 1,
+ min_orchestrator_collators: 4,
+ max_orchestrator_collators: 45,
+ collators_per_container: 5,
+ full_rotation_period: 1,
+ collators_per_parathread: 1,
+ parathreads_per_collator: 1,
+ target_container_chain_fullness: Perbill::from_percent(65),
+ max_parachain_cores_percentage: Some(Perbill::from_percent(75)),
+ ..Default::default()
+ },
+ ),
+ (
+ 5678u32,
+ pallet_configuration::HostConfiguration {
+ max_collators: 1,
+ min_orchestrator_collators: 4,
+ max_orchestrator_collators: 45,
+ collators_per_container: 5,
+ full_rotation_period: 1,
+ collators_per_parathread: 1,
+ parathreads_per_collator: 1,
+ target_container_chain_fullness: Perbill::from_percent(65),
+ max_parachain_cores_percentage: Some(Perbill::from_percent(75)),
+ ..Default::default()
+ },
+ ),
+ ];
+ assert_eq!(CollatorConfiguration::pending_configs(), expected_pending);
+ });
+}
diff --git a/solo-chains/runtime/dancelight/src/tests/mod.rs b/solo-chains/runtime/dancelight/src/tests/mod.rs
index 9b6b57bee..35a6715c4 100644
--- a/solo-chains/runtime/dancelight/src/tests/mod.rs
+++ b/solo-chains/runtime/dancelight/src/tests/mod.rs
@@ -16,9 +16,10 @@
//! Tests for the Dancelight Runtime Configuration
-use {crate::*, std::collections::HashSet};
-
-use {frame_support::traits::WhitelistedStorageKeys, sp_core::hexdisplay::HexDisplay};
+use {
+ crate::*, frame_support::traits::WhitelistedStorageKeys, sp_core::hexdisplay::HexDisplay,
+ std::collections::HashSet,
+};
mod author_noting_tests;
mod beefy;
diff --git a/test/suites/dev-tanssi/collator-assignment/test-full-rotation-mode-parathreads.ts b/test/suites/dev-tanssi/collator-assignment/test-full-rotation-mode-parathreads.ts
new file mode 100644
index 000000000..bab2a25b9
--- /dev/null
+++ b/test/suites/dev-tanssi/collator-assignment/test-full-rotation-mode-parathreads.ts
@@ -0,0 +1,275 @@
+import "@tanssi/api-augment";
+import { describeSuite, expect, beforeAll, customDevRpcRequest } from "@moonwall/cli";
+import { ApiPromise } from "@polkadot/api";
+import { jumpBlocks, jumpSessions, jumpToSession } from "util/block";
+import { filterAndApply, generateKeyringPair } from "@moonwall/util";
+import { EventRecord } from "@polkadot/types/interfaces";
+import { bool, u32, u8, Vec } from "@polkadot/types-codec";
+
+describeSuite({
+ id: "DTR0304",
+ title: "Collator assignment tests",
+ foundationMethods: "dev",
+
+ testCases: ({ it, context }) => {
+ let polkadotJs: ApiPromise;
+ let alice;
+
+ beforeAll(async () => {
+ polkadotJs = context.polkadotJs();
+ alice = context.keyring.alice;
+
+ // Enable randomness for this test.
+ // Add a custom seed because with the default one this test fails because collators get assigned to the same
+ // chain again.
+ await customDevRpcRequest("mock_activateRandomness", [
+ "4bbc1fee42ce06ff37f5a07744346c1e0b43c8a4130db8ec5ae41f3234f5c421",
+ ]);
+ });
+
+ it({
+ id: "E01",
+ title: "Collator should rotate",
+ test: async function () {
+ const orchestrator = "RotateAll";
+ const parachain = "KeepAll";
+ const parathread = { KeepPerbill: { percentage: 500_000_000n } }; // 50%
+ const tx = context
+ .polkadotJs()
+ .tx.configuration.setFullRotationMode(orchestrator, parachain, parathread);
+ await context.createBlock(polkadotJs.tx.sudo.sudo(tx).signAsync(alice));
+ const tx2 = context.polkadotJs().tx.configuration.setCollatorsPerParathread(2);
+ await context.createBlock(polkadotJs.tx.sudo.sudo(tx2).signAsync(alice));
+
+ // Add 4 collators more
+ // Use random accounts
+ let aliceNonce = (await polkadotJs.query.system.account(alice.address)).nonce.toNumber();
+ const randomAccounts = [];
+
+ for (let i = 0; i < 4; i++) {
+ const randomAccount = generateKeyringPair("sr25519");
+ randomAccounts.push(randomAccount);
+ }
+
+ // First block, send some balance to each account. This needs to go first because `.signAndSend(randomAccount)`
+ // given an error if the account has no balance, even though we send some balance and it's pending.
+ for (const randomAccount of randomAccounts) {
+ const value = 100_000_000_000n;
+ await polkadotJs.tx.balances
+ .transferAllowDeath(randomAccount.address, value)
+ .signAndSend(alice, { nonce: aliceNonce++ });
+ }
+
+ await context.createBlock();
+
+ // Second block, add keys and register them as invulnerables
+ for (const randomAccount of randomAccounts) {
+ const newKey1 = await polkadotJs.rpc.author.rotateKeys();
+ await polkadotJs.tx.session.setKeys(newKey1, []).signAndSend(randomAccount);
+
+ await polkadotJs.tx.sudo
+ .sudo(polkadotJs.tx.invulnerables.addInvulnerable(randomAccount.address))
+ .signAndSend(alice, { nonce: aliceNonce++ });
+ }
+ await context.createBlock();
+
+ // Deregister container chains and register parathreads instead
+ await deregisterAll(context);
+ await registerParathreads(context);
+
+ // Collators are registered, wait 2 sessions for them to be assigned
+ await jumpSessions(context, 1);
+
+ const fullRotationPeriod = (await polkadotJs.query.configuration.activeConfig())[
+ "fullRotationPeriod"
+ ].toString();
+ const sessionIndex = (await polkadotJs.query.session.currentIndex()).toNumber();
+ // Calculate the remaining sessions for next full rotation
+ // This is a workaround for running moonwall in run mode
+ // as it runs all tests in the same chain instance
+ const remainingSessionsForRotation =
+ sessionIndex > fullRotationPeriod ? sessionIndex % fullRotationPeriod : fullRotationPeriod;
+
+ await jumpToSession(context, remainingSessionsForRotation - 2);
+
+ const initialAssignment = (await polkadotJs.query.collatorAssignment.collatorContainerChain()).toJSON();
+
+ expect(initialAssignment.containerChains[2002].length).to.eq(2);
+ expect((await polkadotJs.query.collatorAssignment.pendingCollatorContainerChain()).isNone);
+
+ // remainingSessionsForRotation - 1
+ await jumpSessions(context, 1);
+ const rotationEndAssignment = (
+ await polkadotJs.query.collatorAssignment.collatorContainerChain()
+ ).toJSON();
+
+ expect((await polkadotJs.query.collatorAssignment.pendingCollatorContainerChain()).isSome);
+ // Assignment shouldn't have changed yet
+ expect(initialAssignment.containerChains[2002].toSorted()).to.deep.eq(
+ rotationEndAssignment.containerChains[2002].toSorted()
+ );
+
+ // In dev-tanssi, randomness depends only on the block number so it is actually deterministic.
+ // First, check that the event has randomness
+ const events = await polkadotJs.query.system.events();
+ const filteredEvents = filterAndApply(
+ events,
+ "collatorAssignment",
+ ["NewPendingAssignment"],
+ ({ event }: EventRecord) =>
+ event.data as unknown as { randomSeed: Vec; fullRotation: bool; targetSession: u32 }
+ );
+ expect(filteredEvents[0].fullRotation.toJSON()).toBe(true);
+ // In dev mode randomness is deterministic so the seed should not change, but we only want to check that
+ // it's not 0x0000..., so it doesn't matter if it changes.
+ expect(filteredEvents[0].randomSeed.toHex()).to.deep.eq(
+ "0x8b145bb9825b580a7a571099151e7ac459b83103abec71bc4d322bcd5bef153f"
+ );
+
+ // Check that the randomness is set in CollatorAssignment the
+ // block previous to the full rotation
+ const sessionDuration = 10;
+ await jumpBlocks(context, sessionDuration - 1);
+
+ const assignmentRandomness = await polkadotJs.query.collatorAssignment.randomness();
+ expect(assignmentRandomness.isEmpty).toBe(false);
+
+ // Start session 5, with the new random assignment
+ await jumpSessions(context, 1);
+
+ const newAssignment = (await polkadotJs.query.collatorAssignment.collatorContainerChain()).toJSON();
+
+ // Assignment should have changed
+ expect(newAssignment).to.not.deep.eq(initialAssignment);
+
+ // Orchestrator collators should change
+ // But they don't change because they are invulnerables, and invulnerables that were previously assigned have priority.
+ expect(newAssignment.orchestratorChain).to.not.eq(initialAssignment.orchestratorChain);
+
+ const arrayIntersection = (arr1, arr2) => {
+ const set2 = new Set(arr2);
+ return arr1.filter((item) => set2.has(item));
+ };
+
+ // Parathread collators should keep 1 and rotate the other one
+ expect(newAssignment.containerChains["2002"].length).toBe(2);
+ const sameCollators2002 = arrayIntersection(
+ newAssignment.containerChains["2002"],
+ initialAssignment.containerChains["2002"]
+ );
+ expect(sameCollators2002.length).toBe(1);
+ expect(newAssignment.containerChains["2003"].length).toBe(2);
+ const sameCollators2003 = arrayIntersection(
+ newAssignment.containerChains["2003"],
+ initialAssignment.containerChains["2003"]
+ );
+ expect(sameCollators2003.length).toBe(1);
+ },
+ });
+ },
+});
+
+async function deregisterAll(context) {
+ const polkadotJs = context.polkadotJs();
+ const alice = context.keyring.alice;
+ const parasRegistered = (await polkadotJs.query.registrar.registeredParaIds()).toJSON();
+
+ const txs = [];
+
+ for (const paraId of parasRegistered) {
+ const tx = polkadotJs.tx.registrar.deregister(paraId);
+ txs.push(tx);
+ }
+
+ await context.createBlock([await polkadotJs.tx.sudo.sudo(polkadotJs.tx.utility.batchAll(txs)).signAsync(alice)]);
+}
+
+async function registerParathreads(context) {
+ const polkadotJs = context.polkadotJs();
+ const alice = context.keyring.alice;
+ await context.createBlock();
+
+ const currentSesssion = await polkadotJs.query.session.currentIndex();
+ const sessionDelay = await polkadotJs.consts.registrar.sessionDelay;
+ const expectedScheduledOnboarding = BigInt(currentSesssion.toString()) + BigInt(sessionDelay.toString());
+
+ const slotFrequency = polkadotJs.createType("TpTraitsSlotFrequency", {
+ min: 1,
+ max: 1,
+ });
+ const emptyGenesisData = () => {
+ const g = polkadotJs.createType("DpContainerChainGenesisDataContainerChainGenesisData", {
+ storage: [
+ {
+ key: "0x636f6465",
+ value: "0x010203040506",
+ },
+ ],
+ name: "0x436f6e7461696e657220436861696e2032303030",
+ id: "0x636f6e7461696e65722d636861696e2d32303030",
+ forkId: null,
+ extensions: "0x",
+ properties: {
+ tokenMetadata: {
+ tokenSymbol: "0x61626364",
+ ss58Format: 42,
+ tokenDecimals: 12,
+ },
+ isEthereum: false,
+ },
+ });
+ return g;
+ };
+
+ const containerChainGenesisData = emptyGenesisData();
+
+ for (const paraId of [2002, 2003]) {
+ const tx = polkadotJs.tx.registrar.registerParathread(paraId, slotFrequency, containerChainGenesisData, null);
+
+ const profileId = await polkadotJs.query.dataPreservers.nextProfileId();
+ const tx2 = polkadotJs.tx.dataPreservers.createProfile({
+ url: "/ip4/127.0.0.1/tcp/33051/ws/p2p/12D3KooWSDsmAa7iFbHdQW4X8B2KbeRYPDLarK6EbevUSYfGkeQw",
+ paraIds: "AnyParaId",
+ mode: "Bootnode",
+ assignmentRequest: "Free",
+ });
+
+ const tx3 = polkadotJs.tx.dataPreservers.startAssignment(profileId, paraId, "Free");
+ const tx4 = polkadotJs.tx.registrar.markValidForCollating(paraId);
+ const nonce = await polkadotJs.rpc.system.accountNextIndex(alice.publicKey);
+ await context.createBlock([
+ await tx.signAsync(alice, { nonce }),
+ await tx2.signAsync(alice, { nonce: nonce.addn(1) }),
+ await tx3.signAsync(alice, { nonce: nonce.addn(2) }),
+ await polkadotJs.tx.sudo.sudo(tx4).signAsync(alice, { nonce: nonce.addn(3) }),
+ ]);
+ }
+
+ const pendingParas = await polkadotJs.query.registrar.pendingParaIds();
+ expect(pendingParas.length).to.be.eq(1);
+ const sessionScheduling = pendingParas[0][0];
+ const parasScheduled = pendingParas[0][1];
+
+ expect(sessionScheduling.toBigInt()).to.be.eq(expectedScheduledOnboarding);
+
+ // These will be the paras in session 2
+ // TODO: fix once we have types
+ expect(parasScheduled.toJSON()).to.deep.equal([2002, 2003]);
+
+ // Check that the on chain genesis data is set correctly
+ const onChainGenesisData = await polkadotJs.query.registrar.paraGenesisData(2002);
+ // TODO: fix once we have types
+ expect(emptyGenesisData().toJSON()).to.deep.equal(onChainGenesisData.toJSON());
+
+ // Check the para id has been given some free credits
+ const credits = (await polkadotJs.query.servicesPayment.blockProductionCredits(2002)).toJSON();
+ expect(credits, "Container chain 2002 should have been given credits").toBeGreaterThan(0);
+
+ // Checking that in session 2 paras are registered
+ await jumpSessions(context, 2);
+
+ // Expect now paraIds to be registered
+ const parasRegistered = await polkadotJs.query.registrar.registeredParaIds();
+ // TODO: fix once we have types
+ expect(parasRegistered.toJSON()).to.deep.equal([2002, 2003]);
+}
diff --git a/test/suites/dev-tanssi/collator-assignment/test-full-rotation-mode.ts b/test/suites/dev-tanssi/collator-assignment/test-full-rotation-mode.ts
new file mode 100644
index 000000000..35f93a494
--- /dev/null
+++ b/test/suites/dev-tanssi/collator-assignment/test-full-rotation-mode.ts
@@ -0,0 +1,159 @@
+import "@tanssi/api-augment";
+import { describeSuite, expect, beforeAll, customDevRpcRequest } from "@moonwall/cli";
+import { ApiPromise } from "@polkadot/api";
+import { jumpBlocks, jumpSessions, jumpToSession } from "util/block";
+import { filterAndApply, generateKeyringPair } from "@moonwall/util";
+import { EventRecord } from "@polkadot/types/interfaces";
+import { bool, u32, u8, Vec } from "@polkadot/types-codec";
+
+describeSuite({
+ id: "DTR0303",
+ title: "Collator assignment tests",
+ foundationMethods: "dev",
+
+ testCases: ({ it, context }) => {
+ let polkadotJs: ApiPromise;
+ let alice;
+
+ beforeAll(async () => {
+ polkadotJs = context.polkadotJs();
+ alice = context.keyring.alice;
+
+ // Enable randomness for this test
+ await customDevRpcRequest("mock_activateRandomness", []);
+ });
+
+ it({
+ id: "E01",
+ title: "Collator should rotate",
+ test: async function () {
+ const orchestrator = "KeepAll";
+ const parachain = { KeepCollators: { keep: 1 } };
+ const parathread = "RotateAll";
+ const tx = context
+ .polkadotJs()
+ .tx.configuration.setFullRotationMode(orchestrator, parachain, parathread);
+ await context.createBlock(polkadotJs.tx.sudo.sudo(tx).signAsync(alice));
+
+ // Add 4 collators more
+ // Use random accounts
+ let aliceNonce = (await polkadotJs.query.system.account(alice.address)).nonce.toNumber();
+ const randomAccounts = [];
+
+ for (let i = 0; i < 4; i++) {
+ const randomAccount = generateKeyringPair("sr25519");
+ randomAccounts.push(randomAccount);
+ }
+
+ // First block, send some balance to each account. This needs to go first because `.signAndSend(randomAccount)`
+ // given an error if the account has no balance, even though we send some balance and it's pending.
+ for (const randomAccount of randomAccounts) {
+ const value = 100_000_000_000n;
+ await polkadotJs.tx.balances
+ .transferAllowDeath(randomAccount.address, value)
+ .signAndSend(alice, { nonce: aliceNonce++ });
+ }
+
+ await context.createBlock();
+
+ // Second block, add keys and register them as invulnerables
+ for (const randomAccount of randomAccounts) {
+ const newKey1 = await polkadotJs.rpc.author.rotateKeys();
+ await polkadotJs.tx.session.setKeys(newKey1, []).signAndSend(randomAccount);
+
+ await polkadotJs.tx.sudo
+ .sudo(polkadotJs.tx.invulnerables.addInvulnerable(randomAccount.address))
+ .signAndSend(alice, { nonce: aliceNonce++ });
+ }
+ await context.createBlock();
+
+ // Collators are registered, wait 2 sessions for them to be assigned
+ await jumpSessions(context, 2);
+
+ const fullRotationPeriod = (await polkadotJs.query.configuration.activeConfig())[
+ "fullRotationPeriod"
+ ].toString();
+ const sessionIndex = (await polkadotJs.query.session.currentIndex()).toNumber();
+ // Calculate the remaining sessions for next full rotation
+ // This is a workaround for running moonwall in run mode
+ // as it runs all tests in the same chain instance
+ const remainingSessionsForRotation =
+ sessionIndex > fullRotationPeriod ? sessionIndex % fullRotationPeriod : fullRotationPeriod;
+
+ await jumpToSession(context, remainingSessionsForRotation - 2);
+
+ const initialAssignment = (await polkadotJs.query.collatorAssignment.collatorContainerChain()).toJSON();
+
+ expect(initialAssignment.containerChains[2000].length).to.eq(2);
+ expect((await polkadotJs.query.collatorAssignment.pendingCollatorContainerChain()).isNone);
+
+ // remainingSessionsForRotation - 1
+ await jumpSessions(context, 1);
+ const rotationEndAssignment = (
+ await polkadotJs.query.collatorAssignment.collatorContainerChain()
+ ).toJSON();
+
+ expect((await polkadotJs.query.collatorAssignment.pendingCollatorContainerChain()).isSome);
+ // Assignment shouldn't have changed yet
+ expect(initialAssignment.containerChains[2000].toSorted()).to.deep.eq(
+ rotationEndAssignment.containerChains[2000].toSorted()
+ );
+
+ // In dev-tanssi, randomness depends only on the block number so it is actually deterministic.
+ // First, check that the event has randomness
+ const events = await polkadotJs.query.system.events();
+ const filteredEvents = filterAndApply(
+ events,
+ "collatorAssignment",
+ ["NewPendingAssignment"],
+ ({ event }: EventRecord) =>
+ event.data as unknown as { randomSeed: Vec; fullRotation: bool; targetSession: u32 }
+ );
+ expect(filteredEvents[0].fullRotation.toJSON()).toBe(true);
+ // Randomness is deterministic so the seed should not change, but we only want to check that it's not 0x0000...,
+ // so it doesn't matter if it changes.
+ expect(filteredEvents[0].randomSeed.toHex()).to.deep.eq(
+ "0xf497424c947d1b548a64ce4697f8fa8f84c6d73b7ceedf4d4f9cb65665fb9cc1"
+ );
+
+ // Check that the randomness is set in CollatorAssignment the
+ // block previous to the full rotation
+ const sessionDuration = 10;
+ await jumpBlocks(context, sessionDuration - 1);
+
+ const assignmentRandomness = await polkadotJs.query.collatorAssignment.randomness();
+ expect(assignmentRandomness.isEmpty).toBe(false);
+
+ // Start session 5, with the new random assignment
+ await jumpSessions(context, 1);
+
+ const newAssignment = (await polkadotJs.query.collatorAssignment.collatorContainerChain()).toJSON();
+
+ // Assignment should have changed
+ expect(newAssignment).to.not.deep.eq(initialAssignment);
+
+ // Orchestrator collators should not change
+ expect(newAssignment.orchestratorChain).to.deep.eq(initialAssignment.orchestratorChain);
+
+ const arrayIntersection = (arr1, arr2) => {
+ const set2 = new Set(arr2);
+ return arr1.filter((item) => set2.has(item));
+ };
+
+ // Parachain collators should keep 1 and rotate the other one
+ expect(newAssignment.containerChains["2000"].length).toBe(2);
+ const sameCollators2000 = arrayIntersection(
+ newAssignment.containerChains["2000"],
+ initialAssignment.containerChains["2000"]
+ );
+ expect(sameCollators2000.length).toBe(1);
+ expect(newAssignment.containerChains["2001"].length).toBe(2);
+ const sameCollators2001 = arrayIntersection(
+ newAssignment.containerChains["2001"],
+ initialAssignment.containerChains["2001"]
+ );
+ expect(sameCollators2001.length).toBe(1);
+ },
+ });
+ },
+});
diff --git a/typescript-api/src/dancebox/interfaces/augment-api-events.ts b/typescript-api/src/dancebox/interfaces/augment-api-events.ts
index ccdfeb8d4..f270aa92d 100644
--- a/typescript-api/src/dancebox/interfaces/augment-api-events.ts
+++ b/typescript-api/src/dancebox/interfaces/augment-api-events.ts
@@ -27,6 +27,7 @@ import type {
StagingXcmV4Response,
StagingXcmV4TraitsOutcome,
StagingXcmV4Xcm,
+ TpTraitsFullRotationModes,
XcmV3TraitsError,
XcmVersionedAssets,
XcmVersionedLocation,
@@ -139,8 +140,18 @@ declare module "@polkadot/api-base/types/events" {
collatorAssignment: {
NewPendingAssignment: AugmentedEvent<
ApiType,
- [randomSeed: U8aFixed, fullRotation: bool, targetSession: u32],
- { randomSeed: U8aFixed; fullRotation: bool; targetSession: u32 }
+ [
+ randomSeed: U8aFixed,
+ fullRotation: bool,
+ targetSession: u32,
+ fullRotationMode: TpTraitsFullRotationModes,
+ ],
+ {
+ randomSeed: U8aFixed;
+ fullRotation: bool;
+ targetSession: u32;
+ fullRotationMode: TpTraitsFullRotationModes;
+ }
>;
/** Generic event */
[key: string]: AugmentedEvent;
diff --git a/typescript-api/src/dancebox/interfaces/augment-api-tx.ts b/typescript-api/src/dancebox/interfaces/augment-api-tx.ts
index 3d243c7b1..d6aeee376 100644
--- a/typescript-api/src/dancebox/interfaces/augment-api-tx.ts
+++ b/typescript-api/src/dancebox/interfaces/augment-api-tx.ts
@@ -46,6 +46,7 @@ import type {
StagingXcmV4Location,
StagingXcmV4Response,
TpAuthorNotingInherentOwnParachainInherentData,
+ TpTraitsFullRotationMode,
TpTraitsParathreadParams,
TpTraitsSlotFrequency,
TpXcmCoreBuyerBuyCoreCollatorProof,
@@ -351,6 +352,41 @@ declare module "@polkadot/api-base/types/submittable" {
(updated: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic,
[u32]
>;
+ setFullRotationMode: AugmentedSubmittable<
+ (
+ orchestrator:
+ | Option
+ | null
+ | Uint8Array
+ | TpTraitsFullRotationMode
+ | { RotateAll: any }
+ | { KeepAll: any }
+ | { KeepCollators: any }
+ | { KeepPerbill: any }
+ | string,
+ parachain:
+ | Option
+ | null
+ | Uint8Array
+ | TpTraitsFullRotationMode
+ | { RotateAll: any }
+ | { KeepAll: any }
+ | { KeepCollators: any }
+ | { KeepPerbill: any }
+ | string,
+ parathread:
+ | Option
+ | null
+ | Uint8Array
+ | TpTraitsFullRotationMode
+ | { RotateAll: any }
+ | { KeepAll: any }
+ | { KeepCollators: any }
+ | { KeepPerbill: any }
+ | string
+ ) => SubmittableExtrinsic,
+ [Option, Option, Option]
+ >;
setFullRotationPeriod: AugmentedSubmittable<
(updated: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic,
[u32]
diff --git a/typescript-api/src/dancebox/interfaces/lookup.ts b/typescript-api/src/dancebox/interfaces/lookup.ts
index be7b56b6c..0812edb90 100644
--- a/typescript-api/src/dancebox/interfaces/lookup.ts
+++ b/typescript-api/src/dancebox/interfaces/lookup.ts
@@ -604,10 +604,30 @@ export default {
randomSeed: "[u8;32]",
fullRotation: "bool",
targetSession: "u32",
+ fullRotationMode: "TpTraitsFullRotationModes",
},
},
},
- /** Lookup63: pallet_author_noting::pallet::Event */
+ /** Lookup63: tp_traits::FullRotationModes */
+ TpTraitsFullRotationModes: {
+ orchestrator: "TpTraitsFullRotationMode",
+ parachain: "TpTraitsFullRotationMode",
+ parathread: "TpTraitsFullRotationMode",
+ },
+ /** Lookup64: tp_traits::FullRotationMode */
+ TpTraitsFullRotationMode: {
+ _enum: {
+ RotateAll: "Null",
+ KeepAll: "Null",
+ KeepCollators: {
+ keep: "u32",
+ },
+ KeepPerbill: {
+ percentage: "Perbill",
+ },
+ },
+ },
+ /** Lookup66: pallet_author_noting::pallet::Event */
PalletAuthorNotingEvent: {
_enum: {
LatestAuthorChanged: {
@@ -621,7 +641,7 @@ export default {
},
},
},
- /** Lookup65: pallet_services_payment::pallet::Event */
+ /** Lookup68: pallet_services_payment::pallet::Event */
PalletServicesPaymentEvent: {
_enum: {
CreditsPurchased: {
@@ -660,7 +680,7 @@ export default {
},
},
},
- /** Lookup67: pallet_data_preservers::pallet::Event */
+ /** Lookup70: pallet_data_preservers::pallet::Event */
PalletDataPreserversEvent: {
_enum: {
BootNodesChanged: {
@@ -690,7 +710,7 @@ export default {
},
},
},
- /** Lookup68: pallet_invulnerables::pallet::Event */
+ /** Lookup71: pallet_invulnerables::pallet::Event */
PalletInvulnerablesEvent: {
_enum: {
InvulnerableAdded: {
@@ -701,7 +721,7 @@ export default {
},
},
},
- /** Lookup69: pallet_session::pallet::Event */
+ /** Lookup72: pallet_session::pallet::Event */
PalletSessionEvent: {
_enum: {
NewSession: {
@@ -709,7 +729,7 @@ export default {
},
},
},
- /** Lookup70: pallet_pooled_staking::pallet::Event */
+ /** Lookup73: pallet_pooled_staking::pallet::Event */
PalletPooledStakingEvent: {
_enum: {
UpdatedCandidatePosition: {
@@ -804,11 +824,11 @@ export default {
},
},
},
- /** Lookup72: pallet_pooled_staking::pallet::TargetPool */
+ /** Lookup75: pallet_pooled_staking::pallet::TargetPool */
PalletPooledStakingTargetPool: {
_enum: ["AutoCompounding", "ManualRewards"],
},
- /** Lookup73: pallet_inflation_rewards::pallet::Event */
+ /** Lookup76: pallet_inflation_rewards::pallet::Event */
PalletInflationRewardsEvent: {
_enum: {
RewardedOrchestrator: {
@@ -822,7 +842,7 @@ export default {
},
},
},
- /** Lookup74: pallet_treasury::pallet::Event */
+ /** Lookup77: pallet_treasury::pallet::Event */
PalletTreasuryEvent: {
_enum: {
Spending: {
@@ -875,7 +895,7 @@ export default {
},
},
},
- /** Lookup75: cumulus_pallet_xcmp_queue::pallet::Event */
+ /** Lookup78: cumulus_pallet_xcmp_queue::pallet::Event */
CumulusPalletXcmpQueueEvent: {
_enum: {
XcmpMessageSent: {
@@ -883,7 +903,7 @@ export default {
},
},
},
- /** Lookup76: cumulus_pallet_xcm::pallet::Event */
+ /** Lookup79: cumulus_pallet_xcm::pallet::Event */
CumulusPalletXcmEvent: {
_enum: {
InvalidFormat: "[u8;32]",
@@ -891,7 +911,7 @@ export default {
ExecutedDownward: "([u8;32],StagingXcmV4TraitsOutcome)",
},
},
- /** Lookup77: staging_xcm::v4::traits::Outcome */
+ /** Lookup80: staging_xcm::v4::traits::Outcome */
StagingXcmV4TraitsOutcome: {
_enum: {
Complete: {
@@ -906,7 +926,7 @@ export default {
},
},
},
- /** Lookup78: xcm::v3::traits::Error */
+ /** Lookup81: xcm::v3::traits::Error */
XcmV3TraitsError: {
_enum: {
Overflow: "Null",
@@ -951,7 +971,7 @@ export default {
ExceedsStackLimit: "Null",
},
},
- /** Lookup79: pallet_xcm::pallet::Event */
+ /** Lookup82: pallet_xcm::pallet::Event */
PalletXcmEvent: {
_enum: {
Attempted: {
@@ -1074,26 +1094,26 @@ export default {
},
},
},
- /** Lookup80: staging_xcm::v4::location::Location */
+ /** Lookup83: staging_xcm::v4::location::Location */
StagingXcmV4Location: {
parents: "u8",
interior: "StagingXcmV4Junctions",
},
- /** Lookup81: staging_xcm::v4::junctions::Junctions */
+ /** Lookup84: staging_xcm::v4::junctions::Junctions */
StagingXcmV4Junctions: {
_enum: {
Here: "Null",
- X1: "[Lookup83;1]",
- X2: "[Lookup83;2]",
- X3: "[Lookup83;3]",
- X4: "[Lookup83;4]",
- X5: "[Lookup83;5]",
- X6: "[Lookup83;6]",
- X7: "[Lookup83;7]",
- X8: "[Lookup83;8]",
+ X1: "[Lookup86;1]",
+ X2: "[Lookup86;2]",
+ X3: "[Lookup86;3]",
+ X4: "[Lookup86;4]",
+ X5: "[Lookup86;5]",
+ X6: "[Lookup86;6]",
+ X7: "[Lookup86;7]",
+ X8: "[Lookup86;8]",
},
},
- /** Lookup83: staging_xcm::v4::junction::Junction */
+ /** Lookup86: staging_xcm::v4::junction::Junction */
StagingXcmV4Junction: {
_enum: {
Parachain: "Compact",
@@ -1123,7 +1143,7 @@ export default {
GlobalConsensus: "StagingXcmV4JunctionNetworkId",
},
},
- /** Lookup86: staging_xcm::v4::junction::NetworkId */
+ /** Lookup89: staging_xcm::v4::junction::NetworkId */
StagingXcmV4JunctionNetworkId: {
_enum: {
ByGenesis: "[u8;32]",
@@ -1144,7 +1164,7 @@ export default {
PolkadotBulletin: "Null",
},
},
- /** Lookup89: xcm::v3::junction::BodyId */
+ /** Lookup92: xcm::v3::junction::BodyId */
XcmV3JunctionBodyId: {
_enum: {
Unit: "Null",
@@ -1159,7 +1179,7 @@ export default {
Treasury: "Null",
},
},
- /** Lookup90: xcm::v3::junction::BodyPart */
+ /** Lookup93: xcm::v3::junction::BodyPart */
XcmV3JunctionBodyPart: {
_enum: {
Voice: "Null",
@@ -1180,9 +1200,9 @@ export default {
},
},
},
- /** Lookup98: staging_xcm::v4::Xcm */
+ /** Lookup101: staging_xcm::v4::Xcm */
StagingXcmV4Xcm: "Vec",
- /** Lookup100: staging_xcm::v4::Instruction */
+ /** Lookup103: staging_xcm::v4::Instruction */
StagingXcmV4Instruction: {
_enum: {
WithdrawAsset: "StagingXcmV4AssetAssets",
@@ -1322,23 +1342,23 @@ export default {
},
},
},
- /** Lookup101: staging_xcm::v4::asset::Assets */
+ /** Lookup104: staging_xcm::v4::asset::Assets */
StagingXcmV4AssetAssets: "Vec",
- /** Lookup103: staging_xcm::v4::asset::Asset */
+ /** Lookup106: staging_xcm::v4::asset::Asset */
StagingXcmV4Asset: {
id: "StagingXcmV4AssetAssetId",
fun: "StagingXcmV4AssetFungibility",
},
- /** Lookup104: staging_xcm::v4::asset::AssetId */
+ /** Lookup107: staging_xcm::v4::asset::AssetId */
StagingXcmV4AssetAssetId: "StagingXcmV4Location",
- /** Lookup105: staging_xcm::v4::asset::Fungibility */
+ /** Lookup108: staging_xcm::v4::asset::Fungibility */
StagingXcmV4AssetFungibility: {
_enum: {
Fungible: "Compact",
NonFungible: "StagingXcmV4AssetAssetInstance",
},
},
- /** Lookup106: staging_xcm::v4::asset::AssetInstance */
+ /** Lookup109: staging_xcm::v4::asset::AssetInstance */
StagingXcmV4AssetAssetInstance: {
_enum: {
Undefined: "Null",
@@ -1349,7 +1369,7 @@ export default {
Array32: "[u8;32]",
},
},
- /** Lookup109: staging_xcm::v4::Response */
+ /** Lookup112: staging_xcm::v4::Response */
StagingXcmV4Response: {
_enum: {
Null: "Null",
@@ -1360,7 +1380,7 @@ export default {
DispatchResult: "XcmV3MaybeErrorCode",
},
},
- /** Lookup113: staging_xcm::v4::PalletInfo */
+ /** Lookup116: staging_xcm::v4::PalletInfo */
StagingXcmV4PalletInfo: {
index: "Compact",
name: "Bytes",
@@ -1369,7 +1389,7 @@ export default {
minor: "Compact",
patch: "Compact",
},
- /** Lookup116: xcm::v3::MaybeErrorCode */
+ /** Lookup119: xcm::v3::MaybeErrorCode */
XcmV3MaybeErrorCode: {
_enum: {
Success: "Null",
@@ -1377,28 +1397,28 @@ export default {
TruncatedError: "Bytes",
},
},
- /** Lookup119: xcm::v3::OriginKind */
+ /** Lookup122: xcm::v3::OriginKind */
XcmV3OriginKind: {
_enum: ["Native", "SovereignAccount", "Superuser", "Xcm"],
},
- /** Lookup120: xcm::double_encoded::DoubleEncoded */
+ /** Lookup123: xcm::double_encoded::DoubleEncoded */
XcmDoubleEncoded: {
encoded: "Bytes",
},
- /** Lookup121: staging_xcm::v4::QueryResponseInfo */
+ /** Lookup124: staging_xcm::v4::QueryResponseInfo */
StagingXcmV4QueryResponseInfo: {
destination: "StagingXcmV4Location",
queryId: "Compact",
maxWeight: "SpWeightsWeightV2Weight",
},
- /** Lookup122: staging_xcm::v4::asset::AssetFilter */
+ /** Lookup125: staging_xcm::v4::asset::AssetFilter */
StagingXcmV4AssetAssetFilter: {
_enum: {
Definite: "StagingXcmV4AssetAssets",
Wild: "StagingXcmV4AssetWildAsset",
},
},
- /** Lookup123: staging_xcm::v4::asset::WildAsset */
+ /** Lookup126: staging_xcm::v4::asset::WildAsset */
StagingXcmV4AssetWildAsset: {
_enum: {
All: "Null",
@@ -1414,18 +1434,18 @@ export default {
},
},
},
- /** Lookup124: staging_xcm::v4::asset::WildFungibility */
+ /** Lookup127: staging_xcm::v4::asset::WildFungibility */
StagingXcmV4AssetWildFungibility: {
_enum: ["Fungible", "NonFungible"],
},
- /** Lookup125: xcm::v3::WeightLimit */
+ /** Lookup128: xcm::v3::WeightLimit */
XcmV3WeightLimit: {
_enum: {
Unlimited: "Null",
Limited: "SpWeightsWeightV2Weight",
},
},
- /** Lookup126: xcm::VersionedAssets */
+ /** Lookup129: xcm::VersionedAssets */
XcmVersionedAssets: {
_enum: {
__Unused0: "Null",
@@ -1435,26 +1455,26 @@ export default {
V4: "StagingXcmV4AssetAssets",
},
},
- /** Lookup127: xcm::v2::multiasset::MultiAssets */
+ /** Lookup130: xcm::v2::multiasset::MultiAssets */
XcmV2MultiassetMultiAssets: "Vec",
- /** Lookup129: xcm::v2::multiasset::MultiAsset */
+ /** Lookup132: xcm::v2::multiasset::MultiAsset */
XcmV2MultiAsset: {
id: "XcmV2MultiassetAssetId",
fun: "XcmV2MultiassetFungibility",
},
- /** Lookup130: xcm::v2::multiasset::AssetId */
+ /** Lookup133: xcm::v2::multiasset::AssetId */
XcmV2MultiassetAssetId: {
_enum: {
Concrete: "XcmV2MultiLocation",
Abstract: "Bytes",
},
},
- /** Lookup131: xcm::v2::multilocation::MultiLocation */
+ /** Lookup134: xcm::v2::multilocation::MultiLocation */
XcmV2MultiLocation: {
parents: "u8",
interior: "XcmV2MultilocationJunctions",
},
- /** Lookup132: xcm::v2::multilocation::Junctions */
+ /** Lookup135: xcm::v2::multilocation::Junctions */
XcmV2MultilocationJunctions: {
_enum: {
Here: "Null",
@@ -1468,7 +1488,7 @@ export default {
X8: "(XcmV2Junction,XcmV2Junction,XcmV2Junction,XcmV2Junction,XcmV2Junction,XcmV2Junction,XcmV2Junction,XcmV2Junction)",
},
},
- /** Lookup133: xcm::v2::junction::Junction */
+ /** Lookup136: xcm::v2::junction::Junction */
XcmV2Junction: {
_enum: {
Parachain: "Compact",
@@ -1494,7 +1514,7 @@ export default {
},
},
},
- /** Lookup134: xcm::v2::NetworkId */
+ /** Lookup137: xcm::v2::NetworkId */
XcmV2NetworkId: {
_enum: {
Any: "Null",
@@ -1503,7 +1523,7 @@ export default {
Kusama: "Null",
},
},
- /** Lookup136: xcm::v2::BodyId */
+ /** Lookup139: xcm::v2::BodyId */
XcmV2BodyId: {
_enum: {
Unit: "Null",
@@ -1518,7 +1538,7 @@ export default {
Treasury: "Null",
},
},
- /** Lookup137: xcm::v2::BodyPart */
+ /** Lookup140: xcm::v2::BodyPart */
XcmV2BodyPart: {
_enum: {
Voice: "Null",
@@ -1539,14 +1559,14 @@ export default {
},
},
},
- /** Lookup138: xcm::v2::multiasset::Fungibility */
+ /** Lookup141: xcm::v2::multiasset::Fungibility */
XcmV2MultiassetFungibility: {
_enum: {
Fungible: "Compact",
NonFungible: "XcmV2MultiassetAssetInstance",
},
},
- /** Lookup139: xcm::v2::multiasset::AssetInstance */
+ /** Lookup142: xcm::v2::multiasset::AssetInstance */
XcmV2MultiassetAssetInstance: {
_enum: {
Undefined: "Null",
@@ -1558,26 +1578,26 @@ export default {
Blob: "Bytes",
},
},
- /** Lookup140: xcm::v3::multiasset::MultiAssets */
+ /** Lookup143: xcm::v3::multiasset::MultiAssets */
XcmV3MultiassetMultiAssets: "Vec",
- /** Lookup142: xcm::v3::multiasset::MultiAsset */
+ /** Lookup145: xcm::v3::multiasset::MultiAsset */
XcmV3MultiAsset: {
id: "XcmV3MultiassetAssetId",
fun: "XcmV3MultiassetFungibility",
},
- /** Lookup143: xcm::v3::multiasset::AssetId */
+ /** Lookup146: xcm::v3::multiasset::AssetId */
XcmV3MultiassetAssetId: {
_enum: {
Concrete: "StagingXcmV3MultiLocation",
Abstract: "[u8;32]",
},
},
- /** Lookup144: staging_xcm::v3::multilocation::MultiLocation */
+ /** Lookup147: staging_xcm::v3::multilocation::MultiLocation */
StagingXcmV3MultiLocation: {
parents: "u8",
interior: "XcmV3Junctions",
},
- /** Lookup145: xcm::v3::junctions::Junctions */
+ /** Lookup148: xcm::v3::junctions::Junctions */
XcmV3Junctions: {
_enum: {
Here: "Null",
@@ -1591,7 +1611,7 @@ export default {
X8: "(XcmV3Junction,XcmV3Junction,XcmV3Junction,XcmV3Junction,XcmV3Junction,XcmV3Junction,XcmV3Junction,XcmV3Junction)",
},
},
- /** Lookup146: xcm::v3::junction::Junction */
+ /** Lookup149: xcm::v3::junction::Junction */
XcmV3Junction: {
_enum: {
Parachain: "Compact",
@@ -1621,7 +1641,7 @@ export default {
GlobalConsensus: "XcmV3JunctionNetworkId",
},
},
- /** Lookup148: xcm::v3::junction::NetworkId */
+ /** Lookup151: xcm::v3::junction::NetworkId */
XcmV3JunctionNetworkId: {
_enum: {
ByGenesis: "[u8;32]",
@@ -1642,14 +1662,14 @@ export default {
PolkadotBulletin: "Null",
},
},
- /** Lookup149: xcm::v3::multiasset::Fungibility */
+ /** Lookup152: xcm::v3::multiasset::Fungibility */
XcmV3MultiassetFungibility: {
_enum: {
Fungible: "Compact",
NonFungible: "XcmV3MultiassetAssetInstance",
},
},
- /** Lookup150: xcm::v3::multiasset::AssetInstance */
+ /** Lookup153: xcm::v3::multiasset::AssetInstance */
XcmV3MultiassetAssetInstance: {
_enum: {
Undefined: "Null",
@@ -1660,7 +1680,7 @@ export default {
Array32: "[u8;32]",
},
},
- /** Lookup151: xcm::VersionedLocation */
+ /** Lookup154: xcm::VersionedLocation */
XcmVersionedLocation: {
_enum: {
__Unused0: "Null",
@@ -1670,7 +1690,7 @@ export default {
V4: "StagingXcmV4Location",
},
},
- /** Lookup152: pallet_assets::pallet::Event */
+ /** Lookup155: pallet_assets::pallet::Event */
PalletAssetsEvent: {
_enum: {
Created: {
@@ -1794,7 +1814,7 @@ export default {
},
},
},
- /** Lookup153: pallet_foreign_asset_creator::pallet::Event */
+ /** Lookup156: pallet_foreign_asset_creator::pallet::Event */
PalletForeignAssetCreatorEvent: {
_enum: {
ForeignAssetCreated: {
@@ -1815,7 +1835,7 @@ export default {
},
},
},
- /** Lookup154: pallet_asset_rate::pallet::Event */
+ /** Lookup157: pallet_asset_rate::pallet::Event */
PalletAssetRateEvent: {
_enum: {
AssetRateCreated: {
@@ -1835,7 +1855,7 @@ export default {
},
},
},
- /** Lookup156: pallet_message_queue::pallet::Event */
+ /** Lookup159: pallet_message_queue::pallet::Event */
PalletMessageQueueEvent: {
_enum: {
ProcessingFailed: {
@@ -1861,7 +1881,7 @@ export default {
},
},
},
- /** Lookup157: cumulus_primitives_core::AggregateMessageOrigin */
+ /** Lookup160: cumulus_primitives_core::AggregateMessageOrigin */
CumulusPrimitivesCoreAggregateMessageOrigin: {
_enum: {
Here: "Null",
@@ -1869,7 +1889,7 @@ export default {
Sibling: "u32",
},
},
- /** Lookup158: frame_support::traits::messages::ProcessMessageError */
+ /** Lookup161: frame_support::traits::messages::ProcessMessageError */
FrameSupportMessagesProcessMessageError: {
_enum: {
BadFormat: "Null",
@@ -1880,7 +1900,7 @@ export default {
StackLimitReached: "Null",
},
},
- /** Lookup159: pallet_xcm_core_buyer::pallet::Event */
+ /** Lookup162: pallet_xcm_core_buyer::pallet::Event */
PalletXcmCoreBuyerEvent: {
_enum: {
BuyCoreXcmSent: {
@@ -1899,11 +1919,11 @@ export default {
},
},
},
- /** Lookup161: pallet_root_testing::pallet::Event */
+ /** Lookup164: pallet_root_testing::pallet::Event */
PalletRootTestingEvent: {
_enum: ["DefensiveTestCall"],
},
- /** Lookup162: frame_system::Phase */
+ /** Lookup165: frame_system::Phase */
FrameSystemPhase: {
_enum: {
ApplyExtrinsic: "u32",
@@ -1911,17 +1931,17 @@ export default {
Initialization: "Null",
},
},
- /** Lookup166: frame_system::LastRuntimeUpgradeInfo */
+ /** Lookup169: frame_system::LastRuntimeUpgradeInfo */
FrameSystemLastRuntimeUpgradeInfo: {
specVersion: "Compact",
specName: "Text",
},
- /** Lookup168: frame_system::CodeUpgradeAuthorization */
+ /** Lookup171: frame_system::CodeUpgradeAuthorization */
FrameSystemCodeUpgradeAuthorization: {
codeHash: "H256",
checkVersion: "bool",
},
- /** Lookup169: frame_system::pallet::Call */
+ /** Lookup172: frame_system::pallet::Call */
FrameSystemCall: {
_enum: {
remark: {
@@ -1964,41 +1984,41 @@ export default {
},
},
},
- /** Lookup173: frame_system::limits::BlockWeights */
+ /** Lookup176: frame_system::limits::BlockWeights */
FrameSystemLimitsBlockWeights: {
baseBlock: "SpWeightsWeightV2Weight",
maxBlock: "SpWeightsWeightV2Weight",
perClass: "FrameSupportDispatchPerDispatchClassWeightsPerClass",
},
- /** Lookup174: frame_support::dispatch::PerDispatchClass */
+ /** Lookup177: frame_support::dispatch::PerDispatchClass */
FrameSupportDispatchPerDispatchClassWeightsPerClass: {
normal: "FrameSystemLimitsWeightsPerClass",
operational: "FrameSystemLimitsWeightsPerClass",
mandatory: "FrameSystemLimitsWeightsPerClass",
},
- /** Lookup175: frame_system::limits::WeightsPerClass */
+ /** Lookup178: frame_system::limits::WeightsPerClass */
FrameSystemLimitsWeightsPerClass: {
baseExtrinsic: "SpWeightsWeightV2Weight",
maxExtrinsic: "Option",
maxTotal: "Option",
reserved: "Option",
},
- /** Lookup177: frame_system::limits::BlockLength */
+ /** Lookup180: frame_system::limits::BlockLength */
FrameSystemLimitsBlockLength: {
max: "FrameSupportDispatchPerDispatchClassU32",
},
- /** Lookup178: frame_support::dispatch::PerDispatchClass */
+ /** Lookup181: frame_support::dispatch::PerDispatchClass */
FrameSupportDispatchPerDispatchClassU32: {
normal: "u32",
operational: "u32",
mandatory: "u32",
},
- /** Lookup179: sp_weights::RuntimeDbWeight */
+ /** Lookup182: sp_weights::RuntimeDbWeight */
SpWeightsRuntimeDbWeight: {
read: "u64",
write: "u64",
},
- /** Lookup180: sp_version::RuntimeVersion */
+ /** Lookup183: sp_version::RuntimeVersion */
SpVersionRuntimeVersion: {
specName: "Text",
implName: "Text",
@@ -2009,7 +2029,7 @@ export default {
transactionVersion: "u32",
stateVersion: "u8",
},
- /** Lookup184: frame_system::pallet::Error */
+ /** Lookup187: frame_system::pallet::Error */
FrameSystemError: {
_enum: [
"InvalidSpecName",
@@ -2023,49 +2043,49 @@ export default {
"Unauthorized",
],
},
- /** Lookup186: cumulus_pallet_parachain_system::unincluded_segment::Ancestor */
+ /** Lookup189: cumulus_pallet_parachain_system::unincluded_segment::Ancestor */
CumulusPalletParachainSystemUnincludedSegmentAncestor: {
usedBandwidth: "CumulusPalletParachainSystemUnincludedSegmentUsedBandwidth",
paraHeadHash: "Option",
consumedGoAheadSignal: "Option",
},
- /** Lookup187: cumulus_pallet_parachain_system::unincluded_segment::UsedBandwidth */
+ /** Lookup190: cumulus_pallet_parachain_system::unincluded_segment::UsedBandwidth */
CumulusPalletParachainSystemUnincludedSegmentUsedBandwidth: {
umpMsgCount: "u32",
umpTotalBytes: "u32",
hrmpOutgoing: "BTreeMap",
},
- /** Lookup189: cumulus_pallet_parachain_system::unincluded_segment::HrmpChannelUpdate */
+ /** Lookup192: cumulus_pallet_parachain_system::unincluded_segment::HrmpChannelUpdate */
CumulusPalletParachainSystemUnincludedSegmentHrmpChannelUpdate: {
msgCount: "u32",
totalBytes: "u32",
},
- /** Lookup194: polkadot_primitives::v8::UpgradeGoAhead */
+ /** Lookup197: polkadot_primitives::v8::UpgradeGoAhead */
PolkadotPrimitivesV8UpgradeGoAhead: {
_enum: ["Abort", "GoAhead"],
},
- /** Lookup195: cumulus_pallet_parachain_system::unincluded_segment::SegmentTracker */
+ /** Lookup198: cumulus_pallet_parachain_system::unincluded_segment::SegmentTracker */
CumulusPalletParachainSystemUnincludedSegmentSegmentTracker: {
usedBandwidth: "CumulusPalletParachainSystemUnincludedSegmentUsedBandwidth",
hrmpWatermark: "Option",
consumedGoAheadSignal: "Option",
},
- /** Lookup196: polkadot_primitives::v8::PersistedValidationData */
+ /** Lookup199: polkadot_primitives::v8::PersistedValidationData */
PolkadotPrimitivesV8PersistedValidationData: {
parentHead: "Bytes",
relayParentNumber: "u32",
relayParentStorageRoot: "H256",
maxPovSize: "u32",
},
- /** Lookup199: polkadot_primitives::v8::UpgradeRestriction */
+ /** Lookup202: polkadot_primitives::v8::UpgradeRestriction */
PolkadotPrimitivesV8UpgradeRestriction: {
_enum: ["Present"],
},
- /** Lookup200: sp_trie::storage_proof::StorageProof */
+ /** Lookup203: sp_trie::storage_proof::StorageProof */
SpTrieStorageProof: {
trieNodes: "BTreeSet",
},
- /** Lookup202: cumulus_pallet_parachain_system::relay_state_snapshot::MessagingStateSnapshot */
+ /** Lookup205: cumulus_pallet_parachain_system::relay_state_snapshot::MessagingStateSnapshot */
CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot: {
dmqMqcHead: "H256",
relayDispatchQueueRemainingCapacity:
@@ -2073,12 +2093,12 @@ export default {
ingressChannels: "Vec<(u32,PolkadotPrimitivesV8AbridgedHrmpChannel)>",
egressChannels: "Vec<(u32,PolkadotPrimitivesV8AbridgedHrmpChannel)>",
},
- /** Lookup203: cumulus_pallet_parachain_system::relay_state_snapshot::RelayDispatchQueueRemainingCapacity */
+ /** Lookup206: cumulus_pallet_parachain_system::relay_state_snapshot::RelayDispatchQueueRemainingCapacity */
CumulusPalletParachainSystemRelayStateSnapshotRelayDispatchQueueRemainingCapacity: {
remainingCount: "u32",
remainingSize: "u32",
},
- /** Lookup206: polkadot_primitives::v8::AbridgedHrmpChannel */
+ /** Lookup209: polkadot_primitives::v8::AbridgedHrmpChannel */
PolkadotPrimitivesV8AbridgedHrmpChannel: {
maxCapacity: "u32",
maxTotalSize: "u32",
@@ -2087,7 +2107,7 @@ export default {
totalSize: "u32",
mqcHead: "Option",
},
- /** Lookup207: polkadot_primitives::v8::AbridgedHostConfiguration */
+ /** Lookup210: polkadot_primitives::v8::AbridgedHostConfiguration */
PolkadotPrimitivesV8AbridgedHostConfiguration: {
maxCodeSize: "u32",
maxHeadDataSize: "u32",
@@ -2100,17 +2120,17 @@ export default {
validationUpgradeDelay: "u32",
asyncBackingParams: "PolkadotPrimitivesV8AsyncBackingAsyncBackingParams",
},
- /** Lookup208: polkadot_primitives::v8::async_backing::AsyncBackingParams */
+ /** Lookup211: polkadot_primitives::v8::async_backing::AsyncBackingParams */
PolkadotPrimitivesV8AsyncBackingAsyncBackingParams: {
maxCandidateDepth: "u32",
allowedAncestryLen: "u32",
},
- /** Lookup214: polkadot_core_primitives::OutboundHrmpMessage */
+ /** Lookup217: polkadot_core_primitives::OutboundHrmpMessage */
PolkadotCorePrimitivesOutboundHrmpMessage: {
recipient: "u32",
data: "Bytes",
},
- /** Lookup215: cumulus_pallet_parachain_system::pallet::Call */
+ /** Lookup218: cumulus_pallet_parachain_system::pallet::Call */
CumulusPalletParachainSystemCall: {
_enum: {
set_validation_data: {
@@ -2121,24 +2141,24 @@ export default {
},
},
},
- /** Lookup216: cumulus_primitives_parachain_inherent::ParachainInherentData */
+ /** Lookup219: cumulus_primitives_parachain_inherent::ParachainInherentData */
CumulusPrimitivesParachainInherentParachainInherentData: {
validationData: "PolkadotPrimitivesV8PersistedValidationData",
relayChainState: "SpTrieStorageProof",
downwardMessages: "Vec