[chore] runtime split tests (#4834)

This commit is contained in:
Bernhard Schuster
2022-02-03 09:12:41 +01:00
committed by GitHub
parent f7564591e7
commit 5b06cc23d6
18 changed files with 6086 additions and 6043 deletions
@@ -26,6 +26,9 @@ use primitives::v1::{Balance, SessionIndex, MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, M
use sp_runtime::traits::Zero; use sp_runtime::traits::Zero;
use sp_std::prelude::*; use sp_std::prelude::*;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")] #[cfg(feature = "runtime-benchmarks")]
mod benchmarking; mod benchmarking;
@@ -1301,565 +1304,3 @@ impl<T: Config> Pallet<T> {
Ok(()) Ok(())
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{new_test_ext, Configuration, Origin, ParasShared, Test};
use frame_support::{assert_err, assert_ok};
fn on_new_session(
session_index: SessionIndex,
) -> (HostConfiguration<u32>, HostConfiguration<u32>) {
ParasShared::set_session_index(session_index);
let SessionChangeOutcome { prev_config, new_config } =
Configuration::initializer_on_new_session(&session_index);
let new_config = new_config.unwrap_or_else(|| prev_config.clone());
(prev_config, new_config)
}
#[test]
fn default_is_consistent() {
new_test_ext(Default::default()).execute_with(|| {
Configuration::config().panic_if_not_consistent();
});
}
#[test]
fn scheduled_session_is_two_sessions_from_now() {
new_test_ext(Default::default()).execute_with(|| {
// The logic here is really tested only with scheduled_session = 2. It should work
// with other values, but that should receive a more rigorious testing.
on_new_session(1);
assert_eq!(Configuration::scheduled_session(), 3);
});
}
#[test]
fn initializer_on_new_session() {
new_test_ext(Default::default()).execute_with(|| {
let (prev_config, new_config) = on_new_session(1);
assert_eq!(prev_config, new_config);
assert_ok!(Configuration::set_validation_upgrade_delay(Origin::root(), 100));
let (prev_config, new_config) = on_new_session(2);
assert_eq!(prev_config, new_config);
let (prev_config, new_config) = on_new_session(3);
assert_eq!(prev_config, HostConfiguration::default());
assert_eq!(
new_config,
HostConfiguration { validation_upgrade_delay: 100, ..prev_config }
);
});
}
#[test]
fn config_changes_after_2_session_boundary() {
new_test_ext(Default::default()).execute_with(|| {
let old_config = Configuration::config();
let mut config = old_config.clone();
config.validation_upgrade_delay = 100;
assert!(old_config != config);
assert_ok!(Configuration::set_validation_upgrade_delay(Origin::root(), 100));
// Verify that the current configuration has not changed and that there is a scheduled
// change for the SESSION_DELAY sessions in advance.
assert_eq!(Configuration::config(), old_config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![(2, config.clone())]);
on_new_session(1);
// One session has passed, we should be still waiting for the pending configuration.
assert_eq!(Configuration::config(), old_config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![(2, config.clone())]);
on_new_session(2);
assert_eq!(Configuration::config(), config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![]);
})
}
#[test]
fn consecutive_changes_within_one_session() {
new_test_ext(Default::default()).execute_with(|| {
let old_config = Configuration::config();
let mut config = old_config.clone();
config.validation_upgrade_delay = 100;
config.validation_upgrade_cooldown = 100;
assert!(old_config != config);
assert_ok!(Configuration::set_validation_upgrade_delay(Origin::root(), 100));
assert_ok!(Configuration::set_validation_upgrade_cooldown(Origin::root(), 100));
assert_eq!(Configuration::config(), old_config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![(2, config.clone())]);
on_new_session(1);
assert_eq!(Configuration::config(), old_config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![(2, config.clone())]);
on_new_session(2);
assert_eq!(Configuration::config(), config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![]);
});
}
#[test]
fn pending_next_session_but_we_upgrade_once_more() {
new_test_ext(Default::default()).execute_with(|| {
let initial_config = Configuration::config();
let intermediate_config =
HostConfiguration { validation_upgrade_delay: 100, ..initial_config.clone() };
let final_config = HostConfiguration {
validation_upgrade_delay: 100,
validation_upgrade_cooldown: 99,
..initial_config.clone()
};
assert_ok!(Configuration::set_validation_upgrade_delay(Origin::root(), 100));
assert_eq!(Configuration::config(), initial_config);
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(2, intermediate_config.clone())]
);
on_new_session(1);
// We are still waiting until the pending configuration is applied and we add another
// update.
assert_ok!(Configuration::set_validation_upgrade_cooldown(Origin::root(), 99));
// This should result in yet another configiguration change scheduled.
assert_eq!(Configuration::config(), initial_config);
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(2, intermediate_config.clone()), (3, final_config.clone())]
);
on_new_session(2);
assert_eq!(Configuration::config(), intermediate_config);
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(3, final_config.clone())]
);
on_new_session(3);
assert_eq!(Configuration::config(), final_config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![]);
});
}
#[test]
fn scheduled_session_config_update_while_next_session_pending() {
new_test_ext(Default::default()).execute_with(|| {
let initial_config = Configuration::config();
let intermediate_config =
HostConfiguration { validation_upgrade_delay: 100, ..initial_config.clone() };
let final_config = HostConfiguration {
validation_upgrade_delay: 100,
validation_upgrade_cooldown: 99,
code_retention_period: 98,
..initial_config.clone()
};
assert_ok!(Configuration::set_validation_upgrade_delay(Origin::root(), 100));
assert_eq!(Configuration::config(), initial_config);
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(2, intermediate_config.clone())]
);
on_new_session(1);
// The second call should fall into the case where we already have a pending config
// update for the scheduled_session, but we want to update it once more.
assert_ok!(Configuration::set_validation_upgrade_cooldown(Origin::root(), 99));
assert_ok!(Configuration::set_code_retention_period(Origin::root(), 98));
// This should result in yet another configiguration change scheduled.
assert_eq!(Configuration::config(), initial_config);
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(2, intermediate_config.clone()), (3, final_config.clone())]
);
on_new_session(2);
assert_eq!(Configuration::config(), intermediate_config);
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(3, final_config.clone())]
);
on_new_session(3);
assert_eq!(Configuration::config(), final_config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![]);
});
}
#[test]
fn invariants() {
new_test_ext(Default::default()).execute_with(|| {
assert_err!(
Configuration::set_max_code_size(Origin::root(), MAX_CODE_SIZE + 1),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_max_pov_size(Origin::root(), MAX_POV_SIZE + 1),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_max_head_data_size(Origin::root(), MAX_HEAD_DATA_SIZE + 1),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_chain_availability_period(Origin::root(), 0),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_thread_availability_period(Origin::root(), 0),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_no_show_slots(Origin::root(), 0),
Error::<Test>::InvalidNewValue
);
<Configuration as Store>::ActiveConfig::put(HostConfiguration {
chain_availability_period: 10,
thread_availability_period: 8,
minimum_validation_upgrade_delay: 11,
..Default::default()
});
assert_err!(
Configuration::set_chain_availability_period(Origin::root(), 12),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_thread_availability_period(Origin::root(), 12),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_minimum_validation_upgrade_delay(Origin::root(), 9),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_validation_upgrade_delay(Origin::root(), 0),
Error::<Test>::InvalidNewValue
);
});
}
#[test]
fn consistency_bypass_works() {
new_test_ext(Default::default()).execute_with(|| {
assert_err!(
Configuration::set_max_code_size(Origin::root(), MAX_CODE_SIZE + 1),
Error::<Test>::InvalidNewValue
);
assert_ok!(Configuration::set_bypass_consistency_check(Origin::root(), true));
assert_ok!(Configuration::set_max_code_size(Origin::root(), MAX_CODE_SIZE + 1));
assert_eq!(
Configuration::config().max_code_size,
HostConfiguration::<u32>::default().max_code_size
);
on_new_session(1);
on_new_session(2);
assert_eq!(Configuration::config().max_code_size, MAX_CODE_SIZE + 1);
});
}
#[test]
fn setting_pending_config_members() {
new_test_ext(Default::default()).execute_with(|| {
let new_config = HostConfiguration {
validation_upgrade_cooldown: 100,
validation_upgrade_delay: 10,
code_retention_period: 5,
max_code_size: 100_000,
max_pov_size: 1024,
max_head_data_size: 1_000,
parathread_cores: 2,
parathread_retries: 5,
group_rotation_frequency: 20,
chain_availability_period: 10,
thread_availability_period: 8,
scheduling_lookahead: 3,
max_validators_per_core: None,
max_validators: None,
dispute_period: 239,
dispute_post_conclusion_acceptance_period: 10,
dispute_max_spam_slots: 2,
dispute_conclusion_by_time_out_period: 512,
no_show_slots: 240,
n_delay_tranches: 241,
zeroth_delay_tranche_width: 242,
needed_approvals: 242,
relay_vrf_modulo_samples: 243,
max_upward_queue_count: 1337,
max_upward_queue_size: 228,
max_downward_message_size: 2048,
ump_service_total_weight: 20000,
max_upward_message_size: 448,
max_upward_message_num_per_candidate: 5,
hrmp_sender_deposit: 22,
hrmp_recipient_deposit: 4905,
hrmp_channel_max_capacity: 3921,
hrmp_channel_max_total_size: 7687,
hrmp_max_parachain_inbound_channels: 37,
hrmp_max_parathread_inbound_channels: 19,
hrmp_channel_max_message_size: 8192,
hrmp_max_parachain_outbound_channels: 10,
hrmp_max_parathread_outbound_channels: 20,
hrmp_max_message_num_per_candidate: 20,
ump_max_individual_weight: 909,
pvf_checking_enabled: true,
pvf_voting_ttl: 3,
minimum_validation_upgrade_delay: 20,
};
assert!(<Configuration as Store>::PendingConfig::get(shared::SESSION_DELAY).is_none());
Configuration::set_validation_upgrade_cooldown(
Origin::root(),
new_config.validation_upgrade_cooldown,
)
.unwrap();
Configuration::set_validation_upgrade_delay(
Origin::root(),
new_config.validation_upgrade_delay,
)
.unwrap();
Configuration::set_code_retention_period(
Origin::root(),
new_config.code_retention_period,
)
.unwrap();
Configuration::set_max_code_size(Origin::root(), new_config.max_code_size).unwrap();
Configuration::set_max_pov_size(Origin::root(), new_config.max_pov_size).unwrap();
Configuration::set_max_head_data_size(Origin::root(), new_config.max_head_data_size)
.unwrap();
Configuration::set_parathread_cores(Origin::root(), new_config.parathread_cores)
.unwrap();
Configuration::set_parathread_retries(Origin::root(), new_config.parathread_retries)
.unwrap();
Configuration::set_group_rotation_frequency(
Origin::root(),
new_config.group_rotation_frequency,
)
.unwrap();
// This comes out of order to satisfy the validity criteria for the chain and thread
// availability periods.
Configuration::set_minimum_validation_upgrade_delay(
Origin::root(),
new_config.minimum_validation_upgrade_delay,
)
.unwrap();
Configuration::set_chain_availability_period(
Origin::root(),
new_config.chain_availability_period,
)
.unwrap();
Configuration::set_thread_availability_period(
Origin::root(),
new_config.thread_availability_period,
)
.unwrap();
Configuration::set_scheduling_lookahead(
Origin::root(),
new_config.scheduling_lookahead,
)
.unwrap();
Configuration::set_max_validators_per_core(
Origin::root(),
new_config.max_validators_per_core,
)
.unwrap();
Configuration::set_max_validators(Origin::root(), new_config.max_validators).unwrap();
Configuration::set_dispute_period(Origin::root(), new_config.dispute_period).unwrap();
Configuration::set_dispute_post_conclusion_acceptance_period(
Origin::root(),
new_config.dispute_post_conclusion_acceptance_period,
)
.unwrap();
Configuration::set_dispute_max_spam_slots(
Origin::root(),
new_config.dispute_max_spam_slots,
)
.unwrap();
Configuration::set_dispute_conclusion_by_time_out_period(
Origin::root(),
new_config.dispute_conclusion_by_time_out_period,
)
.unwrap();
Configuration::set_no_show_slots(Origin::root(), new_config.no_show_slots).unwrap();
Configuration::set_n_delay_tranches(Origin::root(), new_config.n_delay_tranches)
.unwrap();
Configuration::set_zeroth_delay_tranche_width(
Origin::root(),
new_config.zeroth_delay_tranche_width,
)
.unwrap();
Configuration::set_needed_approvals(Origin::root(), new_config.needed_approvals)
.unwrap();
Configuration::set_relay_vrf_modulo_samples(
Origin::root(),
new_config.relay_vrf_modulo_samples,
)
.unwrap();
Configuration::set_max_upward_queue_count(
Origin::root(),
new_config.max_upward_queue_count,
)
.unwrap();
Configuration::set_max_upward_queue_size(
Origin::root(),
new_config.max_upward_queue_size,
)
.unwrap();
Configuration::set_max_downward_message_size(
Origin::root(),
new_config.max_downward_message_size,
)
.unwrap();
Configuration::set_ump_service_total_weight(
Origin::root(),
new_config.ump_service_total_weight,
)
.unwrap();
Configuration::set_max_upward_message_size(
Origin::root(),
new_config.max_upward_message_size,
)
.unwrap();
Configuration::set_max_upward_message_num_per_candidate(
Origin::root(),
new_config.max_upward_message_num_per_candidate,
)
.unwrap();
Configuration::set_hrmp_sender_deposit(Origin::root(), new_config.hrmp_sender_deposit)
.unwrap();
Configuration::set_hrmp_recipient_deposit(
Origin::root(),
new_config.hrmp_recipient_deposit,
)
.unwrap();
Configuration::set_hrmp_channel_max_capacity(
Origin::root(),
new_config.hrmp_channel_max_capacity,
)
.unwrap();
Configuration::set_hrmp_channel_max_total_size(
Origin::root(),
new_config.hrmp_channel_max_total_size,
)
.unwrap();
Configuration::set_hrmp_max_parachain_inbound_channels(
Origin::root(),
new_config.hrmp_max_parachain_inbound_channels,
)
.unwrap();
Configuration::set_hrmp_max_parathread_inbound_channels(
Origin::root(),
new_config.hrmp_max_parathread_inbound_channels,
)
.unwrap();
Configuration::set_hrmp_channel_max_message_size(
Origin::root(),
new_config.hrmp_channel_max_message_size,
)
.unwrap();
Configuration::set_hrmp_max_parachain_outbound_channels(
Origin::root(),
new_config.hrmp_max_parachain_outbound_channels,
)
.unwrap();
Configuration::set_hrmp_max_parathread_outbound_channels(
Origin::root(),
new_config.hrmp_max_parathread_outbound_channels,
)
.unwrap();
Configuration::set_hrmp_max_message_num_per_candidate(
Origin::root(),
new_config.hrmp_max_message_num_per_candidate,
)
.unwrap();
Configuration::set_ump_max_individual_weight(
Origin::root(),
new_config.ump_max_individual_weight,
)
.unwrap();
Configuration::set_pvf_checking_enabled(
Origin::root(),
new_config.pvf_checking_enabled,
)
.unwrap();
Configuration::set_pvf_voting_ttl(Origin::root(), new_config.pvf_voting_ttl).unwrap();
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(shared::SESSION_DELAY, new_config)],
);
})
}
#[test]
fn non_root_cannot_set_config() {
new_test_ext(Default::default()).execute_with(|| {
assert!(Configuration::set_validation_upgrade_delay(Origin::signed(1), 100).is_err());
});
}
#[test]
fn verify_externally_accessible() {
// This test verifies that the value can be accessed through the well known keys and the
// host configuration decodes into the abridged version.
use primitives::v1::{well_known_keys, AbridgedHostConfiguration};
new_test_ext(Default::default()).execute_with(|| {
let ground_truth = HostConfiguration::default();
// Make sure that the configuration is stored in the storage.
<Configuration as Store>::ActiveConfig::put(ground_truth.clone());
// Extract the active config via the well known key.
let raw_active_config = sp_io::storage::get(well_known_keys::ACTIVE_CONFIG)
.expect("config must be present in storage under ACTIVE_CONFIG");
let abridged_config = AbridgedHostConfiguration::decode(&mut &raw_active_config[..])
.expect("HostConfiguration must be decodable into AbridgedHostConfiguration");
assert_eq!(
abridged_config,
AbridgedHostConfiguration {
max_code_size: ground_truth.max_code_size,
max_head_data_size: ground_truth.max_head_data_size,
max_upward_queue_count: ground_truth.max_upward_queue_count,
max_upward_queue_size: ground_truth.max_upward_queue_size,
max_upward_message_size: ground_truth.max_upward_message_size,
max_upward_message_num_per_candidate: ground_truth
.max_upward_message_num_per_candidate,
hrmp_max_message_num_per_candidate: ground_truth
.hrmp_max_message_num_per_candidate,
validation_upgrade_cooldown: ground_truth.validation_upgrade_cooldown,
validation_upgrade_delay: ground_truth.validation_upgrade_delay,
},
);
});
}
}
@@ -0,0 +1,553 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot 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.
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::mock::{new_test_ext, Configuration, Origin, ParasShared, Test};
use frame_support::{assert_err, assert_ok};
fn on_new_session(session_index: SessionIndex) -> (HostConfiguration<u32>, HostConfiguration<u32>) {
ParasShared::set_session_index(session_index);
let SessionChangeOutcome { prev_config, new_config } =
Configuration::initializer_on_new_session(&session_index);
let new_config = new_config.unwrap_or_else(|| prev_config.clone());
(prev_config, new_config)
}
#[test]
fn default_is_consistent() {
new_test_ext(Default::default()).execute_with(|| {
Configuration::config().panic_if_not_consistent();
});
}
#[test]
fn scheduled_session_is_two_sessions_from_now() {
new_test_ext(Default::default()).execute_with(|| {
// The logic here is really tested only with scheduled_session = 2. It should work
// with other values, but that should receive a more rigorious testing.
on_new_session(1);
assert_eq!(Configuration::scheduled_session(), 3);
});
}
#[test]
fn initializer_on_new_session() {
new_test_ext(Default::default()).execute_with(|| {
let (prev_config, new_config) = on_new_session(1);
assert_eq!(prev_config, new_config);
assert_ok!(Configuration::set_validation_upgrade_delay(Origin::root(), 100));
let (prev_config, new_config) = on_new_session(2);
assert_eq!(prev_config, new_config);
let (prev_config, new_config) = on_new_session(3);
assert_eq!(prev_config, HostConfiguration::default());
assert_eq!(new_config, HostConfiguration { validation_upgrade_delay: 100, ..prev_config });
});
}
#[test]
fn config_changes_after_2_session_boundary() {
new_test_ext(Default::default()).execute_with(|| {
let old_config = Configuration::config();
let mut config = old_config.clone();
config.validation_upgrade_delay = 100;
assert!(old_config != config);
assert_ok!(Configuration::set_validation_upgrade_delay(Origin::root(), 100));
// Verify that the current configuration has not changed and that there is a scheduled
// change for the SESSION_DELAY sessions in advance.
assert_eq!(Configuration::config(), old_config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![(2, config.clone())]);
on_new_session(1);
// One session has passed, we should be still waiting for the pending configuration.
assert_eq!(Configuration::config(), old_config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![(2, config.clone())]);
on_new_session(2);
assert_eq!(Configuration::config(), config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![]);
})
}
#[test]
fn consecutive_changes_within_one_session() {
new_test_ext(Default::default()).execute_with(|| {
let old_config = Configuration::config();
let mut config = old_config.clone();
config.validation_upgrade_delay = 100;
config.validation_upgrade_cooldown = 100;
assert!(old_config != config);
assert_ok!(Configuration::set_validation_upgrade_delay(Origin::root(), 100));
assert_ok!(Configuration::set_validation_upgrade_cooldown(Origin::root(), 100));
assert_eq!(Configuration::config(), old_config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![(2, config.clone())]);
on_new_session(1);
assert_eq!(Configuration::config(), old_config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![(2, config.clone())]);
on_new_session(2);
assert_eq!(Configuration::config(), config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![]);
});
}
#[test]
fn pending_next_session_but_we_upgrade_once_more() {
new_test_ext(Default::default()).execute_with(|| {
let initial_config = Configuration::config();
let intermediate_config =
HostConfiguration { validation_upgrade_delay: 100, ..initial_config.clone() };
let final_config = HostConfiguration {
validation_upgrade_delay: 100,
validation_upgrade_cooldown: 99,
..initial_config.clone()
};
assert_ok!(Configuration::set_validation_upgrade_delay(Origin::root(), 100));
assert_eq!(Configuration::config(), initial_config);
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(2, intermediate_config.clone())]
);
on_new_session(1);
// We are still waiting until the pending configuration is applied and we add another
// update.
assert_ok!(Configuration::set_validation_upgrade_cooldown(Origin::root(), 99));
// This should result in yet another configiguration change scheduled.
assert_eq!(Configuration::config(), initial_config);
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(2, intermediate_config.clone()), (3, final_config.clone())]
);
on_new_session(2);
assert_eq!(Configuration::config(), intermediate_config);
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(3, final_config.clone())]
);
on_new_session(3);
assert_eq!(Configuration::config(), final_config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![]);
});
}
#[test]
fn scheduled_session_config_update_while_next_session_pending() {
new_test_ext(Default::default()).execute_with(|| {
let initial_config = Configuration::config();
let intermediate_config =
HostConfiguration { validation_upgrade_delay: 100, ..initial_config.clone() };
let final_config = HostConfiguration {
validation_upgrade_delay: 100,
validation_upgrade_cooldown: 99,
code_retention_period: 98,
..initial_config.clone()
};
assert_ok!(Configuration::set_validation_upgrade_delay(Origin::root(), 100));
assert_eq!(Configuration::config(), initial_config);
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(2, intermediate_config.clone())]
);
on_new_session(1);
// The second call should fall into the case where we already have a pending config
// update for the scheduled_session, but we want to update it once more.
assert_ok!(Configuration::set_validation_upgrade_cooldown(Origin::root(), 99));
assert_ok!(Configuration::set_code_retention_period(Origin::root(), 98));
// This should result in yet another configiguration change scheduled.
assert_eq!(Configuration::config(), initial_config);
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(2, intermediate_config.clone()), (3, final_config.clone())]
);
on_new_session(2);
assert_eq!(Configuration::config(), intermediate_config);
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(3, final_config.clone())]
);
on_new_session(3);
assert_eq!(Configuration::config(), final_config);
assert_eq!(<Configuration as Store>::PendingConfigs::get(), vec![]);
});
}
#[test]
fn invariants() {
new_test_ext(Default::default()).execute_with(|| {
assert_err!(
Configuration::set_max_code_size(Origin::root(), MAX_CODE_SIZE + 1),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_max_pov_size(Origin::root(), MAX_POV_SIZE + 1),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_max_head_data_size(Origin::root(), MAX_HEAD_DATA_SIZE + 1),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_chain_availability_period(Origin::root(), 0),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_thread_availability_period(Origin::root(), 0),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_no_show_slots(Origin::root(), 0),
Error::<Test>::InvalidNewValue
);
<Configuration as Store>::ActiveConfig::put(HostConfiguration {
chain_availability_period: 10,
thread_availability_period: 8,
minimum_validation_upgrade_delay: 11,
..Default::default()
});
assert_err!(
Configuration::set_chain_availability_period(Origin::root(), 12),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_thread_availability_period(Origin::root(), 12),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_minimum_validation_upgrade_delay(Origin::root(), 9),
Error::<Test>::InvalidNewValue
);
assert_err!(
Configuration::set_validation_upgrade_delay(Origin::root(), 0),
Error::<Test>::InvalidNewValue
);
});
}
#[test]
fn consistency_bypass_works() {
new_test_ext(Default::default()).execute_with(|| {
assert_err!(
Configuration::set_max_code_size(Origin::root(), MAX_CODE_SIZE + 1),
Error::<Test>::InvalidNewValue
);
assert_ok!(Configuration::set_bypass_consistency_check(Origin::root(), true));
assert_ok!(Configuration::set_max_code_size(Origin::root(), MAX_CODE_SIZE + 1));
assert_eq!(
Configuration::config().max_code_size,
HostConfiguration::<u32>::default().max_code_size
);
on_new_session(1);
on_new_session(2);
assert_eq!(Configuration::config().max_code_size, MAX_CODE_SIZE + 1);
});
}
#[test]
fn setting_pending_config_members() {
new_test_ext(Default::default()).execute_with(|| {
let new_config = HostConfiguration {
validation_upgrade_cooldown: 100,
validation_upgrade_delay: 10,
code_retention_period: 5,
max_code_size: 100_000,
max_pov_size: 1024,
max_head_data_size: 1_000,
parathread_cores: 2,
parathread_retries: 5,
group_rotation_frequency: 20,
chain_availability_period: 10,
thread_availability_period: 8,
scheduling_lookahead: 3,
max_validators_per_core: None,
max_validators: None,
dispute_period: 239,
dispute_post_conclusion_acceptance_period: 10,
dispute_max_spam_slots: 2,
dispute_conclusion_by_time_out_period: 512,
no_show_slots: 240,
n_delay_tranches: 241,
zeroth_delay_tranche_width: 242,
needed_approvals: 242,
relay_vrf_modulo_samples: 243,
max_upward_queue_count: 1337,
max_upward_queue_size: 228,
max_downward_message_size: 2048,
ump_service_total_weight: 20000,
max_upward_message_size: 448,
max_upward_message_num_per_candidate: 5,
hrmp_sender_deposit: 22,
hrmp_recipient_deposit: 4905,
hrmp_channel_max_capacity: 3921,
hrmp_channel_max_total_size: 7687,
hrmp_max_parachain_inbound_channels: 37,
hrmp_max_parathread_inbound_channels: 19,
hrmp_channel_max_message_size: 8192,
hrmp_max_parachain_outbound_channels: 10,
hrmp_max_parathread_outbound_channels: 20,
hrmp_max_message_num_per_candidate: 20,
ump_max_individual_weight: 909,
pvf_checking_enabled: true,
pvf_voting_ttl: 3,
minimum_validation_upgrade_delay: 20,
};
assert!(<Configuration as Store>::PendingConfig::get(shared::SESSION_DELAY).is_none());
Configuration::set_validation_upgrade_cooldown(
Origin::root(),
new_config.validation_upgrade_cooldown,
)
.unwrap();
Configuration::set_validation_upgrade_delay(
Origin::root(),
new_config.validation_upgrade_delay,
)
.unwrap();
Configuration::set_code_retention_period(Origin::root(), new_config.code_retention_period)
.unwrap();
Configuration::set_max_code_size(Origin::root(), new_config.max_code_size).unwrap();
Configuration::set_max_pov_size(Origin::root(), new_config.max_pov_size).unwrap();
Configuration::set_max_head_data_size(Origin::root(), new_config.max_head_data_size)
.unwrap();
Configuration::set_parathread_cores(Origin::root(), new_config.parathread_cores).unwrap();
Configuration::set_parathread_retries(Origin::root(), new_config.parathread_retries)
.unwrap();
Configuration::set_group_rotation_frequency(
Origin::root(),
new_config.group_rotation_frequency,
)
.unwrap();
// This comes out of order to satisfy the validity criteria for the chain and thread
// availability periods.
Configuration::set_minimum_validation_upgrade_delay(
Origin::root(),
new_config.minimum_validation_upgrade_delay,
)
.unwrap();
Configuration::set_chain_availability_period(
Origin::root(),
new_config.chain_availability_period,
)
.unwrap();
Configuration::set_thread_availability_period(
Origin::root(),
new_config.thread_availability_period,
)
.unwrap();
Configuration::set_scheduling_lookahead(Origin::root(), new_config.scheduling_lookahead)
.unwrap();
Configuration::set_max_validators_per_core(
Origin::root(),
new_config.max_validators_per_core,
)
.unwrap();
Configuration::set_max_validators(Origin::root(), new_config.max_validators).unwrap();
Configuration::set_dispute_period(Origin::root(), new_config.dispute_period).unwrap();
Configuration::set_dispute_post_conclusion_acceptance_period(
Origin::root(),
new_config.dispute_post_conclusion_acceptance_period,
)
.unwrap();
Configuration::set_dispute_max_spam_slots(
Origin::root(),
new_config.dispute_max_spam_slots,
)
.unwrap();
Configuration::set_dispute_conclusion_by_time_out_period(
Origin::root(),
new_config.dispute_conclusion_by_time_out_period,
)
.unwrap();
Configuration::set_no_show_slots(Origin::root(), new_config.no_show_slots).unwrap();
Configuration::set_n_delay_tranches(Origin::root(), new_config.n_delay_tranches).unwrap();
Configuration::set_zeroth_delay_tranche_width(
Origin::root(),
new_config.zeroth_delay_tranche_width,
)
.unwrap();
Configuration::set_needed_approvals(Origin::root(), new_config.needed_approvals).unwrap();
Configuration::set_relay_vrf_modulo_samples(
Origin::root(),
new_config.relay_vrf_modulo_samples,
)
.unwrap();
Configuration::set_max_upward_queue_count(
Origin::root(),
new_config.max_upward_queue_count,
)
.unwrap();
Configuration::set_max_upward_queue_size(Origin::root(), new_config.max_upward_queue_size)
.unwrap();
Configuration::set_max_downward_message_size(
Origin::root(),
new_config.max_downward_message_size,
)
.unwrap();
Configuration::set_ump_service_total_weight(
Origin::root(),
new_config.ump_service_total_weight,
)
.unwrap();
Configuration::set_max_upward_message_size(
Origin::root(),
new_config.max_upward_message_size,
)
.unwrap();
Configuration::set_max_upward_message_num_per_candidate(
Origin::root(),
new_config.max_upward_message_num_per_candidate,
)
.unwrap();
Configuration::set_hrmp_sender_deposit(Origin::root(), new_config.hrmp_sender_deposit)
.unwrap();
Configuration::set_hrmp_recipient_deposit(
Origin::root(),
new_config.hrmp_recipient_deposit,
)
.unwrap();
Configuration::set_hrmp_channel_max_capacity(
Origin::root(),
new_config.hrmp_channel_max_capacity,
)
.unwrap();
Configuration::set_hrmp_channel_max_total_size(
Origin::root(),
new_config.hrmp_channel_max_total_size,
)
.unwrap();
Configuration::set_hrmp_max_parachain_inbound_channels(
Origin::root(),
new_config.hrmp_max_parachain_inbound_channels,
)
.unwrap();
Configuration::set_hrmp_max_parathread_inbound_channels(
Origin::root(),
new_config.hrmp_max_parathread_inbound_channels,
)
.unwrap();
Configuration::set_hrmp_channel_max_message_size(
Origin::root(),
new_config.hrmp_channel_max_message_size,
)
.unwrap();
Configuration::set_hrmp_max_parachain_outbound_channels(
Origin::root(),
new_config.hrmp_max_parachain_outbound_channels,
)
.unwrap();
Configuration::set_hrmp_max_parathread_outbound_channels(
Origin::root(),
new_config.hrmp_max_parathread_outbound_channels,
)
.unwrap();
Configuration::set_hrmp_max_message_num_per_candidate(
Origin::root(),
new_config.hrmp_max_message_num_per_candidate,
)
.unwrap();
Configuration::set_ump_max_individual_weight(
Origin::root(),
new_config.ump_max_individual_weight,
)
.unwrap();
Configuration::set_pvf_checking_enabled(Origin::root(), new_config.pvf_checking_enabled)
.unwrap();
Configuration::set_pvf_voting_ttl(Origin::root(), new_config.pvf_voting_ttl).unwrap();
assert_eq!(
<Configuration as Store>::PendingConfigs::get(),
vec![(shared::SESSION_DELAY, new_config)],
);
})
}
#[test]
fn non_root_cannot_set_config() {
new_test_ext(Default::default()).execute_with(|| {
assert!(Configuration::set_validation_upgrade_delay(Origin::signed(1), 100).is_err());
});
}
#[test]
fn verify_externally_accessible() {
// This test verifies that the value can be accessed through the well known keys and the
// host configuration decodes into the abridged version.
use primitives::v1::{well_known_keys, AbridgedHostConfiguration};
new_test_ext(Default::default()).execute_with(|| {
let ground_truth = HostConfiguration::default();
// Make sure that the configuration is stored in the storage.
<Configuration as Store>::ActiveConfig::put(ground_truth.clone());
// Extract the active config via the well known key.
let raw_active_config = sp_io::storage::get(well_known_keys::ACTIVE_CONFIG)
.expect("config must be present in storage under ACTIVE_CONFIG");
let abridged_config = AbridgedHostConfiguration::decode(&mut &raw_active_config[..])
.expect("HostConfiguration must be decodable into AbridgedHostConfiguration");
assert_eq!(
abridged_config,
AbridgedHostConfiguration {
max_code_size: ground_truth.max_code_size,
max_head_data_size: ground_truth.max_head_data_size,
max_upward_queue_count: ground_truth.max_upward_queue_count,
max_upward_queue_size: ground_truth.max_upward_queue_size,
max_upward_message_size: ground_truth.max_upward_message_size,
max_upward_message_num_per_candidate: ground_truth
.max_upward_message_num_per_candidate,
hrmp_max_message_num_per_candidate: ground_truth.hrmp_max_message_num_per_candidate,
validation_upgrade_cooldown: ground_truth.validation_upgrade_cooldown,
validation_upgrade_delay: ground_truth.validation_upgrade_delay,
},
);
});
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3 -194
View File
@@ -26,6 +26,9 @@ use xcm::latest::SendError;
pub use pallet::*; pub use pallet::*;
#[cfg(test)]
mod tests;
/// An error sending a downward message. /// An error sending a downward message.
#[cfg_attr(test, derive(Debug))] #[cfg_attr(test, derive(Debug))]
pub enum QueueDownwardMessageError { pub enum QueueDownwardMessageError {
@@ -227,197 +230,3 @@ impl<T: Config> Pallet<T> {
<Self as Store>::DownwardMessageQueues::get(&recipient) <Self as Store>::DownwardMessageQueues::get(&recipient)
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{new_test_ext, Configuration, Dmp, MockGenesisConfig, Paras, System};
use hex_literal::hex;
use parity_scale_codec::Encode;
use primitives::v1::BlockNumber;
pub(crate) fn run_to_block(to: BlockNumber, new_session: Option<Vec<BlockNumber>>) {
while System::block_number() < to {
let b = System::block_number();
Paras::initializer_finalize(b);
Dmp::initializer_finalize();
if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) {
Dmp::initializer_on_new_session(&Default::default(), &Vec::new());
}
System::on_finalize(b);
System::on_initialize(b + 1);
System::set_block_number(b + 1);
Paras::initializer_finalize(b + 1);
Dmp::initializer_initialize(b + 1);
}
}
fn default_genesis_config() -> MockGenesisConfig {
MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: crate::configuration::HostConfiguration {
max_downward_message_size: 1024,
..Default::default()
},
},
..Default::default()
}
}
fn queue_downward_message(
para_id: ParaId,
msg: DownwardMessage,
) -> Result<(), QueueDownwardMessageError> {
Dmp::queue_downward_message(&Configuration::config(), para_id, msg)
}
#[test]
fn clean_dmp_works() {
let a = ParaId::from(1312);
let b = ParaId::from(228);
let c = ParaId::from(123);
new_test_ext(default_genesis_config()).execute_with(|| {
// enqueue downward messages to A, B and C.
queue_downward_message(a, vec![1, 2, 3]).unwrap();
queue_downward_message(b, vec![4, 5, 6]).unwrap();
queue_downward_message(c, vec![7, 8, 9]).unwrap();
let notification = crate::initializer::SessionChangeNotification::default();
let outgoing_paras = vec![a, b];
Dmp::initializer_on_new_session(&notification, &outgoing_paras);
assert!(<Dmp as Store>::DownwardMessageQueues::get(&a).is_empty());
assert!(<Dmp as Store>::DownwardMessageQueues::get(&b).is_empty());
assert!(!<Dmp as Store>::DownwardMessageQueues::get(&c).is_empty());
});
}
#[test]
fn dmq_length_and_head_updated_properly() {
let a = ParaId::from(1312);
let b = ParaId::from(228);
new_test_ext(default_genesis_config()).execute_with(|| {
assert_eq!(Dmp::dmq_length(a), 0);
assert_eq!(Dmp::dmq_length(b), 0);
queue_downward_message(a, vec![1, 2, 3]).unwrap();
assert_eq!(Dmp::dmq_length(a), 1);
assert_eq!(Dmp::dmq_length(b), 0);
assert!(!Dmp::dmq_mqc_head(a).is_zero());
assert!(Dmp::dmq_mqc_head(b).is_zero());
});
}
#[test]
fn dmp_mqc_head_fixture() {
let a = ParaId::from(2000);
new_test_ext(default_genesis_config()).execute_with(|| {
run_to_block(2, None);
assert!(Dmp::dmq_mqc_head(a).is_zero());
queue_downward_message(a, vec![1, 2, 3]).unwrap();
run_to_block(3, None);
queue_downward_message(a, vec![4, 5, 6]).unwrap();
assert_eq!(
Dmp::dmq_mqc_head(a),
hex!["88dc00db8cc9d22aa62b87807705831f164387dfa49f80a8600ed1cbe1704b6b"].into(),
);
});
}
#[test]
fn check_processed_downward_messages() {
let a = ParaId::from(1312);
new_test_ext(default_genesis_config()).execute_with(|| {
// processed_downward_messages=0 is allowed when the DMQ is empty.
assert!(Dmp::check_processed_downward_messages(a, 0).is_ok());
queue_downward_message(a, vec![1, 2, 3]).unwrap();
queue_downward_message(a, vec![4, 5, 6]).unwrap();
queue_downward_message(a, vec![7, 8, 9]).unwrap();
// 0 doesn't pass if the DMQ has msgs.
assert!(!Dmp::check_processed_downward_messages(a, 0).is_ok());
// a candidate can consume up to 3 messages
assert!(Dmp::check_processed_downward_messages(a, 1).is_ok());
assert!(Dmp::check_processed_downward_messages(a, 2).is_ok());
assert!(Dmp::check_processed_downward_messages(a, 3).is_ok());
// there is no 4 messages in the queue
assert!(!Dmp::check_processed_downward_messages(a, 4).is_ok());
});
}
#[test]
fn dmq_pruning() {
let a = ParaId::from(1312);
new_test_ext(default_genesis_config()).execute_with(|| {
assert_eq!(Dmp::dmq_length(a), 0);
queue_downward_message(a, vec![1, 2, 3]).unwrap();
queue_downward_message(a, vec![4, 5, 6]).unwrap();
queue_downward_message(a, vec![7, 8, 9]).unwrap();
assert_eq!(Dmp::dmq_length(a), 3);
// pruning 0 elements shouldn't change anything.
Dmp::prune_dmq(a, 0);
assert_eq!(Dmp::dmq_length(a), 3);
Dmp::prune_dmq(a, 2);
assert_eq!(Dmp::dmq_length(a), 1);
});
}
#[test]
fn queue_downward_message_critical() {
let a = ParaId::from(1312);
let mut genesis = default_genesis_config();
genesis.configuration.config.max_downward_message_size = 7;
new_test_ext(genesis).execute_with(|| {
let smol = [0; 3].to_vec();
let big = [0; 8].to_vec();
// still within limits
assert_eq!(smol.encode().len(), 4);
assert!(queue_downward_message(a, smol).is_ok());
// that's too big
assert_eq!(big.encode().len(), 9);
assert!(queue_downward_message(a, big).is_err());
});
}
#[test]
fn verify_dmq_mqc_head_is_externally_accessible() {
use hex_literal::hex;
use primitives::v1::well_known_keys;
let a = ParaId::from(2020);
new_test_ext(default_genesis_config()).execute_with(|| {
let head = sp_io::storage::get(&well_known_keys::dmq_mqc_head(a));
assert_eq!(head, None);
queue_downward_message(a, vec![1, 2, 3]).unwrap();
let head = sp_io::storage::get(&well_known_keys::dmq_mqc_head(a));
assert_eq!(
head,
Some(
hex!["434f8579a2297dfea851bf6be33093c83a78b655a53ae141a7894494c0010589"]
.to_vec()
)
);
});
}
}
@@ -0,0 +1,203 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot 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.
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::mock::{new_test_ext, Configuration, Dmp, MockGenesisConfig, Paras, System};
use hex_literal::hex;
use parity_scale_codec::Encode;
use primitives::v1::BlockNumber;
pub(crate) fn run_to_block(to: BlockNumber, new_session: Option<Vec<BlockNumber>>) {
while System::block_number() < to {
let b = System::block_number();
Paras::initializer_finalize(b);
Dmp::initializer_finalize();
if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) {
Dmp::initializer_on_new_session(&Default::default(), &Vec::new());
}
System::on_finalize(b);
System::on_initialize(b + 1);
System::set_block_number(b + 1);
Paras::initializer_finalize(b + 1);
Dmp::initializer_initialize(b + 1);
}
}
fn default_genesis_config() -> MockGenesisConfig {
MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: crate::configuration::HostConfiguration {
max_downward_message_size: 1024,
..Default::default()
},
},
..Default::default()
}
}
fn queue_downward_message(
para_id: ParaId,
msg: DownwardMessage,
) -> Result<(), QueueDownwardMessageError> {
Dmp::queue_downward_message(&Configuration::config(), para_id, msg)
}
#[test]
fn clean_dmp_works() {
let a = ParaId::from(1312);
let b = ParaId::from(228);
let c = ParaId::from(123);
new_test_ext(default_genesis_config()).execute_with(|| {
// enqueue downward messages to A, B and C.
queue_downward_message(a, vec![1, 2, 3]).unwrap();
queue_downward_message(b, vec![4, 5, 6]).unwrap();
queue_downward_message(c, vec![7, 8, 9]).unwrap();
let notification = crate::initializer::SessionChangeNotification::default();
let outgoing_paras = vec![a, b];
Dmp::initializer_on_new_session(&notification, &outgoing_paras);
assert!(<Dmp as Store>::DownwardMessageQueues::get(&a).is_empty());
assert!(<Dmp as Store>::DownwardMessageQueues::get(&b).is_empty());
assert!(!<Dmp as Store>::DownwardMessageQueues::get(&c).is_empty());
});
}
#[test]
fn dmq_length_and_head_updated_properly() {
let a = ParaId::from(1312);
let b = ParaId::from(228);
new_test_ext(default_genesis_config()).execute_with(|| {
assert_eq!(Dmp::dmq_length(a), 0);
assert_eq!(Dmp::dmq_length(b), 0);
queue_downward_message(a, vec![1, 2, 3]).unwrap();
assert_eq!(Dmp::dmq_length(a), 1);
assert_eq!(Dmp::dmq_length(b), 0);
assert!(!Dmp::dmq_mqc_head(a).is_zero());
assert!(Dmp::dmq_mqc_head(b).is_zero());
});
}
#[test]
fn dmp_mqc_head_fixture() {
let a = ParaId::from(2000);
new_test_ext(default_genesis_config()).execute_with(|| {
run_to_block(2, None);
assert!(Dmp::dmq_mqc_head(a).is_zero());
queue_downward_message(a, vec![1, 2, 3]).unwrap();
run_to_block(3, None);
queue_downward_message(a, vec![4, 5, 6]).unwrap();
assert_eq!(
Dmp::dmq_mqc_head(a),
hex!["88dc00db8cc9d22aa62b87807705831f164387dfa49f80a8600ed1cbe1704b6b"].into(),
);
});
}
#[test]
fn check_processed_downward_messages() {
let a = ParaId::from(1312);
new_test_ext(default_genesis_config()).execute_with(|| {
// processed_downward_messages=0 is allowed when the DMQ is empty.
assert!(Dmp::check_processed_downward_messages(a, 0).is_ok());
queue_downward_message(a, vec![1, 2, 3]).unwrap();
queue_downward_message(a, vec![4, 5, 6]).unwrap();
queue_downward_message(a, vec![7, 8, 9]).unwrap();
// 0 doesn't pass if the DMQ has msgs.
assert!(!Dmp::check_processed_downward_messages(a, 0).is_ok());
// a candidate can consume up to 3 messages
assert!(Dmp::check_processed_downward_messages(a, 1).is_ok());
assert!(Dmp::check_processed_downward_messages(a, 2).is_ok());
assert!(Dmp::check_processed_downward_messages(a, 3).is_ok());
// there is no 4 messages in the queue
assert!(!Dmp::check_processed_downward_messages(a, 4).is_ok());
});
}
#[test]
fn dmq_pruning() {
let a = ParaId::from(1312);
new_test_ext(default_genesis_config()).execute_with(|| {
assert_eq!(Dmp::dmq_length(a), 0);
queue_downward_message(a, vec![1, 2, 3]).unwrap();
queue_downward_message(a, vec![4, 5, 6]).unwrap();
queue_downward_message(a, vec![7, 8, 9]).unwrap();
assert_eq!(Dmp::dmq_length(a), 3);
// pruning 0 elements shouldn't change anything.
Dmp::prune_dmq(a, 0);
assert_eq!(Dmp::dmq_length(a), 3);
Dmp::prune_dmq(a, 2);
assert_eq!(Dmp::dmq_length(a), 1);
});
}
#[test]
fn queue_downward_message_critical() {
let a = ParaId::from(1312);
let mut genesis = default_genesis_config();
genesis.configuration.config.max_downward_message_size = 7;
new_test_ext(genesis).execute_with(|| {
let smol = [0; 3].to_vec();
let big = [0; 8].to_vec();
// still within limits
assert_eq!(smol.encode().len(), 4);
assert!(queue_downward_message(a, smol).is_ok());
// that's too big
assert_eq!(big.encode().len(), 9);
assert!(queue_downward_message(a, big).is_err());
});
}
#[test]
fn verify_dmq_mqc_head_is_externally_accessible() {
use hex_literal::hex;
use primitives::v1::well_known_keys;
let a = ParaId::from(2020);
new_test_ext(default_genesis_config()).execute_with(|| {
let head = sp_io::storage::get(&well_known_keys::dmq_mqc_head(a));
assert_eq!(head, None);
queue_downward_message(a, vec![1, 2, 3]).unwrap();
let head = sp_io::storage::get(&well_known_keys::dmq_mqc_head(a));
assert_eq!(
head,
Some(hex!["434f8579a2297dfea851bf6be33093c83a78b655a53ae141a7894494c0010589"].to_vec())
);
});
}
+3 -591
View File
@@ -43,6 +43,9 @@ pub const HRMP_MAX_INBOUND_CHANNELS_BOUND: u32 = 128;
/// Same as [`HRMP_MAX_INBOUND_CHANNELS_BOUND`], but for outbound channels. /// Same as [`HRMP_MAX_INBOUND_CHANNELS_BOUND`], but for outbound channels.
pub const HRMP_MAX_OUTBOUND_CHANNELS_BOUND: u32 = 128; pub const HRMP_MAX_OUTBOUND_CHANNELS_BOUND: u32 = 128;
#[cfg(test)]
pub(crate) mod tests;
#[cfg(feature = "runtime-benchmarks")] #[cfg(feature = "runtime-benchmarks")]
mod benchmarking; mod benchmarking;
@@ -1543,594 +1546,3 @@ impl<T: Config> Pallet<T> {
} }
} }
} }
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::mock::{
new_test_ext, Configuration, Event as MockEvent, Hrmp, MockGenesisConfig, Paras,
ParasShared, System, Test,
};
use frame_support::{assert_noop, assert_ok, traits::Currency as _};
use primitives::v1::BlockNumber;
use std::collections::BTreeMap;
fn run_to_block(to: BlockNumber, new_session: Option<Vec<BlockNumber>>) {
let config = Configuration::config();
while System::block_number() < to {
let b = System::block_number();
// NOTE: this is in reverse initialization order.
Hrmp::initializer_finalize();
Paras::initializer_finalize(b);
ParasShared::initializer_finalize();
if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) {
let notification = crate::initializer::SessionChangeNotification {
prev_config: config.clone(),
new_config: config.clone(),
session_index: ParasShared::session_index() + 1,
..Default::default()
};
// NOTE: this is in initialization order.
ParasShared::initializer_on_new_session(
notification.session_index,
notification.random_seed,
&notification.new_config,
notification.validators.clone(),
);
let outgoing_paras = Paras::initializer_on_new_session(&notification);
Hrmp::initializer_on_new_session(&notification, &outgoing_paras);
}
System::on_finalize(b);
System::on_initialize(b + 1);
System::set_block_number(b + 1);
// NOTE: this is in initialization order.
ParasShared::initializer_initialize(b + 1);
Paras::initializer_initialize(b + 1);
Hrmp::initializer_initialize(b + 1);
}
}
#[derive(Debug)]
pub(super) struct GenesisConfigBuilder {
hrmp_channel_max_capacity: u32,
hrmp_channel_max_message_size: u32,
hrmp_max_parathread_outbound_channels: u32,
hrmp_max_parachain_outbound_channels: u32,
hrmp_max_parathread_inbound_channels: u32,
hrmp_max_parachain_inbound_channels: u32,
hrmp_max_message_num_per_candidate: u32,
hrmp_channel_max_total_size: u32,
hrmp_sender_deposit: Balance,
hrmp_recipient_deposit: Balance,
}
impl Default for GenesisConfigBuilder {
fn default() -> Self {
Self {
hrmp_channel_max_capacity: 2,
hrmp_channel_max_message_size: 8,
hrmp_max_parathread_outbound_channels: 1,
hrmp_max_parachain_outbound_channels: 2,
hrmp_max_parathread_inbound_channels: 1,
hrmp_max_parachain_inbound_channels: 2,
hrmp_max_message_num_per_candidate: 2,
hrmp_channel_max_total_size: 16,
hrmp_sender_deposit: 100,
hrmp_recipient_deposit: 100,
}
}
}
impl GenesisConfigBuilder {
pub(super) fn build(self) -> crate::mock::MockGenesisConfig {
let mut genesis = default_genesis_config();
let config = &mut genesis.configuration.config;
config.hrmp_channel_max_capacity = self.hrmp_channel_max_capacity;
config.hrmp_channel_max_message_size = self.hrmp_channel_max_message_size;
config.hrmp_max_parathread_outbound_channels =
self.hrmp_max_parathread_outbound_channels;
config.hrmp_max_parachain_outbound_channels = self.hrmp_max_parachain_outbound_channels;
config.hrmp_max_parathread_inbound_channels = self.hrmp_max_parathread_inbound_channels;
config.hrmp_max_parachain_inbound_channels = self.hrmp_max_parachain_inbound_channels;
config.hrmp_max_message_num_per_candidate = self.hrmp_max_message_num_per_candidate;
config.hrmp_channel_max_total_size = self.hrmp_channel_max_total_size;
config.hrmp_sender_deposit = self.hrmp_sender_deposit;
config.hrmp_recipient_deposit = self.hrmp_recipient_deposit;
genesis
}
}
fn default_genesis_config() -> MockGenesisConfig {
MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: crate::configuration::HostConfiguration {
max_downward_message_size: 1024,
pvf_checking_enabled: false,
..Default::default()
},
},
..Default::default()
}
}
fn register_parachain_with_balance(id: ParaId, balance: Balance) {
assert_ok!(Paras::schedule_para_initialize(
id,
crate::paras::ParaGenesisArgs {
parachain: true,
genesis_head: vec![1].into(),
validation_code: vec![1].into(),
},
));
<Test as Config>::Currency::make_free_balance_be(&id.into_account(), balance);
}
fn register_parachain(id: ParaId) {
register_parachain_with_balance(id, 1000);
}
fn deregister_parachain(id: ParaId) {
assert_ok!(Paras::schedule_para_cleanup(id));
}
fn channel_exists(sender: ParaId, recipient: ParaId) -> bool {
<Hrmp as Store>::HrmpChannels::get(&HrmpChannelId { sender, recipient }).is_some()
}
#[test]
fn empty_state_consistent_state() {
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
Hrmp::assert_storage_consistency_exhaustive();
});
}
#[test]
fn open_channel_works() {
let para_a = 1.into();
let para_a_origin: crate::Origin = 1.into();
let para_b = 3.into();
let para_b_origin: crate::Origin = 3.into();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
// We need both A & B to be registered and alive parachains.
register_parachain(para_a);
register_parachain(para_b);
run_to_block(5, Some(vec![4, 5]));
Hrmp::hrmp_init_open_channel(para_a_origin.into(), para_b, 2, 8).unwrap();
Hrmp::assert_storage_consistency_exhaustive();
assert!(System::events().iter().any(|record| record.event ==
MockEvent::Hrmp(Event::OpenChannelRequested(para_a, para_b, 2, 8))));
Hrmp::hrmp_accept_open_channel(para_b_origin.into(), para_a).unwrap();
Hrmp::assert_storage_consistency_exhaustive();
assert!(System::events().iter().any(|record| record.event ==
MockEvent::Hrmp(Event::OpenChannelAccepted(para_a, para_b))));
// Advance to a block 6, but without session change. That means that the channel has
// not been created yet.
run_to_block(6, None);
assert!(!channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
// Now let the session change happen and thus open the channel.
run_to_block(8, Some(vec![8]));
assert!(channel_exists(para_a, para_b));
});
}
#[test]
fn close_channel_works() {
let para_a = 5.into();
let para_b = 2.into();
let para_b_origin: crate::Origin = 2.into();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
register_parachain(para_a);
register_parachain(para_b);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
run_to_block(6, Some(vec![6]));
assert!(channel_exists(para_a, para_b));
// Close the channel. The effect is not immediate, but rather deferred to the next
// session change.
let channel_id = HrmpChannelId { sender: para_a, recipient: para_b };
Hrmp::hrmp_close_channel(para_b_origin.into(), channel_id.clone()).unwrap();
assert!(channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
// After the session change the channel should be closed.
run_to_block(8, Some(vec![8]));
assert!(!channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
assert!(System::events().iter().any(|record| record.event ==
MockEvent::Hrmp(Event::ChannelClosed(para_b, channel_id.clone()))));
});
}
#[test]
fn send_recv_messages() {
let para_a = 32.into();
let para_b = 64.into();
let mut genesis = GenesisConfigBuilder::default();
genesis.hrmp_channel_max_message_size = 20;
genesis.hrmp_channel_max_total_size = 20;
new_test_ext(genesis.build()).execute_with(|| {
register_parachain(para_a);
register_parachain(para_b);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 20).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
// On Block 6:
// A sends a message to B
run_to_block(6, Some(vec![6]));
assert!(channel_exists(para_a, para_b));
let msgs = vec![OutboundHrmpMessage {
recipient: para_b,
data: b"this is an emergency".to_vec(),
}];
let config = Configuration::config();
assert!(Hrmp::check_outbound_hrmp(&config, para_a, &msgs).is_ok());
let _ = Hrmp::queue_outbound_hrmp(para_a, msgs);
Hrmp::assert_storage_consistency_exhaustive();
// On Block 7:
// B receives the message sent by A. B sets the watermark to 6.
run_to_block(7, None);
assert!(Hrmp::check_hrmp_watermark(para_b, 7, 6).is_ok());
let _ = Hrmp::prune_hrmp(para_b, 6);
Hrmp::assert_storage_consistency_exhaustive();
});
}
#[test]
fn hrmp_mqc_head_fixture() {
let para_a = 2000.into();
let para_b = 2024.into();
let mut genesis = GenesisConfigBuilder::default();
genesis.hrmp_channel_max_message_size = 20;
genesis.hrmp_channel_max_total_size = 20;
new_test_ext(genesis.build()).execute_with(|| {
register_parachain(para_a);
register_parachain(para_b);
run_to_block(2, Some(vec![1, 2]));
Hrmp::init_open_channel(para_a, para_b, 2, 20).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
run_to_block(3, Some(vec![3]));
let _ = Hrmp::queue_outbound_hrmp(
para_a,
vec![OutboundHrmpMessage { recipient: para_b, data: vec![1, 2, 3] }],
);
run_to_block(4, None);
let _ = Hrmp::queue_outbound_hrmp(
para_a,
vec![OutboundHrmpMessage { recipient: para_b, data: vec![4, 5, 6] }],
);
assert_eq!(
Hrmp::hrmp_mqc_heads(para_b),
vec![(
para_a,
hex_literal::hex![
"a964fd3b4f3d3ce92a0e25e576b87590d92bb5cb7031909c7f29050e1f04a375"
]
.into()
),],
);
});
}
#[test]
fn accept_incoming_request_and_offboard() {
let para_a = 32.into();
let para_b = 64.into();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
register_parachain(para_a);
register_parachain(para_b);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
deregister_parachain(para_a);
// On Block 7: 2x session change. The channel should not be created.
run_to_block(7, Some(vec![6, 7]));
assert!(!Paras::is_valid_para(para_a));
assert!(!channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
});
}
#[test]
fn check_sent_messages() {
let para_a = 32.into();
let para_b = 64.into();
let para_c = 97.into();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
register_parachain(para_a);
register_parachain(para_b);
register_parachain(para_c);
run_to_block(5, Some(vec![4, 5]));
// Open two channels to the same receiver, b:
// a -> b, c -> b
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
Hrmp::init_open_channel(para_c, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_c).unwrap();
// On Block 6: session change.
run_to_block(6, Some(vec![6]));
assert!(Paras::is_valid_para(para_a));
let msgs = vec![OutboundHrmpMessage { recipient: para_b, data: b"knock".to_vec() }];
let config = Configuration::config();
assert!(Hrmp::check_outbound_hrmp(&config, para_a, &msgs).is_ok());
let _ = Hrmp::queue_outbound_hrmp(para_a, msgs.clone());
// Verify that the sent messages are there and that also the empty channels are present.
let mqc_heads = Hrmp::hrmp_mqc_heads(para_b);
let contents = Hrmp::inbound_hrmp_channels_contents(para_b);
assert_eq!(
contents,
vec![
(para_a, vec![InboundHrmpMessage { sent_at: 6, data: b"knock".to_vec() }]),
(para_c, vec![])
]
.into_iter()
.collect::<BTreeMap::<_, _>>(),
);
assert_eq!(
mqc_heads,
vec![
(
para_a,
hex_literal::hex!(
"3bba6404e59c91f51deb2ae78f1273ebe75896850713e13f8c0eba4b0996c483"
)
.into()
),
(para_c, Default::default())
],
);
Hrmp::assert_storage_consistency_exhaustive();
});
}
#[test]
fn verify_externally_accessible() {
use primitives::v1::{well_known_keys, AbridgedHrmpChannel};
let para_a = 20.into();
let para_b = 21.into();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
// Register two parachains, wait until a session change, then initiate channel open
// request and accept that, and finally wait until the next session.
register_parachain(para_a);
register_parachain(para_b);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
run_to_block(8, Some(vec![8]));
// Here we have a channel a->b opened.
//
// Try to obtain this channel from the storage and
// decode it into the abridged version.
assert!(channel_exists(para_a, para_b));
let raw_hrmp_channel =
sp_io::storage::get(&well_known_keys::hrmp_channels(HrmpChannelId {
sender: para_a,
recipient: para_b,
}))
.expect("the channel exists and we must be able to get it through well known keys");
let abridged_hrmp_channel = AbridgedHrmpChannel::decode(&mut &raw_hrmp_channel[..])
.expect("HrmpChannel should be decodable as AbridgedHrmpChannel");
assert_eq!(
abridged_hrmp_channel,
AbridgedHrmpChannel {
max_capacity: 2,
max_total_size: 16,
max_message_size: 8,
msg_count: 0,
total_size: 0,
mqc_head: None,
},
);
let raw_ingress_index =
sp_io::storage::get(&well_known_keys::hrmp_ingress_channel_index(para_b))
.expect("the ingress index must be present for para_b");
let ingress_index = <Vec<ParaId>>::decode(&mut &raw_ingress_index[..])
.expect("ingress indexx should be decodable as a list of para ids");
assert_eq!(ingress_index, vec![para_a]);
// Now, verify that we can access and decode the egress index.
let raw_egress_index =
sp_io::storage::get(&well_known_keys::hrmp_egress_channel_index(para_a))
.expect("the egress index must be present for para_a");
let egress_index = <Vec<ParaId>>::decode(&mut &raw_egress_index[..])
.expect("egress index should be decodable as a list of para ids");
assert_eq!(egress_index, vec![para_b]);
});
}
#[test]
fn charging_deposits() {
let para_a = 32.into();
let para_b = 64.into();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
register_parachain_with_balance(para_a, 0);
register_parachain(para_b);
run_to_block(5, Some(vec![4, 5]));
assert_noop!(
Hrmp::init_open_channel(para_a, para_b, 2, 8),
pallet_balances::Error::<Test, _>::InsufficientBalance
);
});
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
register_parachain(para_a);
register_parachain_with_balance(para_b, 0);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
assert_noop!(
Hrmp::accept_open_channel(para_b, para_a),
pallet_balances::Error::<Test, _>::InsufficientBalance
);
});
}
#[test]
fn refund_deposit_on_normal_closure() {
let para_a = 32.into();
let para_b = 64.into();
let mut genesis = GenesisConfigBuilder::default();
genesis.hrmp_sender_deposit = 20;
genesis.hrmp_recipient_deposit = 15;
new_test_ext(genesis.build()).execute_with(|| {
// Register two parachains funded with different amounts of funds and arrange a channel.
register_parachain_with_balance(para_a, 100);
register_parachain_with_balance(para_b, 110);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 80);
assert_eq!(<Test as Config>::Currency::free_balance(&para_b.into_account()), 95);
run_to_block(8, Some(vec![8]));
// Now, we close the channel and wait until the next session.
Hrmp::close_channel(para_b, HrmpChannelId { sender: para_a, recipient: para_b })
.unwrap();
run_to_block(10, Some(vec![10]));
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 100);
assert_eq!(<Test as Config>::Currency::free_balance(&para_b.into_account()), 110);
});
}
#[test]
fn refund_deposit_on_offboarding() {
let para_a = 32.into();
let para_b = 64.into();
let mut genesis = GenesisConfigBuilder::default();
genesis.hrmp_sender_deposit = 20;
genesis.hrmp_recipient_deposit = 15;
new_test_ext(genesis.build()).execute_with(|| {
// Register two parachains and open a channel between them.
register_parachain_with_balance(para_a, 100);
register_parachain_with_balance(para_b, 110);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 80);
assert_eq!(<Test as Config>::Currency::free_balance(&para_b.into_account()), 95);
run_to_block(8, Some(vec![8]));
assert!(channel_exists(para_a, para_b));
// Then deregister one parachain.
deregister_parachain(para_a);
run_to_block(10, Some(vec![9, 10]));
// The channel should be removed.
assert!(!Paras::is_valid_para(para_a));
assert!(!channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 100);
assert_eq!(<Test as Config>::Currency::free_balance(&para_b.into_account()), 110);
});
}
#[test]
fn no_dangling_open_requests() {
let para_a = 32.into();
let para_b = 64.into();
let mut genesis = GenesisConfigBuilder::default();
genesis.hrmp_sender_deposit = 20;
genesis.hrmp_recipient_deposit = 15;
new_test_ext(genesis.build()).execute_with(|| {
// Register two parachains and open a channel between them.
register_parachain_with_balance(para_a, 100);
register_parachain_with_balance(para_b, 110);
run_to_block(5, Some(vec![4, 5]));
// Start opening a channel a->b
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 80);
// Then deregister one parachain, but don't wait two sessions until it takes effect.
// Instead, `para_b` will confirm the request, which will take place the same time
// the offboarding should happen.
deregister_parachain(para_a);
run_to_block(9, Some(vec![9]));
Hrmp::accept_open_channel(para_b, para_a).unwrap();
assert_eq!(<Test as Config>::Currency::free_balance(&para_b.into_account()), 95);
assert!(!channel_exists(para_a, para_b));
run_to_block(10, Some(vec![10]));
// The outcome we expect is `para_b` should receive the refund.
assert_eq!(<Test as Config>::Currency::free_balance(&para_b.into_account()), 110);
assert!(!channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
});
}
#[test]
fn cancel_pending_open_channel_request() {
let para_a = 32.into();
let para_b = 64.into();
let mut genesis = GenesisConfigBuilder::default();
genesis.hrmp_sender_deposit = 20;
genesis.hrmp_recipient_deposit = 15;
new_test_ext(genesis.build()).execute_with(|| {
// Register two parachains and open a channel between them.
register_parachain_with_balance(para_a, 100);
register_parachain_with_balance(para_b, 110);
run_to_block(5, Some(vec![4, 5]));
// Start opening a channel a->b
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 80);
// Cancel opening the channel
Hrmp::cancel_open_request(para_a, HrmpChannelId { sender: para_a, recipient: para_b })
.unwrap();
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 100);
run_to_block(10, Some(vec![10]));
assert!(!channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
});
}
}
@@ -0,0 +1,601 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot 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.
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::mock::{
new_test_ext, Configuration, Event as MockEvent, Hrmp, MockGenesisConfig, Paras, ParasShared,
System, Test,
};
use frame_support::{assert_noop, assert_ok, traits::Currency as _};
use primitives::v1::BlockNumber;
use std::collections::BTreeMap;
fn run_to_block(to: BlockNumber, new_session: Option<Vec<BlockNumber>>) {
let config = Configuration::config();
while System::block_number() < to {
let b = System::block_number();
// NOTE: this is in reverse initialization order.
Hrmp::initializer_finalize();
Paras::initializer_finalize(b);
ParasShared::initializer_finalize();
if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) {
let notification = crate::initializer::SessionChangeNotification {
prev_config: config.clone(),
new_config: config.clone(),
session_index: ParasShared::session_index() + 1,
..Default::default()
};
// NOTE: this is in initialization order.
ParasShared::initializer_on_new_session(
notification.session_index,
notification.random_seed,
&notification.new_config,
notification.validators.clone(),
);
let outgoing_paras = Paras::initializer_on_new_session(&notification);
Hrmp::initializer_on_new_session(&notification, &outgoing_paras);
}
System::on_finalize(b);
System::on_initialize(b + 1);
System::set_block_number(b + 1);
// NOTE: this is in initialization order.
ParasShared::initializer_initialize(b + 1);
Paras::initializer_initialize(b + 1);
Hrmp::initializer_initialize(b + 1);
}
}
#[derive(Debug)]
pub(super) struct GenesisConfigBuilder {
hrmp_channel_max_capacity: u32,
hrmp_channel_max_message_size: u32,
hrmp_max_parathread_outbound_channels: u32,
hrmp_max_parachain_outbound_channels: u32,
hrmp_max_parathread_inbound_channels: u32,
hrmp_max_parachain_inbound_channels: u32,
hrmp_max_message_num_per_candidate: u32,
hrmp_channel_max_total_size: u32,
hrmp_sender_deposit: Balance,
hrmp_recipient_deposit: Balance,
}
impl Default for GenesisConfigBuilder {
fn default() -> Self {
Self {
hrmp_channel_max_capacity: 2,
hrmp_channel_max_message_size: 8,
hrmp_max_parathread_outbound_channels: 1,
hrmp_max_parachain_outbound_channels: 2,
hrmp_max_parathread_inbound_channels: 1,
hrmp_max_parachain_inbound_channels: 2,
hrmp_max_message_num_per_candidate: 2,
hrmp_channel_max_total_size: 16,
hrmp_sender_deposit: 100,
hrmp_recipient_deposit: 100,
}
}
}
impl GenesisConfigBuilder {
pub(super) fn build(self) -> crate::mock::MockGenesisConfig {
let mut genesis = default_genesis_config();
let config = &mut genesis.configuration.config;
config.hrmp_channel_max_capacity = self.hrmp_channel_max_capacity;
config.hrmp_channel_max_message_size = self.hrmp_channel_max_message_size;
config.hrmp_max_parathread_outbound_channels = self.hrmp_max_parathread_outbound_channels;
config.hrmp_max_parachain_outbound_channels = self.hrmp_max_parachain_outbound_channels;
config.hrmp_max_parathread_inbound_channels = self.hrmp_max_parathread_inbound_channels;
config.hrmp_max_parachain_inbound_channels = self.hrmp_max_parachain_inbound_channels;
config.hrmp_max_message_num_per_candidate = self.hrmp_max_message_num_per_candidate;
config.hrmp_channel_max_total_size = self.hrmp_channel_max_total_size;
config.hrmp_sender_deposit = self.hrmp_sender_deposit;
config.hrmp_recipient_deposit = self.hrmp_recipient_deposit;
genesis
}
}
fn default_genesis_config() -> MockGenesisConfig {
MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: crate::configuration::HostConfiguration {
max_downward_message_size: 1024,
pvf_checking_enabled: false,
..Default::default()
},
},
..Default::default()
}
}
fn register_parachain_with_balance(id: ParaId, balance: Balance) {
assert_ok!(Paras::schedule_para_initialize(
id,
crate::paras::ParaGenesisArgs {
parachain: true,
genesis_head: vec![1].into(),
validation_code: vec![1].into(),
},
));
<Test as Config>::Currency::make_free_balance_be(&id.into_account(), balance);
}
fn register_parachain(id: ParaId) {
register_parachain_with_balance(id, 1000);
}
fn deregister_parachain(id: ParaId) {
assert_ok!(Paras::schedule_para_cleanup(id));
}
fn channel_exists(sender: ParaId, recipient: ParaId) -> bool {
<Hrmp as Store>::HrmpChannels::get(&HrmpChannelId { sender, recipient }).is_some()
}
#[test]
fn empty_state_consistent_state() {
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
Hrmp::assert_storage_consistency_exhaustive();
});
}
#[test]
fn open_channel_works() {
let para_a = 1.into();
let para_a_origin: crate::Origin = 1.into();
let para_b = 3.into();
let para_b_origin: crate::Origin = 3.into();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
// We need both A & B to be registered and alive parachains.
register_parachain(para_a);
register_parachain(para_b);
run_to_block(5, Some(vec![4, 5]));
Hrmp::hrmp_init_open_channel(para_a_origin.into(), para_b, 2, 8).unwrap();
Hrmp::assert_storage_consistency_exhaustive();
assert!(System::events().iter().any(|record| record.event ==
MockEvent::Hrmp(Event::OpenChannelRequested(para_a, para_b, 2, 8))));
Hrmp::hrmp_accept_open_channel(para_b_origin.into(), para_a).unwrap();
Hrmp::assert_storage_consistency_exhaustive();
assert!(System::events()
.iter()
.any(|record| record.event ==
MockEvent::Hrmp(Event::OpenChannelAccepted(para_a, para_b))));
// Advance to a block 6, but without session change. That means that the channel has
// not been created yet.
run_to_block(6, None);
assert!(!channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
// Now let the session change happen and thus open the channel.
run_to_block(8, Some(vec![8]));
assert!(channel_exists(para_a, para_b));
});
}
#[test]
fn close_channel_works() {
let para_a = 5.into();
let para_b = 2.into();
let para_b_origin: crate::Origin = 2.into();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
register_parachain(para_a);
register_parachain(para_b);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
run_to_block(6, Some(vec![6]));
assert!(channel_exists(para_a, para_b));
// Close the channel. The effect is not immediate, but rather deferred to the next
// session change.
let channel_id = HrmpChannelId { sender: para_a, recipient: para_b };
Hrmp::hrmp_close_channel(para_b_origin.into(), channel_id.clone()).unwrap();
assert!(channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
// After the session change the channel should be closed.
run_to_block(8, Some(vec![8]));
assert!(!channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
assert!(System::events().iter().any(|record| record.event ==
MockEvent::Hrmp(Event::ChannelClosed(para_b, channel_id.clone()))));
});
}
#[test]
fn send_recv_messages() {
let para_a = 32.into();
let para_b = 64.into();
let mut genesis = GenesisConfigBuilder::default();
genesis.hrmp_channel_max_message_size = 20;
genesis.hrmp_channel_max_total_size = 20;
new_test_ext(genesis.build()).execute_with(|| {
register_parachain(para_a);
register_parachain(para_b);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 20).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
// On Block 6:
// A sends a message to B
run_to_block(6, Some(vec![6]));
assert!(channel_exists(para_a, para_b));
let msgs =
vec![OutboundHrmpMessage { recipient: para_b, data: b"this is an emergency".to_vec() }];
let config = Configuration::config();
assert!(Hrmp::check_outbound_hrmp(&config, para_a, &msgs).is_ok());
let _ = Hrmp::queue_outbound_hrmp(para_a, msgs);
Hrmp::assert_storage_consistency_exhaustive();
// On Block 7:
// B receives the message sent by A. B sets the watermark to 6.
run_to_block(7, None);
assert!(Hrmp::check_hrmp_watermark(para_b, 7, 6).is_ok());
let _ = Hrmp::prune_hrmp(para_b, 6);
Hrmp::assert_storage_consistency_exhaustive();
});
}
#[test]
fn hrmp_mqc_head_fixture() {
let para_a = 2000.into();
let para_b = 2024.into();
let mut genesis = GenesisConfigBuilder::default();
genesis.hrmp_channel_max_message_size = 20;
genesis.hrmp_channel_max_total_size = 20;
new_test_ext(genesis.build()).execute_with(|| {
register_parachain(para_a);
register_parachain(para_b);
run_to_block(2, Some(vec![1, 2]));
Hrmp::init_open_channel(para_a, para_b, 2, 20).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
run_to_block(3, Some(vec![3]));
let _ = Hrmp::queue_outbound_hrmp(
para_a,
vec![OutboundHrmpMessage { recipient: para_b, data: vec![1, 2, 3] }],
);
run_to_block(4, None);
let _ = Hrmp::queue_outbound_hrmp(
para_a,
vec![OutboundHrmpMessage { recipient: para_b, data: vec![4, 5, 6] }],
);
assert_eq!(
Hrmp::hrmp_mqc_heads(para_b),
vec![(
para_a,
hex_literal::hex![
"a964fd3b4f3d3ce92a0e25e576b87590d92bb5cb7031909c7f29050e1f04a375"
]
.into()
),],
);
});
}
#[test]
fn accept_incoming_request_and_offboard() {
let para_a = 32.into();
let para_b = 64.into();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
register_parachain(para_a);
register_parachain(para_b);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
deregister_parachain(para_a);
// On Block 7: 2x session change. The channel should not be created.
run_to_block(7, Some(vec![6, 7]));
assert!(!Paras::is_valid_para(para_a));
assert!(!channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
});
}
#[test]
fn check_sent_messages() {
let para_a = 32.into();
let para_b = 64.into();
let para_c = 97.into();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
register_parachain(para_a);
register_parachain(para_b);
register_parachain(para_c);
run_to_block(5, Some(vec![4, 5]));
// Open two channels to the same receiver, b:
// a -> b, c -> b
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
Hrmp::init_open_channel(para_c, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_c).unwrap();
// On Block 6: session change.
run_to_block(6, Some(vec![6]));
assert!(Paras::is_valid_para(para_a));
let msgs = vec![OutboundHrmpMessage { recipient: para_b, data: b"knock".to_vec() }];
let config = Configuration::config();
assert!(Hrmp::check_outbound_hrmp(&config, para_a, &msgs).is_ok());
let _ = Hrmp::queue_outbound_hrmp(para_a, msgs.clone());
// Verify that the sent messages are there and that also the empty channels are present.
let mqc_heads = Hrmp::hrmp_mqc_heads(para_b);
let contents = Hrmp::inbound_hrmp_channels_contents(para_b);
assert_eq!(
contents,
vec![
(para_a, vec![InboundHrmpMessage { sent_at: 6, data: b"knock".to_vec() }]),
(para_c, vec![])
]
.into_iter()
.collect::<BTreeMap::<_, _>>(),
);
assert_eq!(
mqc_heads,
vec![
(
para_a,
hex_literal::hex!(
"3bba6404e59c91f51deb2ae78f1273ebe75896850713e13f8c0eba4b0996c483"
)
.into()
),
(para_c, Default::default())
],
);
Hrmp::assert_storage_consistency_exhaustive();
});
}
#[test]
fn verify_externally_accessible() {
use primitives::v1::{well_known_keys, AbridgedHrmpChannel};
let para_a = 20.into();
let para_b = 21.into();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
// Register two parachains, wait until a session change, then initiate channel open
// request and accept that, and finally wait until the next session.
register_parachain(para_a);
register_parachain(para_b);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
run_to_block(8, Some(vec![8]));
// Here we have a channel a->b opened.
//
// Try to obtain this channel from the storage and
// decode it into the abridged version.
assert!(channel_exists(para_a, para_b));
let raw_hrmp_channel =
sp_io::storage::get(&well_known_keys::hrmp_channels(HrmpChannelId {
sender: para_a,
recipient: para_b,
}))
.expect("the channel exists and we must be able to get it through well known keys");
let abridged_hrmp_channel = AbridgedHrmpChannel::decode(&mut &raw_hrmp_channel[..])
.expect("HrmpChannel should be decodable as AbridgedHrmpChannel");
assert_eq!(
abridged_hrmp_channel,
AbridgedHrmpChannel {
max_capacity: 2,
max_total_size: 16,
max_message_size: 8,
msg_count: 0,
total_size: 0,
mqc_head: None,
},
);
let raw_ingress_index =
sp_io::storage::get(&well_known_keys::hrmp_ingress_channel_index(para_b))
.expect("the ingress index must be present for para_b");
let ingress_index = <Vec<ParaId>>::decode(&mut &raw_ingress_index[..])
.expect("ingress indexx should be decodable as a list of para ids");
assert_eq!(ingress_index, vec![para_a]);
// Now, verify that we can access and decode the egress index.
let raw_egress_index =
sp_io::storage::get(&well_known_keys::hrmp_egress_channel_index(para_a))
.expect("the egress index must be present for para_a");
let egress_index = <Vec<ParaId>>::decode(&mut &raw_egress_index[..])
.expect("egress index should be decodable as a list of para ids");
assert_eq!(egress_index, vec![para_b]);
});
}
#[test]
fn charging_deposits() {
let para_a = 32.into();
let para_b = 64.into();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
register_parachain_with_balance(para_a, 0);
register_parachain(para_b);
run_to_block(5, Some(vec![4, 5]));
assert_noop!(
Hrmp::init_open_channel(para_a, para_b, 2, 8),
pallet_balances::Error::<Test, _>::InsufficientBalance
);
});
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
register_parachain(para_a);
register_parachain_with_balance(para_b, 0);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
assert_noop!(
Hrmp::accept_open_channel(para_b, para_a),
pallet_balances::Error::<Test, _>::InsufficientBalance
);
});
}
#[test]
fn refund_deposit_on_normal_closure() {
let para_a = 32.into();
let para_b = 64.into();
let mut genesis = GenesisConfigBuilder::default();
genesis.hrmp_sender_deposit = 20;
genesis.hrmp_recipient_deposit = 15;
new_test_ext(genesis.build()).execute_with(|| {
// Register two parachains funded with different amounts of funds and arrange a channel.
register_parachain_with_balance(para_a, 100);
register_parachain_with_balance(para_b, 110);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 80);
assert_eq!(<Test as Config>::Currency::free_balance(&para_b.into_account()), 95);
run_to_block(8, Some(vec![8]));
// Now, we close the channel and wait until the next session.
Hrmp::close_channel(para_b, HrmpChannelId { sender: para_a, recipient: para_b }).unwrap();
run_to_block(10, Some(vec![10]));
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 100);
assert_eq!(<Test as Config>::Currency::free_balance(&para_b.into_account()), 110);
});
}
#[test]
fn refund_deposit_on_offboarding() {
let para_a = 32.into();
let para_b = 64.into();
let mut genesis = GenesisConfigBuilder::default();
genesis.hrmp_sender_deposit = 20;
genesis.hrmp_recipient_deposit = 15;
new_test_ext(genesis.build()).execute_with(|| {
// Register two parachains and open a channel between them.
register_parachain_with_balance(para_a, 100);
register_parachain_with_balance(para_b, 110);
run_to_block(5, Some(vec![4, 5]));
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
Hrmp::accept_open_channel(para_b, para_a).unwrap();
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 80);
assert_eq!(<Test as Config>::Currency::free_balance(&para_b.into_account()), 95);
run_to_block(8, Some(vec![8]));
assert!(channel_exists(para_a, para_b));
// Then deregister one parachain.
deregister_parachain(para_a);
run_to_block(10, Some(vec![9, 10]));
// The channel should be removed.
assert!(!Paras::is_valid_para(para_a));
assert!(!channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 100);
assert_eq!(<Test as Config>::Currency::free_balance(&para_b.into_account()), 110);
});
}
#[test]
fn no_dangling_open_requests() {
let para_a = 32.into();
let para_b = 64.into();
let mut genesis = GenesisConfigBuilder::default();
genesis.hrmp_sender_deposit = 20;
genesis.hrmp_recipient_deposit = 15;
new_test_ext(genesis.build()).execute_with(|| {
// Register two parachains and open a channel between them.
register_parachain_with_balance(para_a, 100);
register_parachain_with_balance(para_b, 110);
run_to_block(5, Some(vec![4, 5]));
// Start opening a channel a->b
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 80);
// Then deregister one parachain, but don't wait two sessions until it takes effect.
// Instead, `para_b` will confirm the request, which will take place the same time
// the offboarding should happen.
deregister_parachain(para_a);
run_to_block(9, Some(vec![9]));
Hrmp::accept_open_channel(para_b, para_a).unwrap();
assert_eq!(<Test as Config>::Currency::free_balance(&para_b.into_account()), 95);
assert!(!channel_exists(para_a, para_b));
run_to_block(10, Some(vec![10]));
// The outcome we expect is `para_b` should receive the refund.
assert_eq!(<Test as Config>::Currency::free_balance(&para_b.into_account()), 110);
assert!(!channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
});
}
#[test]
fn cancel_pending_open_channel_request() {
let para_a = 32.into();
let para_b = 64.into();
let mut genesis = GenesisConfigBuilder::default();
genesis.hrmp_sender_deposit = 20;
genesis.hrmp_recipient_deposit = 15;
new_test_ext(genesis.build()).execute_with(|| {
// Register two parachains and open a channel between them.
register_parachain_with_balance(para_a, 100);
register_parachain_with_balance(para_b, 110);
run_to_block(5, Some(vec![4, 5]));
// Start opening a channel a->b
Hrmp::init_open_channel(para_a, para_b, 2, 8).unwrap();
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 80);
// Cancel opening the channel
Hrmp::cancel_open_request(para_a, HrmpChannelId { sender: para_a, recipient: para_b })
.unwrap();
assert_eq!(<Test as Config>::Currency::free_balance(&para_a.into_account()), 100);
run_to_block(10, Some(vec![10]));
assert!(!channel_exists(para_a, para_b));
Hrmp::assert_storage_consistency_exhaustive();
});
}
+3 -135
View File
@@ -34,6 +34,9 @@ use primitives::v1::{BlockNumber, ConsensusLog, SessionIndex, ValidatorId};
use scale_info::TypeInfo; use scale_info::TypeInfo;
use sp_std::prelude::*; use sp_std::prelude::*;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")] #[cfg(feature = "runtime-benchmarks")]
mod benchmarking; mod benchmarking;
@@ -328,138 +331,3 @@ impl<T: pallet_session::Config + Config> OneSessionHandler<T::AccountId> for Pal
fn on_disabled(_i: u32) {} fn on_disabled(_i: u32) {}
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{
new_test_ext, Configuration, Dmp, Initializer, MockGenesisConfig, Paras, SessionInfo,
System,
};
use primitives::v1::{HeadData, Id as ParaId};
use test_helpers::dummy_validation_code;
use frame_support::{
assert_ok,
traits::{OnFinalize, OnInitialize},
};
#[test]
fn session_0_is_instantly_applied() {
new_test_ext(Default::default()).execute_with(|| {
Initializer::on_new_session(
false,
0,
Vec::new().into_iter(),
Some(Vec::new().into_iter()),
);
let v = <Initializer as Store>::BufferedSessionChanges::get();
assert!(v.is_empty());
assert_eq!(SessionInfo::earliest_stored_session(), 0);
assert!(SessionInfo::session_info(0).is_some());
});
}
#[test]
fn session_change_before_initialize_is_still_buffered_after() {
new_test_ext(Default::default()).execute_with(|| {
Initializer::on_new_session(
false,
1,
Vec::new().into_iter(),
Some(Vec::new().into_iter()),
);
let now = System::block_number();
Initializer::on_initialize(now);
let v = <Initializer as Store>::BufferedSessionChanges::get();
assert_eq!(v.len(), 1);
});
}
#[test]
fn session_change_applied_on_finalize() {
new_test_ext(Default::default()).execute_with(|| {
Initializer::on_initialize(1);
Initializer::on_new_session(
false,
1,
Vec::new().into_iter(),
Some(Vec::new().into_iter()),
);
Initializer::on_finalize(1);
assert!(<Initializer as Store>::BufferedSessionChanges::get().is_empty());
});
}
#[test]
fn sets_flag_on_initialize() {
new_test_ext(Default::default()).execute_with(|| {
Initializer::on_initialize(1);
assert!(<Initializer as Store>::HasInitialized::get().is_some());
})
}
#[test]
fn clears_flag_on_finalize() {
new_test_ext(Default::default()).execute_with(|| {
Initializer::on_initialize(1);
Initializer::on_finalize(1);
assert!(<Initializer as Store>::HasInitialized::get().is_none());
})
}
#[test]
fn scheduled_cleanup_performed() {
let a = ParaId::from(1312);
let b = ParaId::from(228);
let c = ParaId::from(123);
let mock_genesis = crate::paras::ParaGenesisArgs {
parachain: true,
genesis_head: HeadData(vec![4, 5, 6]),
validation_code: dummy_validation_code(),
};
new_test_ext(MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: crate::configuration::HostConfiguration {
max_downward_message_size: 1024,
..Default::default()
},
},
paras: crate::paras::GenesisConfig {
paras: vec![
(a, mock_genesis.clone()),
(b, mock_genesis.clone()),
(c, mock_genesis.clone()),
],
..Default::default()
},
..Default::default()
})
.execute_with(|| {
// enqueue downward messages to A, B and C.
assert_ok!(Dmp::queue_downward_message(&Configuration::config(), a, vec![1, 2, 3]));
assert_ok!(Dmp::queue_downward_message(&Configuration::config(), b, vec![4, 5, 6]));
assert_ok!(Dmp::queue_downward_message(&Configuration::config(), c, vec![7, 8, 9]));
assert_ok!(Paras::schedule_para_cleanup(a));
assert_ok!(Paras::schedule_para_cleanup(b));
// Apply session 2 in the future
Initializer::apply_new_session(2, vec![], vec![]);
assert!(Dmp::dmq_contents(a).is_empty());
assert!(Dmp::dmq_contents(b).is_empty());
assert!(!Dmp::dmq_contents(c).is_empty());
});
}
}
@@ -0,0 +1,131 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot 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.
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::mock::{
new_test_ext, Configuration, Dmp, Initializer, MockGenesisConfig, Paras, SessionInfo, System,
};
use primitives::v1::{HeadData, Id as ParaId};
use test_helpers::dummy_validation_code;
use frame_support::{
assert_ok,
traits::{OnFinalize, OnInitialize},
};
#[test]
fn session_0_is_instantly_applied() {
new_test_ext(Default::default()).execute_with(|| {
Initializer::on_new_session(false, 0, Vec::new().into_iter(), Some(Vec::new().into_iter()));
let v = <Initializer as Store>::BufferedSessionChanges::get();
assert!(v.is_empty());
assert_eq!(SessionInfo::earliest_stored_session(), 0);
assert!(SessionInfo::session_info(0).is_some());
});
}
#[test]
fn session_change_before_initialize_is_still_buffered_after() {
new_test_ext(Default::default()).execute_with(|| {
Initializer::on_new_session(false, 1, Vec::new().into_iter(), Some(Vec::new().into_iter()));
let now = System::block_number();
Initializer::on_initialize(now);
let v = <Initializer as Store>::BufferedSessionChanges::get();
assert_eq!(v.len(), 1);
});
}
#[test]
fn session_change_applied_on_finalize() {
new_test_ext(Default::default()).execute_with(|| {
Initializer::on_initialize(1);
Initializer::on_new_session(false, 1, Vec::new().into_iter(), Some(Vec::new().into_iter()));
Initializer::on_finalize(1);
assert!(<Initializer as Store>::BufferedSessionChanges::get().is_empty());
});
}
#[test]
fn sets_flag_on_initialize() {
new_test_ext(Default::default()).execute_with(|| {
Initializer::on_initialize(1);
assert!(<Initializer as Store>::HasInitialized::get().is_some());
})
}
#[test]
fn clears_flag_on_finalize() {
new_test_ext(Default::default()).execute_with(|| {
Initializer::on_initialize(1);
Initializer::on_finalize(1);
assert!(<Initializer as Store>::HasInitialized::get().is_none());
})
}
#[test]
fn scheduled_cleanup_performed() {
let a = ParaId::from(1312);
let b = ParaId::from(228);
let c = ParaId::from(123);
let mock_genesis = crate::paras::ParaGenesisArgs {
parachain: true,
genesis_head: HeadData(vec![4, 5, 6]),
validation_code: dummy_validation_code(),
};
new_test_ext(MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: crate::configuration::HostConfiguration {
max_downward_message_size: 1024,
..Default::default()
},
},
paras: crate::paras::GenesisConfig {
paras: vec![
(a, mock_genesis.clone()),
(b, mock_genesis.clone()),
(c, mock_genesis.clone()),
],
..Default::default()
},
..Default::default()
})
.execute_with(|| {
// enqueue downward messages to A, B and C.
assert_ok!(Dmp::queue_downward_message(&Configuration::config(), a, vec![1, 2, 3]));
assert_ok!(Dmp::queue_downward_message(&Configuration::config(), b, vec![4, 5, 6]));
assert_ok!(Dmp::queue_downward_message(&Configuration::config(), c, vec![7, 8, 9]));
assert_ok!(Paras::schedule_para_cleanup(a));
assert_ok!(Paras::schedule_para_cleanup(b));
// Apply session 2 in the future
Initializer::apply_new_session(2, vec![], vec![]);
assert!(Dmp::dmq_contents(a).is_empty());
assert!(Dmp::dmq_contents(b).is_empty());
assert!(!Dmp::dmq_contents(c).is_empty());
});
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3 -202
View File
@@ -34,6 +34,9 @@ pub use pallet::*;
pub mod migration; pub mod migration;
#[cfg(test)]
mod tests;
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
@@ -187,205 +190,3 @@ impl<T: pallet_session::Config + Config> OneSessionHandler<T::AccountId> for Pal
fn on_disabled(_i: u32) {} fn on_disabled(_i: u32) {}
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::{
configuration::HostConfiguration,
initializer::SessionChangeNotification,
mock::{
new_test_ext, Configuration, MockGenesisConfig, Origin, ParasShared, SessionInfo,
System, Test,
},
util::take_active_subset,
};
use keyring::Sr25519Keyring;
use primitives::v1::{BlockNumber, ValidatorId, ValidatorIndex};
fn run_to_block(
to: BlockNumber,
new_session: impl Fn(BlockNumber) -> Option<SessionChangeNotification<BlockNumber>>,
) {
while System::block_number() < to {
let b = System::block_number();
SessionInfo::initializer_finalize();
ParasShared::initializer_finalize();
Configuration::initializer_finalize();
if let Some(notification) = new_session(b + 1) {
Configuration::initializer_on_new_session(&notification.session_index);
ParasShared::initializer_on_new_session(
notification.session_index,
notification.random_seed,
&notification.new_config,
notification.validators.clone(),
);
SessionInfo::initializer_on_new_session(&notification);
}
System::on_finalize(b);
System::on_initialize(b + 1);
System::set_block_number(b + 1);
Configuration::initializer_initialize(b + 1);
ParasShared::initializer_initialize(b + 1);
SessionInfo::initializer_initialize(b + 1);
}
}
fn default_config() -> HostConfiguration<BlockNumber> {
HostConfiguration {
parathread_cores: 1,
dispute_period: 2,
needed_approvals: 3,
..Default::default()
}
}
fn genesis_config() -> MockGenesisConfig {
MockGenesisConfig {
configuration: configuration::GenesisConfig {
config: default_config(),
..Default::default()
},
..Default::default()
}
}
fn session_changes(n: BlockNumber) -> Option<SessionChangeNotification<BlockNumber>> {
if n % 10 == 0 {
Some(SessionChangeNotification { session_index: n / 10, ..Default::default() })
} else {
None
}
}
fn new_session_every_block(n: BlockNumber) -> Option<SessionChangeNotification<BlockNumber>> {
Some(SessionChangeNotification { session_index: n, ..Default::default() })
}
#[test]
fn session_pruning_is_based_on_dispute_period() {
new_test_ext(genesis_config()).execute_with(|| {
// Dispute period starts at 2
let config = Configuration::config();
assert_eq!(config.dispute_period, 2);
// Move to session 10
run_to_block(100, session_changes);
// Earliest stored session is 10 - 2 = 8
assert_eq!(EarliestStoredSession::<Test>::get(), 8);
// Pruning works as expected
assert!(Sessions::<Test>::get(7).is_none());
assert!(Sessions::<Test>::get(8).is_some());
assert!(Sessions::<Test>::get(9).is_some());
// changing `dispute_period` works
let dispute_period = 5;
Configuration::set_dispute_period(Origin::root(), dispute_period).unwrap();
// Dispute period does not automatically change
let config = Configuration::config();
assert_eq!(config.dispute_period, 2);
// Two sessions later it will though
run_to_block(120, session_changes);
let config = Configuration::config();
assert_eq!(config.dispute_period, 5);
run_to_block(200, session_changes);
assert_eq!(EarliestStoredSession::<Test>::get(), 20 - dispute_period);
// Increase dispute period even more
let new_dispute_period = 16;
Configuration::set_dispute_period(Origin::root(), new_dispute_period).unwrap();
run_to_block(210, session_changes);
assert_eq!(EarliestStoredSession::<Test>::get(), 21 - dispute_period);
// Two sessions later it kicks in
run_to_block(220, session_changes);
let config = Configuration::config();
assert_eq!(config.dispute_period, 16);
// Earliest session stays the same
assert_eq!(EarliestStoredSession::<Test>::get(), 21 - dispute_period);
// We still don't have enough stored sessions to start pruning
run_to_block(300, session_changes);
assert_eq!(EarliestStoredSession::<Test>::get(), 21 - dispute_period);
// now we do
run_to_block(420, session_changes);
assert_eq!(EarliestStoredSession::<Test>::get(), 42 - new_dispute_period);
})
}
#[test]
fn session_info_is_based_on_config() {
new_test_ext(genesis_config()).execute_with(|| {
run_to_block(1, new_session_every_block);
let session = Sessions::<Test>::get(&1).unwrap();
assert_eq!(session.needed_approvals, 3);
// change some param
Configuration::set_needed_approvals(Origin::root(), 42).unwrap();
// 2 sessions later
run_to_block(3, new_session_every_block);
let session = Sessions::<Test>::get(&3).unwrap();
assert_eq!(session.needed_approvals, 42);
})
}
#[test]
fn session_info_active_subsets() {
let unscrambled = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
];
let active_set = vec![ValidatorIndex(4), ValidatorIndex(0), ValidatorIndex(2)];
let unscrambled_validators: Vec<ValidatorId> =
unscrambled.iter().map(|v| v.public().into()).collect();
let unscrambled_discovery: Vec<AuthorityDiscoveryId> =
unscrambled.iter().map(|v| v.public().into()).collect();
let unscrambled_assignment: Vec<AssignmentId> =
unscrambled.iter().map(|v| v.public().into()).collect();
let validators = take_active_subset(&active_set, &unscrambled_validators);
new_test_ext(genesis_config()).execute_with(|| {
ParasShared::set_active_validators_with_indices(active_set.clone(), validators.clone());
assert_eq!(ParasShared::active_validator_indices(), active_set);
AssignmentKeysUnsafe::<Test>::set(unscrambled_assignment.clone());
crate::mock::set_discovery_authorities(unscrambled_discovery.clone());
assert_eq!(<Test>::authorities(), unscrambled_discovery);
// invoke directly, because `run_to_block` will invoke `Shared` and clobber our
// values.
SessionInfo::initializer_on_new_session(&SessionChangeNotification {
session_index: 1,
validators: validators.clone(),
..Default::default()
});
let session = Sessions::<Test>::get(&1).unwrap();
assert_eq!(session.validators, validators);
assert_eq!(
session.discovery_keys,
take_active_subset_and_inactive(&active_set, &unscrambled_discovery),
);
assert_eq!(
session.assignment_keys,
take_active_subset(&active_set, &unscrambled_assignment),
);
})
}
}
@@ -0,0 +1,214 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot 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.
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::{
configuration::HostConfiguration,
initializer::SessionChangeNotification,
mock::{
new_test_ext, Configuration, MockGenesisConfig, Origin, ParasShared, SessionInfo, System,
Test,
},
util::take_active_subset,
};
use keyring::Sr25519Keyring;
use primitives::v1::{BlockNumber, ValidatorId, ValidatorIndex};
fn run_to_block(
to: BlockNumber,
new_session: impl Fn(BlockNumber) -> Option<SessionChangeNotification<BlockNumber>>,
) {
while System::block_number() < to {
let b = System::block_number();
SessionInfo::initializer_finalize();
ParasShared::initializer_finalize();
Configuration::initializer_finalize();
if let Some(notification) = new_session(b + 1) {
Configuration::initializer_on_new_session(&notification.session_index);
ParasShared::initializer_on_new_session(
notification.session_index,
notification.random_seed,
&notification.new_config,
notification.validators.clone(),
);
SessionInfo::initializer_on_new_session(&notification);
}
System::on_finalize(b);
System::on_initialize(b + 1);
System::set_block_number(b + 1);
Configuration::initializer_initialize(b + 1);
ParasShared::initializer_initialize(b + 1);
SessionInfo::initializer_initialize(b + 1);
}
}
fn default_config() -> HostConfiguration<BlockNumber> {
HostConfiguration {
parathread_cores: 1,
dispute_period: 2,
needed_approvals: 3,
..Default::default()
}
}
fn genesis_config() -> MockGenesisConfig {
MockGenesisConfig {
configuration: configuration::GenesisConfig {
config: default_config(),
..Default::default()
},
..Default::default()
}
}
fn session_changes(n: BlockNumber) -> Option<SessionChangeNotification<BlockNumber>> {
if n % 10 == 0 {
Some(SessionChangeNotification { session_index: n / 10, ..Default::default() })
} else {
None
}
}
fn new_session_every_block(n: BlockNumber) -> Option<SessionChangeNotification<BlockNumber>> {
Some(SessionChangeNotification { session_index: n, ..Default::default() })
}
#[test]
fn session_pruning_is_based_on_dispute_period() {
new_test_ext(genesis_config()).execute_with(|| {
// Dispute period starts at 2
let config = Configuration::config();
assert_eq!(config.dispute_period, 2);
// Move to session 10
run_to_block(100, session_changes);
// Earliest stored session is 10 - 2 = 8
assert_eq!(EarliestStoredSession::<Test>::get(), 8);
// Pruning works as expected
assert!(Sessions::<Test>::get(7).is_none());
assert!(Sessions::<Test>::get(8).is_some());
assert!(Sessions::<Test>::get(9).is_some());
// changing `dispute_period` works
let dispute_period = 5;
Configuration::set_dispute_period(Origin::root(), dispute_period).unwrap();
// Dispute period does not automatically change
let config = Configuration::config();
assert_eq!(config.dispute_period, 2);
// Two sessions later it will though
run_to_block(120, session_changes);
let config = Configuration::config();
assert_eq!(config.dispute_period, 5);
run_to_block(200, session_changes);
assert_eq!(EarliestStoredSession::<Test>::get(), 20 - dispute_period);
// Increase dispute period even more
let new_dispute_period = 16;
Configuration::set_dispute_period(Origin::root(), new_dispute_period).unwrap();
run_to_block(210, session_changes);
assert_eq!(EarliestStoredSession::<Test>::get(), 21 - dispute_period);
// Two sessions later it kicks in
run_to_block(220, session_changes);
let config = Configuration::config();
assert_eq!(config.dispute_period, 16);
// Earliest session stays the same
assert_eq!(EarliestStoredSession::<Test>::get(), 21 - dispute_period);
// We still don't have enough stored sessions to start pruning
run_to_block(300, session_changes);
assert_eq!(EarliestStoredSession::<Test>::get(), 21 - dispute_period);
// now we do
run_to_block(420, session_changes);
assert_eq!(EarliestStoredSession::<Test>::get(), 42 - new_dispute_period);
})
}
#[test]
fn session_info_is_based_on_config() {
new_test_ext(genesis_config()).execute_with(|| {
run_to_block(1, new_session_every_block);
let session = Sessions::<Test>::get(&1).unwrap();
assert_eq!(session.needed_approvals, 3);
// change some param
Configuration::set_needed_approvals(Origin::root(), 42).unwrap();
// 2 sessions later
run_to_block(3, new_session_every_block);
let session = Sessions::<Test>::get(&3).unwrap();
assert_eq!(session.needed_approvals, 42);
})
}
#[test]
fn session_info_active_subsets() {
let unscrambled = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
];
let active_set = vec![ValidatorIndex(4), ValidatorIndex(0), ValidatorIndex(2)];
let unscrambled_validators: Vec<ValidatorId> =
unscrambled.iter().map(|v| v.public().into()).collect();
let unscrambled_discovery: Vec<AuthorityDiscoveryId> =
unscrambled.iter().map(|v| v.public().into()).collect();
let unscrambled_assignment: Vec<AssignmentId> =
unscrambled.iter().map(|v| v.public().into()).collect();
let validators = take_active_subset(&active_set, &unscrambled_validators);
new_test_ext(genesis_config()).execute_with(|| {
ParasShared::set_active_validators_with_indices(active_set.clone(), validators.clone());
assert_eq!(ParasShared::active_validator_indices(), active_set);
AssignmentKeysUnsafe::<Test>::set(unscrambled_assignment.clone());
crate::mock::set_discovery_authorities(unscrambled_discovery.clone());
assert_eq!(<Test>::authorities(), unscrambled_discovery);
// invoke directly, because `run_to_block` will invoke `Shared` and clobber our
// values.
SessionInfo::initializer_on_new_session(&SessionChangeNotification {
session_index: 1,
validators: validators.clone(),
..Default::default()
});
let session = Sessions::<Test>::get(&1).unwrap();
assert_eq!(session.validators, validators);
assert_eq!(
session.discovery_keys,
take_active_subset_and_inactive(&active_set, &unscrambled_discovery),
);
assert_eq!(
session.assignment_keys,
take_active_subset(&active_set, &unscrambled_assignment),
);
})
}
+3 -90
View File
@@ -35,6 +35,9 @@ pub use pallet::*;
// which guarantees that at least one full session has passed before any changes are applied. // which guarantees that at least one full session has passed before any changes are applied.
pub(crate) const SESSION_DELAY: SessionIndex = 2; pub(crate) const SESSION_DELAY: SessionIndex = 2;
#[cfg(test)]
mod tests;
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
@@ -139,93 +142,3 @@ impl<T: Config> Pallet<T> {
ActiveValidatorKeys::<T>::set(keys); ActiveValidatorKeys::<T>::set(keys);
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::{
configuration::HostConfiguration,
mock::{new_test_ext, MockGenesisConfig, ParasShared},
};
use keyring::Sr25519Keyring;
fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec<ValidatorId> {
val_ids.iter().map(|v| v.public().into()).collect()
}
#[test]
fn sets_and_shuffles_validators() {
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Ferdie,
];
let mut config = HostConfiguration::default();
config.max_validators = None;
let pubkeys = validator_pubkeys(&validators);
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
let validators = ParasShared::initializer_on_new_session(1, [1; 32], &config, pubkeys);
assert_eq!(
validators,
validator_pubkeys(&[
Sr25519Keyring::Ferdie,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Alice,
])
);
assert_eq!(ParasShared::active_validator_keys(), validators);
assert_eq!(
ParasShared::active_validator_indices(),
vec![
ValidatorIndex(4),
ValidatorIndex(1),
ValidatorIndex(2),
ValidatorIndex(3),
ValidatorIndex(0),
]
);
});
}
#[test]
fn sets_truncates_and_shuffles_validators() {
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Ferdie,
];
let mut config = HostConfiguration::default();
config.max_validators = Some(2);
let pubkeys = validator_pubkeys(&validators);
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
let validators = ParasShared::initializer_on_new_session(1, [1; 32], &config, pubkeys);
assert_eq!(
validators,
validator_pubkeys(&[Sr25519Keyring::Ferdie, Sr25519Keyring::Bob,])
);
assert_eq!(ParasShared::active_validator_keys(), validators);
assert_eq!(
ParasShared::active_validator_indices(),
vec![ValidatorIndex(4), ValidatorIndex(1),]
);
});
}
}
@@ -0,0 +1,99 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot 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.
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::{
configuration::HostConfiguration,
mock::{new_test_ext, MockGenesisConfig, ParasShared},
};
use keyring::Sr25519Keyring;
fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec<ValidatorId> {
val_ids.iter().map(|v| v.public().into()).collect()
}
#[test]
fn sets_and_shuffles_validators() {
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Ferdie,
];
let mut config = HostConfiguration::default();
config.max_validators = None;
let pubkeys = validator_pubkeys(&validators);
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
let validators = ParasShared::initializer_on_new_session(1, [1; 32], &config, pubkeys);
assert_eq!(
validators,
validator_pubkeys(&[
Sr25519Keyring::Ferdie,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Alice,
])
);
assert_eq!(ParasShared::active_validator_keys(), validators);
assert_eq!(
ParasShared::active_validator_indices(),
vec![
ValidatorIndex(4),
ValidatorIndex(1),
ValidatorIndex(2),
ValidatorIndex(3),
ValidatorIndex(0),
]
);
});
}
#[test]
fn sets_truncates_and_shuffles_validators() {
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Ferdie,
];
let mut config = HostConfiguration::default();
config.max_validators = Some(2);
let pubkeys = validator_pubkeys(&validators);
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
let validators = ParasShared::initializer_on_new_session(1, [1; 32], &config, pubkeys);
assert_eq!(validators, validator_pubkeys(&[Sr25519Keyring::Ferdie, Sr25519Keyring::Bob,]));
assert_eq!(ParasShared::active_validator_keys(), validators);
assert_eq!(
ParasShared::active_validator_indices(),
vec![ValidatorIndex(4), ValidatorIndex(1),]
);
});
}
+3 -339
View File
@@ -28,6 +28,9 @@ use xcm::latest::Outcome;
pub use pallet::*; pub use pallet::*;
#[cfg(test)]
pub(crate) mod tests;
/// All upward messages coming from parachains will be funneled into an implementation of this trait. /// All upward messages coming from parachains will be funneled into an implementation of this trait.
/// ///
/// The message is opaque from the perspective of UMP. The message size can range from 0 to /// The message is opaque from the perspective of UMP. The message size can range from 0 to
@@ -703,342 +706,3 @@ impl NeedsDispatchCursor {
<Pallet<T> as Store>::NeedsDispatch::put(self.needs_dispatch); <Pallet<T> as Store>::NeedsDispatch::put(self.needs_dispatch);
} }
} }
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::mock::{
assert_last_event, new_test_ext, take_processed, Configuration, MockGenesisConfig, Origin,
System, Test, Ump,
};
use frame_support::{assert_noop, assert_ok, weights::Weight};
use std::collections::HashSet;
struct GenesisConfigBuilder {
max_upward_message_size: u32,
max_upward_message_num_per_candidate: u32,
max_upward_queue_count: u32,
max_upward_queue_size: u32,
ump_service_total_weight: Weight,
ump_max_individual_weight: Weight,
}
impl Default for GenesisConfigBuilder {
fn default() -> Self {
Self {
max_upward_message_size: 16,
max_upward_message_num_per_candidate: 2,
max_upward_queue_count: 4,
max_upward_queue_size: 64,
ump_service_total_weight: 1000,
ump_max_individual_weight: 100,
}
}
}
impl GenesisConfigBuilder {
fn build(self) -> crate::mock::MockGenesisConfig {
let mut genesis = default_genesis_config();
let config = &mut genesis.configuration.config;
config.max_upward_message_size = self.max_upward_message_size;
config.max_upward_message_num_per_candidate = self.max_upward_message_num_per_candidate;
config.max_upward_queue_count = self.max_upward_queue_count;
config.max_upward_queue_size = self.max_upward_queue_size;
config.ump_service_total_weight = self.ump_service_total_weight;
config.ump_max_individual_weight = self.ump_max_individual_weight;
genesis
}
}
fn default_genesis_config() -> MockGenesisConfig {
MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: crate::configuration::HostConfiguration {
max_downward_message_size: 1024,
..Default::default()
},
},
..Default::default()
}
}
fn queue_upward_msg(para: ParaId, msg: UpwardMessage) {
let msgs = vec![msg];
assert!(Ump::check_upward_messages(&Configuration::config(), para, &msgs).is_ok());
let _ = Ump::receive_upward_messages(para, msgs);
}
fn assert_storage_consistency_exhaustive() {
// check that empty queues don't clutter the storage.
for (_para, queue) in <Ump as Store>::RelayDispatchQueues::iter() {
assert!(!queue.is_empty());
}
// actually count the counts and sizes in queues and compare them to the bookkept version.
for (para, queue) in <Ump as Store>::RelayDispatchQueues::iter() {
let (expected_count, expected_size) = <Ump as Store>::RelayDispatchQueueSize::get(para);
let (actual_count, actual_size) =
queue.into_iter().fold((0, 0), |(acc_count, acc_size), x| {
(acc_count + 1, acc_size + x.len() as u32)
});
assert_eq!(expected_count, actual_count);
assert_eq!(expected_size, actual_size);
}
// since we wipe the empty queues the sets of paras in queue contents, queue sizes and
// need dispatch set should all be equal.
let queue_contents_set = <Ump as Store>::RelayDispatchQueues::iter()
.map(|(k, _)| k)
.collect::<HashSet<ParaId>>();
let queue_sizes_set = <Ump as Store>::RelayDispatchQueueSize::iter()
.map(|(k, _)| k)
.collect::<HashSet<ParaId>>();
let needs_dispatch_set =
<Ump as Store>::NeedsDispatch::get().into_iter().collect::<HashSet<ParaId>>();
assert_eq!(queue_contents_set, queue_sizes_set);
assert_eq!(queue_contents_set, needs_dispatch_set);
// `NextDispatchRoundStartWith` should point into a para that is tracked.
if let Some(para) = <Ump as Store>::NextDispatchRoundStartWith::get() {
assert!(queue_contents_set.contains(&para));
}
// `NeedsDispatch` is always sorted.
assert!(<Ump as Store>::NeedsDispatch::get().windows(2).all(|xs| xs[0] <= xs[1]));
}
#[test]
fn dispatch_empty() {
new_test_ext(default_genesis_config()).execute_with(|| {
assert_storage_consistency_exhaustive();
// make sure that the case with empty queues is handled properly
Ump::process_pending_upward_messages();
assert_storage_consistency_exhaustive();
});
}
#[test]
fn dispatch_single_message() {
let a = ParaId::from(228);
let msg = 1000u32.encode();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
queue_upward_msg(a, msg.clone());
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, msg)]);
assert_storage_consistency_exhaustive();
});
}
#[test]
fn dispatch_resume_after_exceeding_dispatch_stage_weight() {
let a = ParaId::from(128);
let c = ParaId::from(228);
let q = ParaId::from(911);
let a_msg_1 = (200u32, "a_msg_1").encode();
let a_msg_2 = (100u32, "a_msg_2").encode();
let c_msg_1 = (300u32, "c_msg_1").encode();
let c_msg_2 = (100u32, "c_msg_2").encode();
let q_msg = (500u32, "q_msg").encode();
new_test_ext(
GenesisConfigBuilder { ump_service_total_weight: 500, ..Default::default() }.build(),
)
.execute_with(|| {
queue_upward_msg(q, q_msg.clone());
queue_upward_msg(c, c_msg_1.clone());
queue_upward_msg(a, a_msg_1.clone());
queue_upward_msg(a, a_msg_2.clone());
assert_storage_consistency_exhaustive();
// we expect only two first messages to fit in the first iteration.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_1), (c, c_msg_1)]);
assert_storage_consistency_exhaustive();
queue_upward_msg(c, c_msg_2.clone());
assert_storage_consistency_exhaustive();
// second iteration should process the second message.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(q, q_msg)]);
assert_storage_consistency_exhaustive();
// 3rd iteration.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_2), (c, c_msg_2)]);
assert_storage_consistency_exhaustive();
// finally, make sure that the queue is empty.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![]);
assert_storage_consistency_exhaustive();
});
}
#[test]
fn dispatch_keeps_message_after_weight_exhausted() {
let a = ParaId::from(128);
let a_msg_1 = (300u32, "a_msg_1").encode();
let a_msg_2 = (300u32, "a_msg_2").encode();
new_test_ext(
GenesisConfigBuilder {
ump_service_total_weight: 500,
ump_max_individual_weight: 300,
..Default::default()
}
.build(),
)
.execute_with(|| {
queue_upward_msg(a, a_msg_1.clone());
queue_upward_msg(a, a_msg_2.clone());
assert_storage_consistency_exhaustive();
// we expect only one message to fit in the first iteration.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_1)]);
assert_storage_consistency_exhaustive();
// second iteration should process the remaining message.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_2)]);
assert_storage_consistency_exhaustive();
// finally, make sure that the queue is empty.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![]);
assert_storage_consistency_exhaustive();
});
}
#[test]
fn dispatch_correctly_handle_remove_of_latest() {
let a = ParaId::from(1991);
let b = ParaId::from(1999);
let a_msg_1 = (300u32, "a_msg_1").encode();
let a_msg_2 = (300u32, "a_msg_2").encode();
let b_msg_1 = (300u32, "b_msg_1").encode();
new_test_ext(
GenesisConfigBuilder { ump_service_total_weight: 900, ..Default::default() }.build(),
)
.execute_with(|| {
// We want to test here an edge case, where we remove the queue with the highest
// para id (i.e. last in the `needs_dispatch` order).
//
// If the last entry was removed we should proceed execution, assuming we still have
// weight available.
queue_upward_msg(a, a_msg_1.clone());
queue_upward_msg(a, a_msg_2.clone());
queue_upward_msg(b, b_msg_1.clone());
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_1), (b, b_msg_1), (a, a_msg_2)]);
});
}
#[test]
fn verify_relay_dispatch_queue_size_is_externally_accessible() {
// Make sure that the relay dispatch queue size storage entry is accessible via well known
// keys and is decodable into a (u32, u32).
use parity_scale_codec::Decode as _;
use primitives::v1::well_known_keys;
let a = ParaId::from(228);
let msg = vec![1, 2, 3];
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
queue_upward_msg(a, msg);
let raw_queue_size =
sp_io::storage::get(&well_known_keys::relay_dispatch_queue_size(a)).expect(
"enqueing a message should create the dispatch queue\
and it should be accessible via the well known keys",
);
let (cnt, size) = <(u32, u32)>::decode(&mut &raw_queue_size[..])
.expect("the dispatch queue size should be decodable into (u32, u32)");
assert_eq!(cnt, 1);
assert_eq!(size, 3);
});
}
#[test]
fn service_overweight_unknown() {
// This test just makes sure that 0 is not a valid index and we can use it not worrying in
// the next test.
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
assert_noop!(
Ump::service_overweight(Origin::root(), 0, 1000),
Error::<Test>::UnknownMessageIndex
);
});
}
#[test]
fn overweight_queue_works() {
let para_a = ParaId::from(2021);
let a_msg_1 = (301u32, "a_msg_1").encode();
let a_msg_2 = (500u32, "a_msg_2").encode();
let a_msg_3 = (500u32, "a_msg_3").encode();
new_test_ext(
GenesisConfigBuilder {
ump_service_total_weight: 900,
ump_max_individual_weight: 300,
..Default::default()
}
.build(),
)
.execute_with(|| {
// HACK: Start with the block number 1. This is needed because should an event be
// emitted during the genesis block they will be implicitly wiped.
System::set_block_number(1);
// This one is overweight. However, the weight is plenty and we can afford to execute
// this message, thus expect it.
queue_upward_msg(para_a, a_msg_1.clone());
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(para_a, a_msg_1)]);
// This is overweight and this message cannot fit into the total weight budget.
queue_upward_msg(para_a, a_msg_2.clone());
queue_upward_msg(para_a, a_msg_3.clone());
Ump::process_pending_upward_messages();
assert_last_event(
Event::OverweightEnqueued(para_a, upward_message_id(&a_msg_3[..]), 0, 500).into(),
);
// Now verify that if we wanted to service this overweight message with less than enough
// weight it will fail.
assert_noop!(
Ump::service_overweight(Origin::root(), 0, 499),
Error::<Test>::WeightOverLimit
);
// ... and if we try to service it with just enough weight it will succeed as well.
assert_ok!(Ump::service_overweight(Origin::root(), 0, 500));
assert_last_event(Event::OverweightServiced(0, 500).into());
// ... and if we try to service a message with index that doesn't exist it will error
// out.
assert_noop!(
Ump::service_overweight(Origin::root(), 1, 1000),
Error::<Test>::UnknownMessageIndex
);
});
}
}
@@ -0,0 +1,350 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot 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.
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::mock::{
assert_last_event, new_test_ext, take_processed, Configuration, MockGenesisConfig, Origin,
System, Test, Ump,
};
use frame_support::{assert_noop, assert_ok, weights::Weight};
use std::collections::HashSet;
struct GenesisConfigBuilder {
max_upward_message_size: u32,
max_upward_message_num_per_candidate: u32,
max_upward_queue_count: u32,
max_upward_queue_size: u32,
ump_service_total_weight: Weight,
ump_max_individual_weight: Weight,
}
impl Default for GenesisConfigBuilder {
fn default() -> Self {
Self {
max_upward_message_size: 16,
max_upward_message_num_per_candidate: 2,
max_upward_queue_count: 4,
max_upward_queue_size: 64,
ump_service_total_weight: 1000,
ump_max_individual_weight: 100,
}
}
}
impl GenesisConfigBuilder {
fn build(self) -> crate::mock::MockGenesisConfig {
let mut genesis = default_genesis_config();
let config = &mut genesis.configuration.config;
config.max_upward_message_size = self.max_upward_message_size;
config.max_upward_message_num_per_candidate = self.max_upward_message_num_per_candidate;
config.max_upward_queue_count = self.max_upward_queue_count;
config.max_upward_queue_size = self.max_upward_queue_size;
config.ump_service_total_weight = self.ump_service_total_weight;
config.ump_max_individual_weight = self.ump_max_individual_weight;
genesis
}
}
fn default_genesis_config() -> MockGenesisConfig {
MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: crate::configuration::HostConfiguration {
max_downward_message_size: 1024,
..Default::default()
},
},
..Default::default()
}
}
fn queue_upward_msg(para: ParaId, msg: UpwardMessage) {
let msgs = vec![msg];
assert!(Ump::check_upward_messages(&Configuration::config(), para, &msgs).is_ok());
let _ = Ump::receive_upward_messages(para, msgs);
}
fn assert_storage_consistency_exhaustive() {
// check that empty queues don't clutter the storage.
for (_para, queue) in <Ump as Store>::RelayDispatchQueues::iter() {
assert!(!queue.is_empty());
}
// actually count the counts and sizes in queues and compare them to the bookkept version.
for (para, queue) in <Ump as Store>::RelayDispatchQueues::iter() {
let (expected_count, expected_size) = <Ump as Store>::RelayDispatchQueueSize::get(para);
let (actual_count, actual_size) = queue
.into_iter()
.fold((0, 0), |(acc_count, acc_size), x| (acc_count + 1, acc_size + x.len() as u32));
assert_eq!(expected_count, actual_count);
assert_eq!(expected_size, actual_size);
}
// since we wipe the empty queues the sets of paras in queue contents, queue sizes and
// need dispatch set should all be equal.
let queue_contents_set = <Ump as Store>::RelayDispatchQueues::iter()
.map(|(k, _)| k)
.collect::<HashSet<ParaId>>();
let queue_sizes_set = <Ump as Store>::RelayDispatchQueueSize::iter()
.map(|(k, _)| k)
.collect::<HashSet<ParaId>>();
let needs_dispatch_set =
<Ump as Store>::NeedsDispatch::get().into_iter().collect::<HashSet<ParaId>>();
assert_eq!(queue_contents_set, queue_sizes_set);
assert_eq!(queue_contents_set, needs_dispatch_set);
// `NextDispatchRoundStartWith` should point into a para that is tracked.
if let Some(para) = <Ump as Store>::NextDispatchRoundStartWith::get() {
assert!(queue_contents_set.contains(&para));
}
// `NeedsDispatch` is always sorted.
assert!(<Ump as Store>::NeedsDispatch::get().windows(2).all(|xs| xs[0] <= xs[1]));
}
#[test]
fn dispatch_empty() {
new_test_ext(default_genesis_config()).execute_with(|| {
assert_storage_consistency_exhaustive();
// make sure that the case with empty queues is handled properly
Ump::process_pending_upward_messages();
assert_storage_consistency_exhaustive();
});
}
#[test]
fn dispatch_single_message() {
let a = ParaId::from(228);
let msg = 1000u32.encode();
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
queue_upward_msg(a, msg.clone());
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, msg)]);
assert_storage_consistency_exhaustive();
});
}
#[test]
fn dispatch_resume_after_exceeding_dispatch_stage_weight() {
let a = ParaId::from(128);
let c = ParaId::from(228);
let q = ParaId::from(911);
let a_msg_1 = (200u32, "a_msg_1").encode();
let a_msg_2 = (100u32, "a_msg_2").encode();
let c_msg_1 = (300u32, "c_msg_1").encode();
let c_msg_2 = (100u32, "c_msg_2").encode();
let q_msg = (500u32, "q_msg").encode();
new_test_ext(
GenesisConfigBuilder { ump_service_total_weight: 500, ..Default::default() }.build(),
)
.execute_with(|| {
queue_upward_msg(q, q_msg.clone());
queue_upward_msg(c, c_msg_1.clone());
queue_upward_msg(a, a_msg_1.clone());
queue_upward_msg(a, a_msg_2.clone());
assert_storage_consistency_exhaustive();
// we expect only two first messages to fit in the first iteration.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_1), (c, c_msg_1)]);
assert_storage_consistency_exhaustive();
queue_upward_msg(c, c_msg_2.clone());
assert_storage_consistency_exhaustive();
// second iteration should process the second message.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(q, q_msg)]);
assert_storage_consistency_exhaustive();
// 3rd iteration.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_2), (c, c_msg_2)]);
assert_storage_consistency_exhaustive();
// finally, make sure that the queue is empty.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![]);
assert_storage_consistency_exhaustive();
});
}
#[test]
fn dispatch_keeps_message_after_weight_exhausted() {
let a = ParaId::from(128);
let a_msg_1 = (300u32, "a_msg_1").encode();
let a_msg_2 = (300u32, "a_msg_2").encode();
new_test_ext(
GenesisConfigBuilder {
ump_service_total_weight: 500,
ump_max_individual_weight: 300,
..Default::default()
}
.build(),
)
.execute_with(|| {
queue_upward_msg(a, a_msg_1.clone());
queue_upward_msg(a, a_msg_2.clone());
assert_storage_consistency_exhaustive();
// we expect only one message to fit in the first iteration.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_1)]);
assert_storage_consistency_exhaustive();
// second iteration should process the remaining message.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_2)]);
assert_storage_consistency_exhaustive();
// finally, make sure that the queue is empty.
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![]);
assert_storage_consistency_exhaustive();
});
}
#[test]
fn dispatch_correctly_handle_remove_of_latest() {
let a = ParaId::from(1991);
let b = ParaId::from(1999);
let a_msg_1 = (300u32, "a_msg_1").encode();
let a_msg_2 = (300u32, "a_msg_2").encode();
let b_msg_1 = (300u32, "b_msg_1").encode();
new_test_ext(
GenesisConfigBuilder { ump_service_total_weight: 900, ..Default::default() }.build(),
)
.execute_with(|| {
// We want to test here an edge case, where we remove the queue with the highest
// para id (i.e. last in the `needs_dispatch` order).
//
// If the last entry was removed we should proceed execution, assuming we still have
// weight available.
queue_upward_msg(a, a_msg_1.clone());
queue_upward_msg(a, a_msg_2.clone());
queue_upward_msg(b, b_msg_1.clone());
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(a, a_msg_1), (b, b_msg_1), (a, a_msg_2)]);
});
}
#[test]
fn verify_relay_dispatch_queue_size_is_externally_accessible() {
// Make sure that the relay dispatch queue size storage entry is accessible via well known
// keys and is decodable into a (u32, u32).
use parity_scale_codec::Decode as _;
use primitives::v1::well_known_keys;
let a = ParaId::from(228);
let msg = vec![1, 2, 3];
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
queue_upward_msg(a, msg);
let raw_queue_size = sp_io::storage::get(&well_known_keys::relay_dispatch_queue_size(a))
.expect(
"enqueing a message should create the dispatch queue\
and it should be accessible via the well known keys",
);
let (cnt, size) = <(u32, u32)>::decode(&mut &raw_queue_size[..])
.expect("the dispatch queue size should be decodable into (u32, u32)");
assert_eq!(cnt, 1);
assert_eq!(size, 3);
});
}
#[test]
fn service_overweight_unknown() {
// This test just makes sure that 0 is not a valid index and we can use it not worrying in
// the next test.
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
assert_noop!(
Ump::service_overweight(Origin::root(), 0, 1000),
Error::<Test>::UnknownMessageIndex
);
});
}
#[test]
fn overweight_queue_works() {
let para_a = ParaId::from(2021);
let a_msg_1 = (301u32, "a_msg_1").encode();
let a_msg_2 = (500u32, "a_msg_2").encode();
let a_msg_3 = (500u32, "a_msg_3").encode();
new_test_ext(
GenesisConfigBuilder {
ump_service_total_weight: 900,
ump_max_individual_weight: 300,
..Default::default()
}
.build(),
)
.execute_with(|| {
// HACK: Start with the block number 1. This is needed because should an event be
// emitted during the genesis block they will be implicitly wiped.
System::set_block_number(1);
// This one is overweight. However, the weight is plenty and we can afford to execute
// this message, thus expect it.
queue_upward_msg(para_a, a_msg_1.clone());
Ump::process_pending_upward_messages();
assert_eq!(take_processed(), vec![(para_a, a_msg_1)]);
// This is overweight and this message cannot fit into the total weight budget.
queue_upward_msg(para_a, a_msg_2.clone());
queue_upward_msg(para_a, a_msg_3.clone());
Ump::process_pending_upward_messages();
assert_last_event(
Event::OverweightEnqueued(para_a, upward_message_id(&a_msg_3[..]), 0, 500).into(),
);
// Now verify that if we wanted to service this overweight message with less than enough
// weight it will fail.
assert_noop!(
Ump::service_overweight(Origin::root(), 0, 499),
Error::<Test>::WeightOverLimit
);
// ... and if we try to service it with just enough weight it will succeed as well.
assert_ok!(Ump::service_overweight(Origin::root(), 0, 500));
assert_last_event(Event::OverweightServiced(0, 500).into());
// ... and if we try to service a message with index that doesn't exist it will error
// out.
assert_noop!(
Ump::service_overweight(Origin::root(), 1, 1000),
Error::<Test>::UnknownMessageIndex
);
});
}