Skip to content

Commit

Permalink
Store collator fullness in collator_assignment pallet (#672)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmpolaczyk authored Sep 3, 2024
1 parent e189687 commit 944c771
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 62 deletions.
35 changes: 34 additions & 1 deletion pallets/collator-assignment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ use {
rand_chacha::ChaCha20Rng,
sp_runtime::{
traits::{AtLeast32BitUnsigned, One, Zero},
Saturating,
Perbill, Saturating,
},
sp_std::{collections::btree_set::BTreeSet, fmt::Debug, prelude::*, vec},
tp_traits::{
Expand Down Expand Up @@ -140,6 +140,10 @@ pub mod pallet {
#[pallet::storage]
pub(crate) type Randomness<T: Config> = StorageValue<_, [u8; 32], ValueQuery>;

/// Ratio of assigned collators to max collators.
#[pallet::storage]
pub type CollatorFullnessRatio<T: Config> = StorageValue<_, Perbill, OptionQuery>;

#[pallet::call]
impl<T: Config> Pallet<T> {}

Expand Down Expand Up @@ -387,6 +391,11 @@ pub mod pallet {
}
}

Self::store_collator_fullness(
&new_assigned,
T::HostConfiguration::max_collators(target_session_index),
);

let mut pending = PendingCollatorContainerChain::<T>::get();

let old_assigned_changed = old_assigned != new_assigned;
Expand Down Expand Up @@ -422,6 +431,30 @@ pub mod pallet {
}
}

/// Count number of collators assigned to any chain, divide that by `max_collators` and store
/// in pallet storage.
fn store_collator_fullness(
new_assigned: &AssignedCollators<T::AccountId>,
max_collators: u32,
) {
// Count number of assigned collators
let mut num_collators = 0;
num_collators += new_assigned.orchestrator_chain.len();
for (_para_id, collators) in &new_assigned.container_chains {
num_collators += collators.len();
}

let mut num_collators = num_collators as u32;
if num_collators > max_collators {
// Shouldn't happen but just in case
num_collators = max_collators;
}

let ratio = Perbill::from_rational(num_collators, max_collators);

CollatorFullnessRatio::<T>::put(ratio);
}

// Returns the assigned collators as read from storage.
// If there is any item in PendingCollatorContainerChain, returns that element.
// Otherwise, reads and returns the current CollatorContainerChain
Expand Down
47 changes: 32 additions & 15 deletions pallets/collator-assignment/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use {
sp_core::{Get, H256},
sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
BuildStorage,
BuildStorage, Perbill,
},
sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet},
tp_traits::{
Expand Down Expand Up @@ -115,7 +115,6 @@ pub mod mock_data {
}

#[derive(
Default,
Clone,
Encode,
Decode,
Expand All @@ -126,10 +125,12 @@ pub mod mock_data {
serde::Deserialize,
)]
pub struct Mocks {
pub max_collators: u32,
pub min_orchestrator_chain_collators: u32,
pub max_orchestrator_chain_collators: u32,
pub collators_per_container: u32,
pub collators_per_parathread: u32,
pub target_container_chain_fullness: Perbill,
pub collators: Vec<u64>,
pub container_chains: Vec<u32>,
pub parathreads: Vec<u32>,
Expand All @@ -140,6 +141,27 @@ pub struct Mocks {
pub assignment_hook_errors: bool,
}

impl Default for Mocks {
fn default() -> Self {
Self {
max_collators: Default::default(),
min_orchestrator_chain_collators: 1,
max_orchestrator_chain_collators: Default::default(),
collators_per_container: Default::default(),
collators_per_parathread: Default::default(),
target_container_chain_fullness: Perbill::from_percent(80),
// Initialize collators with 1 collator to avoid error `ZeroCollators` in session 0
collators: vec![100],
container_chains: Default::default(),
parathreads: Default::default(),
random_seed: Default::default(),
full_rotation_period: Default::default(),
apply_tip: Default::default(),
assignment_hook_errors: Default::default(),
}
}
}

impl mock_data::Config for Test {}

// In tests, we ignore the session_index param, so changes to the configuration are instant
Expand All @@ -152,7 +174,7 @@ parameter_types! {

impl pallet_collator_assignment::GetHostConfiguration<u32> for HostConfigurationGetter {
fn max_collators(_session_index: u32) -> u32 {
unimplemented!()
MockData::mock().max_collators
}

fn min_collators_for_orchestrator(_session_index: u32) -> u32 {
Expand All @@ -170,6 +192,11 @@ impl pallet_collator_assignment::GetHostConfiguration<u32> for HostConfiguration
fn collators_per_parathread(_session_index: u32) -> u32 {
MockData::mock().collators_per_parathread
}

fn target_container_chain_fullness(_session_index: u32) -> Perbill {
MockData::mock().target_container_chain_fullness
}

#[cfg(feature = "runtime-benchmarks")]
fn set_host_configuration(_session_index: u32) {
MockData::mutate(|mocks| {
Expand Down Expand Up @@ -302,20 +329,10 @@ impl pallet_collator_assignment::Config for Test {

// Build genesis storage according to the mock runtime.
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut ext: sp_io::TestExternalities = system::GenesisConfig::<Test>::default()
system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap()
.into();

ext.execute_with(|| {
MockData::mutate(|mocks| {
// Initialize collators with 1 collator to avoid error `ZeroCollators` in session 0
mocks.collators = vec![100];
mocks.min_orchestrator_chain_collators = 1;
})
});

ext
.into()
}

pub trait GetCollators<AccountId, SessionIndex> {
Expand Down
59 changes: 16 additions & 43 deletions pallets/configuration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,10 +537,8 @@ pub mod pallet {
pub fn pending_configs() -> Vec<(T::SessionIndex, HostConfiguration)> {
PendingConfigs::<T>::get()
}
}

impl<T: Config> GetHostConfiguration<T::SessionIndex> for Pallet<T> {
fn max_collators(session_index: T::SessionIndex) -> u32 {
pub fn config_at_session(session_index: T::SessionIndex) -> HostConfiguration {
let (past_and_present, _) = Pallet::<T>::pending_configs()
.into_iter()
.partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
Expand All @@ -550,59 +548,34 @@ pub mod pallet {
} else {
Pallet::<T>::config()
};
config.max_collators

config
}
}

fn collators_per_container(session_index: T::SessionIndex) -> u32 {
let (past_and_present, _) = Pallet::<T>::pending_configs()
.into_iter()
.partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
impl<T: Config> GetHostConfiguration<T::SessionIndex> for Pallet<T> {
fn max_collators(session_index: T::SessionIndex) -> u32 {
Self::config_at_session(session_index).max_collators
}

let config = if let Some(last) = past_and_present.last() {
last.1.clone()
} else {
Pallet::<T>::config()
};
config.collators_per_container
fn collators_per_container(session_index: T::SessionIndex) -> u32 {
Self::config_at_session(session_index).collators_per_container
}

fn collators_per_parathread(session_index: T::SessionIndex) -> u32 {
let (past_and_present, _) = Pallet::<T>::pending_configs()
.into_iter()
.partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);

let config = if let Some(last) = past_and_present.last() {
last.1.clone()
} else {
Pallet::<T>::config()
};
config.collators_per_parathread
Self::config_at_session(session_index).collators_per_parathread
}

fn min_collators_for_orchestrator(session_index: T::SessionIndex) -> u32 {
let (past_and_present, _) = Pallet::<T>::pending_configs()
.into_iter()
.partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);

let config = if let Some(last) = past_and_present.last() {
last.1.clone()
} else {
Pallet::<T>::config()
};
config.min_orchestrator_collators
Self::config_at_session(session_index).min_orchestrator_collators
}

fn max_collators_for_orchestrator(session_index: T::SessionIndex) -> u32 {
let (past_and_present, _) = Pallet::<T>::pending_configs()
.into_iter()
.partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
Self::config_at_session(session_index).max_orchestrator_collators
}

let config = if let Some(last) = past_and_present.last() {
last.1.clone()
} else {
Pallet::<T>::config()
};
config.max_orchestrator_collators
fn target_container_chain_fullness(session_index: T::SessionIndex) -> Perbill {
Self::config_at_session(session_index).target_container_chain_fullness
}
}
}
3 changes: 2 additions & 1 deletion primitives/traits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use {
sp_runtime::{
app_crypto::sp_core,
traits::{CheckedAdd, CheckedMul},
ArithmeticError,
ArithmeticError, Perbill,
},
sp_std::{collections::btree_set::BTreeSet, vec::Vec},
};
Expand Down Expand Up @@ -227,6 +227,7 @@ pub trait GetHostConfiguration<SessionIndex> {
fn max_collators_for_orchestrator(session_index: SessionIndex) -> u32;
fn collators_per_container(session_index: SessionIndex) -> u32;
fn collators_per_parathread(session_index: SessionIndex) -> u32;
fn target_container_chain_fullness(session_index: SessionIndex) -> Perbill;
#[cfg(feature = "runtime-benchmarks")]
fn set_host_configuration(_session_index: SessionIndex) {}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import "@tanssi/api-augment";
import { describeSuite, expect, beforeAll } from "@moonwall/cli";
import { ApiPromise } from "@polkadot/api";
import { jumpBlocks, jumpSessions, jumpToSession } from "util/block";
import { filterAndApply } from "@moonwall/util";
import { EventRecord } from "@polkadot/types/interfaces";
import { bool, u32, u8, Vec } from "@polkadot/types-codec";

describeSuite({
id: "DT0801",
title: "Collator assignment tests",
foundationMethods: "dev",

testCases: ({ it, context }) => {
let polkadotJs: ApiPromise;

beforeAll(async () => {
polkadotJs = context.polkadotJs();
});

it({
id: "E01",
title: "Collator should rotate",
test: async function () {
const fullRotationPeriod = (await context.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()
);

// As randomness isn't deterministic in starlight we can't be
// 100% certain that the assignation will indeed change. So the
// best we can do is verify that the pending rotation event for
// next session is emitted and is a full rotation as expected
const events = await polkadotJs.query.system.events();
const filteredEvents = filterAndApply(
events,
"collatorAssignment",
["NewPendingAssignment"],
({ event }: EventRecord) =>
event.data as unknown as { randomSeed: Vec<u8>; fullRotation: bool; targetSession: u32 }
);
expect(filteredEvents[0].fullRotation.toJSON()).toBe(true);

// 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();
// TODO: in starlight isEmpty == false because we have randomness there
// In dancebox dev tests there is no rotation because there is no randomness
expect(assignmentRandomness.isEmpty).toBe(true);
},
});
},
});
Loading

0 comments on commit 944c771

Please sign in to comment.