// 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 .
//! The paras module is responsible for storing data on parachains and parathreads.
//!
//! It tracks which paras are parachains, what their current head data is in
//! this fork of the relay chain, what their validation code is, and what their past and upcoming
//! validation code is.
//!
//! A para is not considered live until it is registered and activated in this module. Activation can
//! only occur at session boundaries.
use sp_std::prelude::*;
use sp_std::result;
#[cfg(feature = "std")]
use sp_std::marker::PhantomData;
use primitives::v1::{
Id as ParaId, ValidationCode, HeadData,
};
use sp_runtime::traits::One;
use frame_support::{
decl_storage, decl_module, decl_error,
traits::Get,
weights::Weight,
};
use parity_scale_codec::{Encode, Decode};
use crate::{configuration, initializer::SessionChangeNotification};
use sp_core::RuntimeDebug;
#[cfg(feature = "std")]
use serde::{Serialize, Deserialize};
pub use crate::Origin;
pub trait Config: frame_system::Config + configuration::Config {
/// The outer origin type.
type Origin: From
+ From<::Origin>
+ Into::Origin>>;
}
// the two key times necessary to track for every code replacement.
#[derive(Default, Encode, Decode)]
#[cfg_attr(test, derive(Debug, Clone, PartialEq))]
pub struct ReplacementTimes {
/// The relay-chain block number that the code upgrade was expected to be activated.
/// This is when the code change occurs from the para's perspective - after the
/// first parablock included with a relay-parent with number >= this value.
expected_at: N,
/// The relay-chain block number at which the parablock activating the code upgrade was
/// actually included. This means considered included and available, so this is the time at which
/// that parablock enters the acceptance period in this fork of the relay-chain.
activated_at: N,
}
/// Metadata used to track previous parachain validation code that we keep in
/// the state.
#[derive(Default, Encode, Decode)]
#[cfg_attr(test, derive(Debug, Clone, PartialEq))]
pub struct ParaPastCodeMeta {
/// Block numbers where the code was expected to be replaced and where the code
/// was actually replaced, respectively. The first is used to do accurate lookups
/// of historic code in historic contexts, whereas the second is used to do
/// pruning on an accurate timeframe. These can be used as indices
/// into the `PastCode` map along with the `ParaId` to fetch the code itself.
upgrade_times: Vec>,
/// Tracks the highest pruned code-replacement, if any. This is the `expected_at` value,
/// not the `activated_at` value.
last_pruned: Option,
}
#[cfg_attr(test, derive(Debug, PartialEq))]
enum UseCodeAt {
/// Use the current code.
Current,
/// Use the code that was replaced at the given block number.
/// This is an inclusive endpoint - a parablock in the context of a relay-chain block on this fork
/// with number N should use the code that is replaced at N.
ReplacedAt(N),
}
impl ParaPastCodeMeta {
// note a replacement has occurred at a given block number.
fn note_replacement(&mut self, expected_at: N, activated_at: N) {
self.upgrade_times.push(ReplacementTimes { expected_at, activated_at })
}
// Yields an identifier that should be used for validating a
// parablock in the context of a particular relay-chain block number.
//
// a return value of `None` means that there is no code we are aware of that
// should be used to validate at the given height.
#[allow(unused)]
fn code_at(&self, para_at: N) -> Option> {
// Find out
// a) if there is a point where code was replaced _after_ execution in this context and
// b) what the index of that point is.
let replaced_after_pos = self.upgrade_times.iter().position(|t| t.expected_at >= para_at);
if let Some(replaced_after_pos) = replaced_after_pos {
// The earliest stored code replacement needs to be special-cased, since we need to check
// against the pruning state to see if this replacement represents the correct code, or
// is simply after a replacement that actually represents the correct code, but has been pruned.
let was_pruned = replaced_after_pos == 0
&& self.last_pruned.map_or(false, |t| t >= para_at);
if was_pruned {
None
} else {
Some(UseCodeAt::ReplacedAt(self.upgrade_times[replaced_after_pos].expected_at))
}
} else {
// No code replacements after this context.
// This means either that the current code is valid, or `para_at` is so old that
// we don't know the code necessary anymore. Compare against `last_pruned` to determine.
self.last_pruned.as_ref().map_or(
Some(UseCodeAt::Current), // nothing pruned, use current
|t| if t >= ¶_at { None } else { Some(UseCodeAt::Current) },
)
}
}
// The block at which the most recently tracked code change occurred, from the perspective
// of the para.
fn most_recent_change(&self) -> Option {
self.upgrade_times.last().map(|x| x.expected_at.clone())
}
// prunes all code upgrade logs occurring at or before `max`.
// note that code replaced at `x` is the code used to validate all blocks before
// `x`. Thus, `max` should be outside of the slashing window when this is invoked.
//
// Since we don't want to prune anything inside the acceptance period, and the parablock only
// enters the acceptance period after being included, we prune based on the activation height of
// the code change, not the expected height of the code change.
//
// returns an iterator of block numbers at which code was replaced, where the replaced
// code should be now pruned, in ascending order.
fn prune_up_to(&'_ mut self, max: N) -> impl Iterator- + '_ {
let to_prune = self.upgrade_times.iter().take_while(|t| t.activated_at <= max).count();
let drained = if to_prune == 0 {
// no-op prune.
self.upgrade_times.drain(self.upgrade_times.len()..)
} else {
// if we are actually pruning something, update the last_pruned member.
self.last_pruned = Some(self.upgrade_times[to_prune - 1].expected_at);
self.upgrade_times.drain(..to_prune)
};
drained.map(|times| times.expected_at)
}
}
/// Arguments for initializing a para.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct ParaGenesisArgs {
/// The initial head data to use.
pub genesis_head: HeadData,
/// The initial validation code to use.
pub validation_code: ValidationCode,
/// True if parachain, false if parathread.
pub parachain: bool,
}
decl_storage! {
trait Store for Module as Paras {
/// All parachains. Ordered ascending by ParaId. Parathreads are not included.
Parachains get(fn parachains): Vec;
/// All parathreads.
Parathreads: map hasher(twox_64_concat) ParaId => Option<()>;
/// The head-data of every registered para.
Heads get(fn para_head): map hasher(twox_64_concat) ParaId => Option;
/// The validation code of every live para.
CurrentCode get(fn current_code): map hasher(twox_64_concat) ParaId => Option;
/// Actual past code, indicated by the para id as well as the block number at which it became outdated.
PastCode: map hasher(twox_64_concat) (ParaId, T::BlockNumber) => Option;
/// Past code of parachains. The parachains themselves may not be registered anymore,
/// but we also keep their code on-chain for the same amount of time as outdated code
/// to keep it available for secondary checkers.
PastCodeMeta get(fn past_code_meta):
map hasher(twox_64_concat) ParaId => ParaPastCodeMeta;
/// Which paras have past code that needs pruning and the relay-chain block at which the code was replaced.
/// Note that this is the actual height of the included block, not the expected height at which the
/// code upgrade would be applied, although they may be equal.
/// This is to ensure the entire acceptance period is covered, not an offset acceptance period starting
/// from the time at which the parachain perceives a code upgrade as having occurred.
/// Multiple entries for a single para are permitted. Ordered ascending by block number.
PastCodePruning: Vec<(ParaId, T::BlockNumber)>;
/// The block number at which the planned code change is expected for a para.
/// The change will be applied after the first parablock for this ID included which executes
/// in the context of a relay chain block with a number >= `expected_at`.
FutureCodeUpgrades get(fn future_code_upgrade_at): map hasher(twox_64_concat) ParaId => Option;
/// The actual future code of a para.
FutureCode: map hasher(twox_64_concat) ParaId => Option;
/// Upcoming paras (chains and threads). These are only updated on session change. Corresponds to an
/// entry in the upcoming-genesis map.
UpcomingParas get(fn upcoming_paras): Vec;
/// Upcoming paras instantiation arguments.
UpcomingParasGenesis: map hasher(twox_64_concat) ParaId => Option;
/// Paras that are to be cleaned up at the end of the session.
OutgoingParas get(fn outgoing_paras): Vec;
}
add_extra_genesis {
config(paras): Vec<(ParaId, ParaGenesisArgs)>;
config(_phdata): PhantomData;
build(build::);
}
}
#[cfg(feature = "std")]
fn build(config: &GenesisConfig) {
let mut parachains: Vec<_> = config.paras
.iter()
.filter(|(_, args)| args.parachain)
.map(|&(ref id, _)| id)
.cloned()
.collect();
parachains.sort();
parachains.dedup();
Parachains::put(¶chains);
for (id, genesis_args) in &config.paras {
as Store>::CurrentCode::insert(&id, &genesis_args.validation_code);
as Store>::Heads::insert(&id, &genesis_args.genesis_head);
}
}
decl_error! {
pub enum Error for Module { }
}
decl_module! {
/// The parachains configuration module.
pub struct Module for enum Call where origin: ::Origin {
type Error = Error;
}
}
impl Module {
/// Called by the initializer to initialize the configuration module.
pub(crate) fn initializer_initialize(now: T::BlockNumber) -> Weight {
Self::prune_old_code(now)
}
/// Called by the initializer to finalize the configuration module.
pub(crate) fn initializer_finalize() { }
/// Called by the initializer to note that a new session has started.
pub(crate) fn initializer_on_new_session(_notification: &SessionChangeNotification) {
let now = >::block_number();
let mut parachains = Self::clean_up_outgoing(now);
Self::apply_incoming(&mut parachains);
::Parachains::set(parachains);
}
/// Cleans up all outgoing paras. Returns the new set of parachains
fn clean_up_outgoing(now: T::BlockNumber) -> Vec {
let mut parachains = ::Parachains::get();
let outgoing = ::OutgoingParas::take();
for outgoing_para in outgoing {
if let Ok(i) = parachains.binary_search(&outgoing_para) {
parachains.remove(i);
} else {
::Parathreads::remove(&outgoing_para);
}
::Heads::remove(&outgoing_para);
::FutureCodeUpgrades::remove(&outgoing_para);
::FutureCode::remove(&outgoing_para);
let removed_code = ::CurrentCode::take(&outgoing_para);
if let Some(removed_code) = removed_code {
Self::note_past_code(outgoing_para, now, now, removed_code);
}
}
parachains
}
/// Applies all incoming paras, updating the parachains list for those that are parachains.
fn apply_incoming(parachains: &mut Vec) {
let upcoming = ::UpcomingParas::take();
for upcoming_para in upcoming {
let genesis_data = match ::UpcomingParasGenesis::take(&upcoming_para) {
None => continue,
Some(g) => g,
};
if genesis_data.parachain {
if let Err(i) = parachains.binary_search(&upcoming_para) {
parachains.insert(i, upcoming_para);
}
} else {
::Parathreads::insert(&upcoming_para, ());
}
::Heads::insert(&upcoming_para, genesis_data.genesis_head);
::CurrentCode::insert(&upcoming_para, genesis_data.validation_code);
}
}
// note replacement of the code of para with given `id`, which occured in the
// context of the given relay-chain block number. provide the replaced code.
//
// `at` for para-triggered replacement is the block number of the relay-chain
// block in whose context the parablock was executed
// (i.e. number of `relay_parent` in the receipt)
fn note_past_code(
id: ParaId,
at: T::BlockNumber,
now: T::BlockNumber,
old_code: ValidationCode,
) -> Weight {
::PastCodeMeta::mutate(&id, |past_meta| {
past_meta.note_replacement(at, now);
});
::PastCode::insert(&(id, at), old_code);
// Schedule pruning for this past-code to be removed as soon as it
// exits the slashing window.
::PastCodePruning::mutate(|pruning| {
let insert_idx = pruning.binary_search_by_key(&at, |&(_, b)| b)
.unwrap_or_else(|idx| idx);
pruning.insert(insert_idx, (id, now));
});
T::DbWeight::get().reads_writes(2, 3)
}
// looks at old code metadata, compares them to the current acceptance window, and prunes those
// that are too old.
fn prune_old_code(now: T::BlockNumber) -> Weight {
let config = configuration::Module::::config();
let acceptance_period = config.acceptance_period;
if now <= acceptance_period {
let weight = T::DbWeight::get().reads_writes(1, 0);
return weight;
}
// The height of any changes we no longer should keep around.
let pruning_height = now - (acceptance_period + One::one());
let pruning_tasks_done =
::PastCodePruning::mutate(|pruning_tasks: &mut Vec<(_, T::BlockNumber)>| {
let (pruning_tasks_done, pruning_tasks_to_do) = {
// find all past code that has just exited the pruning window.
let up_to_idx = pruning_tasks.iter()
.take_while(|&(_, at)| at <= &pruning_height)
.count();
(up_to_idx, pruning_tasks.drain(..up_to_idx))
};
for (para_id, _) in pruning_tasks_to_do {
let full_deactivate = ::PastCodeMeta::mutate(¶_id, |meta| {
for pruned_repl_at in meta.prune_up_to(pruning_height) {
::PastCode::remove(&(para_id, pruned_repl_at));
}
meta.most_recent_change().is_none() && Self::para_head(¶_id).is_none()
});
// This parachain has been removed and now the vestigial code
// has been removed from the state. clean up meta as well.
if full_deactivate {
::PastCodeMeta::remove(¶_id);
}
}
pruning_tasks_done as u64
});
// 1 read for the meta for each pruning task, 1 read for the config
// 2 writes: updating the meta and pruning the code
T::DbWeight::get().reads_writes(1 + pruning_tasks_done, 2 * pruning_tasks_done)
}
/// Schedule a para to be initialized at the start of the next session.
pub(crate) fn schedule_para_initialize(id: ParaId, genesis: ParaGenesisArgs) -> Weight {
let dup = UpcomingParas::mutate(|v| {
match v.binary_search(&id) {
Ok(_) => true,
Err(i) => {
v.insert(i, id);
false
}
}
});
if dup {
let weight = T::DbWeight::get().reads_writes(1, 0);
return weight;
}
UpcomingParasGenesis::insert(&id, &genesis);
T::DbWeight::get().reads_writes(1, 2)
}
/// Schedule a para to be cleaned up at the start of the next session.
pub(crate) fn schedule_para_cleanup(id: ParaId) -> Weight {
let upcoming_weight = UpcomingParas::mutate(|v| {
match v.binary_search(&id) {
Ok(i) => {
v.remove(i);
UpcomingParasGenesis::remove(id);
// If a para was only in the pending state it should not be moved to `Outgoing`
return T::DbWeight::get().reads_writes(2, 2);
}
Err(_) => T::DbWeight::get().reads_writes(1, 0),
}
});
let outgoing_weight = OutgoingParas::mutate(|v| {
match v.binary_search(&id) {
Ok(_) => T::DbWeight::get().reads_writes(1, 0),
Err(i) => {
v.insert(i, id);
T::DbWeight::get().reads_writes(1, 1)
}
}
});
outgoing_weight + upcoming_weight
}
/// Schedule a future code upgrade of the given parachain, to be applied after inclusion
/// of a block of the same parachain executed in the context of a relay-chain block
/// with number >= `expected_at`
///
/// If there is already a scheduled code upgrade for the para, this is a no-op.
pub(crate) fn schedule_code_upgrade(
id: ParaId,
new_code: ValidationCode,
expected_at: T::BlockNumber,
) -> Weight {
::FutureCodeUpgrades::mutate(&id, |up| {
if up.is_some() {
T::DbWeight::get().reads_writes(1, 0)
} else {
*up = Some(expected_at);
FutureCode::insert(&id, new_code);
T::DbWeight::get().reads_writes(1, 2)
}
})
}
/// Note that a para has progressed to a new head, where the new head was executed in the context
/// of a relay-chain block with given number. This will apply pending code upgrades based
/// on the block number provided.
pub(crate) fn note_new_head(
id: ParaId,
new_head: HeadData,
execution_context: T::BlockNumber,
) -> Weight {
Heads::insert(&id, new_head);
if let Some(expected_at) = ::FutureCodeUpgrades::get(&id) {
if expected_at <= execution_context {
::FutureCodeUpgrades::remove(&id);
// Both should always be `Some` in this case, since a code upgrade is scheduled.
let new_code = FutureCode::take(&id).unwrap_or_default();
let prior_code = CurrentCode::get(&id).unwrap_or_default();
CurrentCode::insert(&id, &new_code);
// `now` is only used for registering pruning as part of `fn note_past_code`
let now = >::block_number();
let weight = Self::note_past_code(
id,
expected_at,
now,
prior_code,
);
// add 1 to writes due to heads update.
weight + T::DbWeight::get().reads_writes(3, 1 + 3)
} else {
T::DbWeight::get().reads_writes(1, 1 + 0)
}
} else {
T::DbWeight::get().reads_writes(1, 1)
}
}
/// Fetches the validation code to be used when validating a block in the context of the given
/// relay-chain height. A second block number parameter may be used to tell the lookup to proceed
/// as if an intermediate parablock has been with the given relay-chain height as its context.
/// This may return past, current, or (with certain choices of `assume_intermediate`) future code.
///
/// `assume_intermediate`, if provided, must be before `at`. This will return `None` if the validation
/// code has been pruned.
#[allow(unused)]
pub(crate) fn validation_code_at(
id: ParaId,
at: T::BlockNumber,
assume_intermediate: Option,
) -> Option {
let now = >::block_number();
let config = >::config();
if assume_intermediate.as_ref().map_or(false, |i| &at <= i) {
return None;
}
let planned_upgrade = ::FutureCodeUpgrades::get(&id);
let upgrade_applied_intermediate = match assume_intermediate {
Some(a) => planned_upgrade.as_ref().map_or(false, |u| u <= &a),
None => false,
};
if upgrade_applied_intermediate {
FutureCode::get(&id)
} else {
match Self::past_code_meta(&id).code_at(at) {
None => None,
Some(UseCodeAt::Current) => CurrentCode::get(&id),
Some(UseCodeAt::ReplacedAt(replaced)) => ::PastCode::get(&(id, replaced))
}
}
}
/// Returns whether the given ID refers to a valid para.
pub fn is_valid_para(id: ParaId) -> bool {
Self::parachains().binary_search(&id).is_ok()
|| Self::is_parathread(id)
}
/// Whether a para ID corresponds to any live parathread.
pub(crate) fn is_parathread(id: ParaId) -> bool {
Parathreads::get(&id).is_some()
}
/// The block number of the last scheduled upgrade of the requested para. Includes future upgrades
/// if the flag is set. This is the `expected_at` number, not the `activated_at` number.
pub(crate) fn last_code_upgrade(id: ParaId, include_future: bool) -> Option {
if include_future {
if let Some(at) = Self::future_code_upgrade_at(id) {
return Some(at);
}
}
Self::past_code_meta(&id).most_recent_change()
}
}
#[cfg(test)]
mod tests {
use super::*;
use primitives::v1::BlockNumber;
use frame_support::traits::{OnFinalize, OnInitialize};
use crate::mock::{new_test_ext, Paras, System, GenesisConfig as MockGenesisConfig};
use crate::configuration::HostConfiguration;
fn run_to_block(to: BlockNumber, new_session: Option>) {
while System::block_number() < to {
let b = System::block_number();
Paras::initializer_finalize();
System::on_finalize(b);
System::on_initialize(b + 1);
System::set_block_number(b + 1);
if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) {
Paras::initializer_on_new_session(&Default::default());
}
Paras::initializer_initialize(b + 1);
}
}
fn upgrade_at(expected_at: BlockNumber, activated_at: BlockNumber) -> ReplacementTimes {
ReplacementTimes { expected_at, activated_at }
}
#[test]
fn para_past_code_meta_gives_right_code() {
let mut past_code = ParaPastCodeMeta::default();
assert_eq!(past_code.code_at(0u32), Some(UseCodeAt::Current));
past_code.note_replacement(10, 12);
assert_eq!(past_code.code_at(0), Some(UseCodeAt::ReplacedAt(10)));
assert_eq!(past_code.code_at(10), Some(UseCodeAt::ReplacedAt(10)));
assert_eq!(past_code.code_at(11), Some(UseCodeAt::Current));
past_code.note_replacement(20, 25);
assert_eq!(past_code.code_at(1), Some(UseCodeAt::ReplacedAt(10)));
assert_eq!(past_code.code_at(10), Some(UseCodeAt::ReplacedAt(10)));
assert_eq!(past_code.code_at(11), Some(UseCodeAt::ReplacedAt(20)));
assert_eq!(past_code.code_at(20), Some(UseCodeAt::ReplacedAt(20)));
assert_eq!(past_code.code_at(21), Some(UseCodeAt::Current));
past_code.last_pruned = Some(5);
assert_eq!(past_code.code_at(1), None);
assert_eq!(past_code.code_at(5), None);
assert_eq!(past_code.code_at(6), Some(UseCodeAt::ReplacedAt(10)));
}
#[test]
fn para_past_code_pruning_works_correctly() {
let mut past_code = ParaPastCodeMeta::default();
past_code.note_replacement(10u32, 10);
past_code.note_replacement(20, 25);
past_code.note_replacement(30, 35);
let old = past_code.clone();
assert!(past_code.prune_up_to(9).collect::>().is_empty());
assert_eq!(old, past_code);
assert_eq!(past_code.prune_up_to(10).collect::>(), vec![10]);
assert_eq!(past_code, ParaPastCodeMeta {
upgrade_times: vec![upgrade_at(20, 25), upgrade_at(30, 35)],
last_pruned: Some(10),
});
assert!(past_code.prune_up_to(21).collect::>().is_empty());
assert_eq!(past_code.prune_up_to(26).collect::>(), vec![20]);
assert_eq!(past_code, ParaPastCodeMeta {
upgrade_times: vec![upgrade_at(30, 35)],
last_pruned: Some(20),
});
past_code.note_replacement(40, 42);
past_code.note_replacement(50, 53);
past_code.note_replacement(60, 66);
assert_eq!(past_code, ParaPastCodeMeta {
upgrade_times: vec![upgrade_at(30, 35), upgrade_at(40, 42), upgrade_at(50, 53), upgrade_at(60, 66)],
last_pruned: Some(20),
});
assert_eq!(past_code.prune_up_to(60).collect::>(), vec![30, 40, 50]);
assert_eq!(past_code, ParaPastCodeMeta {
upgrade_times: vec![upgrade_at(60, 66)],
last_pruned: Some(50),
});
assert_eq!(past_code.prune_up_to(66).collect::>(), vec![60]);
assert_eq!(past_code, ParaPastCodeMeta {
upgrade_times: Vec::new(),
last_pruned: Some(60),
});
}
#[test]
fn para_past_code_pruning_in_initialize() {
let acceptance_period = 10;
let paras = vec![
(0u32.into(), ParaGenesisArgs {
parachain: true,
genesis_head: Default::default(),
validation_code: Default::default(),
}),
(1u32.into(), ParaGenesisArgs {
parachain: false,
genesis_head: Default::default(),
validation_code: Default::default(),
}),
];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
acceptance_period,
..Default::default()
},
..Default::default()
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let id = ParaId::from(0u32);
let at_block: BlockNumber = 10;
let included_block: BlockNumber = 12;
::PastCode::insert(&(id, at_block), &ValidationCode(vec![1, 2, 3]));
::PastCodePruning::put(&vec![(id, included_block)]);
{
let mut code_meta = Paras::past_code_meta(&id);
code_meta.note_replacement(at_block, included_block);
::PastCodeMeta::insert(&id, &code_meta);
}
let pruned_at: BlockNumber = included_block + acceptance_period + 1;
assert_eq!(::PastCode::get(&(id, at_block)), Some(vec![1, 2, 3].into()));
run_to_block(pruned_at - 1, None);
assert_eq!(::PastCode::get(&(id, at_block)), Some(vec![1, 2, 3].into()));
assert_eq!(Paras::past_code_meta(&id).most_recent_change(), Some(at_block));
run_to_block(pruned_at, None);
assert!(::PastCode::get(&(id, at_block)).is_none());
assert!(Paras::past_code_meta(&id).most_recent_change().is_none());
});
}
#[test]
fn note_new_head_sets_head() {
let acceptance_period = 10;
let paras = vec![
(0u32.into(), ParaGenesisArgs {
parachain: true,
genesis_head: Default::default(),
validation_code: Default::default(),
}),
];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
acceptance_period,
..Default::default()
},
..Default::default()
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let id_a = ParaId::from(0u32);
assert_eq!(Paras::para_head(&id_a), Some(Default::default()));
Paras::note_new_head(id_a, vec![1, 2, 3].into(), 0);
assert_eq!(Paras::para_head(&id_a), Some(vec![1, 2, 3].into()));
});
}
#[test]
fn note_past_code_sets_up_pruning_correctly() {
let acceptance_period = 10;
let paras = vec![
(0u32.into(), ParaGenesisArgs {
parachain: true,
genesis_head: Default::default(),
validation_code: Default::default(),
}),
(1u32.into(), ParaGenesisArgs {
parachain: false,
genesis_head: Default::default(),
validation_code: Default::default(),
}),
];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
acceptance_period,
..Default::default()
},
..Default::default()
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let id_a = ParaId::from(0u32);
let id_b = ParaId::from(1u32);
Paras::note_past_code(id_a, 10, 12, vec![1, 2, 3].into());
Paras::note_past_code(id_b, 20, 23, vec![4, 5, 6].into());
assert_eq!(::PastCodePruning::get(), vec![(id_a, 12), (id_b, 23)]);
assert_eq!(
Paras::past_code_meta(&id_a),
ParaPastCodeMeta {
upgrade_times: vec![upgrade_at(10, 12)],
last_pruned: None,
}
);
assert_eq!(
Paras::past_code_meta(&id_b),
ParaPastCodeMeta {
upgrade_times: vec![upgrade_at(20, 23)],
last_pruned: None,
}
);
});
}
#[test]
fn code_upgrade_applied_after_delay() {
let acceptance_period = 10;
let validation_upgrade_delay = 5;
let paras = vec![
(0u32.into(), ParaGenesisArgs {
parachain: true,
genesis_head: Default::default(),
validation_code: vec![1, 2, 3].into(),
}),
];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
acceptance_period,
validation_upgrade_delay,
..Default::default()
},
..Default::default()
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let para_id = ParaId::from(0);
let new_code = ValidationCode(vec![4, 5, 6]);
run_to_block(2, None);
assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into()));
let expected_at = {
// this parablock is in the context of block 1.
let expected_at = 1 + validation_upgrade_delay;
Paras::schedule_code_upgrade(para_id, new_code.clone(), expected_at);
Paras::note_new_head(para_id, Default::default(), 1);
assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none());
assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at));
assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone()));
assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into()));
expected_at
};
run_to_block(expected_at, None);
// the candidate is in the context of the parent of `expected_at`,
// thus does not trigger the code upgrade.
{
Paras::note_new_head(para_id, Default::default(), expected_at - 1);
assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none());
assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at));
assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone()));
assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into()));
}
run_to_block(expected_at + 1, None);
// the candidate is in the context of `expected_at`, and triggers
// the upgrade.
{
Paras::note_new_head(para_id, Default::default(), expected_at);
assert_eq!(
Paras::past_code_meta(¶_id).most_recent_change(),
Some(expected_at),
);
assert_eq!(
::PastCode::get(&(para_id, expected_at)),
Some(vec![1, 2, 3,].into()),
);
assert!(::FutureCodeUpgrades::get(¶_id).is_none());
assert!(::FutureCode::get(¶_id).is_none());
assert_eq!(Paras::current_code(¶_id), Some(new_code));
}
});
}
#[test]
fn code_upgrade_applied_after_delay_even_when_late() {
let acceptance_period = 10;
let validation_upgrade_delay = 5;
let paras = vec![
(0u32.into(), ParaGenesisArgs {
parachain: true,
genesis_head: Default::default(),
validation_code: vec![1, 2, 3].into(),
}),
];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
acceptance_period,
validation_upgrade_delay,
..Default::default()
},
..Default::default()
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let para_id = ParaId::from(0);
let new_code = ValidationCode(vec![4, 5, 6]);
run_to_block(2, None);
assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into()));
let expected_at = {
// this parablock is in the context of block 1.
let expected_at = 1 + validation_upgrade_delay;
Paras::schedule_code_upgrade(para_id, new_code.clone(), expected_at);
Paras::note_new_head(para_id, Default::default(), 1);
assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none());
assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at));
assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone()));
assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into()));
expected_at
};
run_to_block(expected_at + 1 + 4, None);
// the candidate is in the context of the first descendent of `expected_at`, and triggers
// the upgrade.
{
Paras::note_new_head(para_id, Default::default(), expected_at + 4);
assert_eq!(
Paras::past_code_meta(¶_id).most_recent_change(),
Some(expected_at),
);
assert_eq!(
::PastCode::get(&(para_id, expected_at)),
Some(vec![1, 2, 3,].into()),
);
assert!(::FutureCodeUpgrades::get(¶_id).is_none());
assert!(::FutureCode::get(¶_id).is_none());
assert_eq!(Paras::current_code(¶_id), Some(new_code));
}
});
}
#[test]
fn submit_code_change_when_not_allowed_is_err() {
let acceptance_period = 10;
let paras = vec![
(0u32.into(), ParaGenesisArgs {
parachain: true,
genesis_head: Default::default(),
validation_code: vec![1, 2, 3].into(),
}),
];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
acceptance_period,
..Default::default()
},
..Default::default()
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let para_id = ParaId::from(0);
let new_code = ValidationCode(vec![4, 5, 6]);
let newer_code = ValidationCode(vec![4, 5, 6, 7]);
run_to_block(1, None);
Paras::schedule_code_upgrade(para_id, new_code.clone(), 8);
assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(8));
assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone()));
Paras::schedule_code_upgrade(para_id, newer_code.clone(), 10);
assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(8));
assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone()));
});
}
#[test]
fn full_parachain_cleanup_storage() {
let acceptance_period = 10;
let paras = vec![
(0u32.into(), ParaGenesisArgs {
parachain: true,
genesis_head: Default::default(),
validation_code: vec![1, 2, 3].into(),
}),
];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
acceptance_period,
..Default::default()
},
..Default::default()
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let para_id = ParaId::from(0);
let new_code = ValidationCode(vec![4, 5, 6]);
run_to_block(2, None);
assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into()));
let expected_at = {
// this parablock is in the context of block 1.
let expected_at = 1 + 5;
Paras::schedule_code_upgrade(para_id, new_code.clone(), expected_at);
Paras::note_new_head(para_id, Default::default(), 1);
assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none());
assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at));
assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone()));
assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into()));
expected_at
};
Paras::schedule_para_cleanup(para_id);
// Just scheduling cleanup shouldn't change anything.
{
assert_eq!(::OutgoingParas::get(), vec![para_id]);
assert_eq!(Paras::parachains(), vec![para_id]);
assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none());
assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at));
assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone()));
assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into()));
assert_eq!(::Heads::get(¶_id), Some(Default::default()));
}
// run to block, with a session change at that block.
run_to_block(3, Some(vec![3]));
// cleaning up the parachain should place the current parachain code
// into the past code buffer & schedule cleanup.
assert_eq!(Paras::past_code_meta(¶_id).most_recent_change(), Some(3));
assert_eq!(::PastCode::get(&(para_id, 3)), Some(vec![1, 2, 3].into()));
assert_eq!(::PastCodePruning::get(), vec![(para_id, 3)]);
// any future upgrades haven't been used to validate yet, so those
// are cleaned up immediately.
assert!(::FutureCodeUpgrades::get(¶_id).is_none());
assert!(::FutureCode::get(¶_id).is_none());
assert!(Paras::current_code(¶_id).is_none());
// run to do the final cleanup
let cleaned_up_at = 3 + acceptance_period + 1;
run_to_block(cleaned_up_at, None);
// now the final cleanup: last past code cleaned up, and this triggers meta cleanup.
assert_eq!(Paras::past_code_meta(¶_id), Default::default());
assert!(::PastCode::get(&(para_id, 3)).is_none());
assert!(::PastCodePruning::get().is_empty());
});
}
#[test]
fn para_incoming_at_session() {
new_test_ext(Default::default()).execute_with(|| {
run_to_block(1, None);
let b = ParaId::from(525);
let a = ParaId::from(999);
let c = ParaId::from(333);
Paras::schedule_para_initialize(
b,
ParaGenesisArgs {
parachain: true,
genesis_head: vec![1].into(),
validation_code: vec![1].into(),
},
);
Paras::schedule_para_initialize(
a,
ParaGenesisArgs {
parachain: false,
genesis_head: vec![2].into(),
validation_code: vec![2].into(),
},
);
Paras::schedule_para_initialize(
c,
ParaGenesisArgs {
parachain: true,
genesis_head: vec![3].into(),
validation_code: vec![3].into(),
},
);
assert_eq!(::UpcomingParas::get(), vec![c, b, a]);
assert!(::Parathreads::get(&a).is_none());
// run to block without session change.
run_to_block(2, None);
assert_eq!(Paras::parachains(), Vec::new());
assert_eq!(::UpcomingParas::get(), vec![c, b, a]);
assert!(::Parathreads::get(&a).is_none());
run_to_block(3, Some(vec![3]));
assert_eq!(Paras::parachains(), vec![c, b]);
assert_eq!(::UpcomingParas::get(), Vec::new());
assert!(::Parathreads::get(&a).is_some());
assert_eq!(Paras::current_code(&a), Some(vec![2].into()));
assert_eq!(Paras::current_code(&b), Some(vec![1].into()));
assert_eq!(Paras::current_code(&c), Some(vec![3].into()));
})
}
#[test]
fn para_cleanup_removes_upcoming() {
new_test_ext(Default::default()).execute_with(|| {
run_to_block(1, None);
let b = ParaId::from(525);
let a = ParaId::from(999);
let c = ParaId::from(333);
Paras::schedule_para_initialize(
b,
ParaGenesisArgs {
parachain: true,
genesis_head: vec![1].into(),
validation_code: vec![1].into(),
},
);
Paras::schedule_para_initialize(
a,
ParaGenesisArgs {
parachain: false,
genesis_head: vec![2].into(),
validation_code: vec![2].into(),
},
);
Paras::schedule_para_initialize(
c,
ParaGenesisArgs {
parachain: true,
genesis_head: vec![3].into(),
validation_code: vec![3].into(),
},
);
assert_eq!(::UpcomingParas::get(), vec![c, b, a]);
assert!(::Parathreads::get(&a).is_none());
// run to block without session change.
run_to_block(2, None);
assert_eq!(Paras::parachains(), Vec::new());
assert_eq!(::UpcomingParas::get(), vec![c, b, a]);
assert!(::Parathreads::get(&a).is_none());
Paras::schedule_para_cleanup(c);
run_to_block(3, Some(vec![3]));
assert_eq!(Paras::parachains(), vec![b]);
assert_eq!(::OutgoingParas::get(), vec![]);
assert_eq!(::UpcomingParas::get(), Vec::new());
assert!(::UpcomingParasGenesis::get(a).is_none());
assert!(::Parathreads::get(&a).is_some());
assert_eq!(Paras::current_code(&a), Some(vec![2].into()));
assert_eq!(Paras::current_code(&b), Some(vec![1].into()));
assert!(Paras::current_code(&c).is_none());
});
}
#[test]
fn code_at_with_intermediate() {
let acceptance_period = 10;
let paras = vec![
(0u32.into(), ParaGenesisArgs {
parachain: true,
genesis_head: Default::default(),
validation_code: vec![1, 2, 3].into(),
}),
];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
acceptance_period,
..Default::default()
},
..Default::default()
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let para_id = ParaId::from(0);
let old_code: ValidationCode = vec![1, 2, 3].into();
let new_code: ValidationCode = vec![4, 5, 6].into();
Paras::schedule_code_upgrade(para_id, new_code.clone(), 10);
// no intermediate, falls back on current/past.
assert_eq!(Paras::validation_code_at(para_id, 1, None), Some(old_code.clone()));
assert_eq!(Paras::validation_code_at(para_id, 10, None), Some(old_code.clone()));
assert_eq!(Paras::validation_code_at(para_id, 100, None), Some(old_code.clone()));
// intermediate before upgrade meant to be applied, falls back on current.
assert_eq!(Paras::validation_code_at(para_id, 9, Some(8)), Some(old_code.clone()));
assert_eq!(Paras::validation_code_at(para_id, 10, Some(9)), Some(old_code.clone()));
assert_eq!(Paras::validation_code_at(para_id, 11, Some(9)), Some(old_code.clone()));
// intermediate at or after upgrade applied
assert_eq!(Paras::validation_code_at(para_id, 11, Some(10)), Some(new_code.clone()));
assert_eq!(Paras::validation_code_at(para_id, 100, Some(11)), Some(new_code.clone()));
run_to_block(acceptance_period + 5, None);
// at <= intermediate not allowed
assert_eq!(Paras::validation_code_at(para_id, 10, Some(10)), None);
assert_eq!(Paras::validation_code_at(para_id, 9, Some(10)), None);
});
}
#[test]
fn code_at_returns_up_to_end_of_acceptance_period() {
let acceptance_period = 10;
let paras = vec![
(0u32.into(), ParaGenesisArgs {
parachain: true,
genesis_head: Default::default(),
validation_code: vec![1, 2, 3].into(),
}),
];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
acceptance_period,
..Default::default()
},
..Default::default()
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let para_id = ParaId::from(0);
let old_code: ValidationCode = vec![1, 2, 3].into();
let new_code: ValidationCode = vec![4, 5, 6].into();
Paras::schedule_code_upgrade(para_id, new_code.clone(), 2);
run_to_block(10, None);
Paras::note_new_head(para_id, Default::default(), 7);
assert_eq!(
Paras::past_code_meta(¶_id).upgrade_times,
vec![upgrade_at(2, 10)],
);
assert_eq!(Paras::validation_code_at(para_id, 2, None), Some(old_code.clone()));
assert_eq!(Paras::validation_code_at(para_id, 3, None), Some(new_code.clone()));
run_to_block(10 + acceptance_period, None);
assert_eq!(Paras::validation_code_at(para_id, 2, None), Some(old_code.clone()));
assert_eq!(Paras::validation_code_at(para_id, 3, None), Some(new_code.clone()));
run_to_block(10 + acceptance_period + 1, None);
// code entry should be pruned now.
assert_eq!(
Paras::past_code_meta(¶_id),
ParaPastCodeMeta {
upgrade_times: Vec::new(),
last_pruned: Some(2),
},
);
assert_eq!(Paras::validation_code_at(para_id, 2, None), None); // pruned :(
assert_eq!(Paras::validation_code_at(para_id, 3, None), Some(new_code.clone()));
});
}
}