From 0831f74fe312a813610ae8616e54fd3fc1090d25 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet <cecile@parity.io> Date: Fri, 16 Apr 2021 08:48:13 +0100 Subject: [PATCH] Runtime upgrade test (#364) --- cumulus/Cargo.lock | 36 ++++++ cumulus/Cargo.toml | 1 + cumulus/test/runtime-upgrade/Cargo.toml | 69 +++++++++++ cumulus/test/runtime-upgrade/build.rs | 1 + cumulus/test/runtime-upgrade/src | 1 + cumulus/test/runtime/Cargo.toml | 1 + cumulus/test/runtime/src/lib.rs | 28 ++++- cumulus/test/service/Cargo.toml | 9 +- cumulus/test/service/src/lib.rs | 113 ++++++++++++++++-- cumulus/test/service/tests/integration.rs | 1 - cumulus/test/service/tests/runtime_upgrade.rs | 102 ++++++++++++++++ 11 files changed, 352 insertions(+), 10 deletions(-) create mode 100644 cumulus/test/runtime-upgrade/Cargo.toml create mode 120000 cumulus/test/runtime-upgrade/build.rs create mode 120000 cumulus/test/runtime-upgrade/src create mode 100644 cumulus/test/service/tests/runtime_upgrade.rs diff --git a/cumulus/Cargo.lock b/cumulus/Cargo.lock index 075033bc490..60edb42e31f 100644 --- a/cumulus/Cargo.lock +++ b/cumulus/Cargo.lock @@ -1673,6 +1673,37 @@ dependencies = [ "substrate-wasm-builder 3.0.0", ] +[[package]] +name = "cumulus-test-runtime-upgrade" +version = "0.1.0" +dependencies = [ + "cumulus-pallet-parachain-system", + "cumulus-primitives-core", + "frame-executive", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-randomness-collective-flip", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "parity-scale-codec", + "polkadot-parachain", + "serde", + "sp-api", + "sp-block-builder", + "sp-core", + "sp-inherents", + "sp-io", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std", + "sp-transaction-pool", + "sp-version", + "substrate-wasm-builder 3.0.0", +] + [[package]] name = "cumulus-test-service" version = "0.1.0" @@ -1683,8 +1714,11 @@ dependencies = [ "cumulus-primitives-core", "cumulus-test-relay-validation-worker-provider", "cumulus-test-runtime", + "cumulus-test-runtime-upgrade", + "frame-system", "futures 0.3.14", "jsonrpc-core", + "pallet-transaction-payment", "parity-scale-codec", "polkadot-overseer", "polkadot-primitives", @@ -1705,7 +1739,9 @@ dependencies = [ "sc-transaction-pool", "serde", "sp-api", + "sp-arithmetic", "sp-block-builder", + "sp-blockchain", "sp-consensus", "sp-core", "sp-inherents", diff --git a/cumulus/Cargo.toml b/cumulus/Cargo.toml index 1966e362659..ae875082a7c 100644 --- a/cumulus/Cargo.toml +++ b/cumulus/Cargo.toml @@ -18,6 +18,7 @@ members = [ "rococo-parachains/runtime", "rococo-parachains/shell-runtime", "test/runtime", + "test/runtime-upgrade", "test/client", "test/service", "test/relay-sproof-builder", diff --git a/cumulus/test/runtime-upgrade/Cargo.toml b/cumulus/test/runtime-upgrade/Cargo.toml new file mode 100644 index 00000000000..898cb4677e7 --- /dev/null +++ b/cumulus/test/runtime-upgrade/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "cumulus-test-runtime-upgrade" +version = "0.1.0" +authors = ["Parity Technologies <admin@parity.io>"] +edition = "2018" + +[dependencies] +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.101", optional = true, features = ["derive"] } + +# Substrate dependencies +frame-executive = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-sudo = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-block-builder = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-offchain = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-session = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-transaction-pool = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-version = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } + +# Cumulus dependencies +cumulus-pallet-parachain-system = { path = "../../pallets/parachain-system", default-features = false } +cumulus-primitives-core = { path = "../../primitives/core", default-features = false } + +# Polkadot dependencies +polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } + +[build-dependencies] +substrate-wasm-builder = "3.0.0" + +[features] +default = [ "std", "upgrade" ] +std = [ + "codec/std", + "cumulus-pallet-parachain-system/std", + "cumulus-primitives-core/std", + "frame-executive/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "pallet-randomness-collective-flip/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment/std", + "serde", + "sp-api/std", + "sp-block-builder/std", + "sp-core/std", + "sp-inherents/std", + "sp-io/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-transaction-pool/std", + "sp-version/std", +] +upgrade = [] diff --git a/cumulus/test/runtime-upgrade/build.rs b/cumulus/test/runtime-upgrade/build.rs new file mode 120000 index 00000000000..f0e55a8c430 --- /dev/null +++ b/cumulus/test/runtime-upgrade/build.rs @@ -0,0 +1 @@ +../runtime/build.rs \ No newline at end of file diff --git a/cumulus/test/runtime-upgrade/src b/cumulus/test/runtime-upgrade/src new file mode 120000 index 00000000000..b374eea6a39 --- /dev/null +++ b/cumulus/test/runtime-upgrade/src @@ -0,0 +1 @@ +../runtime/src \ No newline at end of file diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index b6dad7f162f..5b0798e7869 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -66,3 +66,4 @@ std = [ "sp-transaction-pool/std", "sp-version/std", ] +upgrade = [] diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index ecb758fd20a..6d5bf24eeea 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -47,6 +47,7 @@ pub use frame_support::{ }; use frame_system::limits::{BlockLength, BlockWeights}; pub use pallet_balances::Call as BalancesCall; +pub use pallet_sudo::Call as SudoCall; pub use pallet_timestamp::Call as TimestampCall; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; @@ -58,12 +59,17 @@ impl_opaque_keys! { pub struct SessionKeys {} } +const SPEC_VERSION: u32 = 3; + /// This runtime version. pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("cumulus-test-parachain"), impl_name: create_runtime_str!("cumulus-test-parachain"), authoring_version: 1, - spec_version: 3, + #[cfg(feature = "upgrade")] + spec_version: SPEC_VERSION + 1, + #[cfg(not(feature = "upgrade"))] + spec_version: SPEC_VERSION, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -220,6 +226,15 @@ impl cumulus_pallet_parachain_system::Config for Runtime { parameter_types! { pub storage ParachainId: cumulus_primitives_core::ParaId = 100.into(); + pub storage UpgradeDetection: bool = false; +} + +pub struct UpgradeDetectionOnRuntimeUpgrade; +impl frame_support::traits::OnRuntimeUpgrade for UpgradeDetectionOnRuntimeUpgrade { + fn on_runtime_upgrade() -> u64 { + UpgradeDetection::set(&true); + 0 + } } construct_runtime! { @@ -284,6 +299,7 @@ pub type Executive = frame_executive::Executive< frame_system::ChainContext<Runtime>, Runtime, AllPallets, + UpgradeDetectionOnRuntimeUpgrade, >; /// The payload being signed in transactions. pub type SignedPayload = generic::SignedPayload<Call, SignedExtra>; @@ -293,6 +309,10 @@ decl_runtime_apis! { /// Returns the last timestamp of a runtime. fn get_last_timestamp() -> u64; } + pub trait GetUpgradeDetection { + /// Returns `true` if the runtime has been upgraded at least once. + fn has_upgraded() -> bool; + } } impl_runtime_apis! { @@ -372,6 +392,12 @@ impl_runtime_apis! { Timestamp::now() } } + + impl crate::GetUpgradeDetection<Block> for Runtime { + fn has_upgraded() -> bool { + UpgradeDetection::get() + } + } } cumulus_pallet_parachain_system::register_validate_block!(Runtime, Executive); diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index 47553cb45ff..a42775ddcaa 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -10,6 +10,8 @@ rand = "0.7.3" serde = { version = "1.0.101", features = ["derive"] } # Substrate +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-chain-spec = { git = "https://github.com/paritytech/substrate", branch = "master" } @@ -22,7 +24,9 @@ sc-telemetry = { git = "https://github.com/paritytech/substrate", branch = "mast sc-tracing = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master" } @@ -58,5 +62,8 @@ futures = { version = "0.3.5" } tokio = { version = "0.2.21", features = ["macros"] } # Substrate dependencies -substrate-test-utils = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-cli = { git = "https://github.com/paritytech/substrate", branch = "master" } +substrate-test-utils = { git = "https://github.com/paritytech/substrate", branch = "master" } + +# Cumulus +cumulus-test-runtime-upgrade = { path = "../runtime-upgrade" } diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index 4028d26c73d..4ffbf8f7d03 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -41,13 +41,21 @@ use sc_service::{ BasePath, ChainSpec, Configuration, Error as ServiceError, PartialComponents, Role, RpcHandlers, TFullBackend, TFullClient, TaskExecutor, TaskManager, }; +use sp_arithmetic::traits::SaturatedConversion; +use sp_blockchain::HeaderBackend; use sp_core::{Pair, H256}; use sp_keyring::Sr25519Keyring; -use sp_runtime::traits::BlakeTwo256; +use sp_runtime::{ + codec::Encode, + generic, + traits::BlakeTwo256 +}; use sp_state_machine::BasicExternalities; use sp_trie::PrefixedMemoryDB; use std::sync::Arc; -use substrate_test_client::BlockchainEventsExt; +use substrate_test_client::{ + BlockchainEventsExt, RpcHandlersExt, RpcTransactionError, RpcTransactionOutput, +}; pub use chain_spec::*; pub use cumulus_test_runtime as runtime; @@ -61,6 +69,9 @@ native_executor_instance!( cumulus_test_runtime::native_version, ); +/// The client type being used by the test service. +pub type Client = TFullClient<runtime::NodeBlock, runtime::RuntimeApi, RuntimeExecutor>; + /// Starts a `ServiceBuilder` for a full service. /// /// Use this macro if you don't actually need the full service, but just the builder in order to @@ -69,11 +80,11 @@ pub fn new_partial( config: &mut Configuration, ) -> Result< PartialComponents< - TFullClient<Block, RuntimeApi, RuntimeExecutor>, + Client, TFullBackend<Block>, (), sp_consensus::import_queue::BasicQueue<Block, PrefixedMemoryDB<BlakeTwo256>>, - sc_transaction_pool::FullPool<Block, TFullClient<Block, RuntimeApi, RuntimeExecutor>>, + sc_transaction_pool::FullPool<Block, Client>, (), >, sc_service::Error, @@ -279,7 +290,7 @@ pub struct TestNode { /// TaskManager's instance. pub task_manager: TaskManager, /// Client's instance. - pub client: Arc<TFullClient<Block, RuntimeApi, RuntimeExecutor>>, + pub client: Arc<Client>, /// Node's network. pub network: Arc<NetworkService<Block, H256>>, /// The `MultiaddrWithPeerId` to this node. This is useful if you want to pass it as "boot node" @@ -299,6 +310,8 @@ pub struct TestNodeBuilder { parachain_nodes: Vec<MultiaddrWithPeerId>, parachain_nodes_exclusive: bool, relay_chain_nodes: Vec<MultiaddrWithPeerId>, + storage_update_func_parachain: Option<Box<dyn Fn()>>, + storage_update_func_relay_chain: Option<Box<dyn Fn()>>, } impl TestNodeBuilder { @@ -316,6 +329,8 @@ impl TestNodeBuilder { parachain_nodes: Vec::new(), parachain_nodes_exclusive: false, relay_chain_nodes: Vec::new(), + storage_update_func_parachain: None, + storage_update_func_relay_chain: None, } } @@ -380,10 +395,28 @@ impl TestNodeBuilder { self } + /// Allows accessing the parachain storage before the test node is built. + pub fn update_storage_parachain( + mut self, + updater: impl Fn() + 'static, + ) -> Self { + self.storage_update_func_parachain = Some(Box::new(updater)); + self + } + + /// Allows accessing the relay chain storage before the test node is built. + pub fn update_storage_relay_chain( + mut self, + updater: impl Fn() + 'static, + ) -> Self { + self.storage_update_func_relay_chain = Some(Box::new(updater)); + self + } + /// Build the [`TestNode`]. pub async fn build(self) -> TestNode { let parachain_config = node_config( - || (), + self.storage_update_func_parachain.unwrap_or_else(|| Box::new(|| ())), self.task_executor.clone(), self.key.clone(), self.parachain_nodes, @@ -393,7 +426,7 @@ impl TestNodeBuilder { ) .expect("could not generate Configuration"); let mut relay_chain_config = polkadot_test_service::node_config( - || (), + self.storage_update_func_relay_chain.unwrap_or_else(|| Box::new(|| ())), self.task_executor, self.key, self.relay_chain_nodes, @@ -546,6 +579,72 @@ impl TestNode { pub fn wait_for_blocks(&self, count: usize) -> impl Future<Output = ()> { self.client.wait_for_blocks(count) } + + /// Send an extrinsic to this node. + pub async fn send_extrinsic( + &self, + function: impl Into<runtime::Call>, + caller: Sr25519Keyring, + ) -> Result<RpcTransactionOutput, RpcTransactionError> { + let extrinsic = construct_extrinsic(&*self.client, function, caller); + + self.rpc_handlers.send_transaction(extrinsic.into()).await + } + + /// Register a parachain at this relay chain. + pub async fn schedule_upgrade(&self, validation: Vec<u8>) -> Result<(), RpcTransactionError> { + let call = frame_system::Call::set_code_without_checks(validation); + + self.send_extrinsic( + runtime::SudoCall::sudo_unchecked_weight(Box::new(call.into()), 1_000), + Sr25519Keyring::Alice, + ).await.map(drop) + } +} + +/// Construct an extrinsic that can be applied to the test runtime. +pub fn construct_extrinsic( + client: &Client, + function: impl Into<runtime::Call>, + caller: Sr25519Keyring, +) -> runtime::UncheckedExtrinsic { + let function = function.into(); + let current_block_hash = client.info().best_hash; + let current_block = client.info().best_number.saturated_into(); + let genesis_block = client.hash(0).unwrap().unwrap(); + let nonce = 0; + let period = runtime::BlockHashCount::get() + .checked_next_power_of_two() + .map(|c| c / 2) + .unwrap_or(2) as u64; + let tip = 0; + let extra: runtime::SignedExtra = ( + frame_system::CheckSpecVersion::<runtime::Runtime>::new(), + frame_system::CheckGenesis::<runtime::Runtime>::new(), + frame_system::CheckEra::<runtime::Runtime>::from(generic::Era::mortal(period, current_block)), + frame_system::CheckNonce::<runtime::Runtime>::from(nonce), + frame_system::CheckWeight::<runtime::Runtime>::new(), + pallet_transaction_payment::ChargeTransactionPayment::<runtime::Runtime>::from(tip), + ); + let raw_payload = runtime::SignedPayload::from_raw( + function.clone(), + extra.clone(), + ( + runtime::VERSION.spec_version, + genesis_block, + current_block_hash, + (), + (), + (), + ), + ); + let signature = raw_payload.using_encoded(|e| caller.sign(e)); + runtime::UncheckedExtrinsic::new_signed( + function.clone(), + caller.public().into(), + runtime::Signature::Sr25519(signature.clone()), + extra.clone(), + ) } /// Run a relay-chain validator node. diff --git a/cumulus/test/service/tests/integration.rs b/cumulus/test/service/tests/integration.rs index 5fbe7c89a85..76bcd4c5317 100644 --- a/cumulus/test/service/tests/integration.rs +++ b/cumulus/test/service/tests/integration.rs @@ -61,7 +61,6 @@ async fn test_collating_and_non_collator_mode_catching_up(task_executor: TaskExe .connect_to_relay_chain_nodes(vec![&alice, &bob]) .build() .await; - dave.wait_for_blocks(7).await; join!( diff --git a/cumulus/test/service/tests/runtime_upgrade.rs b/cumulus/test/service/tests/runtime_upgrade.rs new file mode 100644 index 00000000000..12d1c6bf70b --- /dev/null +++ b/cumulus/test/service/tests/runtime_upgrade.rs @@ -0,0 +1,102 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// 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 <http://www.gnu.org/licenses/>. + +use cumulus_primitives_core::ParaId; +use cumulus_test_service::{initial_head_data, run_relay_chain_validator_node, Keyring::*}; +use futures::join; +use sc_service::TaskExecutor; +use sc_client_api::client::BlockchainEvents; +use futures::StreamExt; +use sp_api::ProvideRuntimeApi; +use cumulus_test_runtime::GetUpgradeDetection; +use sp_runtime::generic::BlockId; + +#[substrate_test_utils::test] +async fn test_runtime_upgrade(task_executor: TaskExecutor) { + let mut builder = sc_cli::LoggerBuilder::new("runtime=debug"); + builder.with_colors(false); + let _ = builder.init(); + + let para_id = ParaId::from(100); + + // start alice + let alice = run_relay_chain_validator_node(task_executor.clone(), Alice, || {}, vec![]); + + // start bob + let bob = + run_relay_chain_validator_node(task_executor.clone(), Bob, || {}, vec![alice.addr.clone()]); + + // register parachain + alice + .register_parachain( + para_id, + cumulus_test_runtime::WASM_BINARY + .expect("You need to build the WASM binary to run this test!") + .to_vec(), + initial_head_data(para_id), + ) + .await + .unwrap(); + + // run cumulus charlie (a parachain collator) + let charlie = + cumulus_test_service::TestNodeBuilder::new(para_id, task_executor.clone(), Charlie) + .enable_collator() + .connect_to_relay_chain_nodes(vec![&alice, &bob]) + .build() + .await; + + // run cumulus dave (a parachain full node) and wait for it to sync some blocks + let dave = cumulus_test_service::TestNodeBuilder::new(para_id, task_executor.clone(), Dave) + .connect_to_parachain_node(&charlie) + .connect_to_relay_chain_nodes(vec![&alice, &bob]) + .build() + .await; + + let mut import_notification_stream = charlie.client.import_notification_stream(); + + while let Some(notification) = import_notification_stream.next().await { + if notification.is_new_best { + let res = charlie.client.runtime_api() + .has_upgraded(&BlockId::Hash(notification.hash)); + if matches!(res, Ok(false)) { + break; + } + } + } + + // schedule runtime upgrade + charlie.schedule_upgrade(cumulus_test_runtime_upgrade::WASM_BINARY.unwrap().to_vec()) + .await + .unwrap(); + + while let Some(notification) = import_notification_stream.next().await { + if notification.is_new_best { + let res = charlie.client.runtime_api() + .has_upgraded(&BlockId::Hash(notification.hash)); + if res.unwrap_or(false) { + break; + } + } + } + + join!( + alice.task_manager.clean_shutdown(), + bob.task_manager.clean_shutdown(), + charlie.task_manager.clean_shutdown(), + dave.task_manager.clean_shutdown(), + ); +} -- GitLab