Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store collator fullness in collator_assignment pallet #672

Merged
merged 7 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 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,9 @@ pub mod pallet {
#[pallet::storage]
pub(crate) type Randomness<T: Config> = StorageValue<_, [u8; 32], ValueQuery>;

#[pallet::storage]
pub type CollatorFullnessRatio<T: Config> = StorageValue<_, Perbill, OptionQuery>;
girazoki marked this conversation as resolved.
Show resolved Hide resolved

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

Expand Down Expand Up @@ -387,6 +390,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 +430,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
Loading