diff --git a/Cargo.lock b/Cargo.lock index d374fa071f6e..258d786f380a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,34 +10,6 @@ dependencies = [ "regex", ] -[[package]] -name = "adder" -version = "0.7.29-pre1" -dependencies = [ - "dlmalloc", - "parity-scale-codec", - "polkadot-parachain", - "sp-io", - "substrate-wasm-builder-runner 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "tiny-keccak 1.5.0", -] - -[[package]] -name = "adder-collator" -version = "0.1.0" -dependencies = [ - "adder", - "futures 0.3.4", - "parity-scale-codec", - "parking_lot 0.10.0", - "polkadot-collator", - "polkadot-parachain", - "polkadot-primitives", - "sc-client", - "sc-client-api", - "sp-core", -] - [[package]] name = "adler32" version = "1.0.4" @@ -1767,13 +1739,6 @@ dependencies = [ "tokio-util", ] -[[package]] -name = "halt" -version = "0.7.29-pre1" -dependencies = [ - "substrate-wasm-builder-runner 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "hash-db" version = "0.15.2" @@ -4257,9 +4222,7 @@ dependencies = [ name = "polkadot-parachain" version = "0.7.29-pre1" dependencies = [ - "adder", "derive_more 0.99.3", - "halt", "log 0.4.8", "parity-scale-codec", "parking_lot 0.10.0", @@ -4272,7 +4235,6 @@ dependencies = [ "sp-runtime-interface", "sp-std", "sp-wasm-interface", - "tiny-keccak 1.5.0", ] [[package]] @@ -4280,7 +4242,6 @@ name = "polkadot-primitives" version = "0.7.29-pre1" dependencies = [ "bitvec", - "pallet-babe", "parity-scale-codec", "polkadot-parachain", "pretty_assertions", @@ -7412,6 +7373,65 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-parachain-adder" +version = "0.7.29-pre1" +dependencies = [ + "dlmalloc", + "parity-scale-codec", + "polkadot-parachain", + "sp-io", + "substrate-wasm-builder-runner 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tiny-keccak 1.5.0", +] + +[[package]] +name = "test-parachain-adder-collator" +version = "0.1.0" +dependencies = [ + "futures 0.3.4", + "parity-scale-codec", + "parking_lot 0.10.0", + "polkadot-collator", + "polkadot-parachain", + "polkadot-primitives", + "sc-client", + "sc-client-api", + "sp-core", + "test-parachain-adder", +] + +[[package]] +name = "test-parachain-code-upgrader" +version = "0.7.22" +dependencies = [ + "dlmalloc", + "parity-scale-codec", + "polkadot-parachain", + "sp-io", + "substrate-wasm-builder-runner 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tiny-keccak 1.5.0", +] + +[[package]] +name = "test-parachain-halt" +version = "0.7.29-pre1" +dependencies = [ + "substrate-wasm-builder-runner 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "test-parachains" +version = "0.7.22" +dependencies = [ + "parity-scale-codec", + "polkadot-parachain", + "test-parachain-adder", + "test-parachain-code-upgrader", + "test-parachain-halt", + "tiny-keccak 1.5.0", +] + [[package]] name = "textwrap" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index a83c19a0e649..2fe3d06ba2d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,13 +41,15 @@ members = [ "service", "validation", - "test-parachains/adder", - "test-parachains/adder/collator", + "parachain/test-parachains", + "parachain/test-parachains/adder", + "parachain/test-parachains/adder/collator", + "parachain/test-parachains/code-upgrader", ] exclude = [ "runtime/polkadot/wasm", "runtime/kusama/wasm", - "test-parachains/adder/wasm", + "parachain/test-parachains/adder/wasm", ] [badges] diff --git a/collator/src/lib.rs b/collator/src/lib.rs index 68d88137f0e3..9a5908f5cbb7 100644 --- a/collator/src/lib.rs +++ b/collator/src/lib.rs @@ -59,7 +59,7 @@ use polkadot_primitives::{ BlockId, Hash, Block, parachain::{ self, BlockData, DutyRoster, HeadData, Id as ParaId, - PoVBlock, ValidatorId, CollatorPair, LocalValidationData + PoVBlock, ValidatorId, CollatorPair, LocalValidationData, GlobalValidationSchedule, } }; use polkadot_cli::{ @@ -154,7 +154,8 @@ pub trait ParachainContext: Clone { fn produce_candidate( &mut self, relay_parent: Hash, - status: LocalValidationData, + global_validation: GlobalValidationSchedule, + local_validation: LocalValidationData, ) -> Self::ProduceCandidate; } @@ -162,6 +163,7 @@ pub trait ParachainContext: Clone { pub async fn collate

( relay_parent: Hash, local_id: ParaId, + global_validation: GlobalValidationSchedule, local_validation_data: LocalValidationData, mut para_context: P, key: Arc, @@ -173,6 +175,7 @@ pub async fn collate

( { let (block_data, head_data) = para_context.produce_candidate( relay_parent, + global_validation, local_validation_data, ).map_err(Error::Collator).await?; @@ -281,6 +284,7 @@ fn build_collator_service( let work = future::lazy(move |_| { let api = client.runtime_api(); + let global_validation = try_fr!(api.global_validation_schedule(&id)); let local_validation = match try_fr!(api.local_validation_data(&id, para_id)) { Some(local_validation) => local_validation, None => return future::Either::Left(future::ok(())), @@ -297,6 +301,7 @@ fn build_collator_service( let collation_work = collate( relay_parent, para_id, + global_validation, local_validation, parachain_context, key, @@ -427,6 +432,7 @@ mod tests { fn produce_candidate( &mut self, _relay_parent: Hash, + _global: GlobalValidationSchedule, _local_validation: LocalValidationData, ) -> Self::ProduceCandidate { // send messages right back. diff --git a/parachain/Cargo.toml b/parachain/Cargo.toml index e8d34d080b77..8cdebc52f1aa 100644 --- a/parachain/Cargo.toml +++ b/parachain/Cargo.toml @@ -6,13 +6,18 @@ description = "Types and utilities for creating and working with parachains" edition = "2018" [dependencies] -codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = [ "derive" ] } -derive_more = { version = "0.99.2", optional = true } -serde = { version = "1.0.102", default-features = false, features = [ "derive" ], optional = true } +# note: special care is taken to avoid inclusion of `sp-io` externals when compiling +# this crate for WASM. This is critical to avoid forcing all parachain WASM into implementing +# various unnecessary Substrate-specific endpoints. +codec = { package = "parity-scale-codec", version = "1.1.0", default-features = false, features = [ "derive" ] } sp-std = { package = "sp-std", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } -sp-runtime-interface = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-wasm-interface = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +# all optional crates. +derive_more = { version = "0.99.2", optional = true } +serde = { version = "1.0.102", default-features = false, features = [ "derive" ], optional = true } +sp-runtime-interface = { git = "https://github.com/paritytech/substrate", branch = "master", optional = true, default-features = false } sp-externalities = { git = "https://github.com/paritytech/substrate", branch = "master", optional = true } sc-executor = { git = "https://github.com/paritytech/substrate", branch = "master", optional = true } sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", optional = true } @@ -22,14 +27,9 @@ log = { version = "0.4.8", optional = true } [target.'cfg(not(target_os = "unknown"))'.dependencies] shared_memory = { version = "0.10.0", optional = true } -[dev-dependencies] -tiny-keccak = "1.5.0" -adder = { path = "../test-parachains/adder" } -halt = { path = "../test-parachains/halt" } - [features] default = ["std"] -wasm-api = [] +wasm-api = ["sp-runtime-interface"] std = [ "codec/std", "derive_more", @@ -39,6 +39,7 @@ std = [ "sp-core/std", "parking_lot", "log", + "sp-runtime-interface", "sp-runtime-interface/std", "sp-externalities", "sc-executor", diff --git a/parachain/src/lib.rs b/parachain/src/lib.rs index 2aeb59a34092..67a5a6839245 100644 --- a/parachain/src/lib.rs +++ b/parachain/src/lib.rs @@ -45,182 +45,9 @@ #[cfg(feature = "std")] pub mod wasm_executor; +pub mod primitives; mod wasm_api; -use sp_std::vec::Vec; - -use codec::{Encode, Decode, CompactAs}; -use sp_core::{RuntimeDebug, TypeId}; - #[cfg(all(not(feature = "std"), feature = "wasm-api"))] pub use wasm_api::*; - -/// Validation parameters for evaluating the parachain validity function. -// TODO: balance downloads (https://github.com/paritytech/polkadot/issues/220) -#[derive(PartialEq, Eq, Decode)] -#[cfg_attr(feature = "std", derive(Debug, Encode))] -pub struct ValidationParams { - /// The collation body. - pub block_data: Vec, - /// Previous head-data. - pub parent_head: Vec, -} - -/// The result of parachain validation. -// TODO: egress and balance uploads (https://github.com/paritytech/polkadot/issues/220) -#[derive(PartialEq, Eq, Encode)] -#[cfg_attr(feature = "std", derive(Debug, Decode))] -pub struct ValidationResult { - /// New head data that should be included in the relay chain state. - pub head_data: Vec, -} - -/// Unique identifier of a parachain. -#[derive( - Clone, CompactAs, Copy, Decode, Default, Encode, Eq, - Hash, Ord, PartialEq, PartialOrd, RuntimeDebug, -)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, derive_more::Display))] -pub struct Id(u32); - -impl TypeId for Id { - const TYPE_ID: [u8; 4] = *b"para"; -} - -/// Type for determining the active set of parachains. -pub trait ActiveThreads { - /// Return the current ordered set of `Id`s of active parathreads. - fn active_threads() -> Vec; -} - -impl From for u32 { - fn from(x: Id) -> Self { x.0 } -} - -impl From for Id { - fn from(x: u32) -> Self { Id(x) } -} - -const USER_INDEX_START: u32 = 1000; - -/// The ID of the first user (non-system) parachain. -pub const LOWEST_USER_ID: Id = Id(USER_INDEX_START); - -impl Id { - /// Create an `Id`. - pub const fn new(id: u32) -> Self { - Self(id) - } - - /// Returns `true` if this parachain runs with system-level privileges. - pub fn is_system(&self) -> bool { self.0 < USER_INDEX_START } -} - -impl sp_std::ops::Add for Id { - type Output = Self; - - fn add(self, other: u32) -> Self { - Self(self.0 + other) - } -} - -// TODO: Remove all of this, move sp-runtime::AccountIdConversion to own crate and and use that. -// #360 -struct TrailingZeroInput<'a>(&'a [u8]); -impl<'a> codec::Input for TrailingZeroInput<'a> { - fn remaining_len(&mut self) -> Result, codec::Error> { - Ok(None) - } - - fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> { - let len = into.len().min(self.0.len()); - into[..len].copy_from_slice(&self.0[..len]); - for i in &mut into[len..] { - *i = 0; - } - self.0 = &self.0[len..]; - Ok(()) - } -} - -/// This type can be converted into and possibly from an AccountId (which itself is generic). -pub trait AccountIdConversion: Sized { - /// Convert into an account ID. This is infallible. - fn into_account(&self) -> AccountId; - - /// Try to convert an account ID into this type. Might not succeed. - fn try_from_account(a: &AccountId) -> Option; -} - -/// Format is b"para" ++ encode(parachain ID) ++ 00.... where 00... is indefinite trailing -/// zeroes to fill AccountId. -impl AccountIdConversion for Id { - fn into_account(&self) -> T { - (b"para", self).using_encoded(|b| - T::decode(&mut TrailingZeroInput(b)) - ).unwrap_or_default() - } - - fn try_from_account(x: &T) -> Option { - x.using_encoded(|d| { - if &d[0..4] != b"para" { return None } - let mut cursor = &d[4..]; - let result = Decode::decode(&mut cursor).ok()?; - if cursor.iter().all(|x| *x == 0) { - Some(result) - } else { - None - } - }) - } -} - -/// Which origin a parachain's message to the relay chain should be dispatched from. -#[derive(Clone, PartialEq, Eq, Encode, Decode)] -#[cfg_attr(feature = "std", derive(Debug))] -#[repr(u8)] -pub enum ParachainDispatchOrigin { - /// As a simple `Origin::Signed`, using `ParaId::account_id` as its value. This is good when - /// interacting with standard modules such as `balances`. - Signed, - /// As the special `Origin::Parachain(ParaId)`. This is good when interacting with parachain- - /// aware modules which need to succinctly verify that the origin is a parachain. - Parachain, - /// As the simple, superuser `Origin::Root`. This can only be done on specially permissioned - /// parachains. - Root, -} - -impl sp_std::convert::TryFrom for ParachainDispatchOrigin { - type Error = (); - fn try_from(x: u8) -> core::result::Result { - const SIGNED: u8 = ParachainDispatchOrigin::Signed as u8; - const PARACHAIN: u8 = ParachainDispatchOrigin::Parachain as u8; - Ok(match x { - SIGNED => ParachainDispatchOrigin::Signed, - PARACHAIN => ParachainDispatchOrigin::Parachain, - _ => return Err(()), - }) - } -} - -/// A message from a parachain to its Relay Chain. -#[derive(Clone, PartialEq, Eq, Encode, Decode, sp_runtime_interface::pass_by::PassByCodec)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct UpwardMessage { - /// The origin for the message to be sent from. - pub origin: ParachainDispatchOrigin, - /// The message data. - pub data: Vec, -} - -/// An incoming message. -#[derive(PartialEq, Eq, Decode)] -#[cfg_attr(feature = "std", derive(Debug, Encode))] -pub struct IncomingMessage { - /// The source parachain. - pub source: Id, - /// The data of the message. - pub data: Vec, -} diff --git a/parachain/src/primitives.rs b/parachain/src/primitives.rs new file mode 100644 index 000000000000..ff32cc6395b3 --- /dev/null +++ b/parachain/src/primitives.rs @@ -0,0 +1,221 @@ +// 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 . + +//! Primitive types which are strictly necessary from a parachain-execution point +//! of view. + +use sp_std::vec::Vec; + +use codec::{Encode, Decode, CompactAs}; +use sp_core::{RuntimeDebug, TypeId}; + +#[cfg(feature = "std")] +use serde::{Serialize, Deserialize}; + +#[cfg(feature = "std")] +use sp_core::bytes; + +/// The block number of the relay chain. +/// 32-bits will allow for 136 years of blocks assuming 1 block per second. +pub type RelayChainBlockNumber = u32; + +/// Parachain head data included in the chain. +#[derive(PartialEq, Eq, Clone, PartialOrd, Ord, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug, Default))] +pub struct HeadData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +/// Parachain validation code. +#[derive(Default, PartialEq, Eq, Clone, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct ValidationCode(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +/// Parachain block data. +/// +/// Contains everything required to validate para-block, may contain block and witness data. +#[derive(PartialEq, Eq, Clone, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct BlockData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +/// Unique identifier of a parachain. +#[derive( + Clone, CompactAs, Copy, Decode, Default, Encode, Eq, + Hash, Ord, PartialEq, PartialOrd, RuntimeDebug, +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, derive_more::Display))] +pub struct Id(u32); + +impl TypeId for Id { + const TYPE_ID: [u8; 4] = *b"para"; +} + +impl From for u32 { + fn from(x: Id) -> Self { x.0 } +} + +impl From for Id { + fn from(x: u32) -> Self { Id(x) } +} + +const USER_INDEX_START: u32 = 1000; + +/// The ID of the first user (non-system) parachain. +pub const LOWEST_USER_ID: Id = Id(USER_INDEX_START); + +impl Id { + /// Create an `Id`. + pub const fn new(id: u32) -> Self { + Self(id) + } + + /// Returns `true` if this parachain runs with system-level privileges. + pub fn is_system(&self) -> bool { self.0 < USER_INDEX_START } +} + +impl sp_std::ops::Add for Id { + type Output = Self; + + fn add(self, other: u32) -> Self { + Self(self.0 + other) + } +} + +/// This type can be converted into and possibly from an AccountId (which itself is generic). +pub trait AccountIdConversion: Sized { + /// Convert into an account ID. This is infallible. + fn into_account(&self) -> AccountId; + + /// Try to convert an account ID into this type. Might not succeed. + fn try_from_account(a: &AccountId) -> Option; +} + +// TODO: Remove all of this, move sp-runtime::AccountIdConversion to own crate and and use that. +// #360 +struct TrailingZeroInput<'a>(&'a [u8]); +impl<'a> codec::Input for TrailingZeroInput<'a> { + fn remaining_len(&mut self) -> Result, codec::Error> { + Ok(None) + } + + fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> { + let len = into.len().min(self.0.len()); + into[..len].copy_from_slice(&self.0[..len]); + for i in &mut into[len..] { + *i = 0; + } + self.0 = &self.0[len..]; + Ok(()) + } +} + +/// Format is b"para" ++ encode(parachain ID) ++ 00.... where 00... is indefinite trailing +/// zeroes to fill AccountId. +impl AccountIdConversion for Id { + fn into_account(&self) -> T { + (b"para", self).using_encoded(|b| + T::decode(&mut TrailingZeroInput(b)) + ).unwrap_or_default() + } + + fn try_from_account(x: &T) -> Option { + x.using_encoded(|d| { + if &d[0..4] != b"para" { return None } + let mut cursor = &d[4..]; + let result = Decode::decode(&mut cursor).ok()?; + if cursor.iter().all(|x| *x == 0) { + Some(result) + } else { + None + } + }) + } +} + +/// Which origin a parachain's message to the relay chain should be dispatched from. +#[derive(Clone, PartialEq, Eq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Debug))] +#[repr(u8)] +pub enum ParachainDispatchOrigin { + /// As a simple `Origin::Signed`, using `ParaId::account_id` as its value. This is good when + /// interacting with standard modules such as `balances`. + Signed, + /// As the special `Origin::Parachain(ParaId)`. This is good when interacting with parachain- + /// aware modules which need to succinctly verify that the origin is a parachain. + Parachain, + /// As the simple, superuser `Origin::Root`. This can only be done on specially permissioned + /// parachains. + Root, +} + +impl sp_std::convert::TryFrom for ParachainDispatchOrigin { + type Error = (); + fn try_from(x: u8) -> core::result::Result { + const SIGNED: u8 = ParachainDispatchOrigin::Signed as u8; + const PARACHAIN: u8 = ParachainDispatchOrigin::Parachain as u8; + Ok(match x { + SIGNED => ParachainDispatchOrigin::Signed, + PARACHAIN => ParachainDispatchOrigin::Parachain, + _ => return Err(()), + }) + } +} + +/// A message from a parachain to its Relay Chain. +#[derive(Clone, PartialEq, Eq, Encode, Decode)] +#[cfg_attr( + any(feature = "std", feature = "wasm-api"), + derive(sp_runtime_interface::pass_by::PassByCodec, +))] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct UpwardMessage { + /// The origin for the message to be sent from. + pub origin: ParachainDispatchOrigin, + /// The message data. + pub data: Vec, +} + +/// Validation parameters for evaluating the parachain validity function. +// TODO: balance downloads (https://github.com/paritytech/polkadot/issues/220) +#[derive(PartialEq, Eq, Decode)] +#[cfg_attr(feature = "std", derive(Debug, Encode))] +pub struct ValidationParams { + /// The collation body. + pub block_data: BlockData, + /// Previous head-data. + pub parent_head: HeadData, + /// The maximum code size permitted, in bytes. + pub max_code_size: u32, + /// The maximum head-data size permitted, in bytes. + pub max_head_data_size: u32, + /// The current relay-chain block number. + pub relay_chain_height: RelayChainBlockNumber, + /// Whether a code upgrade is allowed or not, and at which height the upgrade + /// would be applied after, if so. The parachain logic should apply any upgrade + /// issued in this block after the first block + /// with `relay_chain_height` at least this value, if `Some`. if `None`, issue + /// no upgrade. + pub code_upgrade_allowed: Option, +} + +/// The result of parachain validation. +// TODO: egress and balance uploads (https://github.com/paritytech/polkadot/issues/220) +#[derive(PartialEq, Eq, Encode)] +#[cfg_attr(feature = "std", derive(Debug, Decode))] +pub struct ValidationResult { + /// New head data that should be included in the relay chain state. + pub head_data: HeadData, + /// An update to the validation code that should be scheduled in the relay chain. + pub new_validation_code: Option, +} diff --git a/parachain/src/wasm_api.rs b/parachain/src/wasm_api.rs index 4ab8449e899e..f5a82f57819c 100644 --- a/parachain/src/wasm_api.rs +++ b/parachain/src/wasm_api.rs @@ -16,7 +16,9 @@ //! Utilities for writing parachain WASM. -use crate::UpwardMessage; +#[cfg(any(feature = "std", all(not(feature = "std"), feature = "wasm-api")))] +use crate::primitives::UpwardMessage; +#[cfg(any(feature = "std", all(not(feature = "std"), feature = "wasm-api")))] use sp_runtime_interface::runtime_interface; #[cfg(feature = "std")] use sp_externalities::ExternalitiesExt; @@ -42,7 +44,9 @@ pub trait Parachain { /// Offset and length must have been provided by the validation /// function's entry point. #[cfg(not(feature = "std"))] -pub unsafe fn load_params(params: *const u8, len: usize) -> crate::ValidationParams { +pub unsafe fn load_params(params: *const u8, len: usize) + -> crate::primitives::ValidationParams +{ let mut slice = sp_std::slice::from_raw_parts(params, len); codec::Decode::decode(&mut slice).expect("Invalid input data") @@ -53,6 +57,6 @@ pub unsafe fn load_params(params: *const u8, len: usize) -> crate::ValidationPar /// As described in the crate docs, this is a pointer to the appended length /// of the vector. #[cfg(not(feature = "std"))] -pub fn write_result(result: &crate::ValidationResult) -> u64 { +pub fn write_result(result: &crate::primitives::ValidationResult) -> u64 { sp_core::to_substrate_wasm_fn_return_value(&result) } diff --git a/parachain/src/wasm_executor/mod.rs b/parachain/src/wasm_executor/mod.rs index 9b5369b0d867..883a9f6895a7 100644 --- a/parachain/src/wasm_executor/mod.rs +++ b/parachain/src/wasm_executor/mod.rs @@ -21,7 +21,7 @@ //! a WASM VM for re-execution of a parachain candidate. use std::any::{TypeId, Any}; -use crate::{ValidationParams, ValidationResult, UpwardMessage}; +use crate::primitives::{ValidationParams, ValidationResult, UpwardMessage}; use codec::{Decode, Encode}; use sp_core::storage::{ChildStorageKey, ChildInfo}; use sp_core::traits::CallInWasm; diff --git a/parachain/src/wasm_executor/validation_host.rs b/parachain/src/wasm_executor/validation_host.rs index 06829a6a0b08..84be3deeb4e8 100644 --- a/parachain/src/wasm_executor/validation_host.rs +++ b/parachain/src/wasm_executor/validation_host.rs @@ -18,7 +18,7 @@ use std::{process, env, sync::Arc, sync::atomic, mem}; use codec::{Decode, Encode, EncodeAppend}; -use crate::{ValidationParams, ValidationResult, UpwardMessage}; +use crate::primitives::{ValidationParams, ValidationResult, UpwardMessage}; use super::{validate_candidate_internal, Error, Externalities}; use super::{MAX_CODE_MEM, MAX_RUNTIME_MEM}; use shared_memory::{SharedMem, SharedMemConf, EventState, WriteLockable, EventWait, EventSet}; diff --git a/test-parachains/.gitignore b/parachain/test-parachains/.gitignore similarity index 100% rename from test-parachains/.gitignore rename to parachain/test-parachains/.gitignore diff --git a/parachain/test-parachains/Cargo.toml b/parachain/test-parachains/Cargo.toml new file mode 100644 index 000000000000..8b903b85734e --- /dev/null +++ b/parachain/test-parachains/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "test-parachains" +version = "0.7.22" +authors = ["Parity Technologies "] +description = "Integration tests using the test-parachains" +edition = "2018" + +[dependencies] +tiny-keccak = "1.5.0" +codec = { package = "parity-scale-codec", version = "1.1.0", default-features = false, features = ["derive"] } + +parachain = { package = "polkadot-parachain", path = ".." } +adder = { package = "test-parachain-adder", path = "adder" } +halt = { package = "test-parachain-halt", path = "halt" } +code-upgrader = { package = "test-parachain-code-upgrader", path = "code-upgrader" } + +[features] +default = [ "std" ] +std = [ + "adder/std", + "halt/std", + "code-upgrader/std", +] diff --git a/test-parachains/README.md b/parachain/test-parachains/README.md similarity index 100% rename from test-parachains/README.md rename to parachain/test-parachains/README.md diff --git a/test-parachains/adder/Cargo.toml b/parachain/test-parachains/adder/Cargo.toml similarity index 82% rename from test-parachains/adder/Cargo.toml rename to parachain/test-parachains/adder/Cargo.toml index eb064df1e72a..f90572493df9 100644 --- a/test-parachains/adder/Cargo.toml +++ b/parachain/test-parachains/adder/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "adder" +name = "test-parachain-adder" version = "0.7.29-pre1" authors = ["Parity Technologies "] description = "Test parachain which adds to a number as its state transition" @@ -7,7 +7,7 @@ edition = "2018" build = "build.rs" [dependencies] -parachain = { package = "polkadot-parachain", path = "../../parachain/", default-features = false, features = [ "wasm-api" ] } +parachain = { package = "polkadot-parachain", path = "../../", default-features = false, features = [ "wasm-api" ] } codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } tiny-keccak = "1.5.0" dlmalloc = { version = "0.1.3", features = [ "global" ] } @@ -20,4 +20,4 @@ wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "1. [features] default = [ "std" ] -std = [] +std = ["parachain/std"] diff --git a/test-parachains/adder/build.rs b/parachain/test-parachains/adder/build.rs similarity index 100% rename from test-parachains/adder/build.rs rename to parachain/test-parachains/adder/build.rs diff --git a/test-parachains/adder/collator/Cargo.toml b/parachain/test-parachains/adder/collator/Cargo.toml similarity index 69% rename from test-parachains/adder/collator/Cargo.toml rename to parachain/test-parachains/adder/collator/Cargo.toml index 699647033332..1d56b6924128 100644 --- a/test-parachains/adder/collator/Cargo.toml +++ b/parachain/test-parachains/adder/collator/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "adder-collator" +name = "test-parachain-adder-collator" version = "0.1.0" authors = ["Parity Technologies "] edition = "2018" [dependencies] -adder = { path = ".." } -parachain = { package = "polkadot-parachain", path = "../../../parachain" } -collator = { package = "polkadot-collator", path = "../../../collator" } -primitives = { package = "polkadot-primitives", path = "../../../primitives" } +adder = { package = "test-parachain-adder", path = ".." } +parachain = { package = "polkadot-parachain", path = "../../.." } +collator = { package = "polkadot-collator", path = "../../../../collator" } +primitives = { package = "polkadot-primitives", path = "../../../../primitives" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } client = { package = "sc-client", git = "https://github.com/paritytech/substrate", branch = "master" } client-api = { package = "sc-client-api", git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/test-parachains/adder/collator/src/main.rs b/parachain/test-parachains/adder/collator/src/main.rs similarity index 96% rename from test-parachains/adder/collator/src/main.rs rename to parachain/test-parachains/adder/collator/src/main.rs index 69772181382d..a0ea9c247cc0 100644 --- a/test-parachains/adder/collator/src/main.rs +++ b/parachain/test-parachains/adder/collator/src/main.rs @@ -24,7 +24,7 @@ use sp_core::Pair; use codec::{Encode, Decode}; use primitives::{ Hash, - parachain::{HeadData, BlockData, Id as ParaId, LocalValidationData}, + parachain::{HeadData, BlockData, Id as ParaId, LocalValidationData, GlobalValidationSchedule}, }; use collator::{ InvalidHead, ParachainContext, Network, BuildParachainContext, load_spec, Configuration, @@ -60,6 +60,7 @@ impl ParachainContext for AdderContext { fn produce_candidate( &mut self, _relay_parent: Hash, + _global_validation: GlobalValidationSchedule, local_validation: LocalValidationData, ) -> Self::ProduceCandidate { @@ -83,7 +84,7 @@ impl ParachainContext for AdderContext { add: adder_head.number % 100, }; - let next_head = ::adder::execute(adder_head.hash(), adder_head, &next_body) + let next_head = adder::execute(adder_head.hash(), adder_head, &next_body) .expect("good execution params; qed"); let encoded_head = HeadData(next_head.encode()); diff --git a/test-parachains/adder/src/lib.rs b/parachain/test-parachains/adder/src/lib.rs similarity index 83% rename from test-parachains/adder/src/lib.rs rename to parachain/test-parachains/adder/src/lib.rs index dfb828a1de31..d910eb0fc1af 100644 --- a/test-parachains/adder/src/lib.rs +++ b/parachain/test-parachains/adder/src/lib.rs @@ -63,27 +63,10 @@ pub fn hash_state(state: u64) -> [u8; 32] { tiny_keccak::keccak256(state.encode().as_slice()) } -#[derive(Default, Encode, Decode)] -pub struct AddMessage { - /// The amount to add based on this message. - pub amount: u64, -} - /// Start state mismatched with parent header's state hash. #[derive(Debug)] pub struct StateMismatch; -/// Process all incoming messages, yielding the amount of addition from messages. -/// -/// Ignores unknown message kinds. -pub fn process_messages(iterable: I) -> u64 - where I: IntoIterator, T: AsRef<[u8]> -{ - iterable.into_iter() - .filter_map(|data| AddMessage::decode(&mut data.as_ref()).ok()) - .fold(0u64, |a, c| a.overflowing_add(c.amount).0) -} - /// Execute a block body on top of given parent head, producing new parent head /// if valid. pub fn execute( diff --git a/test-parachains/adder/src/wasm_validation.rs b/parachain/test-parachains/adder/src/wasm_validation.rs similarity index 79% rename from test-parachains/adder/src/wasm_validation.rs rename to parachain/test-parachains/adder/src/wasm_validation.rs index d887003b68ad..eaa5101ba927 100644 --- a/test-parachains/adder/src/wasm_validation.rs +++ b/parachain/test-parachains/adder/src/wasm_validation.rs @@ -18,7 +18,7 @@ use crate::{HeadData, BlockData}; use core::{intrinsics, panic}; -use parachain::ValidationResult; +use parachain::primitives::{ValidationResult, HeadData as GenericHeadData}; use codec::{Encode, Decode}; #[panic_handler] @@ -40,17 +40,20 @@ pub fn oom(_: core::alloc::Layout) -> ! { #[no_mangle] pub extern fn validate_block(params: *const u8, len: usize) -> u64 { let params = unsafe { parachain::load_params(params, len) }; - let parent_head = HeadData::decode(&mut ¶ms.parent_head[..]) + let parent_head = HeadData::decode(&mut ¶ms.parent_head.0[..]) .expect("invalid parent head format."); - let block_data = BlockData::decode(&mut ¶ms.block_data[..]) + let block_data = BlockData::decode(&mut ¶ms.block_data.0[..]) .expect("invalid block data format."); - let parent_hash = tiny_keccak::keccak256(¶ms.parent_head[..]); + let parent_hash = tiny_keccak::keccak256(¶ms.parent_head.0[..]); match crate::execute(parent_hash, parent_head, &block_data) { Ok(new_head) => parachain::write_result( - &ValidationResult { head_data: new_head.encode() } + &ValidationResult { + head_data: GenericHeadData(new_head.encode()), + new_validation_code: None, + } ), Err(_) => panic!("execution failure"), } diff --git a/test-parachains/adder/wasm/Cargo.toml b/parachain/test-parachains/adder/wasm/Cargo.toml similarity index 100% rename from test-parachains/adder/wasm/Cargo.toml rename to parachain/test-parachains/adder/wasm/Cargo.toml diff --git a/parachain/test-parachains/code-upgrader/Cargo.toml b/parachain/test-parachains/code-upgrader/Cargo.toml new file mode 100644 index 000000000000..c475334ecfd3 --- /dev/null +++ b/parachain/test-parachains/code-upgrader/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "test-parachain-code-upgrader" +version = "0.7.22" +authors = ["Parity Technologies "] +description = "Test parachain which can upgrade code" +edition = "2018" +build = "build.rs" + +[dependencies] +parachain = { package = "polkadot-parachain", path = "../../", default-features = false, features = [ "wasm-api" ] } +codec = { package = "parity-scale-codec", version = "1.1.0", default-features = false, features = ["derive"] } +tiny-keccak = "1.5.0" +dlmalloc = { version = "0.1.3", features = [ "global" ] } + +# We need to make sure the global allocator is disabled until we have support of full substrate externalities +runtime-io = { package = "sp-io", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, features = [ "disable_allocator" ] } + +[build-dependencies] +wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "1.0.5" } + +[features] +default = [ "std" ] +std = ["parachain/std"] diff --git a/test-parachains/halt/build.rs b/parachain/test-parachains/code-upgrader/build.rs similarity index 100% rename from test-parachains/halt/build.rs rename to parachain/test-parachains/code-upgrader/build.rs diff --git a/parachain/test-parachains/code-upgrader/src/lib.rs b/parachain/test-parachains/code-upgrader/src/lib.rs new file mode 100644 index 000000000000..4a717af0084c --- /dev/null +++ b/parachain/test-parachains/code-upgrader/src/lib.rs @@ -0,0 +1,156 @@ +// 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 . + +//! Test parachain WASM which implements code ugprades. + +#![no_std] + +#![cfg_attr(not(feature = "std"), feature(core_intrinsics, lang_items, core_panic_info, alloc_error_handler))] + +use codec::{Encode, Decode}; +use parachain::primitives::{RelayChainBlockNumber, ValidationCode}; + +#[cfg(not(feature = "std"))] +mod wasm_validation; + +#[cfg(not(feature = "std"))] +#[global_allocator] +static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc; + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +#[derive(Encode, Decode, Clone, Default)] +pub struct State { + /// The current code that is "active" in this chain. + pub code: ValidationCode, + /// Code upgrade that is pending. + pub pending_code: Option<(ValidationCode, RelayChainBlockNumber)>, +} + +/// Head data for this parachain. +#[derive(Default, Clone, Hash, Eq, PartialEq, Encode, Decode)] +pub struct HeadData { + /// Block number + pub number: u64, + /// parent block keccak256 + pub parent_hash: [u8; 32], + /// hash of post-execution state. + pub post_state: [u8; 32], +} + +impl HeadData { + pub fn hash(&self) -> [u8; 32] { + tiny_keccak::keccak256(&self.encode()) + } +} + +/// Block data for this parachain. +#[derive(Default, Clone, Encode, Decode)] +pub struct BlockData { + /// State to begin from. + pub state: State, + /// Code to upgrade to. + pub new_validation_code: Option, +} + +pub fn hash_state(state: &State) -> [u8; 32] { + tiny_keccak::keccak256(state.encode().as_slice()) +} + +#[derive(Debug)] +pub enum Error { + /// Start state mismatched with parent header's state hash. + StateMismatch, + /// New validation code too large. + NewCodeTooLarge, + /// Code upgrades not allowed at this time. + CodeUpgradeDisallowed, +} + +pub struct ValidationResult { + /// The new head data. + pub head_data: HeadData, + /// The new validation code. + pub new_validation_code: Option, +} + +pub struct RelayChainParams { + /// Whether a code upgrade is allowed and at what relay-chain block number + /// to process it after. + pub code_upgrade_allowed: Option, + /// The maximum code size allowed for an upgrade. + pub max_code_size: u32, + /// The relay-chain block number. + pub relay_chain_block_number: RelayChainBlockNumber, +} + +/// Execute a block body on top of given parent head, producing new parent head +/// if valid. +pub fn execute( + parent_hash: [u8; 32], + parent_head: HeadData, + block_data: BlockData, + relay_params: &RelayChainParams, +) -> Result { + debug_assert_eq!(parent_hash, parent_head.hash()); + + if hash_state(&block_data.state) != parent_head.post_state { + return Err(Error::StateMismatch); + } + + let mut new_state = block_data.state; + + if let Some((pending_code, after)) = new_state.pending_code.take() { + if after <= relay_params.relay_chain_block_number { + // code applied. + new_state.code = pending_code; + } else { + // reinstate. + new_state.pending_code = Some((pending_code, after)); + } + } + + let new_validation_code = if let Some(ref new_validation_code) = block_data.new_validation_code { + if new_validation_code.0.len() as u32 > relay_params.max_code_size { + return Err(Error::NewCodeTooLarge); + } + + // replace the code if allowed and we don't have an upgrade pending. + match (new_state.pending_code.is_some(), relay_params.code_upgrade_allowed) { + (_, None) => return Err(Error::CodeUpgradeDisallowed), + (false, Some(after)) => { + new_state.pending_code = Some((new_validation_code.clone(), after)); + Some(new_validation_code.clone()) + } + (true, Some(_)) => None, + } + } else { + None + }; + + let head_data = HeadData { + number: parent_head.number + 1, + parent_hash, + post_state: hash_state(&new_state), + }; + + Ok(ValidationResult { + head_data, + new_validation_code: new_validation_code, + }) +} diff --git a/parachain/test-parachains/code-upgrader/src/wasm_validation.rs b/parachain/test-parachains/code-upgrader/src/wasm_validation.rs new file mode 100644 index 000000000000..8ebc3ae3c6f3 --- /dev/null +++ b/parachain/test-parachains/code-upgrader/src/wasm_validation.rs @@ -0,0 +1,71 @@ +// Copyright 2019-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 . + +//! WASM validation for adder parachain. + +use crate::{HeadData, BlockData, RelayChainParams}; +use core::{intrinsics, panic}; +use parachain::primitives::{ValidationResult, HeadData as GenericHeadData}; +use codec::{Encode, Decode}; + +#[panic_handler] +#[no_mangle] +pub fn panic(_info: &panic::PanicInfo) -> ! { + unsafe { + intrinsics::abort() + } +} + +#[alloc_error_handler] +#[no_mangle] +pub fn oom(_: core::alloc::Layout) -> ! { + unsafe { + intrinsics::abort(); + } +} + +#[no_mangle] +pub extern fn validate_block(params: *const u8, len: usize) -> u64 { + let params = unsafe { parachain::load_params(params, len) }; + let parent_head = HeadData::decode(&mut ¶ms.parent_head.0[..]) + .expect("invalid parent head format."); + + let block_data = BlockData::decode(&mut ¶ms.block_data.0[..]) + .expect("invalid block data format."); + + let parent_hash = tiny_keccak::keccak256(¶ms.parent_head.0[..]); + + let res = crate::execute( + parent_hash, + parent_head, + block_data, + &RelayChainParams { + code_upgrade_allowed: params.code_upgrade_allowed, + max_code_size: params.max_code_size, + relay_chain_block_number: params.relay_chain_height, + }, + ); + + match res { + Ok(output) => parachain::write_result( + &ValidationResult { + head_data: GenericHeadData(output.head_data.encode()), + new_validation_code: output.new_validation_code, + } + ), + Err(_) => panic!("execution failure"), + } +} diff --git a/parachain/test-parachains/code-upgrader/wasm/Cargo.toml b/parachain/test-parachains/code-upgrader/wasm/Cargo.toml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test-parachains/halt/Cargo.toml b/parachain/test-parachains/halt/Cargo.toml similarity index 92% rename from test-parachains/halt/Cargo.toml rename to parachain/test-parachains/halt/Cargo.toml index 314123dd86dc..39f087f34334 100644 --- a/test-parachains/halt/Cargo.toml +++ b/parachain/test-parachains/halt/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "halt" +name = "test-parachain-halt" version = "0.7.29-pre1" authors = ["Parity Technologies "] description = "Test parachain which executes forever" diff --git a/parachain/test-parachains/halt/build.rs b/parachain/test-parachains/halt/build.rs new file mode 100644 index 000000000000..19339843f51f --- /dev/null +++ b/parachain/test-parachains/halt/build.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Substrate 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. + +// Substrate 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 Substrate. If not, see . + +use wasm_builder_runner::WasmBuilder; + +fn main() { + WasmBuilder::new() + .with_current_project() + .with_wasm_builder_from_git("https://github.com/paritytech/substrate.git", "8c672e107789ed10973d937ba8cac245404377e2") + .export_heap_base() + .build() +} diff --git a/test-parachains/halt/src/lib.rs b/parachain/test-parachains/halt/src/lib.rs similarity index 100% rename from test-parachains/halt/src/lib.rs rename to parachain/test-parachains/halt/src/lib.rs diff --git a/parachain/test-parachains/src/lib.rs b/parachain/test-parachains/src/lib.rs new file mode 100644 index 000000000000..5220c2848612 --- /dev/null +++ b/parachain/test-parachains/src/lib.rs @@ -0,0 +1,17 @@ +// 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 . + +//! Stub - the fundamental logic of this crate is the integration tests. diff --git a/parachain/tests/adder/mod.rs b/parachain/test-parachains/tests/adder/mod.rs similarity index 76% rename from parachain/tests/adder/mod.rs rename to parachain/test-parachains/tests/adder/mod.rs index 7493a211cfd5..d97db998fd54 100644 --- a/parachain/tests/adder/mod.rs +++ b/parachain/test-parachains/tests/adder/mod.rs @@ -16,9 +16,16 @@ //! Basic parachain that adds a number as part of its state. -use polkadot_parachain as parachain; - -use crate::{DummyExt, parachain::ValidationParams}; +use crate::{ + DummyExt, + parachain, + parachain::primitives::{ + RelayChainBlockNumber, + BlockData as GenericBlockData, + HeadData as GenericHeadData, + ValidationParams, + }, +}; use codec::{Decode, Encode}; /// Head data for this parachain. @@ -41,12 +48,6 @@ struct BlockData { add: u64, } -#[derive(Encode, Decode)] -struct AddMessage { - /// amount to add. - amount: u64, -} - const TEST_CODE: &[u8] = adder::WASM_BINARY; fn hash_state(state: u64) -> [u8; 32] { @@ -75,14 +76,18 @@ pub fn execute_good_on_parent() { let ret = parachain::wasm_executor::validate_candidate( TEST_CODE, ValidationParams { - parent_head: parent_head.encode(), - block_data: block_data.encode(), + parent_head: GenericHeadData(parent_head.encode()), + block_data: GenericBlockData(block_data.encode()), + max_code_size: 1024, + max_head_data_size: 1024, + relay_chain_height: 1, + code_upgrade_allowed: None, }, DummyExt, parachain::wasm_executor::ExecutionMode::RemoteTest(&pool), ).unwrap(); - let new_head = HeadData::decode(&mut &ret.head_data[..]).unwrap(); + let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap(); assert_eq!(new_head.number, 1); assert_eq!(new_head.parent_hash, hash_head(&parent_head)); @@ -111,14 +116,18 @@ fn execute_good_chain_on_parent() { let ret = parachain::wasm_executor::validate_candidate( TEST_CODE, ValidationParams { - parent_head: parent_head.encode(), - block_data: block_data.encode(), + parent_head: GenericHeadData(parent_head.encode()), + block_data: GenericBlockData(block_data.encode()), + max_code_size: 1024, + max_head_data_size: 1024, + relay_chain_height: number as RelayChainBlockNumber + 1, + code_upgrade_allowed: None, }, DummyExt, parachain::wasm_executor::ExecutionMode::RemoteTest(&pool), ).unwrap(); - let new_head = HeadData::decode(&mut &ret.head_data[..]).unwrap(); + let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap(); assert_eq!(new_head.number, number + 1); assert_eq!(new_head.parent_hash, hash_head(&parent_head)); @@ -148,8 +157,12 @@ fn execute_bad_on_parent() { let _ret = parachain::wasm_executor::validate_candidate( TEST_CODE, ValidationParams { - parent_head: parent_head.encode(), - block_data: block_data.encode(), + parent_head: GenericHeadData(parent_head.encode()), + block_data: GenericBlockData(block_data.encode()), + max_code_size: 1024, + max_head_data_size: 1024, + relay_chain_height: 1, + code_upgrade_allowed: None, }, DummyExt, parachain::wasm_executor::ExecutionMode::RemoteTest(&pool), diff --git a/parachain/test-parachains/tests/code_upgrader/mod.rs b/parachain/test-parachains/tests/code_upgrader/mod.rs new file mode 100644 index 000000000000..c59e44fc122f --- /dev/null +++ b/parachain/test-parachains/tests/code_upgrader/mod.rs @@ -0,0 +1,224 @@ +// Copyright 2017-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 . + +//! Basic parachain that adds a number as part of its state. + +use parachain; + +use crate::{ + DummyExt, + parachain::primitives::{ + BlockData as GenericBlockData, + HeadData as GenericHeadData, + ValidationParams, ValidationCode, + }, +}; +use codec::{Decode, Encode}; +use code_upgrader::{hash_state, HeadData, BlockData, State}; + +const TEST_CODE: &[u8] = code_upgrader::WASM_BINARY; + +#[test] +pub fn execute_good_no_upgrade() { + let pool = parachain::wasm_executor::ValidationPool::new(); + + let parent_head = HeadData { + number: 0, + parent_hash: [0; 32], + post_state: hash_state(&State::default()), + }; + + let block_data = BlockData { + state: State::default(), + new_validation_code: None, + }; + + let ret = parachain::wasm_executor::validate_candidate( + TEST_CODE, + ValidationParams { + parent_head: GenericHeadData(parent_head.encode()), + block_data: GenericBlockData(block_data.encode()), + max_code_size: 1024, + max_head_data_size: 1024, + relay_chain_height: 1, + code_upgrade_allowed: None, + }, + DummyExt, + parachain::wasm_executor::ExecutionMode::RemoteTest(&pool), + ).unwrap(); + + let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap(); + + assert!(ret.new_validation_code.is_none()); + assert_eq!(new_head.number, 1); + assert_eq!(new_head.parent_hash, parent_head.hash()); + assert_eq!(new_head.post_state, hash_state(&State::default())); +} + +#[test] +pub fn execute_good_with_upgrade() { + let pool = parachain::wasm_executor::ValidationPool::new(); + + let parent_head = HeadData { + number: 0, + parent_hash: [0; 32], + post_state: hash_state(&State::default()), + }; + + let block_data = BlockData { + state: State::default(), + new_validation_code: Some(ValidationCode(vec![1, 2, 3])), + }; + + let ret = parachain::wasm_executor::validate_candidate( + TEST_CODE, + ValidationParams { + parent_head: GenericHeadData(parent_head.encode()), + block_data: GenericBlockData(block_data.encode()), + max_code_size: 1024, + max_head_data_size: 1024, + relay_chain_height: 1, + code_upgrade_allowed: Some(20), + }, + DummyExt, + parachain::wasm_executor::ExecutionMode::RemoteTest(&pool), + ).unwrap(); + + let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap(); + + assert_eq!(ret.new_validation_code.unwrap(), ValidationCode(vec![1, 2, 3])); + assert_eq!(new_head.number, 1); + assert_eq!(new_head.parent_hash, parent_head.hash()); + assert_eq!( + new_head.post_state, + hash_state(&State { + code: ValidationCode::default(), + pending_code: Some((ValidationCode(vec![1, 2, 3]), 20)), + }), + ); +} + +#[test] +#[should_panic] +pub fn code_upgrade_not_allowed() { + let pool = parachain::wasm_executor::ValidationPool::new(); + + let parent_head = HeadData { + number: 0, + parent_hash: [0; 32], + post_state: hash_state(&State::default()), + }; + + let block_data = BlockData { + state: State::default(), + new_validation_code: Some(ValidationCode(vec![1, 2, 3])), + }; + + parachain::wasm_executor::validate_candidate( + TEST_CODE, + ValidationParams { + parent_head: GenericHeadData(parent_head.encode()), + block_data: GenericBlockData(block_data.encode()), + max_code_size: 1024, + max_head_data_size: 1024, + relay_chain_height: 1, + code_upgrade_allowed: None, + }, + DummyExt, + parachain::wasm_executor::ExecutionMode::RemoteTest(&pool), + ).unwrap(); +} + +#[test] +pub fn applies_code_upgrade_after_delay() { + let pool = parachain::wasm_executor::ValidationPool::new(); + + let (new_head, state) = { + let parent_head = HeadData { + number: 0, + parent_hash: [0; 32], + post_state: hash_state(&State::default()), + }; + + let block_data = BlockData { + state: State::default(), + new_validation_code: Some(ValidationCode(vec![1, 2, 3])), + }; + + let ret = parachain::wasm_executor::validate_candidate( + TEST_CODE, + ValidationParams { + parent_head: GenericHeadData(parent_head.encode()), + block_data: GenericBlockData(block_data.encode()), + max_code_size: 1024, + max_head_data_size: 1024, + relay_chain_height: 1, + code_upgrade_allowed: Some(2), + }, + DummyExt, + parachain::wasm_executor::ExecutionMode::RemoteTest(&pool), + ).unwrap(); + + let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap(); + + let parent_hash = parent_head.hash(); + let state = State { + code: ValidationCode::default(), + pending_code: Some((ValidationCode(vec![1, 2, 3]), 2)), + }; + assert_eq!(ret.new_validation_code.unwrap(), ValidationCode(vec![1, 2, 3])); + assert_eq!(new_head.number, 1); + assert_eq!(new_head.parent_hash, parent_hash); + assert_eq!(new_head.post_state, hash_state(&state)); + + (new_head, state) + }; + + { + let parent_head = new_head; + let block_data = BlockData { + state, + new_validation_code: None, + }; + + let ret = parachain::wasm_executor::validate_candidate( + TEST_CODE, + ValidationParams { + parent_head: GenericHeadData(parent_head.encode()), + block_data: GenericBlockData(block_data.encode()), + max_code_size: 1024, + max_head_data_size: 1024, + relay_chain_height: 2, + code_upgrade_allowed: None, + }, + DummyExt, + parachain::wasm_executor::ExecutionMode::RemoteTest(&pool), + ).unwrap(); + + let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap(); + + assert!(ret.new_validation_code.is_none()); + assert_eq!(new_head.number, 2); + assert_eq!(new_head.parent_hash, parent_head.hash()); + assert_eq!( + new_head.post_state, + hash_state(&State { + code: ValidationCode(vec![1, 2, 3]), + pending_code: None, + }), + ); + } +} diff --git a/parachain/tests/lib.rs b/parachain/test-parachains/tests/lib.rs similarity index 91% rename from parachain/tests/lib.rs rename to parachain/test-parachains/tests/lib.rs index 762fba9d9196..cf6d63fd2acf 100644 --- a/parachain/tests/lib.rs +++ b/parachain/test-parachains/tests/lib.rs @@ -15,11 +15,11 @@ // along with Polkadot. If not, see . mod adder; +mod code_upgrader; mod wasm_executor; -use polkadot_parachain as parachain; -use crate::parachain::{ - UpwardMessage, wasm_executor::{Externalities, run_worker}, +use parachain::{ + self, primitives::UpwardMessage, wasm_executor::{Externalities, run_worker}, }; struct DummyExt; diff --git a/parachain/tests/wasm_executor/mod.rs b/parachain/test-parachains/tests/wasm_executor/mod.rs similarity index 80% rename from parachain/tests/wasm_executor/mod.rs rename to parachain/test-parachains/tests/wasm_executor/mod.rs index 92e657a2ff8c..c6cf2407b3af 100644 --- a/parachain/tests/wasm_executor/mod.rs +++ b/parachain/test-parachains/tests/wasm_executor/mod.rs @@ -16,9 +16,12 @@ //! Basic parachain that adds a number as part of its state. -use polkadot_parachain as parachain; +use parachain; use crate::{adder, DummyExt}; -use crate::parachain::{ValidationParams, wasm_executor::EXECUTION_TIMEOUT_SEC}; +use crate::parachain::{ + primitives::{BlockData, ValidationParams}, + wasm_executor::EXECUTION_TIMEOUT_SEC, +}; // Code that exposes `validate_block` and loops infinitely const INFINITE_LOOP_CODE: &[u8] = halt::WASM_BINARY; @@ -30,8 +33,12 @@ fn terminates_on_timeout() { let result = parachain::wasm_executor::validate_candidate( INFINITE_LOOP_CODE, ValidationParams { + block_data: BlockData(Vec::new()), parent_head: Default::default(), - block_data: Vec::new(), + max_code_size: 1024, + max_head_data_size: 1024, + relay_chain_height: 1, + code_upgrade_allowed: None, }, DummyExt, parachain::wasm_executor::ExecutionMode::RemoteTest(&pool), @@ -56,8 +63,12 @@ fn parallel_execution() { parachain::wasm_executor::validate_candidate( INFINITE_LOOP_CODE, ValidationParams { + block_data: BlockData(Vec::new()), parent_head: Default::default(), - block_data: Vec::new(), + max_code_size: 1024, + max_head_data_size: 1024, + relay_chain_height: 1, + code_upgrade_allowed: None, }, DummyExt, parachain::wasm_executor::ExecutionMode::RemoteTest(&pool2), @@ -65,8 +76,12 @@ fn parallel_execution() { let _ = parachain::wasm_executor::validate_candidate( INFINITE_LOOP_CODE, ValidationParams { + block_data: BlockData(Vec::new()), parent_head: Default::default(), - block_data: Vec::new(), + max_code_size: 1024, + max_head_data_size: 1024, + relay_chain_height: 1, + code_upgrade_allowed: None, }, DummyExt, parachain::wasm_executor::ExecutionMode::RemoteTest(&pool), diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 95086ec2c130..236a7200da50 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -18,7 +18,6 @@ runtime_primitives = { package = "sp-runtime", git = "https://github.com/parityt polkadot-parachain = { path = "../parachain", default-features = false } trie = { package = "sp-trie", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } bitvec = { version = "0.17.4", default-features = false, features = ["alloc"] } -babe = { package = "pallet-babe", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } [dev-dependencies] sp-serializer = { git = "https://github.com/paritytech/substrate", branch = "master" } @@ -39,5 +38,4 @@ std = [ "serde", "polkadot-parachain/std", "bitvec/std", - "babe/std" ] diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index b3c75fa97024..716b40edf9e2 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -28,8 +28,7 @@ pub mod parachain; pub use parity_scale_codec::Compact; /// An index to a block. -/// 32-bits will allow for 136 years of blocks assuming 1 block per second. -pub type BlockNumber = u32; +pub type BlockNumber = polkadot_parachain::primitives::RelayChainBlockNumber; /// An instant or duration in time. pub type Moment = u64; diff --git a/primitives/src/parachain.rs b/primitives/src/parachain.rs index db5562bb7222..187d06e33761 100644 --- a/primitives/src/parachain.rs +++ b/primitives/src/parachain.rs @@ -14,13 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Polkadot parachain types. +//! Primitives which are necessary for parachain execution from a relay-chain +//! perspective. use sp_std::prelude::*; use sp_std::cmp::Ordering; use parity_scale_codec::{Encode, Decode}; use bitvec::vec::BitVec; -use super::{Hash, Balance}; +use super::{Hash, Balance, BlockNumber}; #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; @@ -32,8 +33,9 @@ use runtime_primitives::traits::Block as BlockT; use inherents::InherentIdentifier; use application_crypto::KeyTypeId; -pub use polkadot_parachain::{ - Id, ParachainDispatchOrigin, LOWEST_USER_ID, UpwardMessage, +pub use polkadot_parachain::primitives::{ + Id, ParachainDispatchOrigin, LOWEST_USER_ID, UpwardMessage, HeadData, BlockData, + ValidationCode, }; /// The key type ID for a collator key. @@ -174,6 +176,8 @@ pub struct GlobalValidationSchedule { pub max_code_size: u32, /// The maximum head-data size permitted, in bytes. pub max_head_data_size: u32, + /// The relay-chain block number this is in the context of. + pub block_number: BlockNumber, } /// Extra data that is needed along with the other fields in a `CandidateReceipt` @@ -185,6 +189,18 @@ pub struct LocalValidationData { pub parent_head: HeadData, /// The balance of the parachain at the moment of validation. pub balance: Balance, + /// Whether the parachain is allowed to upgrade its validation code. + /// + /// This is `Some` if so, and contains the number of the minimum relay-chain + /// height at which the upgrade will be applied, if an upgrade is signaled + /// now. + /// + /// A parachain should enact its side of the upgrade at the end of the first + /// parablock executing in the context of a relay-chain block with at least this + /// height. This may be equal to the current perceived relay-chain block height, in + /// which case the code upgrade should be applied at the end of the signaling + /// block. + pub code_upgrade_allowed: Option, } /// Commitments made in a `CandidateReceipt`. Many of these are outputs of validation. @@ -197,6 +213,8 @@ pub struct CandidateCommitments { pub upward_messages: Vec, /// The root of a block's erasure encoding Merkle tree. pub erasure_root: Hash, + /// New validation code. + pub new_validation_code: Option>, } /// Get a collator signature payload on a relay-parent, block-data combo. @@ -532,13 +550,6 @@ pub struct AvailableData { // In the future, outgoing messages as well. } -/// Parachain block data. -/// -/// Contains everything required to validate para-block, may contain block and witness data. -#[derive(PartialEq, Eq, Clone, Encode, Decode)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] -pub struct BlockData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); - /// A chunk of erasure-encoded block data. #[derive(PartialEq, Eq, Clone, Encode, Decode, Default)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] @@ -551,29 +562,11 @@ pub struct ErasureChunk { pub proof: Vec>, } -impl BlockData { - /// Compute hash of block data. - #[cfg(feature = "std")] - pub fn hash(&self) -> Hash { - use runtime_primitives::traits::{BlakeTwo256, Hash}; - BlakeTwo256::hash(&self.0[..]) - } -} /// Parachain header raw bytes wrapper type. #[derive(PartialEq, Eq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] pub struct Header(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); -/// Parachain head data included in the chain. -#[derive(PartialEq, Eq, Clone, PartialOrd, Ord, Encode, Decode)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug, Default))] -pub struct HeadData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); - -/// Parachain validation code. -#[derive(PartialEq, Eq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] -pub struct ValidationCode(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); - /// Activity bit field. #[derive(PartialEq, Eq, Clone, Default, Encode, Decode)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] @@ -648,13 +641,13 @@ impl AttestedCandidate { pub struct FeeSchedule { /// The base fee charged for all messages. pub base: Balance, - /// The per-byte fee charged on top of that. + /// The per-byte fee for messages charged on top of that. pub per_byte: Balance, } impl FeeSchedule { /// Compute the fee for a message of given size. - pub fn compute_fee(&self, n_bytes: usize) -> Balance { + pub fn compute_message_fee(&self, n_bytes: usize) -> Balance { use sp_std::mem; debug_assert!(mem::size_of::() >= mem::size_of::()); diff --git a/runtime/common/src/crowdfund.rs b/runtime/common/src/crowdfund.rs index 165923452cca..2e728d46e689 100644 --- a/runtime/common/src/crowdfund.rs +++ b/runtime/common/src/crowdfund.rs @@ -582,7 +582,7 @@ mod tests { }; use frame_support::traits::Contains; use sp_core::H256; - use primitives::parachain::{Info as ParaInfo, Id as ParaId}; + use primitives::parachain::{Info as ParaInfo, Id as ParaId, Scheduling}; // The testing primitives are very useful for avoiding having to work with signatures // or public keys. `u64` is used as the `AccountId` and no `Signature`s are requried. use sp_runtime::{ @@ -698,6 +698,10 @@ mod tests { code_size <= MAX_CODE_SIZE } + fn para_info(_id: ParaId) -> Option { + Some(ParaInfo { scheduling: Scheduling::Always }) + } + fn register_para( id: ParaId, _info: ParaInfo, diff --git a/runtime/common/src/parachains.rs b/runtime/common/src/parachains.rs index 11f1ac5e605a..8615e90788cd 100644 --- a/runtime/common/src/parachains.rs +++ b/runtime/common/src/parachains.rs @@ -23,7 +23,7 @@ use codec::{Decode, Encode}; use sp_runtime::{ KeyTypeId, Perbill, RuntimeDebug, traits::{ - Hash as HashT, BlakeTwo256, Saturating, One, Dispatchable, + Hash as HashT, BlakeTwo256, Saturating, One, Zero, Dispatchable, AccountIdConversion, BadOrigin, Convert, SignedExtension, AppVerify, }, transaction_validity::{TransactionValidityError, ValidTransaction, TransactionValidity}, @@ -39,11 +39,12 @@ use frame_support::{ }; use primitives::{ Balance, + BlockNumber, parachain::{ - self, Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, ParachainDispatchOrigin, + Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, ParachainDispatchOrigin, UpwardMessage, ValidatorId, ActiveParas, CollatorId, Retriable, OmittedValidationData, CandidateReceipt, GlobalValidationSchedule, AbridgedCandidateReceipt, - LocalValidationData, ValidityAttestation, NEW_HEADS_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, + LocalValidationData, Scheduling, ValidityAttestation, NEW_HEADS_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, ValidatorSignature, SigningContext, }, }; @@ -225,6 +226,11 @@ pub trait Trait: attestations::Trait + session::historical::Trait { /// Some way of interacting with balances for fees. type ParachainCurrency: ParachainCurrency; + /// Polkadot in practice will always use the `BlockNumber` type. + /// Substrate isn't good at giving us ways to bound the supertrait + /// associated type, so we introduce this conversion. + type BlockNumberConversion: Convert; + /// Something that provides randomness in the runtime. type Randomness: Randomness; @@ -241,6 +247,21 @@ pub trait Trait: attestations::Trait + session::historical::Trait { /// Max head data size. type MaxHeadDataSize: Get; + /// The frequency at which paras can upgrade their validation function. + /// This is an integer number of relay-chain blocks that must pass between + /// code upgrades. + type ValidationUpgradeFrequency: Get; + + /// The delay before a validation function upgrade is applied. + type ValidationUpgradeDelay: Get; + + /// The period (in blocks) that slash reports are permitted against an + /// included candidate. + /// + /// After validation function upgrades, the old code is persisted on-chain + /// for this period, to ensure that candidates validated under old functions + /// can be re-checked. + type SlashPeriod: Get; /// Proof type. /// @@ -263,7 +284,7 @@ pub trait Trait: attestations::Trait + session::historical::Trait { type ReportOffence: ReportOffence< Self::AccountId, Self::IdentificationTuple, - DoubleVoteOffence + DoubleVoteOffence, >; /// A type that converts the opaque hash type to exact one. @@ -323,13 +344,121 @@ const MAX_QUEUE_COUNT: usize = 100; /// single message. const WATERMARK_QUEUE_SIZE: usize = 20000; +/// 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 replaced. These can be used as indices + // into the `PastCode` map along with the `ParaId` to fetch the code itself. + upgrade_times: Vec, + // This tracks the highest pruned code-replacement, if any. + 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. + ReplacedAt(N), +} + +impl ParaPastCodeMeta { + // note a replacement has occurred at a given block number. + fn note_replacement(&mut self, at: N) { + self.upgrade_times.insert(0, at) + } + + // Yields the block number of the code that should be used for validating at + // the given 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. + fn code_at(&self, at: N) -> Option> { + // The `PastCode` map stores the code which was replaced at `t`. + let end_position = self.upgrade_times.iter().position(|&t| t < at); + if let Some(end_position) = end_position { + Some(if end_position != 0 { + // `end_position` gives us the replacement time where the code used at `at` + // was set. But that code has been replaced: `end_position - 1` yields + // that index. + UseCodeAt::ReplacedAt(self.upgrade_times[end_position - 1]) + } else { + // the most recent tracked replacement is before `at`. + // this means that the code put in place then (i.e. the current code) + // is correct for validating at `at`. + UseCodeAt::Current + }) + } else { + if self.last_pruned.as_ref().map_or(true, |&n| n < at) { + // Our `last_pruned` is before `at`, so we still have the code! + // but no code upgrade entries found before the `at` parameter. + // + // this means one of two things is true: + // 1. there are no non-pruned upgrade logs. in this case use `Current` + // 2. there are non-pruned upgrade logs all after `at`. + // in this case use the oldest upgrade log. + Some(self.upgrade_times.last() + .map(|n| UseCodeAt::ReplacedAt(*n)) + .unwrap_or(UseCodeAt::Current) + ) + } else { + // We don't have the code anymore. + None + } + } + } + + // The block at which the most recently tracked code change occurred. + fn most_recent_change(&self) -> Option { + self.upgrade_times.first().map(|x| x.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. + // + // 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 + '_ { + match self.upgrade_times.iter().position(|&t| t <= max) { + None => { + // this is a no-op `drain` - desired because all + // logged code upgrades occurred after `max`. + self.upgrade_times.drain(self.upgrade_times.len()..).rev() + } + Some(pos) => { + self.last_pruned = Some(self.upgrade_times[pos]); + self.upgrade_times.drain(pos..).rev() + } + } + } +} + decl_storage! { trait Store for Module as Parachains { /// All authorities' keys at the moment. pub Authorities get(fn authorities): Vec; - /// The parachains registered at present. + /// The active code of a currently-registered parachain. pub Code get(fn parachain_code): map hasher(twox_64_concat) ParaId => 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 assist with availability. + PastCodeMeta get(fn past_code_meta): map hasher(twox_64_concat) ParaId => ParaPastCodeMeta; + /// Actual past code, indicated by the parachain and the block number at which it + /// became outdated. + PastCode: map hasher(twox_64_concat) (ParaId, T::BlockNumber) => Option>; + /// Past code pruning, in order of priority. + PastCodePruning get(fn past_code_pruning_tasks): 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 code_upgrade_schedule): map hasher(twox_64_concat) ParaId => Option; + // The actual future code of a para. + FutureCode: map hasher(twox_64_concat) ParaId => Vec; + /// The heads of the parachains registered at present. pub Heads get(fn parachain_head): map hasher(twox_64_concat) ParaId => Option>; /// Messages ready to be dispatched onto the relay chain. It is subject to @@ -386,6 +515,10 @@ decl_error! { ParentMismatch, /// Head data was too large. HeadDataTooLarge, + /// New validation code was too large. + ValidationCodeTooLarge, + /// Disallowed code upgrade. + DisallowedCodeUpgrade, /// Para does not have enough balance to pay fees. CannotPayFees, /// Unexpected relay-parent for a candidate receipt. @@ -398,6 +531,19 @@ decl_module! { pub struct Module for enum Call where origin: ::Origin { type Error = Error; + fn on_initialize(now: T::BlockNumber) -> Weight { + ::DidUpdate::kill(); + + Self::do_old_code_pruning(now); + + // TODO https://github.com/paritytech/polkadot/issues/977: set correctly + SimpleDispatchInfo::default().weigh_data(()) + } + + fn on_finalize() { + assert!(::DidUpdate::exists(), "Parachain heads must be updated once in the block"); + } + /// Provide candidate receipts for parachains, in ascending order by id. #[weight = SimpleDispatchInfo::FixedNormal(1_000_000)] pub fn set_heads(origin, heads: Vec) -> DispatchResult { @@ -411,10 +557,7 @@ decl_module! { let mut proceeded = Vec::with_capacity(heads.len()); - let schedule = GlobalValidationSchedule { - max_code_size: T::MaxCodeSize::get(), - max_head_data_size: T::MaxHeadDataSize::get(), - }; + let schedule = Self::global_validation_schedule(); if !active_parachains.is_empty() { // perform integrity checks before writing to storage. @@ -517,16 +660,6 @@ decl_module! { Ok(()) } - - fn on_initialize() -> Weight { - ::DidUpdate::kill(); - - SimpleDispatchInfo::default().weigh_data(()) - } - - fn on_finalize() { - assert!(::DidUpdate::exists(), "Parachain heads must be updated once in the block"); - } } } @@ -554,11 +687,89 @@ impl Module { ::insert(id, initial_head_data); } + /// Cleanup all storage related to a para. Some pieces of data may remain + /// available in the on-chain state. pub fn cleanup_para( id: ParaId, ) { - ::remove(id); + let code = ::take(id); ::remove(id); + + // clean up from all code-upgrade maps. + // we don't clean up the meta or planned-code maps as that's handled + // by the pruning process. + if let Some(_planned_future_at) = ::FutureCodeUpgrades::take(&id) { + ::FutureCode::remove(&id); + } + + if let Some(code) = code { + Self::note_past_code(id, >::block_number(), 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, old_code: Vec) { + ::PastCodeMeta::mutate(&id, |past_meta| { + past_meta.note_replacement(at); + }); + + ::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, at)); + }) + } + + // does old code pruning. + fn do_old_code_pruning(now: T::BlockNumber) { + let slash_period = T::SlashPeriod::get(); + if now <= slash_period { return } + + // The height of any changes we no longer should keep around. + let pruning_height = now - (slash_period + One::one()); + + ::PastCodePruning::mutate(|pruning_tasks: &mut Vec<(_, T::BlockNumber)>| { + let 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(); + 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); + } + } + }); + } + + // Performs a code upgrade of a parachain. + fn do_code_upgrade(id: ParaId, at: T::BlockNumber, new_code: &[u8]) { + let old_code = Self::parachain_code(&id).unwrap_or_default(); + Code::insert(&id, new_code); + + Self::note_past_code(id, at, old_code); } /// Get a `SigningContext` with a current `SessionIndex` and parent hash. @@ -791,17 +1002,81 @@ impl Module { /// Get the global validation schedule for all parachains. pub fn global_validation_schedule() -> GlobalValidationSchedule { + let now = >::block_number(); GlobalValidationSchedule { max_code_size: T::MaxCodeSize::get(), max_head_data_size: T::MaxHeadDataSize::get(), + block_number: T::BlockNumberConversion::convert(if now.is_zero() { + now + } else { + // parablocks included in this block will execute in the context + // of the current block's parent. + now - One::one() + }), } } /// Get the local validation schedule for a particular parachain. - pub fn local_validation_data(id: ¶chain::Id) -> Option { + pub fn local_validation_data(id: &ParaId, perceived_height: T::BlockNumber) -> Option { + if perceived_height + One::one() != >::block_number() { + // sanity-check - no non-direct-parent blocks allowed at the moment. + return None + } + + let code_upgrade_allowed: Option = (|| { + match T::Registrar::para_info(*id)?.scheduling { + Scheduling::Always => {}, + Scheduling::Dynamic => return None, // parathreads can't upgrade code. + } + + // if perceived-height were not the parent of `now`, then this should + // not be drawn from current-runtime configuration. however the sanity-check + // above prevents that. + let min_upgrade_frequency = T::ValidationUpgradeFrequency::get(); + let upgrade_delay = T::ValidationUpgradeDelay::get(); + + let no_planned = Self::code_upgrade_schedule(id) + .map_or(true, |expected: T::BlockNumber| expected <= perceived_height); + + let can_upgrade_code = no_planned && + Self::past_code_meta(id).most_recent_change() + .map_or(true, |at| at + min_upgrade_frequency < perceived_height); + + if can_upgrade_code { + let applied_at = perceived_height + upgrade_delay; + Some(T::BlockNumberConversion::convert(applied_at)) + } else { + None + } + })(); + Self::parachain_head(id).map(|parent_head| LocalValidationData { parent_head: primitives::parachain::HeadData(parent_head), balance: T::ParachainCurrency::free_balance(*id), + code_upgrade_allowed, + }) + } + + /// Get the local validation data for a particular parent w.r.t. the current + /// block height. + pub fn current_local_validation_data(id: &ParaId) -> Option { + let now: T::BlockNumber = >::block_number(); + if now >= One::one() { + Self::local_validation_data(id, now - One::one()) + } else { + None + } + } + + /// Fetch the code used for verifying a parachain at a particular height. + pub fn parachain_code_at(id: &ParaId, at: T::BlockNumber) -> Option> { + // note - we don't check that the parachain is currently registered + // as this might be a deregistered parachain whose old code should still + // stick around on-chain for some time. + Self::past_code_meta(id).code_at(at).and_then(|to_use| match to_use { + UseCodeAt::Current => Self::parachain_code(id), + UseCodeAt::ReplacedAt(replaced_at) => + ::PastCode::get(&(*id, replaced_at)), }) } @@ -884,23 +1159,22 @@ impl Module { }; // computes the omitted validation data for a particular parachain. - let full_candidate = |abridged: &AbridgedCandidateReceipt| + // + // pass the perceived relay chain height of the para-block. This is the block number of + // `abridged.relay_parent`. + let full_candidate = | + abridged: &AbridgedCandidateReceipt, + perceived_height: T::BlockNumber, + | -> sp_std::result::Result { let para_id = abridged.parachain_index; - let parent_head = match Self::parachain_head(¶_id) - .map(primitives::parachain::HeadData) - { - Some(p) => p, - None => Err(Error::::ParentMismatch)?, - }; + let local_validation = Self::local_validation_data(¶_id, perceived_height) + .ok_or(Error::::ParentMismatch)?; let omitted = OmittedValidationData { global_validation: schedule.clone(), - local_validation: LocalValidationData { - parent_head, - balance: T::ParachainCurrency::free_balance(para_id), - }, + local_validation, }; Ok(abridged.clone().complete(omitted)) @@ -908,9 +1182,11 @@ impl Module { let sorted_validators = make_sorted_duties(&duty_roster.validator_duty); + let relay_height_now = >::block_number(); let parent_hash = >::parent_hash(); let signing_context = Self::signing_context(); let localized_payload = |statement: Statement| localized_payload(statement, &signing_context); + let code_upgrade_delay = T::ValidationUpgradeDelay::get(); let mut validator_groups = GroupedDutyIter::new(&sorted_validators[..]); @@ -927,11 +1203,15 @@ impl Module { // orphaned before a block containing C is finalized. Care must be taken // not to prune the data for C simply because an orphaned block contained // it. + ensure!( candidate.candidate().relay_parent.as_ref() == parent_hash.as_ref(), Error::::UnexpectedRelayParent, ); + // Since we only allow execution in context of parent hash. + let perceived_relay_block_height = >::block_number() - One::one(); + ensure!( candidate.validity_votes.len() >= majority_of(validator_group.len()), Error::::NotEnoughValidityVotes, @@ -947,7 +1227,45 @@ impl Module { Error::::HeadDataTooLarge, ); - let full_candidate = full_candidate(candidate.candidate())?; + let full_candidate = full_candidate( + candidate.candidate(), + perceived_relay_block_height, + )?; + + // apply any scheduled code upgrade. + if let Some(expected_at) = Self::code_upgrade_schedule(¶_id) { + if expected_at <= perceived_relay_block_height { + let new_code = FutureCode::take(¶_id); + ::FutureCodeUpgrades::remove(¶_id); + + Self::do_code_upgrade(para_id, perceived_relay_block_height, &new_code); + } + } + + if let Some(ref new_code) = full_candidate.commitments.new_validation_code { + ensure!( + full_candidate.local_validation.code_upgrade_allowed.is_some(), + Error::::DisallowedCodeUpgrade, + ); + ensure!( + schedule.max_code_size >= new_code.len() as u32, + Error::::ValidationCodeTooLarge, + ); + + if code_upgrade_delay.is_zero() { + Self::do_code_upgrade(para_id, perceived_relay_block_height, new_code); + } else { + ::FutureCodeUpgrades::insert( + ¶_id, + &(perceived_relay_block_height + code_upgrade_delay), + ); + FutureCode::insert( + ¶_id, + new_code, + ); + } + } + let fees = full_candidate.commitments.fees; ensure!( @@ -1012,7 +1330,7 @@ impl Module { } Ok(IncludedBlocks { - actual_number: >::block_number(), + actual_number: relay_height_now, session: >::current_index(), random_seed, active_parachains: active_parachains.iter().map(|x| x.0).collect(), @@ -1201,7 +1519,7 @@ mod tests { use sp_trie::NodeCodec; use sp_runtime::{ impl_opaque_keys, - Perbill, curve::PiecewiseLinear, testing::Header, + Perbill, curve::PiecewiseLinear, traits::{ BlakeTwo256, IdentityLookup, SaturatedConversion, OpaqueKeys, @@ -1210,10 +1528,11 @@ mod tests { }; use primitives::{ parachain::{ - CandidateReceipt, HeadData, ValidityAttestation, ValidatorId, Info as ParaInfo, - Scheduling, LocalValidationData, CandidateCommitments, + CandidateReceipt, ValidityAttestation, ValidatorId, Info as ParaInfo, + Scheduling, CandidateCommitments, HeadData, }, BlockNumber, + Header, }; use keyring::Sr25519Keyring; use frame_support::{ @@ -1264,7 +1583,7 @@ mod tests { type Origin = Origin; type Call = Call; type Index = u64; - type BlockNumber = u64; + type BlockNumber = BlockNumber; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; @@ -1337,7 +1656,7 @@ mod tests { const HOURS: BlockNumber = MINUTES * 60; } parameter_types! { - pub const EpochDuration: u64 = time::EPOCH_DURATION_IN_BLOCKS as u64; + pub const EpochDuration: BlockNumber = time::EPOCH_DURATION_IN_BLOCKS; pub const ExpectedBlockTime: u64 = time::MILLISECS_PER_BLOCK; } @@ -1363,7 +1682,7 @@ mod tests { pallet_staking_reward_curve::build! { const REWARD_CURVE: PiecewiseLinear<'static> = curve!( - min_inflation: 0_025_000, + min_inflation: 0_025_000u64, max_inflation: 0_100_000, ideal_stake: 0_500_000, falloff: 0_050_000, @@ -1420,8 +1739,8 @@ mod tests { } parameter_types!{ - pub const LeasePeriod: u64 = 10; - pub const EndingPeriod: u64 = 3; + pub const LeasePeriod: BlockNumber = 10; + pub const EndingPeriod: BlockNumber = 3; } impl slots::Trait for Test { @@ -1458,17 +1777,25 @@ mod tests { parameter_types! { pub const MaxHeadDataSize: u32 = 100; pub const MaxCodeSize: u32 = 100; + + pub const ValidationUpgradeFrequency: BlockNumber = 10; + pub const ValidationUpgradeDelay: BlockNumber = 2; + pub const SlashPeriod: BlockNumber = 50; } impl Trait for Test { type Origin = Origin; type Call = Call; type ParachainCurrency = Balances; + type BlockNumberConversion = sp_runtime::traits::Identity; type Randomness = RandomnessCollectiveFlip; type ActiveParachains = registrar::Module; type Registrar = registrar::Module; type MaxCodeSize = MaxCodeSize; type MaxHeadDataSize = MaxHeadDataSize; + type ValidationUpgradeFrequency = ValidationUpgradeFrequency; + type ValidationUpgradeDelay = ValidationUpgradeDelay; + type SlashPeriod = SlashPeriod; type Proof = >::Proof; type IdentificationTuple = @@ -1590,14 +1917,8 @@ mod tests { collator: Default::default(), signature: Default::default(), pov_block_hash: Default::default(), - global_validation: GlobalValidationSchedule { - max_code_size: ::MaxCodeSize::get(), - max_head_data_size: ::MaxHeadDataSize::get(), - }, - local_validation: LocalValidationData { - parent_head: HeadData(Parachains::parachain_head(¶_id).unwrap()), - balance: >::free_balance(para_id), - }, + global_validation: Parachains::global_validation_schedule(), + local_validation: Parachains::current_local_validation_data(¶_id).unwrap(), commitments: CandidateCommitments::default(), } } @@ -1674,13 +1995,12 @@ mod tests { fn start_session(session_index: SessionIndex) { let mut parent_hash = System::parent_hash(); - use sp_runtime::traits::Header; for i in Session::current_index()..session_index { println!("session index {}", i); Staking::on_finalize(System::block_number()); System::set_block_number((i + 1).into()); - Timestamp::set_timestamp(System::block_number() * 6000); + Timestamp::set_timestamp(System::block_number() as primitives::Moment * 6000); // In order to be able to use `System::parent_hash()` in the tests // we need to first get it via `System::finalize` and then set it @@ -1694,7 +2014,7 @@ mod tests { } System::initialize( - &(i as u64 + 1), + &(i as BlockNumber + 1), &parent_hash, &Default::default(), &Default::default(), @@ -1718,11 +2038,15 @@ mod tests { Registrar::on_initialize(System::block_number()); Parachains::on_initialize(System::block_number()); } - fn run_to_block(n: u64) { + fn run_to_block(n: BlockNumber) { println!("Running until block {}", n); while System::block_number() < n { if System::block_number() > 1 { println!("Finalizing {}", System::block_number()); + if !DidUpdate::get().is_some() { + Parachains::set_heads(Origin::NONE, vec![]).unwrap(); + } + Parachains::on_finalize(System::block_number()); Registrar::on_finalize(System::block_number()); System::on_finalize(System::block_number()); @@ -2195,6 +2519,396 @@ mod tests { assert_eq!(hashed_null_node, EMPTY_TRIE_ROOT.into()) } + #[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); + 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); + 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); + past_code.note_replacement(20); + past_code.note_replacement(30); + + 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![30, 20], + last_pruned: Some(10), + }); + + assert_eq!(past_code.prune_up_to(21).collect::>(), vec![20]); + assert_eq!(past_code, ParaPastCodeMeta { + upgrade_times: vec![30], + last_pruned: Some(20), + }); + + past_code.note_replacement(40); + past_code.note_replacement(50); + past_code.note_replacement(60); + + assert_eq!(past_code, ParaPastCodeMeta { + upgrade_times: vec![60, 50, 40, 30], + last_pruned: Some(20), + }); + + assert_eq!(past_code.prune_up_to(60).collect::>(), vec![30, 40, 50, 60]); + assert_eq!(past_code, ParaPastCodeMeta { + upgrade_times: Vec::new(), + last_pruned: Some(60), + }); + } + + #[test] + fn para_past_code_pruning_in_initialize() { + let parachains = vec![ + (0u32.into(), vec![], vec![]), + (1u32.into(), vec![], vec![]), + ]; + + new_test_ext(parachains.clone()).execute_with(|| { + let id = ParaId::from(0u32); + let at_block: BlockNumber = 10; + ::PastCode::insert(&(id, at_block), vec![1, 2, 3]); + ::PastCodePruning::put(&vec![(id, at_block)]); + + { + let mut code_meta = Parachains::past_code_meta(&id); + code_meta.note_replacement(at_block); + ::PastCodeMeta::insert(&id, &code_meta); + } + + let pruned_at: BlockNumber = at_block + SlashPeriod::get() + 1; + assert_eq!(::PastCode::get(&(id, at_block)), Some(vec![1, 2, 3])); + + run_to_block(pruned_at - 1); + assert_eq!(::PastCode::get(&(id, at_block)), Some(vec![1, 2, 3])); + assert_eq!(Parachains::past_code_meta(&id).most_recent_change(), Some(at_block)); + + run_to_block(pruned_at); + assert!(::PastCode::get(&(id, at_block)).is_none()); + assert!(Parachains::past_code_meta(&id).most_recent_change().is_none()); + }); + } + + #[test] + fn note_past_code_sets_up_pruning_correctly() { + let parachains = vec![ + (0u32.into(), vec![], vec![]), + (1u32.into(), vec![], vec![]), + ]; + + new_test_ext(parachains.clone()).execute_with(|| { + let id_a = ParaId::from(0u32); + let id_b = ParaId::from(1u32); + + Parachains::note_past_code(id_a, 10, vec![1, 2, 3]); + Parachains::note_past_code(id_b, 20, vec![4, 5, 6]); + + assert_eq!(Parachains::past_code_pruning_tasks(), vec![(id_a, 10), (id_b, 20)]); + assert_eq!( + Parachains::past_code_meta(&id_a), + ParaPastCodeMeta { + upgrade_times: vec![10], + last_pruned: None, + } + ); + assert_eq!( + Parachains::past_code_meta(&id_b), + ParaPastCodeMeta { + upgrade_times: vec![20], + last_pruned: None, + } + ); + }); + } + + #[test] + fn code_upgrade_applied_after_delay() { + let parachains = vec![ + (0u32.into(), vec![1, 2, 3], vec![]), + ]; + + new_test_ext(parachains.clone()).execute_with(|| { + let para_id = ParaId::from(0); + let new_code = vec![4, 5, 6]; + + run_to_block(2); + assert_eq!(Parachains::active_parachains().len(), 1); + assert_eq!(Parachains::parachain_code(¶_id), Some(vec![1, 2, 3])); + + let applied_after ={ + let raw_candidate = raw_candidate(para_id); + let applied_after = raw_candidate.local_validation.code_upgrade_allowed.unwrap(); + let mut candidate_a = make_blank_attested(raw_candidate); + + candidate_a.candidate.commitments.new_validation_code = Some(new_code.clone()); + + // this parablock is in the context of block 1. + assert_eq!(applied_after, 1 + ValidationUpgradeDelay::get()); + make_attestations(&mut candidate_a); + + assert_ok!(Parachains::dispatch( + set_heads(vec![candidate_a.clone()]), + Origin::NONE, + )); + + assert!(Parachains::past_code_meta(¶_id).most_recent_change().is_none()); + assert_eq!(Parachains::code_upgrade_schedule(¶_id), Some(applied_after)); + assert_eq!(::FutureCode::get(¶_id), new_code); + assert_eq!(Parachains::parachain_code(¶_id), Some(vec![1, 2, 3])); + + applied_after + }; + + run_to_block(applied_after); + + // the candidate is in the context of the parent of `applied_after`, + // thus does not trigger the code upgrade. + { + let raw_candidate = raw_candidate(para_id); + assert!(raw_candidate.local_validation.code_upgrade_allowed.is_none()); + let mut candidate_a = make_blank_attested(raw_candidate); + + make_attestations(&mut candidate_a); + + assert_ok!(Parachains::dispatch( + set_heads(vec![candidate_a.clone()]), + Origin::NONE, + )); + + assert!(Parachains::past_code_meta(¶_id).most_recent_change().is_none()); + assert_eq!(Parachains::code_upgrade_schedule(¶_id), Some(applied_after)); + assert_eq!(::FutureCode::get(¶_id), new_code); + assert_eq!(Parachains::parachain_code(¶_id), Some(vec![1, 2, 3])); + } + + run_to_block(applied_after + 1); + + // the candidate is in the context of `applied_after`, and triggers + // the upgrade. + { + let raw_candidate = raw_candidate(para_id); + assert!(raw_candidate.local_validation.code_upgrade_allowed.is_some()); + let mut candidate_a = make_blank_attested(raw_candidate); + + make_attestations(&mut candidate_a); + + assert_ok!(Parachains::dispatch( + set_heads(vec![candidate_a.clone()]), + Origin::NONE, + )); + + assert_eq!( + Parachains::past_code_meta(¶_id).most_recent_change(), + Some(applied_after), + ); + assert_eq!( + ::PastCode::get(&(para_id, applied_after)), + Some(vec![1, 2, 3,]), + ); + assert!(Parachains::code_upgrade_schedule(¶_id).is_none()); + assert!(::FutureCode::get(¶_id).is_empty()); + assert_eq!(Parachains::parachain_code(¶_id), Some(new_code)); + } + }); + } + + #[test] + fn code_upgrade_applied_after_delay_even_when_late() { + let parachains = vec![ + (0u32.into(), vec![1, 2, 3], vec![]), + ]; + + new_test_ext(parachains.clone()).execute_with(|| { + let para_id = ParaId::from(0); + let new_code = vec![4, 5, 6]; + + run_to_block(2); + assert_eq!(Parachains::active_parachains().len(), 1); + assert_eq!(Parachains::parachain_code(¶_id), Some(vec![1, 2, 3])); + + let applied_after ={ + let raw_candidate = raw_candidate(para_id); + let applied_after = raw_candidate.local_validation.code_upgrade_allowed.unwrap(); + let mut candidate_a = make_blank_attested(raw_candidate); + + candidate_a.candidate.commitments.new_validation_code = Some(new_code.clone()); + + // this parablock is in the context of block 1. + assert_eq!(applied_after, 1 + ValidationUpgradeDelay::get()); + make_attestations(&mut candidate_a); + + assert_ok!(Parachains::dispatch( + set_heads(vec![candidate_a.clone()]), + Origin::NONE, + )); + + assert!(Parachains::past_code_meta(¶_id).most_recent_change().is_none()); + assert_eq!(Parachains::code_upgrade_schedule(¶_id), Some(applied_after)); + assert_eq!(::FutureCode::get(¶_id), new_code); + assert_eq!(Parachains::parachain_code(¶_id), Some(vec![1, 2, 3])); + + applied_after + }; + + run_to_block(applied_after + 1 + 4); + + { + let raw_candidate = raw_candidate(para_id); + assert!(raw_candidate.local_validation.code_upgrade_allowed.is_some()); + let mut candidate_a = make_blank_attested(raw_candidate); + + make_attestations(&mut candidate_a); + + assert_ok!(Parachains::dispatch( + set_heads(vec![candidate_a.clone()]), + Origin::NONE, + )); + + assert_eq!( + Parachains::past_code_meta(¶_id).most_recent_change(), + Some(applied_after + 4), + ); + assert_eq!( + ::PastCode::get(&(para_id, applied_after + 4)), + Some(vec![1, 2, 3,]), + ); + assert!(Parachains::code_upgrade_schedule(¶_id).is_none()); + assert!(::FutureCode::get(¶_id).is_empty()); + assert_eq!(Parachains::parachain_code(¶_id), Some(new_code)); + } + }); + } + + #[test] + fn submit_code_change_when_not_allowed_is_err() { + let parachains = vec![ + (0u32.into(), vec![1, 2, 3], vec![]), + ]; + + new_test_ext(parachains.clone()).execute_with(|| { + let para_id = ParaId::from(0); + let new_code = vec![4, 5, 6]; + + run_to_block(2); + + { + let raw_candidate = raw_candidate(para_id); + let mut candidate_a = make_blank_attested(raw_candidate); + + candidate_a.candidate.commitments.new_validation_code = Some(new_code.clone()); + + make_attestations(&mut candidate_a); + + assert_ok!(Parachains::dispatch( + set_heads(vec![candidate_a.clone()]), + Origin::NONE, + )); + }; + + run_to_block(3); + + { + let raw_candidate = raw_candidate(para_id); + assert!(raw_candidate.local_validation.code_upgrade_allowed.is_none()); + let mut candidate_a = make_blank_attested(raw_candidate); + candidate_a.candidate.commitments.new_validation_code = Some(vec![1, 2, 3]); + + make_attestations(&mut candidate_a); + + assert_err!( + Parachains::dispatch( + set_heads(vec![candidate_a.clone()]), + Origin::NONE, + ), + Error::::DisallowedCodeUpgrade, + ); + } + }); + } + + #[test] + fn full_parachain_cleanup_storage() { + let parachains = vec![ + (0u32.into(), vec![1, 2, 3], vec![]), + ]; + + new_test_ext(parachains.clone()).execute_with(|| { + let para_id = ParaId::from(0); + let new_code = vec![4, 5, 6]; + + run_to_block(2); + { + let raw_candidate = raw_candidate(para_id); + let applied_after = raw_candidate.local_validation.code_upgrade_allowed.unwrap(); + let mut candidate_a = make_blank_attested(raw_candidate); + + candidate_a.candidate.commitments.new_validation_code = Some(new_code.clone()); + + // this parablock is in the context of block 1. + assert_eq!(applied_after, 1 + ValidationUpgradeDelay::get()); + make_attestations(&mut candidate_a); + + assert_ok!(Parachains::dispatch( + set_heads(vec![candidate_a.clone()]), + Origin::NONE, + )); + + assert!(Parachains::past_code_meta(¶_id).most_recent_change().is_none()); + assert_eq!(Parachains::code_upgrade_schedule(¶_id), Some(applied_after)); + assert_eq!(::FutureCode::get(¶_id), new_code); + assert_eq!(Parachains::parachain_code(¶_id), Some(vec![1, 2, 3])); + + assert!(Parachains::past_code_pruning_tasks().is_empty()); + }; + + Parachains::cleanup_para(para_id); + + // cleaning up the parachain should place the current parachain code + // into the past code buffer & schedule cleanup. + assert_eq!(Parachains::past_code_meta(¶_id).most_recent_change(), Some(2)); + assert_eq!(::PastCode::get(&(para_id, 2)), Some(vec![1, 2, 3])); + assert_eq!(Parachains::past_code_pruning_tasks(), vec![(para_id, 2)]); + + // any future upgrades haven't been used to validate yet, so those + // are cleaned up immediately. + assert!(Parachains::code_upgrade_schedule(¶_id).is_none()); + assert!(::FutureCode::get(¶_id).is_empty()); + assert!(Parachains::parachain_code(¶_id).is_none()); + + let cleaned_up_at = 2 + SlashPeriod::get() + 1; + run_to_block(cleaned_up_at); + + // now the final cleanup: last past code cleaned up, and this triggers meta cleanup. + assert_eq!(Parachains::past_code_meta(¶_id), Default::default()); + assert!(::PastCode::get(&(para_id, 2)).is_none()); + assert!(Parachains::past_code_pruning_tasks().is_empty()); + }); + } + #[test] fn double_vote_candidate_and_valid_works() { let parachains = vec![ @@ -2209,14 +2923,14 @@ mod tests { // Test that a Candidate and Valid statements on the same candidate get slashed. new_test_ext(parachains.clone()).execute_with(|| { - let candidate = raw_candidate(1.into()).abridge().0; - let candidate_hash = candidate.hash(); - assert_eq!(Staking::current_era(), Some(0)); assert_eq!(Session::current_index(), 0); start_era(1); + let candidate = raw_candidate(1.into()).abridge().0; + let candidate_hash = candidate.hash(); + let authorities = Parachains::authorities(); let authority_index = 0; let key = extract_key(authorities[authority_index].clone()); @@ -2307,11 +3021,11 @@ mod tests { // Test that a Candidate and Invalid statements on the same candidate get slashed. new_test_ext(parachains.clone()).execute_with(|| { + start_era(1); + let candidate = raw_candidate(1.into()).abridge().0; let candidate_hash = candidate.hash(); - start_era(1); - let authorities = Parachains::authorities(); let authority_index = 0; let key = extract_key(authorities[authority_index].clone()); @@ -2404,11 +3118,11 @@ mod tests { // Test that an Invalid and Valid statements on the same candidate get slashed. new_test_ext(parachains.clone()).execute_with(|| { + start_era(1); + let candidate = raw_candidate(1.into()).abridge().0; let candidate_hash = candidate.hash(); - start_era(1); - let authorities = Parachains::authorities(); let authority_index = 0; let key = extract_key(authorities[authority_index].clone()); @@ -2501,14 +3215,14 @@ mod tests { // Test that a Candidate and Valid statements on the same candidate get slashed. new_test_ext(parachains.clone()).execute_with(|| { - let candidate = raw_candidate(1.into()).abridge().0; - let candidate_hash = candidate.hash(); - assert_eq!(Staking::current_era(), Some(0)); assert_eq!(Session::current_index(), 0); start_era(1); + let candidate = raw_candidate(1.into()).abridge().0; + let candidate_hash = candidate.hash(); + let authorities = Parachains::authorities(); let authority_index = 0; let key = extract_key(authorities[authority_index].clone()); @@ -2607,14 +3321,14 @@ mod tests { // Test that a Candidate and Valid statements on the same candidate get slashed. new_test_ext(parachains.clone()).execute_with(|| { - let candidate = raw_candidate(1.into()).abridge().0; - let candidate_hash = candidate.hash(); - assert_eq!(Staking::current_era(), Some(0)); assert_eq!(Session::current_index(), 0); start_era(1); + let candidate = raw_candidate(1.into()).abridge().0; + let candidate_hash = candidate.hash(); + let authorities = Parachains::authorities(); let authority_1_index = 0; let authority_2_index = 1; @@ -2667,14 +3381,14 @@ mod tests { // Test that submitting a report with a session mismatch between the `parent_hash` // and the proof itself fails. new_test_ext(parachains.clone()).execute_with(|| { - let candidate = raw_candidate(1.into()).abridge().0; - let candidate_hash = candidate.hash(); - assert_eq!(Staking::current_era(), Some(0)); assert_eq!(Session::current_index(), 0); start_era(1); + let candidate = raw_candidate(1.into()).abridge().0; + let candidate_hash = candidate.hash(); + let authorities = Parachains::authorities(); let authority_index = 0; let key = extract_key(authorities[authority_index].clone()); diff --git a/runtime/common/src/registrar.rs b/runtime/common/src/registrar.rs index c60930f46620..ee9f19345f92 100644 --- a/runtime/common/src/registrar.rs +++ b/runtime/common/src/registrar.rs @@ -52,6 +52,9 @@ pub trait Registrar { /// Checks whether the given validation code falls within the limit. fn code_size_allowed(code_size: u32) -> bool; + /// Fetches metadata for a para by ID, if any. + fn para_info(id: ParaId) -> Option; + /// Register a parachain with given `code` and `initial_head_data`. `id` must not yet be registered or it will /// result in a error. /// @@ -83,6 +86,10 @@ impl Registrar for Module { code_size <= ::MaxCodeSize::get() } + fn para_info(id: ParaId) -> Option { + Self::paras(&id) + } + fn register_para( id: ParaId, info: ParaInfo, @@ -653,15 +660,15 @@ mod tests { traits::{ BlakeTwo256, IdentityLookup, Dispatchable, AccountIdConversion, - }, testing::{UintAuthorityId, Header, TestXt}, KeyTypeId, Perbill, curve::PiecewiseLinear, + }, testing::{UintAuthorityId, TestXt}, KeyTypeId, Perbill, curve::PiecewiseLinear, }; use primitives::{ parachain::{ ValidatorId, Info as ParaInfo, Scheduling, LOWEST_USER_ID, AttestedCandidate, CandidateReceipt, HeadData, ValidityAttestation, Statement, Chain, - CollatorPair, CandidateCommitments, GlobalValidationSchedule, LocalValidationData, + CollatorPair, CandidateCommitments, }, - Balance, BlockNumber, + Balance, BlockNumber, Header, }; use frame_support::{ traits::{KeyOwnerProofSystem, OnInitialize, OnFinalize}, @@ -710,7 +717,7 @@ mod tests { type Origin = Origin; type Call = Call; type Index = u64; - type BlockNumber = u64; + type BlockNumber = BlockNumber; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; @@ -741,8 +748,8 @@ mod tests { } parameter_types!{ - pub const LeasePeriod: u64 = 10; - pub const EndingPeriod: u64 = 3; + pub const LeasePeriod: BlockNumber = 10; + pub const EndingPeriod: BlockNumber = 3; } impl slots::Trait for Test { @@ -791,6 +798,10 @@ mod tests { parameter_types! { pub const MaxHeadDataSize: u32 = 100; pub const MaxCodeSize: u32 = 100; + + pub const ValidationUpgradeFrequency: BlockNumber = 10; + pub const ValidationUpgradeDelay: BlockNumber = 2; + pub const SlashPeriod: BlockNumber = 50; pub const ElectionLookahead: BlockNumber = 0; } @@ -830,11 +841,15 @@ mod tests { type Origin = Origin; type Call = Call; type ParachainCurrency = balances::Module; + type BlockNumberConversion = sp_runtime::traits::Identity; type ActiveParachains = Registrar; type Registrar = Registrar; type Randomness = RandomnessCollectiveFlip; type MaxCodeSize = MaxCodeSize; type MaxHeadDataSize = MaxHeadDataSize; + type ValidationUpgradeFrequency = ValidationUpgradeFrequency; + type ValidationUpgradeDelay = ValidationUpgradeDelay; + type SlashPeriod = SlashPeriod; type Proof = session::historical::Proof; type KeyOwnerProofSystem = session::historical::Module; type IdentificationTuple = )>>::IdentificationTuple; @@ -929,7 +944,7 @@ mod tests { Slots::on_initialize(System::block_number()); } - fn run_to_block(n: u64) { + fn run_to_block(n: BlockNumber) { println!("Running until block {}", n); while System::block_number() < n { if System::block_number() > 1 { @@ -972,18 +987,13 @@ mod tests { collator: collator.public(), signature: pov_block_hash.using_encoded(|d| collator.sign(d)), pov_block_hash, - global_validation: GlobalValidationSchedule { - max_code_size: ::MaxCodeSize::get(), - max_head_data_size: ::MaxHeadDataSize::get(), - }, - local_validation: LocalValidationData { - balance: Balances::free_balance(&id.into_account()), - parent_head: HeadData(Parachains::parachain_head(&id).unwrap()), - }, + global_validation: Parachains::global_validation_schedule(), + local_validation: Parachains::current_local_validation_data(&id).unwrap(), commitments: CandidateCommitments { fees: 0, upward_messages: vec![], erasure_root: [1; 32].into(), + new_validation_code: None, }, }; let (candidate, _) = candidate.abridge(); diff --git a/runtime/common/src/slots.rs b/runtime/common/src/slots.rs index a066d31cfb4b..f54364a426c8 100644 --- a/runtime/common/src/slots.rs +++ b/runtime/common/src/slots.rs @@ -878,7 +878,7 @@ mod tests { use sp_core::H256; use sp_runtime::{ - Perbill, testing::Header, + Perbill, traits::{BlakeTwo256, Hash, IdentityLookup}, }; use frame_support::{ @@ -886,7 +886,8 @@ mod tests { traits::{OnInitialize, OnFinalize} }; use balances; - use primitives::parachain::{Id as ParaId, Info as ParaInfo}; + use primitives::{BlockNumber, Header}; + use primitives::parachain::{Id as ParaId, Info as ParaInfo, Scheduling}; impl_outer_origin! { pub enum Origin for Test {} @@ -907,7 +908,7 @@ mod tests { type Origin = Origin; type Call = (); type Index = u64; - type BlockNumber = u64; + type BlockNumber = BlockNumber; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; @@ -963,6 +964,10 @@ mod tests { code_size <= MAX_CODE_SIZE } + fn para_info(_id: ParaId) -> Option { + Some(ParaInfo { scheduling: Scheduling::Always }) + } + fn register_para( id: ParaId, _info: ParaInfo, @@ -997,8 +1002,8 @@ mod tests { } parameter_types!{ - pub const LeasePeriod: u64 = 10; - pub const EndingPeriod: u64 = 3; + pub const LeasePeriod: BlockNumber = 10; + pub const EndingPeriod: BlockNumber = 3; } impl Trait for Test { @@ -1025,7 +1030,7 @@ mod tests { t.into() } - fn run_to_block(n: u64) { + fn run_to_block(n: BlockNumber) { while System::block_number() < n { Slots::on_finalize(System::block_number()); Balances::on_finalize(System::block_number()); @@ -1453,8 +1458,8 @@ mod tests { assert_ok!(Slots::new_auction(Origin::ROOT, 5, 1)); - for i in 1..6 { - run_to_block(i); + for i in 1..6u64 { + run_to_block(i as _); assert_ok!(Slots::bid(Origin::signed(i), 0, 1, 1, 4, i)); for j in 1..6 { assert_eq!(Balances::reserved_balance(j), if j == i { j } else { 0 }); @@ -1481,8 +1486,8 @@ mod tests { assert_ok!(Slots::new_auction(Origin::ROOT, 5, 1)); - for i in 1..6 { - run_to_block(i + 3); + for i in 1..6u64 { + run_to_block((i + 3) as _); assert_ok!(Slots::bid(Origin::signed(i), 0, 1, 1, 4, i)); for j in 1..6 { assert_eq!(Balances::reserved_balance(j), if j == i { j } else { 0 }); diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index 4fbd5809234f..4e7dca2c8b24 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -499,17 +499,26 @@ impl attestations::Trait for Runtime { parameter_types! { pub const MaxCodeSize: u32 = 10 * 1024 * 1024; // 10 MB pub const MaxHeadDataSize: u32 = 20 * 1024; // 20 KB + pub const ValidationUpgradeFrequency: BlockNumber = 2 * DAYS; + pub const ValidationUpgradeDelay: BlockNumber = 8 * HOURS; + pub const SlashPeriod: BlockNumber = 7 * DAYS; } impl parachains::Trait for Runtime { type Origin = Origin; type Call = Call; type ParachainCurrency = Balances; + type BlockNumberConversion = sp_runtime::traits::Identity; type Randomness = RandomnessCollectiveFlip; type ActiveParachains = Registrar; type Registrar = Registrar; type MaxCodeSize = MaxCodeSize; type MaxHeadDataSize = MaxHeadDataSize; + + type ValidationUpgradeFrequency = ValidationUpgradeFrequency; + type ValidationUpgradeDelay = ValidationUpgradeDelay; + type SlashPeriod = SlashPeriod; + type Proof = session::historical::Proof; type KeyOwnerProofSystem = session::historical::Module; type IdentificationTuple = )>>::IdentificationTuple; @@ -821,7 +830,7 @@ sp_api::impl_runtime_apis! { Parachains::global_validation_schedule() } fn local_validation_data(id: parachain::Id) -> Option { - Parachains::local_validation_data(&id) + Parachains::current_local_validation_data(&id) } fn parachain_code(id: parachain::Id) -> Option> { Parachains::parachain_code(&id) diff --git a/runtime/polkadot/src/lib.rs b/runtime/polkadot/src/lib.rs index 71c221ae6266..2a2b46c88be7 100644 --- a/runtime/polkadot/src/lib.rs +++ b/runtime/polkadot/src/lib.rs @@ -507,17 +507,27 @@ impl attestations::Trait for Runtime { parameter_types! { pub const MaxCodeSize: u32 = 10 * 1024 * 1024; // 10 MB pub const MaxHeadDataSize: u32 = 20 * 1024; // 20 KB + + pub const ValidationUpgradeFrequency: BlockNumber = 7 * DAYS; + pub const ValidationUpgradeDelay: BlockNumber = 1 * DAYS; + pub const SlashPeriod: BlockNumber = 28 * DAYS; } impl parachains::Trait for Runtime { type Origin = Origin; type Call = Call; type ParachainCurrency = Balances; + type BlockNumberConversion = sp_runtime::traits::Identity; type Randomness = RandomnessCollectiveFlip; type ActiveParachains = Registrar; type Registrar = Registrar; type MaxCodeSize = MaxCodeSize; type MaxHeadDataSize = MaxHeadDataSize; + + type ValidationUpgradeFrequency = ValidationUpgradeFrequency; + type ValidationUpgradeDelay = ValidationUpgradeDelay; + type SlashPeriod = SlashPeriod; + type Proof = session::historical::Proof; type KeyOwnerProofSystem = session::historical::Module; type IdentificationTuple = )>>::IdentificationTuple; @@ -741,7 +751,7 @@ sp_api::impl_runtime_apis! { Parachains::global_validation_schedule() } fn local_validation_data(id: parachain::Id) -> Option { - Parachains::local_validation_data(&id) + Parachains::current_local_validation_data(&id) } fn parachain_code(id: parachain::Id) -> Option> { Parachains::parachain_code(&id) diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index fa22284e7630..0a291592272e 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -310,17 +310,27 @@ impl attestations::Trait for Runtime { parameter_types! { pub const MaxCodeSize: u32 = 10 * 1024 * 1024; // 10 MB pub const MaxHeadDataSize: u32 = 20 * 1024; // 20 KB + + pub const ValidationUpgradeFrequency: BlockNumber = 2; + pub const ValidationUpgradeDelay: BlockNumber = 1; + pub const SlashPeriod: BlockNumber = 1 * MINUTES; } impl parachains::Trait for Runtime { type Origin = Origin; type Call = Call; type ParachainCurrency = Balances; + type BlockNumberConversion = sp_runtime::traits::Identity; type Randomness = RandomnessCollectiveFlip; type ActiveParachains = Registrar; type Registrar = Registrar; type MaxCodeSize = MaxCodeSize; type MaxHeadDataSize = MaxHeadDataSize; + + type ValidationUpgradeFrequency = ValidationUpgradeFrequency; + type ValidationUpgradeDelay = ValidationUpgradeDelay; + type SlashPeriod = SlashPeriod; + type Proof = session::historical::Proof; type KeyOwnerProofSystem = session::historical::Module; type IdentificationTuple = < @@ -528,7 +538,7 @@ sp_api::impl_runtime_apis! { Parachains::global_validation_schedule() } fn local_validation_data(id: parachain::Id) -> Option { - Parachains::local_validation_data(&id) + Parachains::current_local_validation_data(&id) } fn parachain_code(id: parachain::Id) -> Option> { Parachains::parachain_code(&id) diff --git a/validation/src/pipeline.rs b/validation/src/pipeline.rs index 04c839a726e2..408d330928e9 100644 --- a/validation/src/pipeline.rs +++ b/validation/src/pipeline.rs @@ -23,13 +23,13 @@ use codec::Encode; use polkadot_erasure_coding as erasure; use polkadot_primitives::parachain::{ CollationInfo, PoVBlock, LocalValidationData, GlobalValidationSchedule, OmittedValidationData, - AvailableData, FeeSchedule, CandidateCommitments, ErasureChunk, HeadData, ParachainHost, - Id as ParaId, AbridgedCandidateReceipt, + AvailableData, FeeSchedule, CandidateCommitments, ErasureChunk, ParachainHost, + Id as ParaId, AbridgedCandidateReceipt }; use polkadot_primitives::{Block, BlockId, Balance, Hash}; use parachain::{ wasm_executor::{self, ExecutionMode}, - UpwardMessage, ValidationParams, + primitives::{UpwardMessage, ValidationParams}, }; use runtime_primitives::traits::{BlakeTwo256, Hash as HashT}; use sp_api::ProvideRuntimeApi; @@ -95,7 +95,7 @@ impl ExternalitiesInner { } fn apply_message_fee(&mut self, message_len: usize) -> Result<(), String> { - let fee = self.fee_schedule.compute_fee(message_len); + let fee = self.fee_schedule.compute_message_fee(message_len); let new_fees_charged = self.fees_charged.saturating_add(fee); if new_fees_charged > self.free_balance { Err("could not cover fee.".into()) @@ -160,8 +160,7 @@ impl FullOutput { pub struct ValidatedCandidate<'a> { pov_block: &'a PoVBlock, global_validation: &'a GlobalValidationSchedule, - parent_head: &'a HeadData, - balance: Balance, + local_validation: &'a LocalValidationData, upward_messages: Vec, fees: Balance, } @@ -173,18 +172,14 @@ impl<'a> ValidatedCandidate<'a> { let ValidatedCandidate { pov_block, global_validation, - parent_head, - balance, + local_validation, upward_messages, fees, } = self; let omitted_validation = OmittedValidationData { global_validation: global_validation.clone(), - local_validation: LocalValidationData { - parent_head: parent_head.clone(), - balance, - }, + local_validation: local_validation.clone(), }; let available_data = AvailableData { @@ -216,6 +211,7 @@ impl<'a> ValidatedCandidate<'a> { upward_messages, fees, erasure_root, + new_validation_code: None, }; Ok(FullOutput { @@ -244,8 +240,12 @@ pub fn validate<'a>( } let params = ValidationParams { - parent_head: local_validation.parent_head.0.clone(), - block_data: pov_block.block_data.0.clone(), + parent_head: local_validation.parent_head.clone(), + block_data: pov_block.block_data.clone(), + max_code_size: global_validation.max_code_size, + max_head_data_size: global_validation.max_head_data_size, + relay_chain_height: global_validation.block_number, + code_upgrade_allowed: local_validation.code_upgrade_allowed, }; // TODO: remove when ext does not do this. @@ -266,7 +266,7 @@ pub fn validate<'a>( execution_mode, ) { Ok(result) => { - if result.head_data == collation.head_data.0 { + if result.head_data == collation.head_data { let (upward_messages, fees) = Arc::try_unwrap(ext.0) .map_err(|_| "") .expect("Wasm executor drops passed externalities on completion; \ @@ -277,8 +277,7 @@ pub fn validate<'a>( Ok(ValidatedCandidate { pov_block, global_validation, - parent_head: &local_validation.parent_head, - balance: local_validation.balance, + local_validation, upward_messages, fees, }) @@ -352,13 +351,13 @@ pub fn full_output_validation_with_api

( mod tests { use super::*; use parachain::wasm_executor::Externalities as ExternalitiesTrait; - use parachain::ParachainDispatchOrigin; + use parachain::primitives::ParachainDispatchOrigin; #[test] fn ext_checks_fees_and_updates_correctly() { let mut ext = ExternalitiesInner { upward: vec![ - UpwardMessage{ data: vec![42], origin: ParachainDispatchOrigin::Parachain }, + UpwardMessage { data: vec![42], origin: ParachainDispatchOrigin::Parachain }, ], fees_charged: 0, free_balance: 1_000_000,