diff --git a/Cargo.lock b/Cargo.lock index ba5926fe8eae..5ad084e9babf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4495,6 +4495,48 @@ dependencies = [ "trie-db", ] +[[package]] +name = "polkadot-runtime-parachains" +version = "0.8.0" +dependencies = [ + "bitvec", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "libsecp256k1", + "log 0.3.9", + "pallet-authorship", + "pallet-babe", + "pallet-balances", + "pallet-offences", + "pallet-randomness-collective-flip", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "pallet-treasury", + "pallet-vesting", + "parity-scale-codec", + "polkadot-primitives", + "rustc-hex", + "serde", + "serde_derive", + "serde_json", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-inherents", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", + "sp-trie", + "trie-db", +] + [[package]] name = "polkadot-service" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 9374f80d3120..f652d028f2dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ "network/test", "primitives", "runtime/common", + "runtime/parachains", "runtime/polkadot", "runtime/kusama", "runtime/westend", diff --git a/roadmap/implementors-guide/guide.md b/roadmap/implementors-guide/guide.md index 4009d502fb7a..55ab469bf91e 100644 --- a/roadmap/implementors-guide/guide.md +++ b/roadmap/implementors-guide/guide.md @@ -497,7 +497,7 @@ It's also responsible for managing parachain validation code upgrades as well as Utility structs: ```rust // the two key times necessary to track for every code replacement. -struct ReplacementTimes { +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. @@ -564,7 +564,7 @@ PastCodePruning: Vec<(ParaId, BlockNumber)>; /// in the context of a relay chain block with a number >= `expected_at`. FutureCodeUpgrades: map ParaId => Option; /// The actual future code of a para. -FutureCode: map ParaId => ValidationCode; +FutureCode: map ParaId => Option; /// Upcoming paras (chains and threads). These are only updated on session change. Corresponds to an /// entry in the upcoming-genesis map. @@ -590,7 +590,7 @@ OutgoingParas: Vec; * `schedule_para_cleanup(ParaId)`: schedule a para to be cleaned up at the next session. * `schedule_code_upgrade(ParaId, ValidationCode, expected_at: BlockNumber)`: 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`. * `note_new_head(ParaId, HeadData, BlockNumber)`: 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. -* `validation_code_at(ParaId, at: BlockNumber, assume_intermediate: Option)`: 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 included at the given relay-chain height. This may return past, current, or (with certain choices of `assume_intermediate`) future code. `assume_intermediate`, if provided, must be before `at`. If `at` is not within `config.acceptance_period` of the current block number, this will return `None`. +* `validation_code_at(ParaId, at: BlockNumber, assume_intermediate: Option)`: 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 included at the given relay-chain height. This may return past, current, or (with certain choices of `assume_intermediate`) future code. `assume_intermediate`, if provided, must be before `at`. If the validation code has been pruned, this will return `None`. #### Finalization @@ -838,6 +838,7 @@ All failed checks should lead to an unrecoverable error making the block invalid 1. Return a list of freed cores consisting of the cores where candidates have become available. * `process_candidates(BackedCandidates, scheduled: Vec)`: 1. check that each candidate corresponds to a scheduled core and that they are ordered in ascending order by `ParaId`. + 1. Ensure that any code upgrade scheduled by the candidate does not happen within `config.validation_upgrade_frequency` of the currently scheduled upgrade, if any, comparing against the value of `Paras::FutureCodeUpgrades` for the given para ID. 1. check the backing of the candidate using the signatures and the bitfields. 1. create an entry in the `PendingAvailability` map for each backed candidate with a blank `availability_votes` bitfield. 1. Return a `Vec` of all scheduled cores of the list of passed assignments that a candidate was successfully backed for, sorted ascending by CoreIndex. diff --git a/runtime/parachains/Cargo.toml b/runtime/parachains/Cargo.toml new file mode 100644 index 000000000000..adaf400a2e2a --- /dev/null +++ b/runtime/parachains/Cargo.toml @@ -0,0 +1,84 @@ +[package] +name = "polkadot-runtime-parachains" +version = "0.8.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +bitvec = { version = "0.17.4", default-features = false, features = ["alloc"] } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +log = { version = "0.3.9", optional = true } +rustc-hex = { version = "2.0.1", default-features = false } +serde = { version = "1.0.102", default-features = false } +serde_derive = { version = "1.0.102", optional = true } + +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +inherents = { package = "sp-inherents", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-std = { package = "sp-std", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-session = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-staking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +authorship = { package = "pallet-authorship", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +balances = { package = "pallet-balances", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +session = { package = "pallet-session", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +staking = { package = "pallet-staking", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +system = { package = "frame-system", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +timestamp = { package = "pallet-timestamp", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +vesting = { package = "pallet-vesting", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +offences = { package = "pallet-offences", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true } + +primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false } +libsecp256k1 = { version = "0.3.2", default-features = false, optional = true } + +[dev-dependencies] +hex-literal = "0.2.1" +keyring = { package = "sp-keyring", git = "https://github.com/paritytech/substrate", branch = "master" } +sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" } +babe = { package = "pallet-babe", git = "https://github.com/paritytech/substrate", branch = "master" } +sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master" } +randomness-collective-flip = { package = "pallet-randomness-collective-flip", git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-staking-reward-curve = { git = "https://github.com/paritytech/substrate", branch = "master" } +treasury = { package = "pallet-treasury", git = "https://github.com/paritytech/substrate", branch = "master" } +trie-db = "0.20.0" +serde_json = "1.0.41" +libsecp256k1 = "0.3.2" + +[features] +default = ["std"] +no_std = [] +std = [ + "bitvec/std", + "codec/std", + "log", + "rustc-hex/std", + "serde_derive", + "serde/std", + "primitives/std", + "inherents/std", + "sp-core/std", + "sp-api/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "authorship/std", + "balances/std", + "sp-runtime/std", + "sp-session/std", + "sp-staking/std", + "session/std", + "staking/std", + "system/std", + "timestamp/std", + "vesting/std", +] +runtime-benchmarks = [ + "libsecp256k1/hmac", + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "system/runtime-benchmarks", +] diff --git a/runtime/parachains/src/configuration.rs b/runtime/parachains/src/configuration.rs new file mode 100644 index 000000000000..b747dbf85cd2 --- /dev/null +++ b/runtime/parachains/src/configuration.rs @@ -0,0 +1,329 @@ +// 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 . + +//! Configuration manager for the Polkadot runtime parachains logic. +//! +//! Configuration can change only at session boundaries and is buffered until then. + +use sp_std::prelude::*; +use primitives::{ + parachain::{ValidatorId}, +}; +use frame_support::{ + decl_storage, decl_module, decl_error, + dispatch::DispatchResult, + weights::{DispatchClass, Weight}, +}; +use codec::{Encode, Decode}; +use system::ensure_root; + +/// All configuration of the runtime with respect to parachains and parathreads. +#[derive(Clone, Encode, Decode, PartialEq, Default)] +#[cfg_attr(test, derive(Debug))] +pub struct HostConfiguration { + /// The minimum frequency at which parachains can update their validation code. + pub validation_upgrade_frequency: BlockNumber, + /// The delay, in blocks, before a validation upgrade is applied. + pub validation_upgrade_delay: BlockNumber, + /// The acceptance period, in blocks. This is the amount of blocks after availability that validators + /// and fishermen have to perform secondary checks or issue reports. + pub acceptance_period: BlockNumber, + /// The maximum validation code size, in bytes. + pub max_code_size: u32, + /// The maximum head-data size, in bytes. + pub max_head_data_size: u32, + /// The amount of execution cores to dedicate to parathread execution. + pub parathread_cores: u32, + /// The number of retries that a parathread author has to submit their block. + pub parathread_retries: u32, + /// How often parachain groups should be rotated across parachains. + pub parachain_rotation_frequency: BlockNumber, + /// The availability period, in blocks, for parachains. This is the amount of blocks + /// after inclusion that validators have to make the block available and signal its availability to + /// the chain. Must be at least 1. + pub chain_availability_period: BlockNumber, + /// The availability period, in blocks, for parathreads. Same as the `chain_availability_period`, + /// but a differing timeout due to differing requirements. Must be at least 1. + pub thread_availability_period: BlockNumber, + /// The amount of blocks ahead to schedule parachains and parathreads. + pub scheduling_lookahead: u32, +} + +pub trait Trait: system::Trait { } + +decl_storage! { + trait Store for Module as Configuration { + /// The active configuration for the current session. + Config get(fn config) config(): HostConfiguration; + /// Pending configuration (if any) for the next session. + PendingConfig: Option>; + } +} + +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; + + /// Set the validation upgrade frequency. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_validation_upgrade_frequency(origin, new: T::BlockNumber) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.validation_upgrade_frequency, new) != new + }); + Ok(()) + } + + /// Set the validation upgrade delay. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_validation_upgrade_delay(origin, new: T::BlockNumber) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.validation_upgrade_delay, new) != new + }); + Ok(()) + } + + /// Set the acceptance period for an included candidate. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_acceptance_period(origin, new: T::BlockNumber) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.acceptance_period, new) != new + }); + Ok(()) + } + + /// Set the max validation code size for incoming upgrades. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_max_code_size(origin, new: u32) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.max_code_size, new) != new + }); + Ok(()) + } + + /// Set the max head data size for paras. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_max_head_data_size(origin, new: u32) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.max_head_data_size, new) != new + }); + Ok(()) + } + + /// Set the number of parathread execution cores. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_parathread_cores(origin, new: u32) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.parathread_cores, new) != new + }); + Ok(()) + } + + /// Set the number of retries for a particular parathread. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_parathread_retries(origin, new: u32) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.parathread_retries, new) != new + }); + Ok(()) + } + + + /// Set the parachain validator-group rotation frequency + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_parachain_rotation_frequency(origin, new: T::BlockNumber) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.parachain_rotation_frequency, new) != new + }); + Ok(()) + } + + /// Set the availability period for parachains. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_chain_availability_period(origin, new: T::BlockNumber) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.chain_availability_period, new) != new + }); + Ok(()) + } + + /// Set the availability period for parathreads. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_thread_availability_period(origin, new: T::BlockNumber) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.thread_availability_period, new) != new + }); + Ok(()) + } + + /// Set the scheduling lookahead, in expected number of blocks at peak throughput. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_scheduling_lookahead(origin, new: u32) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.scheduling_lookahead, new) != new + }); + Ok(()) + } + } +} + +impl Module { + /// Called by the initializer to initialize the configuration module. + pub(crate) fn initializer_initialize(_now: T::BlockNumber) -> Weight { + 0 + } + + /// 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(_validators: &[ValidatorId], _queued: &[ValidatorId]) { + if let Some(pending) = ::PendingConfig::take() { + ::Config::set(pending); + } + } + + fn update_config_member( + updater: impl FnOnce(&mut HostConfiguration) -> bool, + ) { + let pending = ::PendingConfig::get(); + let mut prev = pending.unwrap_or_else(Self::config); + + if updater(&mut prev) { + ::PendingConfig::set(Some(prev)); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{new_test_ext, Initializer, Configuration, Origin}; + + use frame_support::traits::{OnFinalize, OnInitialize}; + + #[test] + fn config_changes_on_session_boundary() { + new_test_ext(Default::default()).execute_with(|| { + let old_config = Configuration::config(); + let mut config = old_config.clone(); + config.validation_upgrade_delay = 100; + + assert!(old_config != config); + + ::PendingConfig::set(Some(config.clone())); + + Initializer::on_initialize(1); + + assert_eq!(Configuration::config(), old_config); + assert_eq!(::PendingConfig::get(), Some(config.clone())); + + Initializer::on_finalize(1); + + Configuration::initializer_on_new_session(&[], &[]); + + assert_eq!(Configuration::config(), config); + assert!(::PendingConfig::get().is_none()); + }) + } + + #[test] + fn setting_pending_config_members() { + new_test_ext(Default::default()).execute_with(|| { + let new_config = HostConfiguration { + validation_upgrade_frequency: 100, + validation_upgrade_delay: 10, + acceptance_period: 5, + max_code_size: 100_000, + max_head_data_size: 1_000, + parathread_cores: 2, + parathread_retries: 5, + parachain_rotation_frequency: 20, + chain_availability_period: 10, + thread_availability_period: 8, + scheduling_lookahead: 3, + }; + + assert!(::PendingConfig::get().is_none()); + + Configuration::set_validation_upgrade_frequency( + Origin::ROOT, new_config.validation_upgrade_frequency, + ).unwrap(); + Configuration::set_validation_upgrade_delay( + Origin::ROOT, new_config.validation_upgrade_delay, + ).unwrap(); + Configuration::set_acceptance_period( + Origin::ROOT, new_config.acceptance_period, + ).unwrap(); + Configuration::set_max_code_size( + Origin::ROOT, new_config.max_code_size, + ).unwrap(); + Configuration::set_max_head_data_size( + Origin::ROOT, new_config.max_head_data_size, + ).unwrap(); + Configuration::set_parathread_cores( + Origin::ROOT, new_config.parathread_cores, + ).unwrap(); + Configuration::set_parathread_retries( + Origin::ROOT, new_config.parathread_retries, + ).unwrap(); + Configuration::set_parachain_rotation_frequency( + Origin::ROOT, new_config.parachain_rotation_frequency, + ).unwrap(); + Configuration::set_chain_availability_period( + Origin::ROOT, new_config.chain_availability_period, + ).unwrap(); + Configuration::set_thread_availability_period( + Origin::ROOT, new_config.thread_availability_period, + ).unwrap(); + Configuration::set_scheduling_lookahead( + Origin::ROOT, new_config.scheduling_lookahead, + ).unwrap(); + + assert_eq!(::PendingConfig::get(), Some(new_config)); + }) + } + + #[test] + fn non_root_cannot_set_config() { + new_test_ext(Default::default()).execute_with(|| { + assert!(Configuration::set_validation_upgrade_delay(Origin::signed(1), 100).is_err()); + }); + } + + #[test] + fn setting_config_to_same_as_current_is_noop() { + new_test_ext(Default::default()).execute_with(|| { + Configuration::set_validation_upgrade_delay(Origin::ROOT, Default::default()).unwrap(); + assert!(::PendingConfig::get().is_none()) + }); + } +} diff --git a/runtime/parachains/src/inclusion.rs b/runtime/parachains/src/inclusion.rs new file mode 100644 index 000000000000..1f45de2df705 --- /dev/null +++ b/runtime/parachains/src/inclusion.rs @@ -0,0 +1,15 @@ +// 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 . diff --git a/runtime/parachains/src/initializer.rs b/runtime/parachains/src/initializer.rs new file mode 100644 index 000000000000..c942de4756c1 --- /dev/null +++ b/runtime/parachains/src/initializer.rs @@ -0,0 +1,154 @@ +// 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 . + +//! This module is responsible for maintaining a consistent initialization order for all other +//! parachains modules. It's also responsible for finalization and session change notifications. +//! +//! This module can throw fatal errors if session-change notifications are received after initialization. + +use sp_std::prelude::*; +use frame_support::weights::Weight; +use primitives::{ + parachain::{ValidatorId}, +}; +use frame_support::{ + decl_storage, decl_module, decl_error, +}; +use crate::{configuration, paras}; + +pub trait Trait: system::Trait + configuration::Trait + paras::Trait { } + +decl_storage! { + trait Store for Module as Initializer { + /// Whether the parachains modules have been initialized within this block. + /// + /// Semantically a bool, but this guarantees it should never hit the trie, + /// as this is cleared in `on_finalize` and Frame optimizes `None` values to be empty values. + /// + /// As a bool, `set(false)` and `remove()` both lead to the next `get()` being false, but one of + /// them writes to the trie and one does not. This confusion makes `Option<()>` more suitable for + /// the semantics of this variable. + HasInitialized: Option<()>; + } +} + +decl_error! { + pub enum Error for Module { } +} + +decl_module! { + /// The initializer module. + pub struct Module for enum Call where origin: ::Origin { + type Error = Error; + + fn on_initialize(now: T::BlockNumber) -> Weight { + // The other modules are initialized in this order: + // - Configuration + // - Paras + // - Scheduler + // - Inclusion + // - Validity + let total_weight = configuration::Module::::initializer_initialize(now) + + paras::Module::::initializer_initialize(now); + + HasInitialized::set(Some(())); + + total_weight + } + + fn on_finalize() { + paras::Module::::initializer_finalize(); + configuration::Module::::initializer_finalize(); + HasInitialized::take(); + } + } +} + +impl Module { + /// Should be called when a new session occurs. Forwards the session notification to all + /// wrapped modules. + /// + /// Panics if the modules have already been initialized. + fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued: I) + where I: Iterator + { + assert!(HasInitialized::get().is_none()); + + let validators: Vec<_> = validators.map(|(_, v)| v).collect(); + let queued: Vec<_> = queued.map(|(_, v)| v).collect(); + + configuration::Module::::initializer_on_new_session(&validators, &queued); + paras::Module::::initializer_on_new_session(&validators, &queued); + } +} + +impl sp_runtime::BoundToRuntimeAppPublic for Module { + type Public = ValidatorId; +} + +impl session::OneSessionHandler for Module { + type Key = ValidatorId; + + fn on_genesis_session<'a, I: 'a>(_validators: I) + where I: Iterator + { + + } + + fn on_new_session<'a, I: 'a>(changed: bool, validators: I, queued: I) + where I: Iterator + { + >::on_new_session(changed, validators, queued); + } + + fn on_disabled(_i: usize) { } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{new_test_ext, Initializer}; + + use frame_support::traits::{OnFinalize, OnInitialize}; + + #[test] + #[should_panic] + fn panics_if_session_changes_after_on_initialize() { + new_test_ext(Default::default()).execute_with(|| { + Initializer::on_initialize(1); + Initializer::on_new_session(false, Vec::new().into_iter(), Vec::new().into_iter()); + }); + } + + #[test] + fn sets_flag_on_initialize() { + new_test_ext(Default::default()).execute_with(|| { + Initializer::on_initialize(1); + + assert!(HasInitialized::get().is_some()); + }) + } + + #[test] + fn clears_flag_on_finalize() { + new_test_ext(Default::default()).execute_with(|| { + Initializer::on_initialize(1); + Initializer::on_finalize(1); + + assert!(HasInitialized::get().is_none()); + }) + } +} diff --git a/runtime/parachains/src/lib.rs b/runtime/parachains/src/lib.rs new file mode 100644 index 000000000000..74c237fd2213 --- /dev/null +++ b/runtime/parachains/src/lib.rs @@ -0,0 +1,31 @@ +// 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 . + +//! Runtime modules for parachains code. +//! +//! It is crucial to include all the modules from this crate in the runtime, in +//! particular the `Initializer` module, as it is responsible for initializing the state +//! of the other modules. + +mod configuration; +mod inclusion; +mod initializer; +mod paras; +mod scheduler; +mod validity; + +#[cfg(test)] +mod mock; diff --git a/runtime/parachains/src/mock.rs b/runtime/parachains/src/mock.rs new file mode 100644 index 000000000000..6f08545008a9 --- /dev/null +++ b/runtime/parachains/src/mock.rs @@ -0,0 +1,114 @@ +// 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 . + +//! Mocks for all the traits. + +use sp_io::TestExternalities; +use sp_core::{H256}; +use sp_runtime::{ + Perbill, + traits::{ + BlakeTwo256, IdentityLookup, + }, +}; +use primitives::{ + BlockNumber, + Header, +}; +use frame_support::{ + impl_outer_origin, impl_outer_dispatch, parameter_types, + weights::Weight, +}; + +/// A test runtime struct. +#[derive(Clone, Eq, PartialEq)] +pub struct Test; + +impl_outer_origin! { + pub enum Origin for Test { } +} + +impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + initializer::Initializer, + } +} + +parameter_types! { + pub const BlockHashCount: u32 = 250; + pub const MaximumBlockWeight: Weight = 4 * 1024 * 1024; + pub const MaximumBlockLength: u32 = 4 * 1024 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); +} + +impl system::Trait for Test { + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type MaximumExtrinsicWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); +} + +impl crate::initializer::Trait for Test { } + +impl crate::configuration::Trait for Test { } + +impl crate::paras::Trait for Test { } + +pub type System = system::Module; + +/// Mocked initializer. +pub type Initializer = crate::initializer::Module; + +/// Mocked configuration. +pub type Configuration = crate::configuration::Module; + +/// Mocked paras. +pub type Paras = crate::paras::Module; + +/// Create a new set of test externalities. +pub fn new_test_ext(state: GenesisConfig) -> TestExternalities { + let mut t = state.system.build_storage::().unwrap(); + state.configuration.assimilate_storage(&mut t).unwrap(); + state.paras.assimilate_storage(&mut t).unwrap(); + + t.into() +} + +#[derive(Default)] +pub struct GenesisConfig { + pub system: system::GenesisConfig, + pub configuration: crate::configuration::GenesisConfig, + pub paras: crate::paras::GenesisConfig, +} diff --git a/runtime/parachains/src/paras.rs b/runtime/parachains/src/paras.rs new file mode 100644 index 000000000000..51f76d6df774 --- /dev/null +++ b/runtime/parachains/src/paras.rs @@ -0,0 +1,1175 @@ +// 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::marker::PhantomData; +use sp_runtime::traits::One; +use primitives::{ + parachain::{ValidatorId, Id as ParaId, ValidationCode, HeadData}, +}; +use frame_support::{ + decl_storage, decl_module, decl_error, + traits::Get, + weights::Weight, +}; +use codec::{Encode, Decode}; +use crate::configuration; + +#[cfg(feature = "std")] +use serde::{Serialize, Deserialize}; + +pub trait Trait: system::Trait + configuration::Trait { } + +// 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(Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ParaGenesisArgs { + /// The initial head data to use. + genesis_head: HeadData, + /// The initial validation code to use. + validation_code: ValidationCode, + /// True if parachain, false if parathread. + 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; + /// The head-data of every registered para. + Heads get(fn parachain_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: 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: 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_unstable(); + 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(_validators: &[ValidatorId], _queued: &[ValidatorId]) { + 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); + } + + ::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); + } + } + + ::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::parachain_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. + #[allow(unused)] + 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. + #[allow(unused)] + pub(crate) fn schedule_para_cleanup(id: ParaId) -> 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) + } + } + }) + } + + /// 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. + #[allow(unused)] + 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. + #[allow(unused)] + pub(crate) fn note_new_head( + id: ParaId, + new_head: HeadData, + execution_context: T::BlockNumber, + ) -> Weight { + if let Some(expected_at) = ::FutureCodeUpgrades::get(&id) { + Heads::insert(&id, new_head); + + 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, 0) + } + } + + /// 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)) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use primitives::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(&[], &[]); + } + 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_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]); + + // 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]); + + run_to_block(3, Some(vec![3])); + + assert_eq!(Paras::parachains(), vec![c, b]); + assert_eq!(::UpcomingParas::get(), Vec::new()); + + 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 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())); + }); + } +} diff --git a/runtime/parachains/src/scheduler.rs b/runtime/parachains/src/scheduler.rs new file mode 100644 index 000000000000..1f45de2df705 --- /dev/null +++ b/runtime/parachains/src/scheduler.rs @@ -0,0 +1,15 @@ +// 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 . diff --git a/runtime/parachains/src/validity.rs b/runtime/parachains/src/validity.rs new file mode 100644 index 000000000000..1f45de2df705 --- /dev/null +++ b/runtime/parachains/src/validity.rs @@ -0,0 +1,15 @@ +// 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 .