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,
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};
#[cfg(feature = "std")]
use serde::{Serialize, Deserialize};
pub trait Config: frame_system::Config + configuration::Config {
/// The outer origin type.
type Origin: From<Origin>
+ From<<Self as frame_system::Config>::Origin>
+ Into<result::Result<Origin, <Self as Config>::Origin>>;
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// 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 `PastCode` 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),
}
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/// The possible states of a para, to take into account delayed lifecycle changes.
#[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.
UpgradingToParachain,
/// Para is a Parachain which is downgrading to a Parathread.
DowngradingToParathread,
/// Parathread is being offboarded.
OutgoingParathread,
/// Parachain is being offboarded.
OutgoingParachain,
}
impl ParaLifecycle {
pub fn is_onboarding(&self) -> bool {
matches!(self, ParaLifecycle::Onboarding)
}
pub fn is_stable(&self) -> bool {
matches!(self, ParaLifecycle::Parathread | ParaLifecycle::Parachain)
}
pub fn is_parachain(&self) -> bool {
matches!(self, ParaLifecycle::Parachain)
}
pub fn is_parathread(&self) -> bool {
matches!(self, ParaLifecycle::Parathread)
}
pub fn is_outgoing(&self) -> bool {
matches!(self, ParaLifecycle::OutgoingParathread | ParaLifecycle::OutgoingParachain)
}
pub fn is_transitioning(&self) -> bool {
!Self::is_stable(self)
}
}
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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
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 of every live para.
CurrentCode get(fn current_code): map hasher(twox_64_concat) ParaId => Option<ValidationCode>;
/// 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<ValidationCode>;
/// 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 of a para.
FutureCode: map hasher(twox_64_concat) ParaId => Option<ValidationCode>;
/// Upcoming paras (chains and threads). These are only updated on session change. Corresponds to an
/// entry in the upcoming-genesis map. Ordered ascending by ParaId.
UpcomingParas get(fn upcoming_paras): Vec<ParaId>;
/// Upcoming paras instantiation arguments.
UpcomingParasGenesis: map hasher(twox_64_concat) ParaId => Option<ParaGenesisArgs>;
/// Paras that are to be cleaned up at the end of the session. Ordered ascending by ParaId.
OutgoingParas get(fn outgoing_paras): Vec<ParaId>;
/// Existing Parathreads that should upgrade to be a Parachain. Ordered ascending by ParaId.
UpcomingUpgrades: Vec<ParaId>;
/// Existing Parachains that should downgrade to be a Parathread. Ordered ascending by ParaId.
UpcomingDowngrades: Vec<ParaId>;
}
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 {
<Module<T> as Store>::CurrentCode::insert(&id, &genesis_args.validation_code);
<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> { }
}
decl_module! {
/// The parachains configuration module.
pub struct Module<T: Config> for enum Call where origin: <T as frame_system::Config>::Origin {
type Error = Error<T>;
}
}
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.
pub(crate) fn initializer_on_new_session(_notification: &SessionChangeNotification<T::BlockNumber>) {
let now = <frame_system::Module<T>>::block_number();
let mut parachains = Self::clean_up_outgoing(now);
Self::apply_incoming(&mut parachains);
Self::apply_upgrades(&mut parachains);
Self::apply_downgrades(&mut parachains);
<Self as Store>::Parachains::set(parachains);
}
/// Cleans up all outgoing paras. Returns the new set of parachains
fn clean_up_outgoing(now: T::BlockNumber) -> Vec<ParaId> {
let mut parachains = <Self as Store>::Parachains::get();
let outgoing = <Self as Store>::OutgoingParas::take();
for outgoing_para in outgoing {
// Warn if there is a state error... but still perform the offboarding to be defensive.
if let Some(state) = ParaLifecycles::get(&outgoing_para) {
if !state.is_outgoing() {
frame_support::debug::error!(
target: "parachains",
"Outgoing parachain has wrong lifecycle state."
)
}
};
if let Ok(i) = parachains.binary_search(&outgoing_para) {
parachains.remove(i);
}
<Self as Store>::Heads::remove(&outgoing_para);
<Self as Store>::FutureCodeUpgrades::remove(&outgoing_para);
<Self as Store>::FutureCode::remove(&outgoing_para);
ParaLifecycles::remove(&outgoing_para);
let removed_code = <Self as Store>::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<ParaId>) {
let upcoming = <Self as Store>::UpcomingParas::take();
for upcoming_para in upcoming {
if ParaLifecycles::get(&upcoming_para) != Some(ParaLifecycle::Onboarding) {
continue;
};
let genesis_data = match <Self as Store>::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);
}
ParaLifecycles::insert(&upcoming_para, ParaLifecycle::Parachain);
ParaLifecycles::insert(&upcoming_para, ParaLifecycle::Parathread);
}
<Self as Store>::Heads::insert(&upcoming_para, genesis_data.genesis_head);
<Self as Store>::CurrentCode::insert(&upcoming_para, genesis_data.validation_code);
}
}
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
/// Take an existing parathread and upgrade it to be a parachain.
fn apply_upgrades(parachains: &mut Vec<ParaId>) {
let upgrades = UpcomingUpgrades::take();
for para in upgrades {
ParaLifecycles::mutate(¶, |state| {
if *state == Some(ParaLifecycle::UpgradingToParachain) {
if let Err(i) = parachains.binary_search(¶) {
parachains.insert(i, para);
}
*state = Some(ParaLifecycle::Parachain);
}
});
}
}
/// Take an existing parachain and downgrade it to be a parathread. Update the list of parachains.
fn apply_downgrades(parachains: &mut Vec<ParaId>) {
let downgrades = UpcomingDowngrades::take();
for para in downgrades {
ParaLifecycles::mutate(¶, |state| {
if *state == Some(ParaLifecycle::DowngradingToParathread) {
if let Ok(i) = parachains.binary_search(¶) {
parachains.remove(i);
}
*state = Some(ParaLifecycle::Parathread);
}
});
}
}
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
// 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 {
<Self as Store>::PastCodeMeta::mutate(&id, |past_meta| {
past_meta.note_replacement(at, now);
});
<Self as Store>::PastCode::insert(&(id, at), old_code);
// 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 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 =
<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) {
<Self as Store>::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 {
<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)
}
/// Schedule a para to be initialized at the start of the next session.
///
/// Noop if Para ID is already registered in the system with some `ParaLifecycle`.
pub(crate) fn schedule_para_initialize(id: ParaId, genesis: ParaGenesisArgs) -> Weight {
let mut weight = T::DbWeight::get().reads_writes(0, 0);
// Make sure parachain isn't already in our system.
if ParaLifecycles::contains_key(&id) {
weight = weight.saturating_add(T::DbWeight::get().reads(1));
return weight;
}
let dup = UpcomingParas::mutate(|v| {
match v.binary_search(&id) {
Ok(_) => true,
Err(i) => {
v.insert(i, id);
false
}
}
});
ParaLifecycles::insert(&id, ParaLifecycle::Onboarding);
weight = weight.saturating_add(T::DbWeight::get().writes(1));
weight = weight.saturating_add(T::DbWeight::get().reads(1));
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
UpcomingParasGenesis::insert(&id, &genesis);
weight = weight.saturating_add(T::DbWeight::get().writes(2));
}
/// Schedule a para to be cleaned up at the start of the next session.
///
/// Noop if para is already outgoing or not known.
pub(crate) fn schedule_para_cleanup(id: ParaId) -> Weight {
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
match ParaLifecycles::get(&id) {
Some(ParaLifecycle::Onboarding) => {
UpcomingParas::mutate(|v| {
match v.binary_search(&id) {
Ok(i) => {
v.remove(i);
UpcomingParasGenesis::remove(&id);
ParaLifecycles::remove(&id);
// If a para was only in the pending state it should not be moved to `Outgoing`
T::DbWeight::get().reads_writes(1, 3)
}
Err(_) => T::DbWeight::get().reads_writes(1, 0),
}
})
},
Some(ParaLifecycle::Parathread) => {
ParaLifecycles::insert(&id, ParaLifecycle::OutgoingParathread);
OutgoingParas::mutate(|v| {
match v.binary_search(&id) {
Ok(_) => T::DbWeight::get().reads_writes(1, 1),
Err(i) => {
v.insert(i, id);
T::DbWeight::get().reads_writes(1, 2)
}
}
})
},
Some(ParaLifecycle::Parachain) => {
OutgoingParas::mutate(|v| {
match v.binary_search(&id) {
Ok(_) => T::DbWeight::get().reads_writes(1, 0),
Err(i) => {
v.insert(i, id);
ParaLifecycles::insert(&id, ParaLifecycle::OutgoingParachain);
T::DbWeight::get().reads_writes(1, 2)
}
}
})
},
Some(ParaLifecycle::UpgradingToParachain) => {
let upgrade_weight = UpcomingUpgrades::mutate(|v| {
match v.binary_search(&id) {
Ok(i) => {
v.remove(i);
T::DbWeight::get().reads_writes(1, 1)
},
Err(_) => T::DbWeight::get().reads(1),
}
});
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);
ParaLifecycles::insert(&id, ParaLifecycle::OutgoingParathread);
T::DbWeight::get().reads_writes(1, 2)
}
}
});
upgrade_weight.saturating_add(outgoing_weight)
},
Some(ParaLifecycle::DowngradingToParathread) => {
let downgrade_weight = UpcomingDowngrades::mutate(|v| {
match v.binary_search(&id) {
Ok(i) => {
v.remove(i);
T::DbWeight::get().reads_writes(1, 1)
},
Err(_) => T::DbWeight::get().reads(1),
}
});
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);
ParaLifecycles::insert(&id, ParaLifecycle::OutgoingParathread);
T::DbWeight::get().reads_writes(1, 2)
}
}
});
downgrade_weight.saturating_add(outgoing_weight)
},
None |
Some(ParaLifecycle::OutgoingParathread) |
Some(ParaLifecycle::OutgoingParachain)
=> { T::DbWeight::get().reads(1) },
}
}
/// Schedule a parathread to be upgraded to a parachain.
///
/// Noop if `ParaLifecycle` is not `Parathread`.
#[allow(unused)]
pub(crate) fn schedule_parathread_upgrade(id: ParaId) -> Weight {
if ParaLifecycles::get(&id) != Some(ParaLifecycle::Parathread) {
let weight = T::DbWeight::get().reads_writes(1, 0);
return weight;
}
let dup = UpcomingUpgrades::mutate(|v| {
Ok(_) => true,
Err(i) => {
v.insert(i, id);
false
ParaLifecycles::insert(&id, ParaLifecycle::UpgradingToParachain);
if dup {
let weight = T::DbWeight::get().reads_writes(2, 1);
return weight;
}
T::DbWeight::get().reads_writes(2, 2)
}
/// 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) -> Weight {
if ParaLifecycles::get(&id) != Some(ParaLifecycle::Parachain) {
let weight = T::DbWeight::get().reads_writes(1, 0);
return weight;
}
let dup = UpcomingDowngrades::mutate(|v| {
Ok(_) => true,
Err(i) => {
v.insert(i, id);
ParaLifecycles::insert(&id, ParaLifecycle::DowngradingToParathread);
if dup {
let weight = T::DbWeight::get().reads_writes(2, 1);
return weight;
}
T::DbWeight::get().reads_writes(2, 2)
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
}
/// 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);
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 {
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 = 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 = <frame_system::Module<T>>::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 {
}
}
/// 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<T::BlockNumber>,
) -> Option<ValidationCode> {
let now = <frame_system::Module<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 {
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)) => <Self as Store>::PastCode::get(&(id, replaced))
}
}
}
/// 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.
pub fn is_valid_para(id: ParaId) -> bool {
if let Some(state) = ParaLifecycles::get(&id) {
!state.is_onboarding() && !state.is_outgoing()
} 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()
}
}
#[cfg(test)]
mod tests {
use super::*;
use frame_support::traits::{OnFinalize, OnInitialize};
use crate::mock::{new_test_ext, Paras, 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();
if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) {
Paras::initializer_on_new_session(&Default::default());
}
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
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
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
System::on_finalize(b);
System::on_initialize(b + 1);
System::set_block_number(b + 1);
Paras::initializer_initialize(b + 1);
}
}
fn upgrade_at(expected_at: BlockNumber, activated_at: BlockNumber) -> ReplacementTimes<BlockNumber> {
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::<Vec<_>>().is_empty());
assert_eq!(old, past_code);
assert_eq!(past_code.prune_up_to(10).collect::<Vec<_>>(), 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::<Vec<_>>().is_empty());
assert_eq!(past_code.prune_up_to(26).collect::<Vec<_>>(), 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<_>>(), 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<_>>(), 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;
<Paras as Store>::PastCode::insert(&(id, at_block), &ValidationCode(vec![1, 2, 3]));
<Paras as Store>::PastCodePruning::put(&vec![(id, included_block)]);
{
let mut code_meta = Paras::past_code_meta(&id);
code_meta.note_replacement(at_block, included_block);
<Paras as Store>::PastCodeMeta::insert(&id, &code_meta);
}
let pruned_at: BlockNumber = included_block + acceptance_period + 1;
assert_eq!(<Paras as Store>::PastCode::get(&(id, at_block)), Some(vec![1, 2, 3].into()));
run_to_block(pruned_at - 1, None);
assert_eq!(<Paras as Store>::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!(<Paras as Store>::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 {