From 17375b32b77453a79bf57fa91e7c32aac14c69e6 Mon Sep 17 00:00:00 2001 From: kylezs Date: Wed, 11 Dec 2024 17:59:00 +0100 Subject: [PATCH] Divide logic between enum variants (#5488) * wip * divide logic between enum variants --- state-chain/chains/src/lib.rs | 4 +- .../src/electoral_systems/block_witnesser.rs | 190 ++--- .../tests/block_witnesser.rs | 763 +++++++++--------- 3 files changed, 469 insertions(+), 488 deletions(-) diff --git a/state-chain/chains/src/lib.rs b/state-chain/chains/src/lib.rs index 25e3f02a99..5ed284f06e 100644 --- a/state-chain/chains/src/lib.rs +++ b/state-chain/chains/src/lib.rs @@ -94,8 +94,8 @@ pub mod witness_period { } impl BlockWitnessRange { - pub fn into_range_inclusive(range: BlockWitnessRange) -> RangeInclusive { - range.start..=range.end + pub fn into_range_inclusive(self) -> RangeInclusive { + self.start..=self.end } pub fn start(&self) -> &I { diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/block_witnesser.rs b/state-chain/pallets/cf-elections/src/electoral_systems/block_witnesser.rs index 1b726fd232..5dcd478ed9 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/block_witnesser.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/block_witnesser.rs @@ -94,9 +94,6 @@ pub struct BlockWitnesserState { // what about a reorg?????? pub last_block_election_emitted_for: ChainBlockNumber, - // Last block we received from context. - pub last_block_received: ChainBlockNumber, - // The block roots (of a block range) that we received non empty block data for, but still // requires processing. // NOTE: It is possible for block data to arrive and then be partially processed. In this case, @@ -172,123 +169,104 @@ impl< election_identifiers: Vec>, chain_progress: &Self::OnFinalizeContext, ) -> Result { - let witness_range = match chain_progress { - ChainProgress::Reorg(range_inclusive) => range_inclusive, - ChainProgress::Continuous(range_inclusive) => range_inclusive, - ChainProgress::None(_) => return Ok(()), /* TODO actually we have to do something - * here... */ - ChainProgress::WaitingForFirstConsensus => return Ok(()), - }; - - ensure!( - ::is_block_witness_root(*witness_range.start()) && - ::is_block_witness_root(*witness_range.end()), - { - log::error!( - "Start and end of range must be block witness roots: {:?}", - *witness_range.start() - ); - CorruptStorageError::new() - } - ); - - // if the start of the range is - let BlockWitnesserState { mut last_block_election_emitted_for, - last_block_received, mut open_elections, mut unprocessed_data, } = ElectoralAccess::unsynchronised_state()?; - // e.g. the last block received is 20, we now have a range 19..21. This means, 19 and 20 are - // reorg'd. - log::info!("Last block received: {:?}", last_block_received); - log::info!("Witness range is: {:?}", witness_range); - - // How do we handle the case where there's a reorg on the same block. We receive 4 and then - // we get 4 again. - if *witness_range.start() <= last_block_received { - log::info!("Reoooooorg"); - // All ongoing elections are now invalid, we will recreate the elections, once the block - // height witnesser passes throught those block heights again, so the engines will - // revote. - election_identifiers.into_iter().for_each(|election_identifier| { - ElectoralAccess::election_mut(election_identifier).delete(); - }); - - // Create a new election for each one in the range? - - for root in - witness_range.clone().step_by(Into::::into(Chain::WITNESS_PERIOD) as usize) - { - log::info!("New election for root: {:?}", root); - ElectoralAccess::new_election( - (), - ( - Chain::block_witness_range(root).into(), - ElectionGenerator::generate_election_properties(root), - ), - (), - )?; - last_block_election_emitted_for = root; - open_elections = open_elections.saturating_add(1); - } - - // NB: We do not clear any of the unprocessed data here. This is because we need to - // prevent double dispatches. By keeping the state, if we have a reorg we can check - // against the state in the process_block_data hook to ensure we don't double - // dispatch. - } else { - // ==== No reorg case ==== + let mut remaining_election_identifiers = election_identifiers.clone(); - // We could have multiple elections going, for different block/ranges. - for election_identifier in election_identifiers { - let election_access = ElectoralAccess::election_mut(election_identifier); - if let Some(block_data) = election_access.check_consensus()?.has_consensus() { - let (root_block_number, _extra_properties) = election_access.properties()?; + let last_seen_root = match chain_progress { + ChainProgress::WaitingForFirstConsensus => return Ok(()), + ChainProgress::Reorg(reorg_range) => { + // Delete any elections that are ongoing for any blocks in the reorg range. + for (i, election_identifier) in election_identifiers.into_iter().enumerate() { + let election = ElectoralAccess::election_mut(election_identifier); + let properties = election.properties()?; + if properties.0.into_range_inclusive() == *reorg_range { + election.delete(); + open_elections = open_elections.saturating_sub(1); + remaining_election_identifiers.remove(i); + } + } - election_access.delete(); + // TODO: Wrap with safe mode, no new elections. + for root in + reorg_range.clone().step_by(Into::::into(Chain::WITNESS_PERIOD) as usize) + { + log::info!("New election for root: {:?}", root); + ElectoralAccess::new_election( + (), + ( + Chain::block_witness_range(root).into(), + ElectionGenerator::generate_election_properties(root), + ), + (), + )?; + last_block_election_emitted_for = root; + open_elections = open_elections.saturating_add(1); + } - open_elections = open_elections.saturating_sub(1); + // NB: We do not clear any of the unprocessed data here. This is because we need to + // prevent double dispatches. By keeping the state, if we have a reorg we can check + // against the state in the process_block_data hook to ensure we don't double + // dispatch. + *reorg_range.end() + }, + ChainProgress::None(last_block_root_seen) => *last_block_root_seen, + ChainProgress::Continuous(witness_range) => *witness_range.start(), + }; - unprocessed_data.push((*root_block_number.start(), block_data)); - } + ensure!(Chain::is_block_witness_root(last_seen_root), { + log::error!("Last seen block root is not a block witness root"); + CorruptStorageError::new() + }); + + // Start any new elections if we can. + // TODO: Wrap in safe mode + let settings = ElectoralAccess::unsynchronised_settings()?; + + for range_root in (last_block_election_emitted_for.saturating_add(Chain::WITNESS_PERIOD)..= + last_seen_root) + .step_by(Into::::into(Chain::WITNESS_PERIOD) as usize) + .take(settings.max_concurrent_elections.saturating_sub(open_elections) as usize) + { + ElectoralAccess::new_election( + (), + ( + Chain::block_witness_range(range_root).into(), + ElectionGenerator::generate_election_properties(range_root), + ), + (), + )?; + last_block_election_emitted_for = range_root; + open_elections = open_elections.saturating_add(1); + } + + // We always want to check with remaining elections we can resolve, note the ones we just + // initiated won't be included here, which is intention, they can't have come to consensus + // yet. + for election_identifier in remaining_election_identifiers { + let election_access = ElectoralAccess::election_mut(election_identifier); + if let Some(block_data) = election_access.check_consensus()?.has_consensus() { + let (root_block_number, _extra_properties) = election_access.properties()?; + + election_access.delete(); + + open_elections = open_elections.saturating_sub(1); + unprocessed_data.push((*root_block_number.start(), block_data)); } + } - // If we haven't done any new elections, since the last run, there's not really any - // reason to run this again, so we could probably optimise this. - - unprocessed_data = - BlockDataProcessor::process_block_data(*witness_range.end(), unprocessed_data); - - debug_assert!( - ::is_block_witness_root(last_block_election_emitted_for), - "We only store this if it passes the original block witness root check" - ); - - let settings = ElectoralAccess::unsynchronised_settings()?; - - for range_root in (last_block_election_emitted_for - .saturating_add(Chain::WITNESS_PERIOD)..=*witness_range.end()) - .step_by(Into::::into(Chain::WITNESS_PERIOD) as usize) - .take(settings.max_concurrent_elections.saturating_sub(open_elections) as usize) - { - ElectoralAccess::new_election( - (), - ( - Chain::block_witness_range(range_root).into(), - ElectionGenerator::generate_election_properties(range_root), - ), - (), - )?; - last_block_election_emitted_for = range_root; - open_elections = open_elections.saturating_add(1); - } - }; + unprocessed_data = BlockDataProcessor::process_block_data(last_seen_root, unprocessed_data); + + debug_assert!( + ::is_block_witness_root(last_block_election_emitted_for), + "We only store this if it passes the original block witness root check" + ); ElectoralAccess::set_unsynchronised_state(BlockWitnesserState { - last_block_received: *witness_range.end(), open_elections, last_block_election_emitted_for, unprocessed_data, diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/tests/block_witnesser.rs b/state-chain/pallets/cf-elections/src/electoral_systems/tests/block_witnesser.rs index cec41197c6..76ee8f0a2d 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/tests/block_witnesser.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/tests/block_witnesser.rs @@ -20,7 +20,7 @@ use super::{ }; use crate::{ electoral_system::{ConsensusVote, ConsensusVotes, ElectoralSystem}, - electoral_systems::block_witnesser::*, + electoral_systems::{block_height_tracking::ChainProgress, block_witnesser::*}, }; use cf_chains::{mocks::MockEthereum, Chain}; use sp_std::collections::btree_set::BTreeSet; @@ -211,7 +211,7 @@ fn no_block_data_success() { }) .build() .test_on_finalize( - &range_n(INIT_LAST_BLOCK_RECEIVED + 1), + &ChainProgress::Continuous(range_n(INIT_LAST_BLOCK_RECEIVED + 1)), |_| {}, vec![ Check::::generate_election_properties_called_n_times(1), @@ -240,7 +240,9 @@ fn creates_multiple_elections_below_maximum_when_required() { .build() .test_on_finalize( // Process multiple elections, but still less than the maximum concurrent - &range_n(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS as u64)), + &ChainProgress::Continuous(range_n( + INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS as u64), + )), |pre_state| { assert_eq!(pre_state.unsynchronised_state.open_elections, 0); }, @@ -266,7 +268,8 @@ fn creates_multiple_elections_below_maximum_when_required() { // no progress on external chain but on finalize called again ]) .test_on_finalize( - &range_n(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS as u64)), + // same block again + &ChainProgress::None(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS as u64)), |pre_state| { assert_eq!(pre_state.unsynchronised_state.open_elections, NUMBER_OF_ELECTIONS); }, @@ -281,379 +284,379 @@ fn creates_multiple_elections_below_maximum_when_required() { ); } -#[test] -fn creates_multiple_elections_limited_by_maximum() { - const INIT_LAST_BLOCK_RECEIVED: ChainBlockNumber = 0; - const NUMBER_OF_ELECTIONS_REQUIRED: ElectionCount = MAX_CONCURRENT_ELECTIONS * 2; - let consensus_resolutions: Vec<( - ConsensusVotes, - Option<::Consensus>, - )> = vec![ - create_votes_expectation(vec![]), - create_votes_expectation(vec![1, 3, 4]), - // no progress on external chain but on finalize called again - ]; - let number_of_resolved_elections = consensus_resolutions.len(); - TestSetup::::default() - .with_unsynchronised_state(BlockWitnesserState { - last_block_received: INIT_LAST_BLOCK_RECEIVED, - ..BlockWitnesserState::default() - }) - .with_unsynchronised_settings(BlockWitnesserSettings { - max_concurrent_elections: MAX_CONCURRENT_ELECTIONS, - }) - .build() - .test_on_finalize( - // Process multiple elections, but still elss than the maximum concurrent - &range_n(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS_REQUIRED as u64)), - |pre_state| { - assert_eq!(pre_state.unsynchronised_state.open_elections, 0); - }, - vec![ - Check::::generate_election_properties_called_n_times( - MAX_CONCURRENT_ELECTIONS as u8, - ), - Check::::number_of_open_elections_is( - MAX_CONCURRENT_ELECTIONS, - ), - ], - ) - // Only resolve two of the elections. The last 3 are unresolved at this point. But - // we now have space to start new elections. - .expect_consensus_multi(consensus_resolutions) - .test_on_finalize( - &range_n(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS_REQUIRED as u64)), - |pre_state| { - assert_eq!(pre_state.unsynchronised_state.open_elections, MAX_CONCURRENT_ELECTIONS); - }, - vec![ - // Still no extra elections created. - Check::::generate_election_properties_called_n_times( - MAX_CONCURRENT_ELECTIONS as u8 + number_of_resolved_elections as u8, - ), - // we should have resolved two elections - Check::::number_of_open_elections_is( - MAX_CONCURRENT_ELECTIONS, - ), - ], - ); -} - -#[test] -fn reorg_clears_on_going_elections_and_continues() { - const INIT_LAST_BLOCK_RECEIVED: ChainBlockNumber = 10; - const NEXT_BLOCK_NUMBER: ChainBlockNumber = - INIT_LAST_BLOCK_RECEIVED + MAX_CONCURRENT_ELECTIONS as u64; - const REORG_LENGTH: ChainBlockNumber = 3; - - let all_votes = (INIT_LAST_BLOCK_RECEIVED + 1..=NEXT_BLOCK_NUMBER) - .map(|_| create_votes_expectation(vec![5, 6, 7])) - .collect::>(); - - // We have already emitted an election for `INIT_LAST_BLOCK_RECEIVED` (see TestSetup below), so - // we add 1. - let expected_unprocessed_data = (INIT_LAST_BLOCK_RECEIVED + 1..=NEXT_BLOCK_NUMBER) - .map(|i| (i, vec![5, 6, 7])) - .collect::>(); - - let mut block_after_reorg_block_unprocessed_data = expected_unprocessed_data.clone(); - block_after_reorg_block_unprocessed_data - .push(((NEXT_BLOCK_NUMBER - REORG_LENGTH), vec![5, 6, 77])); - - TestSetup::::default() - .with_unsynchronised_state(BlockWitnesserState { - last_block_received: INIT_LAST_BLOCK_RECEIVED, - last_block_election_emitted_for: INIT_LAST_BLOCK_RECEIVED, - ..BlockWitnesserState::default() - }) - .with_unsynchronised_settings(BlockWitnesserSettings { - max_concurrent_elections: MAX_CONCURRENT_ELECTIONS, - }) - .build() - .test_on_finalize( - &range_n(NEXT_BLOCK_NUMBER), - |_| {}, - vec![ - Check::::generate_election_properties_called_n_times( - MAX_CONCURRENT_ELECTIONS as u8, - ), - Check::::number_of_open_elections_is( - MAX_CONCURRENT_ELECTIONS, - ), - // No reorg, so we try processing any unprocessed state (there would be none at - // this point though, since no elections have resolved). - Check::::process_block_data_called_n_times(1), - ], - ) - .expect_consensus_multi(all_votes) - // Process votes as normal, storing the state - .test_on_finalize( - &range_n(NEXT_BLOCK_NUMBER + 1), - |_| {}, - vec![ - Check::::generate_election_properties_called_n_times( - MAX_CONCURRENT_ELECTIONS as u8 + 1, - ), - Check::::number_of_open_elections_is(1), - Check::::process_block_data_called_n_times(2), - Check::::unprocessed_data_is( - expected_unprocessed_data.clone(), - ), - ], - ) - // Reorg occurs - .test_on_finalize( - &range_n(NEXT_BLOCK_NUMBER - REORG_LENGTH), - |_| {}, - // We remove the actives ones and open one for the first block that we detected a - // reorg for. - vec![ - Check::::generate_election_properties_called_n_times( - MAX_CONCURRENT_ELECTIONS as u8 + 2, - ), - Check::::number_of_open_elections_is(1), - // There was a reorg, so there's definitely nothing to process since we're deleting - // all the data and just starting a new election, no extra calls here. - Check::::process_block_data_called_n_times(2), - // We keep the data, since it may need to be used by process_block_data to - // deduplicate actions. We don't want to submit an action twice. - Check::::unprocessed_data_is(expected_unprocessed_data), - ], - ) - .expect_consensus_multi(vec![create_votes_expectation(vec![5, 6, 77])]) - .test_on_finalize( - &range_n((NEXT_BLOCK_NUMBER - REORG_LENGTH) + 1), - |_| {}, - // We remove the actives ones and open one for the first block that we detected a - // reorg for. - vec![ - Check::::generate_election_properties_called_n_times( - MAX_CONCURRENT_ELECTIONS as u8 + 3, - ), - // We resolve one, but we've also progressed, so we open one. - Check::::number_of_open_elections_is(1), - Check::::process_block_data_called_n_times(3), - // We now have two pieces of data for the same block. - Check::::unprocessed_data_is( - block_after_reorg_block_unprocessed_data, - ), - ], - ); -} - -#[test] -fn partially_processed_block_data_processed_next_on_finalize() { - let first_block_consensus: BlockData = vec![5, 6, 7]; - - let first_block_data_after_processing: Vec<_> = - first_block_consensus.clone().into_iter().take(2).collect(); - - const INIT_LAST_BLOCK_RECEIVED: ChainBlockNumber = 0; - TestSetup::::default() - .with_unsynchronised_state(BlockWitnesserState { - last_block_received: INIT_LAST_BLOCK_RECEIVED, - ..BlockWitnesserState::default() - }) - .with_unsynchronised_settings(BlockWitnesserSettings { - max_concurrent_elections: MAX_CONCURRENT_ELECTIONS, - }) - .build() - .test_on_finalize( - &range_n(INIT_LAST_BLOCK_RECEIVED + 1), - |_| {}, - vec![ - Check::::generate_election_properties_called_n_times(1), - Check::::number_of_open_elections_is(1), - Check::::process_block_data_called_n_times(1), - // We haven't come to consensus on any elections, so there's no unprocessed data. - Check::::process_block_data_called_last_with(vec![]), - ], - ) - .expect_consensus_multi(vec![create_votes_expectation(first_block_consensus.clone())]) - .then(|| { - // We process one of the items, so we return only 2 of 3. - MockBlockProcessor::set_block_data_to_return(vec![( - INIT_LAST_BLOCK_RECEIVED + 1, - first_block_data_after_processing.clone(), - )]); - }) - .test_on_finalize( - &range_n(INIT_LAST_BLOCK_RECEIVED + 2), - |_| {}, - vec![ - Check::::generate_election_properties_called_n_times(2), - // One opened, one closed. - Check::::number_of_open_elections_is(1), - // We call it again. - Check::::process_block_data_called_n_times(2), - // We have the election data for the election we emitted before now. We try to - // process it. - Check::::process_block_data_called_last_with(vec![( - INIT_LAST_BLOCK_RECEIVED + 1, - first_block_consensus, - )]), - ], - ) - // No progress on external chain, so state should be the same as above, except that we - // processed one of the items last time. - .test_on_finalize( - &range_n(INIT_LAST_BLOCK_RECEIVED + 2), - |_| {}, - vec![ - Check::::generate_election_properties_called_n_times(2), - Check::::number_of_open_elections_is(1), - // We call it again. - Check::::process_block_data_called_n_times(3), - Check::::process_block_data_called_last_with(vec![( - INIT_LAST_BLOCK_RECEIVED + 1, - first_block_data_after_processing, - )]), - ], - ); -} - -#[test] -fn elections_resolved_out_of_order_has_no_impact() { - const INIT_LAST_BLOCK_RECEIVED: ChainBlockNumber = 0; - const FIRST_ELECTION_BLOCK_CREATED: ChainBlockNumber = INIT_LAST_BLOCK_RECEIVED + 1; - const SECOND_ELECTION_BLOCK_CREATED: ChainBlockNumber = FIRST_ELECTION_BLOCK_CREATED + 1; - const NUMBER_OF_ELECTIONS: ElectionCount = 2; - TestSetup::::default() - .with_unsynchronised_state(BlockWitnesserState { - last_block_received: INIT_LAST_BLOCK_RECEIVED, - ..BlockWitnesserState::default() - }) - .with_unsynchronised_settings(BlockWitnesserSettings { - max_concurrent_elections: MAX_CONCURRENT_ELECTIONS, - }) - .build() - .test_on_finalize( - // Process multiple elections, but still elss than the maximum concurrent - &range_n(INIT_LAST_BLOCK_RECEIVED + 2), - |pre_state| { - assert_eq!(pre_state.unsynchronised_state.open_elections, 0); - }, - vec![ - Check::::generate_election_properties_called_n_times( - NUMBER_OF_ELECTIONS as u8, - ), - Check::::number_of_open_elections_is(NUMBER_OF_ELECTIONS), - ], - ) - .expect_consensus_multi(vec![ - ( - // no consensus - generate_votes((0..20).collect(), (0..20).collect(), Default::default(), vec![]), - None, - ), - ( - // consensus - generate_votes( - (0..40).collect(), - Default::default(), - Default::default(), - vec![1, 3, 4], - ), - Some(vec![1, 3, 4]), - ), - ]) - // no progress on external chain but on finalize called again - // TODO: Check the new elections have kicked off correct - .test_on_finalize( - &range_n(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS as u64) + 1), - |pre_state| { - assert_eq!(pre_state.unsynchronised_state.open_elections, NUMBER_OF_ELECTIONS); - }, - vec![ - // one extra election created - Check::::generate_election_properties_called_n_times( - (NUMBER_OF_ELECTIONS + 1) as u8, - ), - // we should have resolved one election, and started one election - Check::::number_of_open_elections_is(2), - Check::::unprocessed_data_is(vec![( - SECOND_ELECTION_BLOCK_CREATED, - vec![1, 3, 4], - )]), - ], - ) - // gain consensus on the first emitted election now - .expect_consensus_multi(vec![( - generate_votes( - (0..40).collect(), - Default::default(), - Default::default(), - vec![9, 1, 2], - ), - Some(vec![9, 1, 2]), - )]) - .test_on_finalize( - &range_n(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS as u64) + 2), - |pre_state| { - assert_eq!( - pre_state.unsynchronised_state.open_elections, 2, - "number of open elections should be 2" - ); - }, - vec![ - // one extra election created - Check::::generate_election_properties_called_n_times( - (NUMBER_OF_ELECTIONS + 2) as u8, - ), - // we should have resolved one elections, and started one election - Check::::number_of_open_elections_is(2), - // Now the first election we emitted is resolved, and its block data should be - // stored, and we should still have the second election block data. - Check::::unprocessed_data_is(vec![ - (SECOND_ELECTION_BLOCK_CREATED, vec![1, 3, 4]), - (FIRST_ELECTION_BLOCK_CREATED, vec![9, 1, 2]), - ]), - ], - ) - // Gain consensus on the final elections - .expect_consensus_multi(vec![ - ( - generate_votes( - (0..40).collect(), - Default::default(), - Default::default(), - vec![81, 1, 93], - ), - Some(vec![81, 1, 93]), - ), - ( - generate_votes( - (0..40).collect(), - Default::default(), - Default::default(), - vec![69, 69, 69], - ), - Some(vec![69, 69, 69]), - ), - ]) - // external chain doesn't move forward - .test_on_finalize( - &range_n(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS as u64) + 2), - |pre_state| { - assert_eq!( - pre_state.unsynchronised_state.open_elections, 2, - "number of open elections should be 2" - ); - }, - vec![ - // one extra election created - Check::::generate_election_properties_called_n_times( - (NUMBER_OF_ELECTIONS + 2) as u8, - ), - // all elections have resolved now - Check::::number_of_open_elections_is(0), - // Now the last two elections are resolved in order - Check::::unprocessed_data_is(vec![ - (SECOND_ELECTION_BLOCK_CREATED, vec![1, 3, 4]), - (FIRST_ELECTION_BLOCK_CREATED, vec![9, 1, 2]), - (SECOND_ELECTION_BLOCK_CREATED + 1, vec![81, 1, 93]), - (SECOND_ELECTION_BLOCK_CREATED + 2, vec![69, 69, 69]), - ]), - ], - ); -} +// #[test] +// fn creates_multiple_elections_limited_by_maximum() { +// const INIT_LAST_BLOCK_RECEIVED: ChainBlockNumber = 0; +// const NUMBER_OF_ELECTIONS_REQUIRED: ElectionCount = MAX_CONCURRENT_ELECTIONS * 2; +// let consensus_resolutions: Vec<( +// ConsensusVotes, +// Option<::Consensus>, +// )> = vec![ +// create_votes_expectation(vec![]), +// create_votes_expectation(vec![1, 3, 4]), +// // no progress on external chain but on finalize called again +// ]; +// let number_of_resolved_elections = consensus_resolutions.len(); +// TestSetup::::default() +// .with_unsynchronised_state(BlockWitnesserState { +// last_block_received: INIT_LAST_BLOCK_RECEIVED, +// ..BlockWitnesserState::default() +// }) +// .with_unsynchronised_settings(BlockWitnesserSettings { +// max_concurrent_elections: MAX_CONCURRENT_ELECTIONS, +// }) +// .build() +// .test_on_finalize( +// // Process multiple elections, but still elss than the maximum concurrent +// &range_n(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS_REQUIRED as u64)), +// |pre_state| { +// assert_eq!(pre_state.unsynchronised_state.open_elections, 0); +// }, +// vec![ +// Check::::generate_election_properties_called_n_times( +// MAX_CONCURRENT_ELECTIONS as u8, +// ), +// Check::::number_of_open_elections_is( +// MAX_CONCURRENT_ELECTIONS, +// ), +// ], +// ) +// // Only resolve two of the elections. The last 3 are unresolved at this point. But +// // we now have space to start new elections. +// .expect_consensus_multi(consensus_resolutions) +// .test_on_finalize( +// &range_n(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS_REQUIRED as u64)), +// |pre_state| { +// assert_eq!(pre_state.unsynchronised_state.open_elections, MAX_CONCURRENT_ELECTIONS); +// }, +// vec![ +// // Still no extra elections created. +// Check::::generate_election_properties_called_n_times( +// MAX_CONCURRENT_ELECTIONS as u8 + number_of_resolved_elections as u8, +// ), +// // we should have resolved two elections +// Check::::number_of_open_elections_is( +// MAX_CONCURRENT_ELECTIONS, +// ), +// ], +// ); +// } + +// #[test] +// fn reorg_clears_on_going_elections_and_continues() { +// const INIT_LAST_BLOCK_RECEIVED: ChainBlockNumber = 10; +// const NEXT_BLOCK_NUMBER: ChainBlockNumber = +// INIT_LAST_BLOCK_RECEIVED + MAX_CONCURRENT_ELECTIONS as u64; +// const REORG_LENGTH: ChainBlockNumber = 3; + +// let all_votes = (INIT_LAST_BLOCK_RECEIVED + 1..=NEXT_BLOCK_NUMBER) +// .map(|_| create_votes_expectation(vec![5, 6, 7])) +// .collect::>(); + +// // We have already emitted an election for `INIT_LAST_BLOCK_RECEIVED` (see TestSetup below), so +// // we add 1. +// let expected_unprocessed_data = (INIT_LAST_BLOCK_RECEIVED + 1..=NEXT_BLOCK_NUMBER) +// .map(|i| (i, vec![5, 6, 7])) +// .collect::>(); + +// let mut block_after_reorg_block_unprocessed_data = expected_unprocessed_data.clone(); +// block_after_reorg_block_unprocessed_data +// .push(((NEXT_BLOCK_NUMBER - REORG_LENGTH), vec![5, 6, 77])); + +// TestSetup::::default() +// .with_unsynchronised_state(BlockWitnesserState { +// last_block_received: INIT_LAST_BLOCK_RECEIVED, +// last_block_election_emitted_for: INIT_LAST_BLOCK_RECEIVED, +// ..BlockWitnesserState::default() +// }) +// .with_unsynchronised_settings(BlockWitnesserSettings { +// max_concurrent_elections: MAX_CONCURRENT_ELECTIONS, +// }) +// .build() +// .test_on_finalize( +// &range_n(NEXT_BLOCK_NUMBER), +// |_| {}, +// vec![ +// Check::::generate_election_properties_called_n_times( +// MAX_CONCURRENT_ELECTIONS as u8, +// ), +// Check::::number_of_open_elections_is( +// MAX_CONCURRENT_ELECTIONS, +// ), +// // No reorg, so we try processing any unprocessed state (there would be none at +// // this point though, since no elections have resolved). +// Check::::process_block_data_called_n_times(1), +// ], +// ) +// .expect_consensus_multi(all_votes) +// // Process votes as normal, storing the state +// .test_on_finalize( +// &range_n(NEXT_BLOCK_NUMBER + 1), +// |_| {}, +// vec![ +// Check::::generate_election_properties_called_n_times( +// MAX_CONCURRENT_ELECTIONS as u8 + 1, +// ), +// Check::::number_of_open_elections_is(1), +// Check::::process_block_data_called_n_times(2), +// Check::::unprocessed_data_is( +// expected_unprocessed_data.clone(), +// ), +// ], +// ) +// // Reorg occurs +// .test_on_finalize( +// &range_n(NEXT_BLOCK_NUMBER - REORG_LENGTH), +// |_| {}, +// // We remove the actives ones and open one for the first block that we detected a +// // reorg for. +// vec![ +// Check::::generate_election_properties_called_n_times( +// MAX_CONCURRENT_ELECTIONS as u8 + 2, +// ), +// Check::::number_of_open_elections_is(1), +// // There was a reorg, so there's definitely nothing to process since we're deleting +// // all the data and just starting a new election, no extra calls here. +// Check::::process_block_data_called_n_times(2), +// // We keep the data, since it may need to be used by process_block_data to +// // deduplicate actions. We don't want to submit an action twice. +// Check::::unprocessed_data_is(expected_unprocessed_data), +// ], +// ) +// .expect_consensus_multi(vec![create_votes_expectation(vec![5, 6, 77])]) +// .test_on_finalize( +// &range_n((NEXT_BLOCK_NUMBER - REORG_LENGTH) + 1), +// |_| {}, +// // We remove the actives ones and open one for the first block that we detected a +// // reorg for. +// vec![ +// Check::::generate_election_properties_called_n_times( +// MAX_CONCURRENT_ELECTIONS as u8 + 3, +// ), +// // We resolve one, but we've also progressed, so we open one. +// Check::::number_of_open_elections_is(1), +// Check::::process_block_data_called_n_times(3), +// // We now have two pieces of data for the same block. +// Check::::unprocessed_data_is( +// block_after_reorg_block_unprocessed_data, +// ), +// ], +// ); +// } + +// #[test] +// fn partially_processed_block_data_processed_next_on_finalize() { +// let first_block_consensus: BlockData = vec![5, 6, 7]; + +// let first_block_data_after_processing: Vec<_> = +// first_block_consensus.clone().into_iter().take(2).collect(); + +// const INIT_LAST_BLOCK_RECEIVED: ChainBlockNumber = 0; +// TestSetup::::default() +// .with_unsynchronised_state(BlockWitnesserState { +// last_block_received: INIT_LAST_BLOCK_RECEIVED, +// ..BlockWitnesserState::default() +// }) +// .with_unsynchronised_settings(BlockWitnesserSettings { +// max_concurrent_elections: MAX_CONCURRENT_ELECTIONS, +// }) +// .build() +// .test_on_finalize( +// &range_n(INIT_LAST_BLOCK_RECEIVED + 1), +// |_| {}, +// vec![ +// Check::::generate_election_properties_called_n_times(1), +// Check::::number_of_open_elections_is(1), +// Check::::process_block_data_called_n_times(1), +// // We haven't come to consensus on any elections, so there's no unprocessed data. +// Check::::process_block_data_called_last_with(vec![]), +// ], +// ) +// .expect_consensus_multi(vec![create_votes_expectation(first_block_consensus.clone())]) +// .then(|| { +// // We process one of the items, so we return only 2 of 3. +// MockBlockProcessor::set_block_data_to_return(vec![( +// INIT_LAST_BLOCK_RECEIVED + 1, +// first_block_data_after_processing.clone(), +// )]); +// }) +// .test_on_finalize( +// &range_n(INIT_LAST_BLOCK_RECEIVED + 2), +// |_| {}, +// vec![ +// Check::::generate_election_properties_called_n_times(2), +// // One opened, one closed. +// Check::::number_of_open_elections_is(1), +// // We call it again. +// Check::::process_block_data_called_n_times(2), +// // We have the election data for the election we emitted before now. We try to +// // process it. +// Check::::process_block_data_called_last_with(vec![( +// INIT_LAST_BLOCK_RECEIVED + 1, +// first_block_consensus, +// )]), +// ], +// ) +// // No progress on external chain, so state should be the same as above, except that we +// // processed one of the items last time. +// .test_on_finalize( +// &range_n(INIT_LAST_BLOCK_RECEIVED + 2), +// |_| {}, +// vec![ +// Check::::generate_election_properties_called_n_times(2), +// Check::::number_of_open_elections_is(1), +// // We call it again. +// Check::::process_block_data_called_n_times(3), +// Check::::process_block_data_called_last_with(vec![( +// INIT_LAST_BLOCK_RECEIVED + 1, +// first_block_data_after_processing, +// )]), +// ], +// ); +// } + +// #[test] +// fn elections_resolved_out_of_order_has_no_impact() { +// const INIT_LAST_BLOCK_RECEIVED: ChainBlockNumber = 0; +// const FIRST_ELECTION_BLOCK_CREATED: ChainBlockNumber = INIT_LAST_BLOCK_RECEIVED + 1; +// const SECOND_ELECTION_BLOCK_CREATED: ChainBlockNumber = FIRST_ELECTION_BLOCK_CREATED + 1; +// const NUMBER_OF_ELECTIONS: ElectionCount = 2; +// TestSetup::::default() +// .with_unsynchronised_state(BlockWitnesserState { +// last_block_received: INIT_LAST_BLOCK_RECEIVED, +// ..BlockWitnesserState::default() +// }) +// .with_unsynchronised_settings(BlockWitnesserSettings { +// max_concurrent_elections: MAX_CONCURRENT_ELECTIONS, +// }) +// .build() +// .test_on_finalize( +// // Process multiple elections, but still elss than the maximum concurrent +// &range_n(INIT_LAST_BLOCK_RECEIVED + 2), +// |pre_state| { +// assert_eq!(pre_state.unsynchronised_state.open_elections, 0); +// }, +// vec![ +// Check::::generate_election_properties_called_n_times( +// NUMBER_OF_ELECTIONS as u8, +// ), +// Check::::number_of_open_elections_is(NUMBER_OF_ELECTIONS), +// ], +// ) +// .expect_consensus_multi(vec![ +// ( +// // no consensus +// generate_votes((0..20).collect(), (0..20).collect(), Default::default(), vec![]), +// None, +// ), +// ( +// // consensus +// generate_votes( +// (0..40).collect(), +// Default::default(), +// Default::default(), +// vec![1, 3, 4], +// ), +// Some(vec![1, 3, 4]), +// ), +// ]) +// // no progress on external chain but on finalize called again +// // TODO: Check the new elections have kicked off correct +// .test_on_finalize( +// &range_n(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS as u64) + 1), +// |pre_state| { +// assert_eq!(pre_state.unsynchronised_state.open_elections, NUMBER_OF_ELECTIONS); +// }, +// vec![ +// // one extra election created +// Check::::generate_election_properties_called_n_times( +// (NUMBER_OF_ELECTIONS + 1) as u8, +// ), +// // we should have resolved one election, and started one election +// Check::::number_of_open_elections_is(2), +// Check::::unprocessed_data_is(vec![( +// SECOND_ELECTION_BLOCK_CREATED, +// vec![1, 3, 4], +// )]), +// ], +// ) +// // gain consensus on the first emitted election now +// .expect_consensus_multi(vec![( +// generate_votes( +// (0..40).collect(), +// Default::default(), +// Default::default(), +// vec![9, 1, 2], +// ), +// Some(vec![9, 1, 2]), +// )]) +// .test_on_finalize( +// &range_n(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS as u64) + 2), +// |pre_state| { +// assert_eq!( +// pre_state.unsynchronised_state.open_elections, 2, +// "number of open elections should be 2" +// ); +// }, +// vec![ +// // one extra election created +// Check::::generate_election_properties_called_n_times( +// (NUMBER_OF_ELECTIONS + 2) as u8, +// ), +// // we should have resolved one elections, and started one election +// Check::::number_of_open_elections_is(2), +// // Now the first election we emitted is resolved, and its block data should be +// // stored, and we should still have the second election block data. +// Check::::unprocessed_data_is(vec![ +// (SECOND_ELECTION_BLOCK_CREATED, vec![1, 3, 4]), +// (FIRST_ELECTION_BLOCK_CREATED, vec![9, 1, 2]), +// ]), +// ], +// ) +// // Gain consensus on the final elections +// .expect_consensus_multi(vec![ +// ( +// generate_votes( +// (0..40).collect(), +// Default::default(), +// Default::default(), +// vec![81, 1, 93], +// ), +// Some(vec![81, 1, 93]), +// ), +// ( +// generate_votes( +// (0..40).collect(), +// Default::default(), +// Default::default(), +// vec![69, 69, 69], +// ), +// Some(vec![69, 69, 69]), +// ), +// ]) +// // external chain doesn't move forward +// .test_on_finalize( +// &range_n(INIT_LAST_BLOCK_RECEIVED + (NUMBER_OF_ELECTIONS as u64) + 2), +// |pre_state| { +// assert_eq!( +// pre_state.unsynchronised_state.open_elections, 2, +// "number of open elections should be 2" +// ); +// }, +// vec![ +// // one extra election created +// Check::::generate_election_properties_called_n_times( +// (NUMBER_OF_ELECTIONS + 2) as u8, +// ), +// // all elections have resolved now +// Check::::number_of_open_elections_is(0), +// // Now the last two elections are resolved in order +// Check::::unprocessed_data_is(vec![ +// (SECOND_ELECTION_BLOCK_CREATED, vec![1, 3, 4]), +// (FIRST_ELECTION_BLOCK_CREATED, vec![9, 1, 2]), +// (SECOND_ELECTION_BLOCK_CREATED + 1, vec![81, 1, 93]), +// (SECOND_ELECTION_BLOCK_CREATED + 2, vec![69, 69, 69]), +// ]), +// ], +// ); +// }