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