Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
kylezs committed Oct 23, 2024
1 parent 7eec351 commit e628e89
Show file tree
Hide file tree
Showing 4 changed files with 296 additions and 222 deletions.
166 changes: 106 additions & 60 deletions state-chain/pallets/cf-elections/src/electoral_systems/mocks.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use crate::electoral_system::{
ConsensusStatus, ConsensusVotes, ElectionIdentifierOf, ElectoralReadAccess, ElectoralSystem,
ElectoralWriteAccess,
use std::collections::BTreeMap;

use crate::{
electoral_system::{
ConsensusStatus, ConsensusVotes, ElectionIdentifierOf, ElectionReadAccess,
ElectoralReadAccess, ElectoralSystem, ElectoralWriteAccess,
},
UniqueMonotonicIdentifier,
};
use cf_traits::mocks::time_source::Mock;
use frame_support::{CloneNoBound, DebugNoBound, EqNoBound, PartialEqNoBound};

pub mod access;
Expand Down Expand Up @@ -93,6 +99,18 @@ where
)
.unwrap();

// These are synchronised with respect to an election, so for simplicity in the tests
// we just assign them to an election.
MockStorageAccess::set_electoral_settings_for_election::<ES>(
election.election_identifier(),
setup.electoral_settings.clone(),
);

println!(
"Election identifier in with initial election: {:?}",
election.election_identifier()
);

// A new election should not have consensus at any authority count.
assert_eq!(election.check_consensus(None, ConsensusVotes { votes: vec![] }).unwrap(), None);

Expand All @@ -102,6 +120,10 @@ where
// We may want to test initialisation of elections within on finalise, so *don't* want to
// initialise an election in the utilities.
pub fn build(self) -> TestContext<ES> {
// We need to clear the storage at every build so if there are multiple test contexts used
// within a single test they do not conflict.
MockStorageAccess::clear_storage();

TestContext { setup: self.clone() }
}
}
Expand Down Expand Up @@ -178,31 +200,26 @@ impl<ES: ElectoralSystem> TestContext<ES> {
/// See [register_checks] and
#[track_caller]
pub fn test_on_finalize(
mut self,
self,
on_finalize_context: &ES::OnFinalizeContext,
pre_finalize_checks: impl FnOnce(&ElectoralSystemState<ES>),
post_finalize_checks: impl IntoIterator<Item = Check<ES>>,
) -> Self {
todo!()
// let pre_finalize = self.electoral_access.clone();
let pre_finalize = ElectoralSystemState::<ES>::load_state();
// // TODO: Move 'hook' static local checks into MockAccess so we can remove this.
// pre_finalize_checks(&pre_finalize);

// self.electoral_access.finalize_elections(on_finalize_context).unwrap();
pre_finalize_checks(&pre_finalize);

// let post_finalize = self.electoral_access.clone();
// for check in post_finalize_checks {
// check.check(&pre_finalize, &post_finalize);
// }
// self
}
ES::on_finalize::<MockAccess<ES>>(
MockStorageAccess::election_identifiers::<ES>(),
on_finalize_context,
)
.unwrap();

pub fn run_checks(mut self, checks: impl IntoIterator<Item = Check<ES>>) -> Self {
// let pre_finalize = self.electoral_access.clone();
// for check in checks {
// check.check(&pre_finalize, &pre_finalize);
// }
// self
todo!()
let post_finalize = ElectoralSystemState::<ES>::load_state();
for check in post_finalize_checks {
check.check(&pre_finalize, &post_finalize);
}
self
}
}

Expand Down Expand Up @@ -245,14 +262,14 @@ macro_rules! register_checks {
(
$system:ident {
$(
$check_name:ident() $check_body:block
$check_name:ident($arg_1:ident, $arg_2:ident) $check_body:block
),+ $(,)*
}
) => {
impl Check<$system>{
$(
pub fn $check_name() -> Self {
Self::new(#[track_caller] || $check_body)
Self::new(#[track_caller] |$arg_1, $arg_2| $check_body)
}
)+
}
Expand All @@ -270,56 +287,85 @@ macro_rules! register_checks {
{
$(
pub fn $check_name() -> Self {
Self::new(#[track_caller] || $check_body)
Self::new(#[track_caller] |$arg_1, $arg_2| $check_body)
}
)+
}
};
}

// Simple examples with register_check:
// register_checks! {
// assert_unchanged(pre_finalize, post_finalize) {
// assert_eq!(pre_finalize, post_finalize);
// },
// // check state is deleted, rather than can't construct election
// last_election_deleted(pre_finalize, post_finalize) {
// todo!()
// },
// election_id_incremented(pre_finalize, post_finalize) {
// assert_eq!(
// MockStorageAccess::next_umi().next_identifier().unwrap(),
// MockStorageAccess::next_umi(),
// "Expected the election id to be incremented.",
// );
// },
// all_elections_deleted(pre_finalize, post_finalize) {
// assert!(
// !pre_finalize.election_identifiers().is_empty(),
// "Expected elections before finalization. This check makes no sense otherwise.",
// );
// assert!(
// post_finalize.election_identifiers().is_empty(),
// "Expected no elections after finalization.",
// );
// },
// }

type CheckFn = Box<dyn Fn()>;
register_checks! {
assert_unchanged(pre_finalize, post_finalize) {
assert_eq!(pre_finalize, post_finalize);
},
// check state is deleted, rather than can't construct election
last_election_deleted(pre_finalize, post_finalize) {
let last_election_id = pre_finalize.election_identifiers.last().expect("Expected an election before finalization");
assert!(
!post_finalize.election_identifiers.contains(last_election_id),
"Last election should have been deleted.",
);
},
election_id_incremented(pre_finalize, post_finalize) {
assert_eq!(
pre_finalize.next_umi.next_identifier().unwrap(),
post_finalize.next_umi,
"Expected the election id to be incremented.",
);
},
all_elections_deleted(pre_finalize, post_finalize) {
assert!(
!pre_finalize.election_identifiers.is_empty(),
"Expected elections before finalization. This check makes no sense otherwise.",
);
assert!(
post_finalize.election_identifiers.is_empty(),
"Expected no elections after finalization.",
);
},
}

#[derive(CloneNoBound, DebugNoBound, PartialEqNoBound, EqNoBound)]
pub struct ElectoralSystemState<ES: ElectoralSystem> {
pub unsynchronised_state: ES::ElectoralUnsynchronisedState,
pub unsynchronised_state_map: BTreeMap<Vec<u8>, Option<Vec<u8>>>,
pub unsynchronised_settings: ES::ElectoralUnsynchronisedSettings,
pub election_identifiers: Vec<ElectionIdentifierOf<ES>>,
pub next_umi: UniqueMonotonicIdentifier,
}

impl<ES: ElectoralSystem> ElectoralSystemState<ES> {
pub fn load_state() -> Self {
Self {
unsynchronised_settings: MockStorageAccess::unsynchronised_settings::<ES>(),
unsynchronised_state: MockStorageAccess::unsynchronised_state::<ES>(),
unsynchronised_state_map: MockStorageAccess::raw_unsynchronised_state_map::<ES>(),
election_identifiers: MockStorageAccess::election_identifiers::<ES>(),
next_umi: MockStorageAccess::next_umi(),
}
}
}

type CheckFn<ES> = Box<dyn Fn(&ElectoralSystemState<ES>, &ElectoralSystemState<ES>)>;

/// Checks that can be applied post-finalization.
pub struct Check<ES: ElectoralSystem> {
check_fn: Box<dyn Fn()>,
// Ensures that checks for different ElectoralSystems do not get mixed up.
phantom: core::marker::PhantomData<ES>,
check_fn: CheckFn<ES>,
}

impl<ES: ElectoralSystem> Check<ES> {
pub fn new(check_fn: impl Fn() + 'static) -> Self {
Self { check_fn: Box::new(check_fn), phantom: Default::default() }
pub fn new(
check_fn: impl Fn(&ElectoralSystemState<ES>, &ElectoralSystemState<ES>) + 'static,
) -> Self {
Self { check_fn: Box::new(check_fn) }
}

pub fn check(&self) {
(self.check_fn)()
pub fn check(
&self,
pre_finalize: &ElectoralSystemState<ES>,
post_finalize: &ElectoralSystemState<ES>,
) {
(self.check_fn)(pre_finalize, post_finalize)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ macro_rules! impl_read_access {
previous_consensus: Option<&ES::Consensus>,
votes: ConsensusVotes<ES>,
) -> Result<Option<ES::Consensus>, CorruptStorageError> {
println!("Calling check consensus on the electoral system struct");
ES::check_consensus(self, previous_consensus, votes)
}
}
Expand All @@ -82,6 +83,7 @@ impl_read_access!(MockWriteAccess<ES>);
thread_local! {
pub static ELECTION_STATE: RefCell<BTreeMap<Vec<u8>, Vec<u8>>> = const { RefCell::new(BTreeMap::new()) };
pub static ELECTION_PROPERTIES: RefCell<BTreeMap<Vec<u8>, Vec<u8>>> = const { RefCell::new(BTreeMap::new()) };
pub static ELECTORAL_SETTINGS: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
// The electoral settings for a particular election
pub static ELECTION_SETTINGS: RefCell<BTreeMap<Vec<u8>, Vec<u8>>> = const { RefCell::new(BTreeMap::new()) };
pub static ELECTORAL_UNSYNCHRONISED_SETTINGS: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
Expand Down Expand Up @@ -125,19 +127,6 @@ impl<ES: ElectoralSystem> ElectionWriteAccess for MockWriteAccess<ES> {
}
}

// impl<ES: ElectoralSystem> MockAccess<ES> {
// pub fn finalize_elections(
// &mut self,
// context: &ES::OnFinalizeContext,
// ) -> Result<ES::OnFinalizeReturn, CorruptStorageError> {
// ES::on_finalize(self.elections.keys().cloned().collect(), context)
// }

// pub fn election_identifiers(&self) -> Vec<ElectionIdentifierOf<ES>> {
// self.elections.keys().cloned().collect()
// }
// }

impl<ES: ElectoralSystem> ElectoralReadAccess for MockAccess<ES> {
type ElectoralSystem = ES;
type ElectionReadAccess = MockReadAccess<ES>;
Expand Down Expand Up @@ -248,6 +237,32 @@ impl MockStorageAccess {
});
}

pub fn set_electoral_settings<ES: ElectoralSystem>(
settings: <ES as ElectoralSystem>::ElectoralSettings,
) {
ELECTORAL_SETTINGS.with(|old_settings| {
let mut settings_ref = old_settings.borrow_mut();
*settings_ref = settings.encode();
});
}

pub fn electoral_settings<ES: ElectoralSystem>() -> ES::ElectoralSettings {
ELECTORAL_SETTINGS.with(|settings| {
let settings_ref = settings.borrow();
ES::ElectoralSettings::decode(&mut &settings_ref[..]).unwrap()
})
}

pub fn set_electoral_settings_for_election<ES: ElectoralSystem>(
identifier: ElectionIdentifierOf<ES>,
settings: <ES as ElectoralSystem>::ElectoralSettings,
) {
ELECTION_SETTINGS.with(|old_settings| {
let mut settings_ref = old_settings.borrow_mut();
settings_ref.insert(identifier.encode(), settings.encode());
});
}

pub fn electoral_settings_for_election<ES: ElectoralSystem>(
identifier: ElectionIdentifierOf<ES>,
) -> <ES as ElectoralSystem>::ElectoralSettings {
Expand Down Expand Up @@ -353,6 +368,14 @@ impl MockStorageAccess {
})
}

pub fn raw_unsynchronised_state_map<ES: ElectoralSystem>() -> BTreeMap<Vec<u8>, Option<Vec<u8>>>
{
ELECTORAL_UNSYNCHRONISED_STATE_MAP.with(|old_state_map| {
let state_map_ref = old_state_map.borrow();
state_map_ref.clone()
})
}

pub fn set_unsynchronised_state_map<ES: ElectoralSystem>(
key: ES::ElectoralUnsynchronisedStateMapKey,
value: Option<ES::ElectoralUnsynchronisedStateMapValue>,
Expand All @@ -377,6 +400,7 @@ impl MockStorageAccess {
identifier: ElectionIdentifierOf<ES>,
status: ConsensusStatus<ES::Consensus>,
) {
println!("Setting consensus status to {:?} for {:?}", status, identifier);
CONSENSUS_STATUS.with(|old_consensus| {
let mut consensus_ref = old_consensus.borrow_mut();
consensus_ref.insert(identifier.encode(), status.encode());
Expand All @@ -393,7 +417,7 @@ impl MockStorageAccess {
.get(&identifier.encode())
.map(|v| ConsensusStatus::<ES::Consensus>::decode(&mut &v[..]).unwrap())
})
.unwrap()
.unwrap_or(ConsensusStatus::None)
}

pub fn new_election<ES: ElectoralSystem>(
Expand All @@ -407,6 +431,13 @@ impl MockStorageAccess {

Self::set_election_properties::<ES>(election_identifier, properties);
Self::set_state::<ES>(election_identifier, state);
// These are normally stored once and synchronised by election identifier. In the tests we
// simplify this by just storing the electoral settings (that would be fetched by
// resolving the synchronisation) alongside the election.
Self::set_electoral_settings_for_election::<ES>(
election_identifier,
Self::electoral_settings::<ES>(),
);

election_identifier
}
Expand Down
Loading

0 comments on commit e628e89

Please sign in to comment.