Newer
Older
// 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/>.
//! 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::*;
Shawn Tabrizi
committed
#[cfg(feature = "std")]
use sp_std::marker::PhantomData;
Id as ParaId, ValidationCode, HeadData, SessionIndex, Hash, ConsensusLog,
use sp_runtime::{traits::One, DispatchResult, SaturatedConversion};
use frame_system::ensure_root;
decl_storage, decl_module, decl_error, decl_event, ensure,
traits::Get,
weights::Weight,
};
use parity_scale_codec::{Encode, Decode};
use crate::{configuration, shared, initializer::SessionChangeNotification};
#[cfg(feature = "std")]
use serde::{Serialize, Deserialize};
pub trait Config:
frame_system::Config +
configuration::Config +
shared::Config
{
/// The outer origin type.
type Origin: From<Origin>
+ From<<Self as frame_system::Config>::Origin>
+ Into<result::Result<Origin, <Self as Config>::Origin>>;
type Event: From<Event> + Into<<Self as frame_system::Config>::Event>;
// 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<N> {
/// 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<N> {
/// 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 `PastCodeHash` map along with the `ParaId` to fetch the code itself.
upgrade_times: Vec<ReplacementTimes<N>>,
/// Tracks the highest pruned code-replacement, if any. This is the `expected_at` value,
/// not the `activated_at` value.
last_pruned: Option<N>,
}
#[cfg_attr(test, derive(Debug, PartialEq))]
enum UseCodeAt<N> {
/// 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),
}
/// The possible states of a para, to take into account delayed lifecycle changes.
///
/// If the para is in a "transition state", it is expected that the parachain is
/// queued in the `ActionsQueue` to transition it into a stable state. Its lifecycle
/// state will be used to determine the state transition to apply to the para.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
pub enum ParaLifecycle {
/// Para is new and is onboarding as a Parathread or Parachain.
Onboarding,
/// Para is a Parathread.
Parathread,
/// Para is a Parachain.
Parachain,
/// Para is a Parathread which is upgrading to a Parachain.
UpgradingParathread,
/// Para is a Parachain which is downgrading to a Parathread.
DowngradingParachain,
/// Parathread is queued to be offboarded.
OffboardingParathread,
/// Parachain is queued to be offboarded.
OffboardingParachain,
}
impl ParaLifecycle {
/// Returns true if parachain is currently onboarding. To learn if the
/// parachain is onboarding as a parachain or parathread, look at the
/// `UpcomingGenesis` storage item.
pub fn is_onboarding(&self) -> bool {
matches!(self, ParaLifecycle::Onboarding)
}
/// Returns true if para is in a stable state, i.e. it is currently
/// a parachain or parathread, and not in any transition state.
pub fn is_stable(&self) -> bool {
matches!(self, ParaLifecycle::Parathread | ParaLifecycle::Parachain)
}
/// Returns true if para is currently treated as a parachain.
/// This also includes transitioning states, so you may want to combine
/// this check with `is_stable` if you specifically want `Paralifecycle::Parachain`.
pub fn is_parachain(&self) -> bool {
matches!(self,
ParaLifecycle::Parachain |
ParaLifecycle::DowngradingParachain |
ParaLifecycle::OffboardingParachain
)
/// Returns true if para is currently treated as a parathread.
/// This also includes transitioning states, so you may want to combine
/// this check with `is_stable` if you specifically want `Paralifecycle::Parathread`.
pub fn is_parathread(&self) -> bool {
matches!(self,
ParaLifecycle::Parathread |
ParaLifecycle::UpgradingParathread |
ParaLifecycle::OffboardingParathread
)
/// Returns true if para is currently offboarding.
pub fn is_offboarding(&self) -> bool {
matches!(self, ParaLifecycle::OffboardingParathread | ParaLifecycle::OffboardingParachain)
/// Returns true if para is in any transitionary state.
pub fn is_transitioning(&self) -> bool {
!Self::is_stable(self)
}
}
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
impl<N: Ord + Copy> ParaPastCodeMeta<N> {
// 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<UseCodeAt<N>> {
// 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<N> {
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<Item=N> + '_ {
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.
/// The initial validation code to use.
/// True if parachain, false if parathread.
trait Store for Module<T: Config> as Paras {
/// All parachains. Ordered ascending by ParaId. Parathreads are not included.
Parachains get(fn parachains): Vec<ParaId>;
/// The current lifecycle of a all known Para IDs.
ParaLifecycles: map hasher(twox_64_concat) ParaId => Option<ParaLifecycle>;
/// The head-data of every registered para.
Heads get(fn para_head): map hasher(twox_64_concat) ParaId => Option<HeadData>;
/// The validation code hash of every live para.
///
/// Corresponding code can be retrieved with [`CodeByHash`].
CurrentCodeHash: map hasher(twox_64_concat) ParaId => Option<Hash>;
/// Actual past code hash, indicated by the para id as well as the block number at which it
/// became outdated.
///
/// Corresponding code can be retrieved with [`CodeByHash`].
PastCodeHash: map hasher(twox_64_concat) (ParaId, T::BlockNumber) => Option<Hash>;
/// 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<T::BlockNumber>;
/// 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<T::BlockNumber>;
/// The actual future code hash of a para.
///
/// Corresponding code can be retrieved with [`CodeByHash`].
FutureCodeHash: map hasher(twox_64_concat) ParaId => Option<Hash>;
/// The actions to perform during the start of a specific session index.
ActionsQueue get(fn actions_queue): map hasher(twox_64_concat) SessionIndex => Vec<ParaId>;
/// Upcoming paras instantiation arguments.
UpcomingParasGenesis: map hasher(twox_64_concat) ParaId => Option<ParaGenesisArgs>;
/// The number of reference on the validation code in [`CodeByHash`] storage.
CodeByHashRefs: map hasher(identity) Hash => u32;
/// Validation code stored by its hash.
///
/// This storage is consistent with [`FutureCodeHash`], [`CurrentCodeHash`] and
/// [`PastCodeHash`].
CodeByHash get(fn code_by_hash): map hasher(identity) Hash => Option<ValidationCode>;
}
add_extra_genesis {
config(paras): Vec<(ParaId, ParaGenesisArgs)>;
config(_phdata): PhantomData<T>;
build(build::<T>);
}
}
#[cfg(feature = "std")]
fn build<T: Config>(config: &GenesisConfig<T>) {
let mut parachains: Vec<_> = config.paras
.iter()
.filter(|(_, args)| args.parachain)
.map(|&(ref id, _)| id)
.cloned()
.collect();
parachains.dedup();
Parachains::put(¶chains);
for (id, genesis_args) in &config.paras {
let code_hash = genesis_args.validation_code.hash();
<Module<T>>::increase_code_ref(&code_hash, &genesis_args.validation_code);
<Module<T> as Store>::CurrentCodeHash::insert(&id, &code_hash);
<Module<T> as Store>::Heads::insert(&id, &genesis_args.genesis_head);
if genesis_args.parachain {
ParaLifecycles::insert(&id, ParaLifecycle::Parachain);
} else {
ParaLifecycles::insert(&id, ParaLifecycle::Parathread);
}
pub enum Error for Module<T: Config> {
/// Para is not registered in our system.
NotRegistered,
/// Para cannot be onboarded because it is already tracked by our system.
CannotOnboard,
/// Para cannot be offboarded at this time.
CannotOffboard,
/// Para cannot be upgraded to a parachain.
CannotUpgrade,
/// Para cannot be downgraded to a parathread.
CannotDowngrade,
}
decl_event! {
pub enum Event {
/// Current code has been updated for a Para. \[para_id\]
CurrentCodeUpdated(ParaId),
/// Current head has been updated for a Para. \[para_id\]
CurrentHeadUpdated(ParaId),
/// A code upgrade has been scheduled for a Para. \[para_id\]
CodeUpgradeScheduled(ParaId),
/// A new head has been noted for a Para. \[para_id\]
NewHeadNoted(ParaId),
/// A para has been queued to execute pending actions. \[para_id\]
ActionQueued(ParaId, SessionIndex),
}
}
decl_module! {
/// The parachains configuration module.
pub struct Module<T: Config> for enum Call where origin: <T as frame_system::Config>::Origin {
fn deposit_event() = default;
/// Set the storage for the parachain validation code immediately.
#[weight = 0]
fn force_set_current_code(origin, para: ParaId, new_code: ValidationCode) {
ensure_root(origin)?;
let prior_code_hash = <Self as Store>::CurrentCodeHash::get(¶).unwrap_or_default();
let new_code_hash = new_code.hash();
Self::increase_code_ref(&new_code_hash, &new_code);
<Self as Store>::CurrentCodeHash::insert(¶, new_code_hash);
let now = frame_system::Pallet::<T>::block_number();
Self::note_past_code(para, now, now, prior_code_hash);
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
Self::deposit_event(Event::CurrentCodeUpdated(para));
}
/// Set the storage for the current parachain head data immediately.
#[weight = 0]
fn force_set_current_head(origin, para: ParaId, new_head: HeadData) {
ensure_root(origin)?;
<Self as Store>::Heads::insert(¶, new_head);
Self::deposit_event(Event::CurrentHeadUpdated(para));
}
/// Schedule a code upgrade for block `expected_at`.
#[weight = 0]
fn force_schedule_code_upgrade(origin, para: ParaId, new_code: ValidationCode, expected_at: T::BlockNumber) {
ensure_root(origin)?;
Self::schedule_code_upgrade(para, new_code, expected_at);
Self::deposit_event(Event::CodeUpgradeScheduled(para));
}
/// Note a new block head for para within the context of the current block.
#[weight = 0]
fn force_note_new_head(origin, para: ParaId, new_head: HeadData) {
ensure_root(origin)?;
let now = frame_system::Pallet::<T>::block_number();
Self::note_new_head(para, new_head, now);
Self::deposit_event(Event::NewHeadNoted(para));
}
/// Put a parachain directly into the next session's action queue.
/// We can't queue it any sooner than this without going into the
/// initializer...
#[weight = 0]
fn force_queue_action(origin, para: ParaId) {
ensure_root(origin)?;
let next_session = shared::Module::<T>::session_index().saturating_add(One::one());
ActionsQueue::mutate(next_session, |v| {
if let Err(i) = v.binary_search(¶) {
v.insert(i, para);
}
});
Self::deposit_event(Event::ActionQueued(para, next_session));
}
impl<T: Config> Module<T> {
/// 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.
/// Returns the list of outgoing paras from the actions queue.
pub(crate) fn initializer_on_new_session(notification: &SessionChangeNotification<T::BlockNumber>) -> Vec<ParaId> {
let outgoing_paras = Self::apply_actions_queue(notification.session_index);
outgoing_paras
/// The validation code of live para.
pub(crate) fn current_code(para_id: &ParaId) -> Option<ValidationCode> {
CurrentCodeHash::get(para_id).and_then(|code_hash| {
let code = CodeByHash::get(&code_hash);
if code.is_none() {
log::error!(
"Pallet paras storage is inconsistent, code not found for hash {}",
code_hash,
);
debug_assert!(false, "inconsistent paras storages");
}
code
})
}
// Apply all para actions queued for the given session index.
//
// The actions to take are based on the lifecycle of of the paras.
//
// The final state of any para after the actions queue should be as a
// parachain, parathread, or not registered. (stable states)
//
// Returns the list of outgoing paras from the actions queue.
fn apply_actions_queue(session: SessionIndex) -> Vec<ParaId> {
let actions = ActionsQueue::take(session);
let mut parachains = <Self as Store>::Parachains::get();
Shaun Wang
committed
let now = <frame_system::Pallet<T>>::block_number();
let mut outgoing = Vec::new();
for para in actions {
let lifecycle = ParaLifecycles::get(¶);
match lifecycle {
None | Some(ParaLifecycle::Parathread) | Some(ParaLifecycle::Parachain) => { /* Nothing to do... */ },
// Onboard a new parathread or parachain.
Some(ParaLifecycle::Onboarding) => {
if let Some(genesis_data) = <Self as Store>::UpcomingParasGenesis::take(¶) {
if genesis_data.parachain {
if let Err(i) = parachains.binary_search(¶) {
parachains.insert(i, para);
}
ParaLifecycles::insert(¶, ParaLifecycle::Parachain);
} else {
ParaLifecycles::insert(¶, ParaLifecycle::Parathread);
}
let code_hash = genesis_data.validation_code.hash();
<Self as Store>::Heads::insert(¶, genesis_data.genesis_head);
Self::increase_code_ref(&code_hash, &genesis_data.validation_code);
<Self as Store>::CurrentCodeHash::insert(¶, code_hash);
}
},
// Upgrade a parathread to a parachain
Some(ParaLifecycle::UpgradingParathread) => {
if let Err(i) = parachains.binary_search(¶) {
parachains.insert(i, para);
}
ParaLifecycles::insert(¶, ParaLifecycle::Parachain);
},
// Downgrade a parachain to a parathread
Some(ParaLifecycle::DowngradingParachain) => {
if let Ok(i) = parachains.binary_search(¶) {
parachains.remove(i);
}
ParaLifecycles::insert(¶, ParaLifecycle::Parathread);
},
// Offboard a parathread or parachain from the system
Some(ParaLifecycle::OffboardingParachain) | Some(ParaLifecycle::OffboardingParathread) => {
if let Ok(i) = parachains.binary_search(¶) {
parachains.remove(i);
}
<Self as Store>::Heads::remove(¶);
<Self as Store>::FutureCodeUpgrades::remove(¶);
ParaLifecycles::remove(¶);
let removed_future_code_hash = <Self as Store>::FutureCodeHash::take(¶);
if let Some(removed_future_code_hash) = removed_future_code_hash {
Self::decrease_code_ref(&removed_future_code_hash);
}
let removed_code_hash = <Self as Store>::CurrentCodeHash::take(¶);
if let Some(removed_code_hash) = removed_code_hash {
Self::note_past_code(para, now, now, removed_code_hash);
}
outgoing.push(para);
},
}
// Place the new parachains set in storage.
<Self as Store>::Parachains::set(parachains);
return outgoing
// 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,
) -> Weight {
<Self as Store>::PastCodeMeta::mutate(&id, |past_meta| {
past_meta.note_replacement(at, now);
});
<Self as Store>::PastCodeHash::insert(&(id, at), old_code_hash);
// Schedule pruning for this past-code to be removed as soon as it
// exits the slashing window.
<Self as Store>::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::<T>::config();
let code_retention_period = config.code_retention_period;
if now <= code_retention_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 - (code_retention_period + One::one());
let pruning_tasks_done =
<Self as Store>::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 = <Self as Store>::PastCodeMeta::mutate(¶_id, |meta| {
for pruned_repl_at in meta.prune_up_to(pruning_height) {
let removed_code_hash =
<Self as Store>::PastCodeHash::take(&(para_id, pruned_repl_at));
if let Some(removed_code_hash) = removed_code_hash {
Self::decrease_code_ref(&removed_code_hash);
} else {
log::warn!(
target: "runtime::paras",
"Missing code for removed hash {:?}",
removed_code_hash,
);
}
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 {
<Self as Store>::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)
}
/// Verify that `schedule_para_initialize` can be called successfully.
///
/// Returns false if para is already registered in the system.
pub fn can_schedule_para_initialize(id: &ParaId, _: &ParaGenesisArgs) -> bool {
let lifecycle = ParaLifecycles::get(id);
lifecycle.is_none()
}
/// Schedule a para to be initialized at the start of the next session.
/// Will return error if para is already registered in the system.
pub(crate) fn schedule_para_initialize(id: ParaId, genesis: ParaGenesisArgs) -> DispatchResult {
let scheduled_session = Self::scheduled_session();
// Make sure parachain isn't already in our system.
ensure!(Self::can_schedule_para_initialize(&id, &genesis), Error::<T>::CannotOnboard);
ParaLifecycles::insert(&id, ParaLifecycle::Onboarding);
UpcomingParasGenesis::insert(&id, genesis);
ActionsQueue::mutate(scheduled_session, |v| {
if let Err(i) = v.binary_search(&id) {
v.insert(i, id);
}
/// Schedule a para to be cleaned up at the start of the next session.
/// Will return error if para is not a stable parachain or parathread.
///
/// No-op if para is not registered at all.
pub(crate) fn schedule_para_cleanup(id: ParaId) -> DispatchResult {
let lifecycle = ParaLifecycles::get(&id);
// If para is not registered, nothing to do!
None => {
return Ok(())
},
Some(ParaLifecycle::Parathread) => {
ParaLifecycles::insert(&id, ParaLifecycle::OffboardingParathread);
Some(ParaLifecycle::Parachain) => {
ParaLifecycles::insert(&id, ParaLifecycle::OffboardingParachain);
_ => return Err(Error::<T>::CannotOffboard)?,
let scheduled_session = Self::scheduled_session();
ActionsQueue::mutate(scheduled_session, |v| {
if let Err(i) = v.binary_search(&id) {
v.insert(i, id);
}
});
Ok(())
}
/// Schedule a parathread to be upgraded to a parachain.
///
/// Will return error if `ParaLifecycle` is not `Parathread`.
#[allow(unused)]
pub(crate) fn schedule_parathread_upgrade(id: ParaId) -> DispatchResult {
let scheduled_session = Self::scheduled_session();
let lifecycle = ParaLifecycles::get(&id).ok_or(Error::<T>::NotRegistered)?;
ensure!(lifecycle == ParaLifecycle::Parathread, Error::<T>::CannotUpgrade);
ParaLifecycles::insert(&id, ParaLifecycle::UpgradingParathread);
ActionsQueue::mutate(scheduled_session, |v| {
if let Err(i) = v.binary_search(&id) {
v.insert(i, id);
}
/// Schedule a parachain to be downgraded to a parathread.
///
/// Noop if `ParaLifecycle` is not `Parachain`.
#[allow(unused)]
pub(crate) fn schedule_parachain_downgrade(id: ParaId) -> DispatchResult {
let scheduled_session = Self::scheduled_session();
let lifecycle = ParaLifecycles::get(&id).ok_or(Error::<T>::NotRegistered)?;
ensure!(lifecycle == ParaLifecycle::Parachain, Error::<T>::CannotDowngrade);
ParaLifecycles::insert(&id, ParaLifecycle::DowngradingParachain);
ActionsQueue::mutate(scheduled_session, |v| {
if let Err(i) = v.binary_search(&id) {
v.insert(i, id);
}
/// 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 {
<Self as Store>::FutureCodeUpgrades::mutate(&id, |up| {
if up.is_some() {
T::DbWeight::get().reads_writes(1, 0)
} else {
*up = Some(expected_at);
let new_code_hash = new_code.hash();
let expected_at_u32 = expected_at.saturated_into();
let log = ConsensusLog::ParaScheduleUpgradeCode(id, new_code_hash, expected_at_u32);
<frame_system::Pallet<T>>::deposit_log(log.into());
let (reads, writes) = Self::increase_code_ref(&new_code_hash, &new_code);
FutureCodeHash::insert(&id, new_code_hash);
T::DbWeight::get().reads_writes(1 + reads, 2 + writes)
}
})
}
/// 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 {
if let Some(expected_at) = <Self as Store>::FutureCodeUpgrades::get(&id) {
if expected_at <= execution_context {
<Self as Store>::FutureCodeUpgrades::remove(&id);
// Both should always be `Some` in this case, since a code upgrade is scheduled.
let new_code_hash = FutureCodeHash::take(&id).unwrap_or_default();
let prior_code_hash = CurrentCodeHash::get(&id).unwrap_or_default();
CurrentCodeHash::insert(&id, &new_code_hash);
let log = ConsensusLog::ParaUpgradeCode(id, new_code_hash);
<frame_system::Pallet<T>>::deposit_log(log.into());
// `now` is only used for registering pruning as part of `fn note_past_code`
Shaun Wang
committed
let now = <frame_system::Pallet<T>>::block_number();
let weight = Self::note_past_code(
id,
expected_at,
now,
);
// 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 {
/// Fetches the validation code hash for 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 the hash for the 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.
///
/// To get associated code see [`Self::validation_code_at`].
pub(crate) fn validation_code_hash_at(
id: ParaId,
at: T::BlockNumber,
assume_intermediate: Option<T::BlockNumber>,
Shaun Wang
committed
let now = <frame_system::Pallet<T>>::block_number();
let config = <configuration::Module<T>>::config();
if assume_intermediate.as_ref().map_or(false, |i| &at <= i) {
return None;
}
let planned_upgrade = <Self as Store>::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 {
} else {
match Self::past_code_meta(&id).code_at(at) {
None => None,
Some(UseCodeAt::Current) => CurrentCodeHash::get(&id),
Some(UseCodeAt::ReplacedAt(replaced)) => <Self as Store>::PastCodeHash::get(&(id, replaced)),
/// Fetch validation code of para in specific context, see [`Self::validation_code_hash_at`].
#[allow(unused)]
pub(crate) fn validation_code_at(
id: ParaId,
at: T::BlockNumber,
assume_intermediate: Option<T::BlockNumber>,
) -> Option<ValidationCode> {
Self::validation_code_hash_at(id, at, assume_intermediate).and_then(|code_hash| {
let code = CodeByHash::get(&code_hash);
if code.is_none() {
log::error!(
"Pallet paras storage is inconsistent, code not found for hash {}",
code_hash,
);
debug_assert!(false, "inconsistent paras storages");
}
code
})
}
/// Returns the current lifecycle state of the para.
pub fn lifecycle(id: ParaId) -> Option<ParaLifecycle> {
ParaLifecycles::get(&id)
}
/// Returns whether the given ID refers to a valid para.
///
/// Paras that are onboarding or offboarding are not included.
pub fn is_valid_para(id: ParaId) -> bool {
if let Some(state) = ParaLifecycles::get(&id) {
!state.is_onboarding() && !state.is_offboarding()
} else {
false
}
}
/// Whether a para ID corresponds to any live parachain.
///
/// Includes parachains which will downgrade to a parathread in the future.
pub fn is_parachain(id: ParaId) -> bool {
if let Some(state) = ParaLifecycles::get(&id) {
state.is_parachain()
} else {
false
}
/// Whether a para ID corresponds to any live parathread.
///
/// Includes parathreads which will upgrade to parachains in the future.
pub fn is_parathread(id: ParaId) -> bool {
if let Some(state) = ParaLifecycles::get(&id) {
state.is_parathread()
} else {
false
}
/// 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<T::BlockNumber> {
if include_future {
if let Some(at) = Self::future_code_upgrade_at(id) {
return Some(at);
}
}
Self::past_code_meta(&id).most_recent_change()
}
/// Return the session index that should be used for any future scheduled changes.
fn scheduled_session() -> SessionIndex {
shared::Module::<T>::scheduled_session()
}
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
/// Store the validation code if not already stored, and increase the number of reference.
///
/// Returns the number of storage reads and number of storage writes.
fn increase_code_ref(code_hash: &Hash, code: &ValidationCode) -> (u64, u64) {
let reads = 1;
let mut writes = 1;
<Self as Store>::CodeByHashRefs::mutate(code_hash, |refs| {
if *refs == 0 {
writes += 1;
<Self as Store>::CodeByHash::insert(code_hash, code);
}
*refs += 1;
});
(reads, writes)
}
/// Decrease the number of reference ofthe validation code and remove it from storage if zero
/// is reached.
fn decrease_code_ref(code_hash: &Hash) {
let refs = <Self as Store>::CodeByHashRefs::get(code_hash);
if refs <= 1 {
<Self as Store>::CodeByHash::remove(code_hash);
<Self as Store>::CodeByHashRefs::remove(code_hash);
} else {
<Self as Store>::CodeByHashRefs::insert(code_hash, refs - 1);
}
}
/// Test function for triggering a new session in this pallet.
#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
pub fn test_on_new_session() {
Self::initializer_on_new_session(&SessionChangeNotification {
session_index: shared::Module::<T>::session_index(),
..Default::default()
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use frame_support::{
assert_ok,
traits::{OnFinalize, OnInitialize}
};
use crate::mock::{new_test_ext, Paras, Shared, System, MockGenesisConfig};
use crate::configuration::HostConfiguration;
fn run_to_block(to: BlockNumber, new_session: Option<Vec<BlockNumber>>) {
while System::block_number() < to {
let b = System::block_number();
Paras::initializer_finalize();
Shared::initializer_finalize();
if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) {
let mut session_change_notification = SessionChangeNotification::default();
session_change_notification.session_index = Shared::session_index() + 1;
Shared::initializer_on_new_session(
session_change_notification.session_index,
session_change_notification.random_seed,
&session_change_notification.new_config,
session_change_notification.validators.clone(),
);
Paras::initializer_on_new_session(&session_change_notification);
System::on_finalize(b);
System::on_initialize(b + 1);
System::set_block_number(b + 1);
Shared::initializer_initialize(b + 1);
Paras::initializer_initialize(b + 1);
}
}
fn upgrade_at(expected_at: BlockNumber, activated_at: BlockNumber) -> ReplacementTimes<BlockNumber> {
ReplacementTimes { expected_at, activated_at }
}
fn check_code_is_stored(validation_code: &ValidationCode) {
assert!(<Paras as Store>::CodeByHashRefs::get(validation_code.hash()) != 0);
assert!(<Paras as Store>::CodeByHash::contains_key(validation_code.hash()));
}