mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-25 23:27:56 +00:00
runtime upgrade (#70)
* Initial commit * Update to 3e65111 * Add cfg_attr ... no_std * Fix version * WIP: add really simple validate_block insert validity check * WIP: create a parachain upgrade pallet This pallet will eventually make life much easier for people attempting to upgrade parachains on their validator nodes, but for the moment, key sections remain unimplemented while dependency details are worked out. * Implement basic admin-auth pallet functionality. This compiles, which means it's probably mostly correct. However, it's pretty far from being finished. Work yet to come: - Integrate with the democracy pallet somehow to eliminate the requirement for the root user to initiate this process. - Figure out what to do in the event that the parachain blocks and relay chain blocks get out of sync / delayed. - Add testing... somehow. (What's reasonable to test?) Open questions: - Is the block number parameter in `on_initialize` the parachain block number, or the relay chain block number? If, as I suspect, it's the parachain block number, how do we deal with the fact that the real upgrade should happen on a very specific parachain block number? - In line 68, is it reasonable to use `if n >= apply_block`, or should that line require strict equality? - Is it reasonable to store/retrieve `CurrentBlockNumber` on every block, or is there a more streamlined way to pass that data between functions? Maybe it can be inserted into `struct Module` somehow? - Can we somehow parametrize ValidationUpgradeDelayBlocks by T in order to eliminate the `.into()` call? * use a better storage name * Add checks ensuring runtime versions increase Largely cribbed from https://github.com/paritytech/substrate/blob/a439a7aa5a9a3df2a42d9b25ea04288d3a0866e8/frame/system/src/lib.rs#L467-L494 * fix tests * WIP: add tests from frame/system set_code Currently doesn't build: line 230 is the problem. Removing or commenting that line results in the new tests failing due to a missing block number. Adding it, in an attempt to fix the problem, fails to compile with this error: ``` Compiling parachain-upgrade-pallet v2.0.0 (/home/coriolinus/Documents/Projects/coriolinus/parachain-upgrade-pallet) error[E0599]: no function or associated item named `set_block_number` found for struct `Module<tests::Test>` in the current scope --> src/lib.rs:230:21 | 47 | / decl_module! { 48 | | pub struct Module<T: Trait> for enum Call where origin: T::Origin { 49 | | // Initializing events 50 | | // this is needed only if you are using events in your pallet ... | 100 | | } 101 | | } | |_- function or associated item `set_block_number` not found for this ... 230 | System::set_block_number(123); | ^^^^^^^^^^^^^^^^ | | | function or associated item not found in `Module<tests::Test>` | help: there is a method with a similar name: `current_block_number` | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error: aborting due to previous error For more information about this error, try `rustc --explain E0599`. error: could not compile `parachain-upgrade-pallet`. ``` That error is very weird, because the function does in fact exist: https://github.com/paritytech/substrate/blob/a439a7aa5a9a3df2a42d9b25ea04288d3a0866e8/frame/system/src/lib.rs#L897 * cause tests to pass Turns out that in fact there was some setup required in order to get everything testing properly, but now we have a set of passing unit tests which test some of the more common error cases. * Add overlapping upgrades test This currently fails, and I don't yet know why. TODO! * Fix some logic errors - In particular, only remove the pending validation function from storage when it's time to apply it. - Don't store our own copy of the current block number. * WIP: delegate most code upgrade permissions checks They're defined in System::can_set_code, so may as well use them. Unfortunately, the tests all fail for this right now, and I don't yet understand why. Pushing to get immutable line number references. * fix tests after delegating runtime checks to can_set_code * WIP: events test Right now, the events struct doesn't seem to contain enough information to validate the particular events that we should have fired. Almost certainly, this is a usage error on my part. * fully initialize and finalize in event test This doesn't change the results, though. * fix events test This was complicated to figure out. For the record, testing events requires: - a private module which publicly exports the crate's event type - impl_outer_event macro to construct an enum wrapping the event types from the listed modules - system::Trait and local Trait both declare `type Event = TestEvent;` - (optional) group events with `System::<Test>::initialize(...)` and `System::<Test>::finalize()` calls. It's not entirely clear why both events show up during the initialization phase; my suspicion is that this is an artifact of not mocking a particular extrinsic, such that they end up in initialization by default. * cleanup and move crate to subdirectory this prepares us to merge this pallet into the cumulus repo * provisionally incorporate polkadot changes to hook everything together This feels like the logical next step, and compiles, at least. Still, there are some big TODOs remaining: - merge the polkadot PR upstream and reset the polkadot branch in `runtime/Cargo.toml` - in `runtime/src/validate_block/implementation.rs:116`, we should almost certainly return `Some(something)` sometime. When, precisely, and how to keep track of the appropriate data are all still open questions. * WIP: further updates to work with the polkadot implementation Hopefully we can upstream `ValidationFunctionParams` into the polkadot trait defs so we can just copy the struct out of `ValidationParams`, but no huge loss if not. This should be more or less everything required at this level. Next up: fix up `pallet-parachain-upgrade` so it reads from `VALIDATION_FUNCTION_PARAMS` to determine upgrade legality and upgrade block, and writes to `NEW_VALIDATION_CODE` when appropriate. * update pallet-parachain-upgrade appropriately to handle new expectations Implements the pallet side of the new flow. Basic tests appear to work. Next up: - make the "real blob" test work - add a bunch of additional tests of all the corners * remove test which set a real WASM blob This test didn't directly test any of the code in this pallet; it existed because we were just copying tests out of the substrate implementation. Now that we have real code of our own to test, (and because it's not compatible with the `BlockTests` abstraction,) we don't need that test anymore. Also added a `Drop` impl to `BlockTests` ensuring they get run at least once. * add test that storage gets manipulated as expected * get validate_block tests compiling again * Check validation function size against polkadot parameters Generate a user-handlable error message if it's too big, so that nothing panics later. * demonstrate that block tests run * don't actually store any magic values in parachain storage We're allowed to use it as a transport layer between validate_block and the parachain upgrade pallet, but actually editing it or, in particular, attempting to persist data there which didn't originate in the extrinsic, breaks things. This means that we can't keep the :code insertion check, because the validate_block layer doesn't know when it is legal to actually upgrade the parachain. However, the rest of the features survive, and all tests currently pass, so I'm counting it as a win. Next up: look into adding an inherent which publishes the ValidationFunctionParams struct to arbitrary pallets. * Add reference to polkadot_client to Collator This enables us to get the validation function parameters at runtime, which unblocks creating an inherent containing them. * remove unused imports * Remove VFPX; build VFP from existing data structures I almost don't want to know how long both global_validation and local_validation have existed in the produce_candidate function signature; they were precisely what I needed, without needing to add anything to the Collator struct at all. Oh well, at least I noticed it before putting the PR up for review. Next up: create a proper inherent definition for the ValidationFunctionParams. * WIP: add cumulus-validation-function-params crate Modeled on the substrate timestamp crate. It's not currently obvious to me why it is desirable to publish an entire crate for what amounts to a single const definition; going to ask about that. * refactor: get rid of validation-function-params crate Everything about the VFPs has been moved into a module of runtime * WIP: get VFP from inherent, when possible Doesn't compile for weird trait errors; probable next steps: just copy over the relevant code directly. * ensure VFPs are available during block production and validation * cleanup in preparation for review request * Copy cumulus-primitives crate from bkchr-message-broker That branch is visible as #80; this message copies the crate as of d4b2045573796955de4e5bf8f74b6c48b44c3bee. This isn't even a cherry-pick, because the commit which introduced the primitives crate also did some work which from the perspective of this PR is irrelevant. With any luck, by coping the crate directly, there won't be too many merge conflicts when the second of these open PRs is merged. * move mod validation_function_params to cumulus_primitives There is some very weird behavior going on with cargo check: every individual crate checks out fine, as verified with this invocation: for crate in $(fd Cargo.toml | xargs dirname); do if [ "$crate" == . ] || [[ "$crate" == *test* ]]; then continue; fi; name=$(toml2json "$crate/Cargo.toml" | jq -r '.package.name') if ! cargo check -p "$name" >/dev/null 2>/dev/null; then echo "failed to build $name" fi done However, `cargo check .` no longer works; it is suddenly demanding clang in order to build an indirect dependency. I'm not going to keep messing around with this anymore; it's more profitable for the moment to knock out the rest of the requested changes. Still, this behavior is weird enough that I really don't have any idea why it might be happening. * convert indentation to tabs * rename parachain upgrade pallet per PR review * use compact form for dependencies * remove pallet readme Move pertinent documentation into pallet's rustdoc. * Add weight data in compliance with updated substrate requirements The substrate API changed, so now we _have_ to invent some kind of weight data for the dispatchables. This commit does so, with the caveat that the numbers are pulled straight out of nowhere. Benchmarking remains a TODO item. * use anonymous fatal error type for brevity * Create, use a Call for setting VFPs Modeled on Timestamp; makes the ProvideInherent impl work much better. * fix pallet tests * Apply suggestions from code review Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com> * fix formatting * add license header * refactor primitive inherents / keys into appropriate modules * impl From<(GlobalValidationSchedule, LocalValidationData)> for ValidationFunctionParams * extract inherent data vfp injection into a function * collapse parachain dependency into compact form * always store vfps under same storage key * fix docs * use minimum weight for VFP inherent * rename module methods for clarity * fix tests: set_code -> schedule_upgrade * Apply pending validation function at inherent creation, not init Initialization happens before inherent creation, which means that at the time of `on_initialize`, the VFPs for the current block have not yet been loaded. This is a problem, because it means that updates would happen one block late, every time. Moving that logic into inherent creation means that we always have current information to work with. * typo: default_features -> default-features * do not panic in create_inherent * revert f741cf0f2bc; don't change behavior, but use correct spelling * move block initialization logic from inherent creation into the inherent * re-disable default features It is very difficult to come up with a coherent theory of what's going on with these default features. Builds were all broken as of 3eb1618. Renaming them in f741cf0 seemed to fix that behavior. Then they broke again locally, prompting aaee1c0. This commit restores the status quo as of f741cf0; with any luck, the build will succeed both locally and in CI. * regenerate Cargo.lock This updates several packages, but by inspection, they are all published crates from crates.io; by semver, this should not cause any behavioral changes. This also updates the lockfile format to the new format. The point of this commit is to deal with the fact that `sc-client` no longer exists. * fix checks given new dependencies Appropriate weight declarations have changed; this follows them, still using timestamp examples. Note that these weights are almost certainly wrong. * fix tests given new dependencies * add another check preventing block VFPs from contaminating validity checks * Add OnValidationFunctionParams trait so other modules can callback There isn't yet an obvious use case for other modules to get the validation function params from this one, but we may as well support it on the off chance. * Add get_validation_function_params This getter allows other modules to simply get the validation function parameters, if they have been updated for this block. Otherwise, it returns None, and they can try again later. * upgrade substrate: panic on div by 0 * Apply whitespace suggestions from code review These suggestions should make no semantic difference. Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Apply semantic from code review These changes affect the semantics of the code; I'll follow up by ensuring that everything still works. Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * add documentation to ValidationFunction type * removing panicing private fn validation_function_params() * expect validation function params to be in inherent data * move OnValidationFunctionParams to primitives * resolve weird formatting * move mod validation_function_params into its own file * add license to new file Co-authored-by: Ricardo Rius <ricardo@parity.io> Co-authored-by: Ricardo Rius <9488369+riusricardo@users.noreply.github.com> Co-authored-by: Joshy Orndorff <admin@joshyorndorff.com> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
bd14f0ed0c
commit
45a7fe2a94
Generated
+4195
-4281
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ members = [
|
||||
"consensus",
|
||||
"message-broker",
|
||||
"network",
|
||||
"parachain-upgrade",
|
||||
"primitives",
|
||||
"runtime",
|
||||
"test/runtime",
|
||||
|
||||
@@ -18,11 +18,14 @@ sc-cli = { git = "https://github.com/paritytech/substrate", branch = "cumulus-br
|
||||
# Polkadot dependencies
|
||||
polkadot-collator = { git = "https://github.com/paritytech/polkadot", branch = "cumulus-branch" }
|
||||
polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "cumulus-branch" }
|
||||
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "cumulus-branch" }
|
||||
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "cumulus-branch" }
|
||||
polkadot-validation = { git = "https://github.com/paritytech/polkadot", branch = "cumulus-branch" }
|
||||
|
||||
# Cumulus dependencies
|
||||
cumulus-consensus = { path = "../consensus" }
|
||||
cumulus-runtime = { path = "../runtime" }
|
||||
cumulus-primitives = { path = "../primitives" }
|
||||
|
||||
# Other dependencies
|
||||
log = "0.4.8"
|
||||
|
||||
+39
-12
@@ -16,13 +16,17 @@
|
||||
|
||||
//! Cumulus Collator implementation for Substrate.
|
||||
|
||||
use cumulus_primitives::{
|
||||
inherents::VALIDATION_FUNCTION_PARAMS_IDENTIFIER as VFP_IDENT,
|
||||
validation_function_params::ValidationFunctionParams,
|
||||
};
|
||||
use cumulus_runtime::ParachainBlockData;
|
||||
|
||||
use sp_consensus::{
|
||||
BlockImport, BlockImportParams, BlockOrigin, Environment, Error as ConsensusError,
|
||||
ForkChoiceStrategy, Proposal, Proposer, RecordProof,
|
||||
};
|
||||
use sp_inherents::InherentDataProviders;
|
||||
use sp_inherents::{InherentData, InherentDataProviders};
|
||||
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, HashFor};
|
||||
use sp_api::{ApiExt, ProvideRuntimeApi};
|
||||
use sc_client_api::{StateBackend, UsageProvider, Finalizer, BlockchainEvents};
|
||||
@@ -77,6 +81,38 @@ impl<Block, PF, BI> Collator<Block, PF, BI> {
|
||||
block_import: Arc::new(Mutex::new(block_import)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the inherent data with validation function parameters injected
|
||||
fn inherent_data(
|
||||
inherent_providers: InherentDataProviders,
|
||||
global_validation: GlobalValidationSchedule,
|
||||
local_validation: LocalValidationData,
|
||||
) -> Result<InherentData, InvalidHead> {
|
||||
let mut inherent_data = inherent_providers
|
||||
.create_inherent_data()
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
target: "cumulus-collator",
|
||||
"Failed to create inherent data: {:?}",
|
||||
e,
|
||||
);
|
||||
InvalidHead
|
||||
})?;
|
||||
|
||||
inherent_data.put_data(
|
||||
VFP_IDENT,
|
||||
&ValidationFunctionParams::from((global_validation, local_validation))
|
||||
).map_err(|e| {
|
||||
error!(
|
||||
target: "cumulus-collator",
|
||||
"Failed to inject validation function params into inherents: {:?}",
|
||||
e,
|
||||
);
|
||||
InvalidHead
|
||||
})?;
|
||||
|
||||
Ok(inherent_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, PF, BI> Clone for Collator<Block, PF, BI> {
|
||||
@@ -112,7 +148,7 @@ where
|
||||
fn produce_candidate(
|
||||
&mut self,
|
||||
_relay_chain_parent: PHash,
|
||||
_global_validation: GlobalValidationSchedule,
|
||||
global_validation: GlobalValidationSchedule,
|
||||
local_validation: LocalValidationData,
|
||||
) -> Self::ProduceCandidate {
|
||||
let factory = self.proposer_factory.clone();
|
||||
@@ -147,16 +183,7 @@ where
|
||||
InvalidHead
|
||||
})?;
|
||||
|
||||
let inherent_data = inherent_providers
|
||||
.create_inherent_data()
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
target: "cumulus-collator",
|
||||
"Failed to create inherent data: {:?}",
|
||||
e,
|
||||
);
|
||||
InvalidHead
|
||||
})?;
|
||||
let inherent_data = Self::inherent_data(inherent_providers, global_validation, local_validation)?;
|
||||
|
||||
let Proposal {
|
||||
block,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
[package]
|
||||
name = "cumulus-parachain-upgrade"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
description = "pallet to manage parachain upgrades"
|
||||
|
||||
[features]
|
||||
default = ['std']
|
||||
std = [
|
||||
'serde',
|
||||
'codec/std',
|
||||
'frame-support/std',
|
||||
'pallet-balances/std',
|
||||
'cumulus-runtime/std',
|
||||
'sp-core/std',
|
||||
'sp-runtime/std',
|
||||
'sp-io/std',
|
||||
'system/std',
|
||||
'cumulus-primitives/std',
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
# Cumulus dependencies
|
||||
cumulus-primitives = { path = "../primitives", default-features = false }
|
||||
cumulus-runtime = { path = "../runtime", default-features = false }
|
||||
|
||||
# Polkadot dependencies
|
||||
parachain = { package = "polkadot-parachain", git = "https://github.com/paritytech/polkadot", branch = "cumulus-branch", default-features = false }
|
||||
|
||||
# Substrate dependencies
|
||||
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "cumulus-branch", default-features = false }
|
||||
pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "cumulus-branch", default-features = false }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "cumulus-branch", version = "2.0.0-dev", default-features = false }
|
||||
sp-inherents = { git = "https://github.com/paritytech/substrate.git", branch = "cumulus-branch", default-features = false }
|
||||
sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "cumulus-branch", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "cumulus-branch", default-features = false }
|
||||
sp-version = { git = "https://github.com/paritytech/substrate.git", branch = "cumulus-branch", default-features = false }
|
||||
system = { package = "frame-system", git = "https://github.com/paritytech/substrate.git", branch = "cumulus-branch", default-features = false }
|
||||
|
||||
# Other Dependencies
|
||||
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"]}
|
||||
serde = { version = "1.0.101", optional = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-externalities = { git = "https://github.com/paritytech/substrate.git", branch = "cumulus-branch", default-features = false }
|
||||
substrate-test-runtime-client = { git = "https://github.com/paritytech/substrate.git", branch = "cumulus-branch", default-features = false }
|
||||
@@ -0,0 +1,625 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Cumulus.
|
||||
|
||||
// Cumulus 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.
|
||||
|
||||
// Cumulus 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
//! Enable Parachain validation function upgrades.
|
||||
//!
|
||||
//! Allow a user to determine when a parachain validation function upgrade
|
||||
//! is legal, and perform the upgrade, triggering runtime events
|
||||
//! for both storing and applying the new validation function.
|
||||
//!
|
||||
//! Depends on no external pallets or traits.
|
||||
//!
|
||||
//! This pallet depends on certain environmental conditions provided by
|
||||
//! Cumulus. It will not work outside a Cumulus Parachain.
|
||||
//!
|
||||
//! Users must ensure that they register this pallet as an inherent provider.
|
||||
|
||||
use cumulus_primitives::{
|
||||
inherents::VALIDATION_FUNCTION_PARAMS_IDENTIFIER as INHERENT_IDENTIFIER,
|
||||
validation_function_params::{OnValidationFunctionParams, ValidationFunctionParams},
|
||||
well_known_keys::{NEW_VALIDATION_CODE, VALIDATION_FUNCTION_PARAMS},
|
||||
};
|
||||
use frame_support::{
|
||||
decl_error, decl_event, decl_module, decl_storage, ensure, storage, traits::Get,
|
||||
weights::DispatchClass,
|
||||
};
|
||||
use parachain::primitives::RelayChainBlockNumber;
|
||||
use sp_core::storage::well_known_keys;
|
||||
use sp_inherents::{InherentData, InherentIdentifier, ProvideInherent};
|
||||
use sp_version::RuntimeVersion;
|
||||
use system::ensure_none;
|
||||
|
||||
/// A ValidationFunction is a compiled WASM blob which, on execution, validates parachain blocks.
|
||||
pub type ValidationFunction = Vec<u8>;
|
||||
type System<T> = system::Module<T>;
|
||||
|
||||
/// The pallet's configuration trait.
|
||||
pub trait Trait: system::Trait {
|
||||
/// The overarching event type.
|
||||
type Event: From<Event> + Into<<Self as system::Trait>::Event>;
|
||||
|
||||
/// Get the chain's current version.
|
||||
type Version: Get<RuntimeVersion>;
|
||||
|
||||
/// Something which can be notified when the validation function params are set.
|
||||
///
|
||||
/// Set this to `()` if not needed.
|
||||
type OnValidationFunctionParams: OnValidationFunctionParams;
|
||||
}
|
||||
|
||||
// This pallet's storage items.
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as ParachainUpgrade {
|
||||
// we need to store the new validation function for the span between
|
||||
// setting it and applying it.
|
||||
PendingValidationFunction get(fn new_validation_function): Option<(RelayChainBlockNumber, ValidationFunction)>;
|
||||
|
||||
/// Were the VFPs updated this block?
|
||||
DidUpdateVFPs: bool;
|
||||
}
|
||||
}
|
||||
|
||||
// The pallet's dispatchable functions.
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
// Initializing events
|
||||
// this is needed only if you are using events in your pallet
|
||||
fn deposit_event() = default;
|
||||
|
||||
// TODO: figure out a better weight than this
|
||||
#[weight = (0, DispatchClass::Operational)]
|
||||
pub fn schedule_upgrade(origin, validation_function: ValidationFunction) {
|
||||
// TODO: in the future, we can't rely on a superuser existing
|
||||
// on-chain who can just wave their hands and make this happen.
|
||||
// Instead, this should hook into the democracy pallet and check
|
||||
// that a validation function upgrade has been approved; potentially,
|
||||
// it should even trigger the validation function upgrade automatically
|
||||
// the moment the vote passes.
|
||||
|
||||
|
||||
System::<T>::can_set_code(origin, &validation_function)?;
|
||||
ensure!(!PendingValidationFunction::exists(), Error::<T>::OverlappingUpgrades);
|
||||
let vfp = Self::validation_function_params().ok_or(Error::<T>::ValidationFunctionParamsNotAvailable)?;
|
||||
ensure!(validation_function.len() <= vfp.max_code_size as usize, Error::<T>::TooBig);
|
||||
let apply_block = vfp.code_upgrade_allowed.ok_or(Error::<T>::ProhibitedByPolkadot)?;
|
||||
|
||||
// When a code upgrade is scheduled, it has to be applied in two
|
||||
// places, synchronized: both polkadot and the individual parachain
|
||||
// have to upgrade on the same relay chain block.
|
||||
//
|
||||
// `notify_polkadot_of_pending_upgrade` notifies polkadot; the `PendingValidationFunction`
|
||||
// storage keeps track locally for the parachain upgrade, which will
|
||||
// be applied later.
|
||||
Self::notify_polkadot_of_pending_upgrade(&validation_function);
|
||||
PendingValidationFunction::put((apply_block, validation_function));
|
||||
Self::deposit_event(Event::ValidationFunctionStored(apply_block));
|
||||
}
|
||||
|
||||
/// Set the current validation function parameters
|
||||
///
|
||||
/// This should be invoked exactly once per block. It will panic at the finalization
|
||||
/// phease if the call was not invoked.
|
||||
///
|
||||
/// The dispatch origin for this call must be `Inherent`
|
||||
///
|
||||
/// As a side effect, this function upgrades the current validation function
|
||||
/// if the appropriate time has come.
|
||||
//
|
||||
// weight data just stolen from Timestamp::set; may be inappropriate
|
||||
#[weight = (0, DispatchClass::Mandatory)]
|
||||
fn set_validation_function_parameters(origin, vfp: ValidationFunctionParams) {
|
||||
ensure_none(origin)?;
|
||||
assert!(!DidUpdateVFPs::exists(), "VFPs must be updated only once in the block");
|
||||
|
||||
// initialization logic: we know that this runs exactly once every block,
|
||||
// which means we can put the initialization logic here to remove the
|
||||
// sequencing problem.
|
||||
if let Some((apply_block, validation_function)) = PendingValidationFunction::get() {
|
||||
if vfp.relay_chain_height >= apply_block {
|
||||
PendingValidationFunction::kill();
|
||||
Self::put_parachain_code(&validation_function);
|
||||
Self::deposit_event(Event::ValidationFunctionApplied(vfp.relay_chain_height));
|
||||
}
|
||||
}
|
||||
|
||||
storage::unhashed::put(VALIDATION_FUNCTION_PARAMS, &vfp);
|
||||
DidUpdateVFPs::put(true);
|
||||
<T::OnValidationFunctionParams as OnValidationFunctionParams>::on_validation_function_params(vfp);
|
||||
}
|
||||
|
||||
fn on_finalize() {
|
||||
assert!(DidUpdateVFPs::take(), "VFPs must be updated once per block");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Get validation function parameters.
|
||||
///
|
||||
/// This will return `None` if this module's inherent has not yet run.
|
||||
/// If it returns `Some(_)`, the validation function params are current for this block.
|
||||
pub fn validation_function_params() -> Option<ValidationFunctionParams> {
|
||||
if DidUpdateVFPs::get() {
|
||||
// this storage value is set by cumulus during block validation,
|
||||
// and also by the inherent from this module.
|
||||
storage::unhashed::get(VALIDATION_FUNCTION_PARAMS)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Put a new validation function into a particular location where polkadot
|
||||
/// monitors for updates. Calling this function notifies polkadot that a new
|
||||
/// upgrade has been scheduled.
|
||||
fn notify_polkadot_of_pending_upgrade(code: &[u8]) {
|
||||
storage::unhashed::put_raw(NEW_VALIDATION_CODE, code);
|
||||
}
|
||||
|
||||
/// Put a new validation function into a particular location where this
|
||||
/// parachain will execute it on subsequent blocks.
|
||||
fn put_parachain_code(code: &[u8]) {
|
||||
storage::unhashed::put_raw(well_known_keys::CODE, code);
|
||||
}
|
||||
|
||||
/// `true` when a code upgrade is currently legal
|
||||
pub fn can_set_code() -> bool {
|
||||
Self::validation_function_params()
|
||||
.map(|vfp| vfp.code_upgrade_allowed.is_some())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The maximum code size permitted, in bytes.
|
||||
pub fn max_code_size() -> Option<u32> {
|
||||
Self::validation_function_params().map(|vfp| vfp.max_code_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> ProvideInherent for Module<T> {
|
||||
type Call = Call<T>;
|
||||
type Error = sp_inherents::MakeFatalError<()>;
|
||||
const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
|
||||
|
||||
fn create_inherent(data: &InherentData) -> Option<Self::Call> {
|
||||
// If the inherent is not present, this returns None early. This in turn will
|
||||
// cause the on_finalize assertion to fail.
|
||||
let vfp: ValidationFunctionParams = data.get_data(&INHERENT_IDENTIFIER).ok().flatten()
|
||||
.expect("validation function params are always injected into inherent data; qed");
|
||||
|
||||
Some(Call::set_validation_function_parameters(vfp))
|
||||
}
|
||||
}
|
||||
|
||||
decl_event! {
|
||||
pub enum Event {
|
||||
// The validation function has been scheduled to apply as of the contained relay chain block number.
|
||||
ValidationFunctionStored(RelayChainBlockNumber),
|
||||
// The validation function was applied as of the contained relay chain block number.
|
||||
ValidationFunctionApplied(RelayChainBlockNumber),
|
||||
}
|
||||
}
|
||||
|
||||
decl_error! {
|
||||
pub enum Error for Module<T: Trait> {
|
||||
/// Attempt to upgrade validation function while existing upgrade pending
|
||||
OverlappingUpgrades,
|
||||
/// Polkadot currently prohibits this parachain from upgrading its validation function
|
||||
ProhibitedByPolkadot,
|
||||
/// The supplied validation function has compiled into a blob larger than Polkadot is willing to run
|
||||
TooBig,
|
||||
/// The inherent which supplies the validation function params did not run this block
|
||||
ValidationFunctionParamsNotAvailable,
|
||||
}
|
||||
}
|
||||
|
||||
/// tests for this pallet
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use codec::Encode;
|
||||
use frame_support::{
|
||||
assert_ok, impl_outer_event, impl_outer_origin, parameter_types,
|
||||
traits::{OnFinalize, OnInitialize},
|
||||
weights::Weight,
|
||||
};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
testing::Header,
|
||||
traits::{BlakeTwo256, Dispatchable, IdentityLookup},
|
||||
Perbill,
|
||||
};
|
||||
use system::{InitKind, RawOrigin};
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test {}
|
||||
}
|
||||
|
||||
mod parachain_upgrade {
|
||||
pub use crate::Event;
|
||||
}
|
||||
|
||||
impl_outer_event! {
|
||||
pub enum TestEvent for Test {
|
||||
system<T>,
|
||||
parachain_upgrade,
|
||||
}
|
||||
}
|
||||
|
||||
// For testing the pallet, we construct most of a mock runtime. This means
|
||||
// first constructing a configuration type (`Test`) which `impl`s each of the
|
||||
// configuration traits of modules we want to use.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: Weight = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
|
||||
pub const Version: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: sp_version::create_runtime_str!("test"),
|
||||
impl_name: sp_version::create_runtime_str!("system-test"),
|
||||
authoring_version: 1,
|
||||
spec_version: 1,
|
||||
impl_version: 1,
|
||||
apis: sp_version::create_apis_vec!([]),
|
||||
transaction_version: 1,
|
||||
};
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Call = ();
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = TestEvent;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
type AvailableBlockRatio = AvailableBlockRatio;
|
||||
type Version = Version;
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type DbWeight = ();
|
||||
type BlockExecutionWeight = ();
|
||||
type ExtrinsicBaseWeight = ();
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Event = TestEvent;
|
||||
type Version = Version;
|
||||
type OnValidationFunctionParams = ();
|
||||
}
|
||||
|
||||
type ParachainUpgrade = Module<Test>;
|
||||
|
||||
// This function basically just builds a genesis storage key/value store according to
|
||||
// our desired mockup.
|
||||
fn new_test_ext() -> sp_io::TestExternalities {
|
||||
system::GenesisConfig::default()
|
||||
.build_storage::<Test>()
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
struct CallInWasm(Vec<u8>);
|
||||
|
||||
impl sp_core::traits::CallInWasm for CallInWasm {
|
||||
fn call_in_wasm(
|
||||
&self,
|
||||
_wasm_code: &[u8],
|
||||
_code_hash: Option<Vec<u8>>,
|
||||
_method: &str,
|
||||
_call_data: &[u8],
|
||||
_ext: &mut dyn sp_externalities::Externalities,
|
||||
_missing_host_functions: sp_core::traits::MissingHostFunctions,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
Ok(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn wasm_ext() -> sp_io::TestExternalities {
|
||||
let version = RuntimeVersion {
|
||||
spec_name: "test".into(),
|
||||
spec_version: 2,
|
||||
impl_version: 1,
|
||||
..Default::default()
|
||||
};
|
||||
let call_in_wasm = CallInWasm(version.encode());
|
||||
|
||||
let mut ext = new_test_ext();
|
||||
ext.register_extension(sp_core::traits::CallInWasmExt::new(call_in_wasm));
|
||||
ext
|
||||
}
|
||||
|
||||
struct BlockTest {
|
||||
n: <Test as system::Trait>::BlockNumber,
|
||||
within_block: Box<dyn Fn()>,
|
||||
after_block: Option<Box<dyn Fn()>>,
|
||||
}
|
||||
|
||||
/// BlockTests exist to test blocks with some setup: we have to assume that
|
||||
/// `validate_block` will mutate and check storage in certain predictable
|
||||
/// ways, for example, and we want to always ensure that tests are executed
|
||||
/// in the context of some particular block number.
|
||||
#[derive(Default)]
|
||||
struct BlockTests {
|
||||
tests: Vec<BlockTest>,
|
||||
pending_upgrade: Option<RelayChainBlockNumber>,
|
||||
ran: bool,
|
||||
vfp_maker: Option<Box<dyn Fn(&BlockTests, RelayChainBlockNumber) -> ValidationFunctionParams>>,
|
||||
}
|
||||
|
||||
impl BlockTests {
|
||||
fn new() -> BlockTests {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_raw(mut self, test: BlockTest) -> Self {
|
||||
self.tests.push(test);
|
||||
self
|
||||
}
|
||||
|
||||
fn add<F>(self, n: <Test as system::Trait>::BlockNumber, within_block: F) -> Self
|
||||
where
|
||||
F: 'static + Fn(),
|
||||
{
|
||||
self.add_raw(BlockTest {
|
||||
n,
|
||||
within_block: Box::new(within_block),
|
||||
after_block: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn add_with_post_test<F1, F2>(
|
||||
self,
|
||||
n: <Test as system::Trait>::BlockNumber,
|
||||
within_block: F1,
|
||||
after_block: F2,
|
||||
) -> Self
|
||||
where
|
||||
F1: 'static + Fn(),
|
||||
F2: 'static + Fn(),
|
||||
{
|
||||
self.add_raw(BlockTest {
|
||||
n,
|
||||
within_block: Box::new(within_block),
|
||||
after_block: Some(Box::new(after_block)),
|
||||
})
|
||||
}
|
||||
|
||||
fn with_validation_function_params<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: 'static + Fn(&BlockTests, RelayChainBlockNumber) -> ValidationFunctionParams,
|
||||
{
|
||||
self.vfp_maker = Some(Box::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
self.ran = true;
|
||||
wasm_ext().execute_with(|| {
|
||||
for BlockTest {
|
||||
n,
|
||||
within_block,
|
||||
after_block,
|
||||
} in self.tests.iter()
|
||||
{
|
||||
// clear pending updates, as applicable
|
||||
if let Some(upgrade_block) = self.pending_upgrade {
|
||||
if n >= &upgrade_block.into() {
|
||||
self.pending_upgrade = None;
|
||||
}
|
||||
}
|
||||
|
||||
// begin initialization
|
||||
System::<Test>::initialize(
|
||||
&n,
|
||||
&Default::default(),
|
||||
&Default::default(),
|
||||
&Default::default(),
|
||||
InitKind::Full,
|
||||
);
|
||||
|
||||
// now mess with the storage the way validate_block does
|
||||
let vfp = match self.vfp_maker {
|
||||
None => ValidationFunctionParams {
|
||||
max_code_size: 10 * 1024 * 1024, // 10 mb
|
||||
relay_chain_height: *n as RelayChainBlockNumber,
|
||||
code_upgrade_allowed: if self.pending_upgrade.is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(*n as RelayChainBlockNumber + 1000)
|
||||
},
|
||||
},
|
||||
Some(ref maker) => maker(self, *n as RelayChainBlockNumber),
|
||||
};
|
||||
storage::unhashed::put(VALIDATION_FUNCTION_PARAMS, &vfp);
|
||||
storage::unhashed::kill(NEW_VALIDATION_CODE);
|
||||
|
||||
// It is insufficient to push the validation function params
|
||||
// to storage; they must also be included in the inherent data.
|
||||
let inherent_data = {
|
||||
let mut inherent_data = InherentData::default();
|
||||
inherent_data
|
||||
.put_data(INHERENT_IDENTIFIER, &vfp)
|
||||
.expect("failed to put VFP inherent");
|
||||
inherent_data
|
||||
};
|
||||
|
||||
// execute the block
|
||||
ParachainUpgrade::on_initialize(*n);
|
||||
ParachainUpgrade::create_inherent(&inherent_data)
|
||||
.expect("got an inherent")
|
||||
.dispatch(RawOrigin::None.into())
|
||||
.expect("dispatch succeeded");
|
||||
within_block();
|
||||
ParachainUpgrade::on_finalize(*n);
|
||||
|
||||
// did block execution set new validation code?
|
||||
if storage::unhashed::exists(NEW_VALIDATION_CODE) {
|
||||
if self.pending_upgrade.is_some() {
|
||||
panic!("attempted to set validation code while upgrade was pending");
|
||||
}
|
||||
self.pending_upgrade = vfp.code_upgrade_allowed;
|
||||
}
|
||||
|
||||
// clean up
|
||||
System::<Test>::finalize();
|
||||
if let Some(after_block) = after_block {
|
||||
after_block();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BlockTests {
|
||||
fn drop(&mut self) {
|
||||
if !self.ran {
|
||||
self.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn block_tests_run_on_drop() {
|
||||
BlockTests::new().add(123, || {
|
||||
panic!("if this test passes, block tests run properly")
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requires_root() {
|
||||
BlockTests::new().add(123, || {
|
||||
assert_eq!(
|
||||
ParachainUpgrade::schedule_upgrade(Origin::signed(1), Default::default()),
|
||||
Err(sp_runtime::DispatchError::BadOrigin),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requires_root_2() {
|
||||
BlockTests::new().add(123, || {
|
||||
assert_ok!(ParachainUpgrade::schedule_upgrade(
|
||||
RawOrigin::Root.into(),
|
||||
Default::default()
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn events() {
|
||||
BlockTests::new()
|
||||
.add_with_post_test(
|
||||
123,
|
||||
|| {
|
||||
assert_ok!(ParachainUpgrade::schedule_upgrade(
|
||||
RawOrigin::Root.into(),
|
||||
Default::default()
|
||||
));
|
||||
},
|
||||
|| {
|
||||
let events = dbg!(System::<Test>::events());
|
||||
assert_eq!(
|
||||
events[0].event,
|
||||
TestEvent::parachain_upgrade(Event::ValidationFunctionStored(1123))
|
||||
);
|
||||
},
|
||||
)
|
||||
.add_with_post_test(
|
||||
1234,
|
||||
|| {},
|
||||
|| {
|
||||
let events = dbg!(System::<Test>::events());
|
||||
assert_eq!(
|
||||
events[0].event,
|
||||
TestEvent::parachain_upgrade(Event::ValidationFunctionApplied(1234))
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_overlapping() {
|
||||
BlockTests::new()
|
||||
.add(123, || {
|
||||
assert_ok!(ParachainUpgrade::schedule_upgrade(
|
||||
RawOrigin::Root.into(),
|
||||
Default::default()
|
||||
));
|
||||
})
|
||||
.add(234, || {
|
||||
assert_eq!(
|
||||
ParachainUpgrade::schedule_upgrade(RawOrigin::Root.into(), Default::default(),),
|
||||
Err(Error::<Test>::OverlappingUpgrades.into()),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manipulates_storage() {
|
||||
BlockTests::new()
|
||||
.add(123, || {
|
||||
assert!(
|
||||
!PendingValidationFunction::exists(),
|
||||
"validation function must not exist yet"
|
||||
);
|
||||
assert_ok!(ParachainUpgrade::schedule_upgrade(
|
||||
RawOrigin::Root.into(),
|
||||
Default::default()
|
||||
));
|
||||
assert!(
|
||||
PendingValidationFunction::exists(),
|
||||
"validation function must now exist"
|
||||
);
|
||||
})
|
||||
.add_with_post_test(
|
||||
1234,
|
||||
|| {},
|
||||
|| {
|
||||
assert!(
|
||||
!PendingValidationFunction::exists(),
|
||||
"validation function must have been unset"
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_size() {
|
||||
BlockTests::new()
|
||||
.with_validation_function_params(|_, n| ValidationFunctionParams {
|
||||
max_code_size: 32,
|
||||
relay_chain_height: n,
|
||||
code_upgrade_allowed: Some(n + 1000),
|
||||
})
|
||||
.add(123, || {
|
||||
assert_eq!(
|
||||
ParachainUpgrade::schedule_upgrade(RawOrigin::Root.into(), vec![0; 64]),
|
||||
Err(Error::<Test>::TooBig.into()),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,22 @@ edition = "2018"
|
||||
[dependencies]
|
||||
# Substrate dependencies
|
||||
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "cumulus-branch", default-features = false }
|
||||
sp-std = { git = "https://github.com/paritytech/substrate", branch = "cumulus-branch", default-features = false }
|
||||
|
||||
# Polkadot dependencies
|
||||
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "cumulus-branch", default-features = false }
|
||||
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "cumulus-branch", default-features = false }
|
||||
|
||||
# Other dependencies
|
||||
codec = { package = "parity-scale-codec", version = "1.0.5", default-features = false, features = [ "derive" ] }
|
||||
impl-trait-for-tuples = "0.1.3"
|
||||
|
||||
[features]
|
||||
default = [ "std" ]
|
||||
std = [
|
||||
"sp-std/std",
|
||||
"codec/std",
|
||||
"polkadot-primitives/std",
|
||||
"polkadot-parachain/std",
|
||||
"sp-inherents/std",
|
||||
]
|
||||
|
||||
+14
-1
@@ -18,6 +18,8 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub mod validation_function_params;
|
||||
|
||||
/// Identifiers and types related to Cumulus Inherents
|
||||
pub mod inherents {
|
||||
use sp_inherents::InherentIdentifier;
|
||||
@@ -26,7 +28,12 @@ pub mod inherents {
|
||||
pub const DOWNWARD_MESSAGES_IDENTIFIER: InherentIdentifier = *b"cumdownm";
|
||||
|
||||
/// The type of the inherent downward messages.
|
||||
pub type DownwardMessagesType = Vec<()>;
|
||||
pub type DownwardMessagesType = sp_std::vec::Vec<()>;
|
||||
|
||||
/// The identifier for the `validation_function_params` inherent.
|
||||
pub const VALIDATION_FUNCTION_PARAMS_IDENTIFIER: InherentIdentifier = *b"valfunp0";
|
||||
/// The type of the inherent.
|
||||
pub type ValidationFunctionParamsType = crate::validation_function_params::ValidationFunctionParams;
|
||||
}
|
||||
|
||||
/// Well known keys for values in the storage.
|
||||
@@ -35,6 +42,12 @@ pub mod well_known_keys {
|
||||
///
|
||||
/// The upward messages are stored as SCALE encoded `Vec<()>`.
|
||||
pub const UPWARD_MESSAGES: &'static [u8] = b":cumulus_upward_messages:";
|
||||
|
||||
/// Current validation function parameters.
|
||||
pub const VALIDATION_FUNCTION_PARAMS: &'static [u8] = b":validation_function_params";
|
||||
|
||||
/// Code upgarde (set as appropriate by a pallet).
|
||||
pub const NEW_VALIDATION_CODE: &'static [u8] = b":new_validation_code";
|
||||
}
|
||||
|
||||
/// Something that should be called when a downward message is received.
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Cumulus.
|
||||
|
||||
// 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Validation Function Parameters govern the ability of parachains to upgrade their validation functions.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use polkadot_parachain::primitives::{RelayChainBlockNumber, ValidationParams};
|
||||
use polkadot_primitives::parachain::{GlobalValidationSchedule, LocalValidationData};
|
||||
|
||||
/// Validation Function Parameters
|
||||
///
|
||||
/// This struct is the subset of [`ValidationParams`](polkadot_parachain::ValidationParams)
|
||||
/// which is of interest when upgrading parachain validation functions.
|
||||
#[derive(PartialEq, Eq, Encode, Decode, Clone, Copy, Default)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct ValidationFunctionParams {
|
||||
/// The maximum code size permitted, in bytes.
|
||||
pub max_code_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<RelayChainBlockNumber>,
|
||||
}
|
||||
|
||||
impl From<&ValidationParams> for ValidationFunctionParams {
|
||||
fn from(vp: &ValidationParams) -> Self {
|
||||
ValidationFunctionParams {
|
||||
max_code_size: vp.max_code_size,
|
||||
relay_chain_height: vp.relay_chain_height,
|
||||
code_upgrade_allowed: vp.code_upgrade_allowed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(GlobalValidationSchedule, LocalValidationData)> for ValidationFunctionParams {
|
||||
fn from(t: (GlobalValidationSchedule, LocalValidationData)) -> Self {
|
||||
let (global_validation, local_validation) = t;
|
||||
ValidationFunctionParams {
|
||||
max_code_size: global_validation.max_code_size,
|
||||
relay_chain_height: global_validation.block_number,
|
||||
code_upgrade_allowed: local_validation.code_upgrade_allowed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait which is called when the validation function parameters are set
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
pub trait OnValidationFunctionParams {
|
||||
fn on_validation_function_params(vfp: ValidationFunctionParams);
|
||||
}
|
||||
@@ -12,6 +12,9 @@ hash-db = { version = "0.15.2", default-features = false }
|
||||
trie-db = { version = "0.20.1", default-features = false }
|
||||
hashbrown = "0.6.1"
|
||||
|
||||
# Cumulus dependencies
|
||||
cumulus-primitives = { path = "../primitives", default-features = false }
|
||||
|
||||
# Substrate dependencies
|
||||
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
@@ -19,6 +22,7 @@ sp-core = { git = "https://github.com/paritytech/substrate", default-features =
|
||||
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
frame-executive = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-trie = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
|
||||
# Polkadot dependencies
|
||||
parachain = { package = "polkadot-parachain", git = "https://github.com/paritytech/polkadot", branch = "cumulus-branch", default-features = false, features = [ "wasm-api" ] }
|
||||
@@ -35,6 +39,7 @@ test-client = { package = "cumulus-test-client", path = "../test/client" }
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"cumulus-primitives/std",
|
||||
"sp-std/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
|
||||
@@ -20,18 +20,22 @@ use crate::WitnessData;
|
||||
use frame_executive::ExecuteBlock;
|
||||
use sp_runtime::traits::{Block as BlockT, HashFor, Header as HeaderT};
|
||||
|
||||
use sp_trie::{delta_trie_root, read_trie_value, Layout, MemoryDB};
|
||||
|
||||
use sp_std::{boxed::Box, vec::Vec};
|
||||
use sp_trie::{delta_trie_root, read_trie_value, Layout, MemoryDB};
|
||||
|
||||
use hash_db::{HashDB, EMPTY_PREFIX};
|
||||
|
||||
use trie_db::{Trie, TrieDB};
|
||||
|
||||
use parachain::primitives::{HeadData, ValidationParams, ValidationResult};
|
||||
use parachain::primitives::{HeadData, ValidationCode, ValidationParams, ValidationResult};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
use cumulus_primitives::{
|
||||
validation_function_params::ValidationFunctionParams,
|
||||
well_known_keys::{NEW_VALIDATION_CODE, VALIDATION_FUNCTION_PARAMS},
|
||||
};
|
||||
|
||||
/// Stores the global [`Storage`] instance.
|
||||
///
|
||||
/// As wasm is always executed with one thread, this global varibale is safe!
|
||||
@@ -87,14 +91,18 @@ pub fn validate_block<B: BlockT, E: ExecuteBlock<B>>(params: ValidationParams) -
|
||||
"Invalid parent hash"
|
||||
);
|
||||
|
||||
let storage = WitnessStorage::<B>::new(
|
||||
// make a copy for later use
|
||||
let validation_function_params = (¶ms).into();
|
||||
|
||||
let storage_inner = WitnessStorage::<B>::new(
|
||||
block_data.witness_data,
|
||||
block_data.witness_data_storage_root,
|
||||
validation_function_params,
|
||||
)
|
||||
.expect("Witness data and storage root always match; qed");
|
||||
|
||||
let _guard = unsafe {
|
||||
STORAGE = Some(Box::new(storage));
|
||||
STORAGE = Some(Box::new(storage_inner));
|
||||
(
|
||||
// Replace storage calls with our own implementations
|
||||
sp_io::storage::host_read.replace_implementation(host_storage_read),
|
||||
@@ -110,9 +118,16 @@ pub fn validate_block<B: BlockT, E: ExecuteBlock<B>>(params: ValidationParams) -
|
||||
|
||||
E::execute_block(block);
|
||||
|
||||
// if in the course of block execution new validation code was set, insert
|
||||
// its scheduled upgrade so we can validate that block number later
|
||||
let new_validation_code = storage().get(NEW_VALIDATION_CODE).map(ValidationCode);
|
||||
if new_validation_code.is_some() && validation_function_params.code_upgrade_allowed.is_none() {
|
||||
panic!("attempt to upgrade validation function when not permitted");
|
||||
}
|
||||
|
||||
ValidationResult {
|
||||
head_data,
|
||||
new_validation_code: None,
|
||||
new_validation_code,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,13 +137,14 @@ struct WitnessStorage<B: BlockT> {
|
||||
witness_data: MemoryDB<HashFor<B>>,
|
||||
overlay: hashbrown::HashMap<Vec<u8>, Option<Vec<u8>>>,
|
||||
storage_root: B::Hash,
|
||||
params: ValidationFunctionParams,
|
||||
}
|
||||
|
||||
impl<B: BlockT> WitnessStorage<B> {
|
||||
/// Initialize from the given witness data and storage root.
|
||||
///
|
||||
/// Returns an error if given storage root was not found in the witness data.
|
||||
fn new(data: WitnessData, storage_root: B::Hash) -> Result<Self, &'static str> {
|
||||
fn new(data: WitnessData, storage_root: B::Hash, params: ValidationFunctionParams) -> Result<Self, &'static str> {
|
||||
let mut db = MemoryDB::default();
|
||||
data.into_iter().for_each(|i| {
|
||||
db.insert(EMPTY_PREFIX, &i);
|
||||
@@ -142,24 +158,30 @@ impl<B: BlockT> WitnessStorage<B> {
|
||||
witness_data: db,
|
||||
overlay: Default::default(),
|
||||
storage_root,
|
||||
params,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BlockT> Storage for WitnessStorage<B> {
|
||||
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
|
||||
self.overlay
|
||||
.get(key)
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
read_trie_value::<Layout<HashFor<B>>, _>(
|
||||
&self.witness_data,
|
||||
&self.storage_root,
|
||||
key,
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
.unwrap_or(None)
|
||||
match key {
|
||||
VALIDATION_FUNCTION_PARAMS => Some(self.params.encode()),
|
||||
key => {
|
||||
self.overlay
|
||||
.get(key)
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
read_trie_value::<Layout<HashFor<B>>, _>(
|
||||
&self.witness_data,
|
||||
&self.storage_root,
|
||||
key,
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
.unwrap_or(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&mut self, key: &[u8], value: &[u8]) {
|
||||
|
||||
@@ -23,7 +23,7 @@ structopt = "0.3.3"
|
||||
parachain-runtime = { package = "cumulus-test-parachain-runtime", path = "runtime" }
|
||||
|
||||
# Substrate dependencies
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-io = { git = "https://github.com/paritytech/substrate", branch = "cumulus-branch" }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", branch = "cumulus-branch" }
|
||||
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "cumulus-branch" }
|
||||
|
||||
@@ -9,26 +9,26 @@ serde = { version = "1.0.101", optional = true, features = ["derive"] }
|
||||
codec = { package = "parity-scale-codec", version = "1.0.6", default-features = false, features = ["derive"] }
|
||||
|
||||
# Substrate dependencies
|
||||
sp-std = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
sp-api = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
sp-io = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
sp-version = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-version = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-session = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-offchain = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-block-builder = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-transaction-pool = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
frame-executive = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
pallet-balances = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
pallet-indices = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
pallet-timestamp = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
pallet-sudo = { git = "https://github.com/paritytech/substrate", default_features = false, branch = "cumulus-branch" }
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
frame-executive = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
pallet-indices = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
pallet-sudo = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "cumulus-branch" }
|
||||
|
||||
# Cumulus dependencies
|
||||
|
||||
Reference in New Issue
Block a user