diff --git a/.github/workflows/rust-checks.yaml b/.github/workflows/rust-checks.yaml index 6fb26152264..6c36abec0bb 100644 --- a/.github/workflows/rust-checks.yaml +++ b/.github/workflows/rust-checks.yaml @@ -10,32 +10,61 @@ concurrency: group: ${{ github.ref }}-${{ github.workflow }} cancel-in-progress: true +env: + RUST_BACKTRACE: 1 + # pin nightly to avoid constantly throwing out cache + TOOLCHAIN_LINT: nightly-2023-11-13 + jobs: - rust-checks: - runs-on: ubuntu-20.04 + lint: + runs-on: ubuntu-latest steps: - - name: Checkout source code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 + - name: Install Rust ${{ env.TOOLCHAIN_LINT }} + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.TOOLCHAIN_LINT }} + targets: wasm32-unknown-unknown + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + with: + key: lint-v0 + - name: cargo fmt + run: cargo +${{ env.TOOLCHAIN_LINT }} fmt --all -- --check + - name: Install deps for musl build + run: | + sudo apt-get update + sudo apt-get install -y protobuf-compiler musl-tools clang build-essential curl llvm-dev libclang-dev linux-headers-generic libsnappy-dev liblz4-dev libzstd-dev libgflags-dev zlib1g-dev libbz2-dev + sudo ln -s /usr/bin/g++ /usr/bin/musl-g++ + - name: cargo clippy + uses: actions-rs-plus/clippy-check@v2 + with: + toolchain: ${{ env.TOOLCHAIN_LINT }} + args: --all-targets --all-features --locked --no-deps -- --deny warnings + test: + strategy: + matrix: + os: [ubuntu-latest] + toolchain: [stable] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Install Rust ${{ matrix.toolchain }} + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.toolchain }} + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 - name: Install Protoc uses: arduino/setup-protoc@v1 with: version: "3.6.1" - - - name: Install clippy and fmt - run: rustup component add clippy rustfmt - - - name: Add wasm32-unknown-unknown target - run: rustup target add wasm32-unknown-unknown - - - name: Run Format Checks - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all - - - name: Clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --all-targets -- --no-deps -D warnings + - name: build try-runtime-cli + # this is required for testing + # build --release or the execution time of the test is too long + run: cargo build --release -p try-runtime-cli + - name: cargo test + run: cargo test --release + - name: Check disk space + run: df . -h diff --git a/Cargo.lock b/Cargo.lock index 2a4d02efc31..cfd788c4a6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11262,7 +11262,7 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "try-runtime-cli" -version = "0.3.5" +version = "0.4.0" dependencies = [ "clap", "env_logger", @@ -11314,7 +11314,7 @@ dependencies = [ [[package]] name = "try-runtime-core" -version = "0.3.5" +version = "0.4.0" dependencies = [ "assert_cmd", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 70d1f1bc9c1..772eccdd395 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ ] [workspace.package] -version = "0.3.5" +version = "0.4.0" authors = ["Parity Technologies <admin@parity.io>"] description = "Substrate's programmatic testing framework." edition = "2021" diff --git a/core/src/commands/execute_block.rs b/core/src/commands/execute_block.rs index 6d5f40a2eb1..f06aad82ffa 100644 --- a/core/src/commands/execute_block.rs +++ b/core/src/commands/execute_block.rs @@ -19,7 +19,6 @@ use std::{fmt::Debug, str::FromStr}; use parity_scale_codec::Encode; use sc_executor::sp_wasm_interface::HostFunctions; -use sp_rpc::{list::ListOrValue, number::NumberOrHex}; use sp_runtime::{ generic::SignedBlock, traits::{Block as BlockT, Header as HeaderT, NumberFor}, @@ -108,18 +107,37 @@ where // get the block number associated with this block. let block_ws_uri = command.block_ws_uri(); let rpc = ws_client(&block_ws_uri).await?; - let next_hash = next_hash_of::<Block>(&rpc, ext.block_hash).await?; - log::info!(target: LOG_TARGET, "fetching next block: {:?} ", next_hash); + let live_state = match command.state { + State::Live(live_state) => live_state, + _ => { + unreachable!("execute block currently only supports Live state") + } + }; - let block = ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::block( - &rpc, - Some(next_hash), - ) - .await - .map_err(rpc_err_handler)? - .expect("header exists, block should also exist; qed") - .block; + // The block we want to *execute* at is the block passed by the user + let execute_at = live_state.at::<Block>()?; + + let prev_block_live_state = live_state.to_prev_block_live_state::<Block>().await?; + + // Get state for the prev block + let ext = State::Live(prev_block_live_state) + .to_ext::<Block, HostFns>( + &shared, + &executor, + None, + TryRuntimeFeatureCheck::Check, + SpecVersionCheck::Skip, + ) + .await?; + + // Execute the desired block on top of it + let block = + ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::block(&rpc, execute_at) + .await + .map_err(rpc_err_handler)? + .expect("header exists, block should also exist; qed") + .block; // A digest item gets added when the runtime is processing the block, so we need to pop // the last one to be consistent with what a gossiped block would contain. @@ -140,6 +158,7 @@ where let _ = state_machine_call_with_proof::<Block, HostFns>( &ext, + &mut Default::default(), &executor, "TryRuntime_execute_block", &payload, @@ -149,35 +168,3 @@ where Ok(()) } - -pub(crate) async fn next_hash_of<Block: BlockT>( - rpc: &substrate_rpc_client::WsClient, - hash: Block::Hash, -) -> sc_cli::Result<Block::Hash> -where - Block: BlockT + serde::de::DeserializeOwned, - Block::Header: serde::de::DeserializeOwned, -{ - let number = ChainApi::<(), Block::Hash, Block::Header, ()>::header(rpc, Some(hash)) - .await - .map_err(rpc_err_handler) - .and_then(|maybe_header| maybe_header.ok_or("header_not_found").map(|h| *h.number()))?; - - let next = number + sp_runtime::traits::One::one(); - - let next_hash = match ChainApi::<(), Block::Hash, Block::Header, ()>::block_hash( - rpc, - Some(ListOrValue::Value(NumberOrHex::Number( - next.try_into() - .map_err(|_| "failed to convert number to block number")?, - ))), - ) - .await - .map_err(rpc_err_handler)? - { - ListOrValue::Value(t) => t.expect("value passed in; value comes out; qed"), - _ => unreachable!(), - }; - - Ok(next_hash) -} diff --git a/core/src/commands/fast_forward.rs b/core/src/commands/fast_forward.rs index 6076ab03121..84f711a154f 100644 --- a/core/src/commands/fast_forward.rs +++ b/core/src/commands/fast_forward.rs @@ -246,6 +246,7 @@ where log::info!("Running migrations..."); state_machine_call_with_proof::<Block, HostFns>( &ext, + &mut Default::default(), &executor, "TryRuntime_on_runtime_upgrade", command.try_state.encode().as_ref(), diff --git a/core/src/commands/follow_chain.rs b/core/src/commands/follow_chain.rs index 0047e064410..367760f251a 100644 --- a/core/src/commands/follow_chain.rs +++ b/core/src/commands/follow_chain.rs @@ -157,8 +157,10 @@ where .as_mut() .expect("state_ext either existed or was just created"); + let mut overlayed_changes = Default::default(); let result = state_machine_call_with_proof::<Block, HostFns>( state_ext, + &mut overlayed_changes, &executor, "TryRuntime_execute_block", ( @@ -186,9 +188,7 @@ where continue; } - let (mut changes, _, _) = result.expect("checked to be Ok; qed"); - - let storage_changes = changes + let storage_changes = overlayed_changes .drain_storage_changes( &state_ext.backend, // Note that in case a block contains a runtime upgrade, state version could diff --git a/core/src/commands/offchain_worker.rs b/core/src/commands/offchain_worker.rs index d2c64b4f5f7..a6853935994 100644 --- a/core/src/commands/offchain_worker.rs +++ b/core/src/commands/offchain_worker.rs @@ -26,7 +26,7 @@ use crate::{ build_executor, commands::execute_block::next_hash_of, full_extensions, parse, rpc_err_handler, - state::{LiveState, RuntimeChecks, State}, + state::{LiveState, RuntimeChecks, SpecVersionCheck, State, TryRuntimeFeatureCheck}, state_machine_call, SharedParams, LOG_TARGET, }; @@ -85,14 +85,20 @@ where .state .to_ext::<Block, HostFns>(&shared, &executor, None, runtime_checks) .await?; + let block_ws_uri = command.header_ws_uri(); + let rpc = ws_client(&block_ws_uri).await?; - let header_ws_uri = command.header_ws_uri(); + // The block we want to *execute* at is the block passed by the user + let execute_at = live_state.at::<Block>()?; - let rpc = ws_client(&header_ws_uri).await?; - let next_hash = next_hash_of::<Block>(&rpc, ext.block_hash).await?; - log::info!(target: LOG_TARGET, "fetching next header: {:?} ", next_hash); + let prev_block_live_state = live_state.to_prev_block_live_state::<Block>().await?; - let header = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, Some(next_hash)) + // Get state for the prev block + let ext = State::Live(prev_block_live_state) + .to_ext::<Block, HostFns>(&shared, &executor, None, runtime_checks) + .await?; + + let header = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, execute_at) .await .map_err(rpc_err_handler) .map(|maybe_header| maybe_header.ok_or("Header does not exist"))??; diff --git a/core/src/commands/on_runtime_upgrade.rs b/core/src/commands/on_runtime_upgrade.rs index 21b52555c62..3d6a2de2084 100644 --- a/core/src/commands/on_runtime_upgrade.rs +++ b/core/src/commands/on_runtime_upgrade.rs @@ -57,7 +57,7 @@ pub struct Command { )] pub checks: UpgradeCheckSelect, - /// Whether to assume that the runtime is a relay chain runtime. + /// Whether to disable weight warnings, useful if the runtime is for a relay chain. /// /// This is used to adjust the behavior of weight measurement warnings. #[clap(long, default_value = "false", default_missing_value = "true")] @@ -66,7 +66,11 @@ pub struct Command { /// Whether to enforce the new runtime `spec_version` is greater or equal to the existing /// `spec_version`. #[clap(long, default_value = "true", default_missing_value = "true")] + pub check_spec_version: bool, + /// Whether to disable migration idempotency checks + #[clap(long, default_value = "false", default_missing_value = "true")] + pub no_idempotency_checks: bool, } // Runs the `on-runtime-upgrade` command. @@ -103,15 +107,18 @@ where "🔬 Running TryRuntime_on_runtime_upgrade with checks: {:?}", command.checks ); - let (_, proof, encoded_result) = state_machine_call_with_proof::<Block, HostFns>( + // Save the overlayed changes from the first run, so we can use them later for idempotency + // checks. + let mut overlayed_changes = Default::default(); + let (proof, encoded_result) = state_machine_call_with_proof::<Block, HostFns>( &ext, + &mut overlayed_changes, &executor, "TryRuntime_on_runtime_upgrade", command.checks.encode().as_ref(), Default::default(), // we don't really need any extensions here. shared.export_proof.clone(), )?; - let ref_time_results = encoded_result.try_into()?; // If the above call ran with checks then we need to run the call again without checks to @@ -124,43 +131,93 @@ where log::info!( "🔬 TryRuntime_on_runtime_upgrade succeeded! Running it again without checks for weight measurements." ); - let (_, proof, encoded_result) = state_machine_call_with_proof::<Block, HostFns>( + let (proof, encoded_result) = state_machine_call_with_proof::<Block, HostFns>( &ext, + &mut Default::default(), &executor, "TryRuntime_on_runtime_upgrade", UpgradeCheckSelect::None.encode().as_ref(), Default::default(), // we don't really need any extensions here. - shared.export_proof, + shared.export_proof.clone(), )?; let ref_time_results = encoded_result.try_into()?; (proof, ref_time_results) } }; + // Check idempotency + let idempotency_ok = match command.no_idempotency_checks { + true => { + log::info!("ℹ Skipping idempotency check"); + true + } + false => { + log::info!( + "🔬 Running TryRuntime_on_runtime_upgrade again to check idempotency: {:?}", + command.checks + ); + let (oc_pre_root, _) = overlayed_changes.storage_root(&ext.backend, ext.state_version); + match state_machine_call_with_proof::<Block, HostFns>( + &ext, + &mut overlayed_changes, + &executor, + "TryRuntime_on_runtime_upgrade", + command.checks.encode().as_ref(), + Default::default(), // we don't really need any extensions here. + shared.export_proof.clone(), + ) { + Ok(_) => { + // Execution was OK, check if the storage root changed. + let (oc_post_root, _) = + overlayed_changes.storage_root(&ext.backend, ext.state_version); + if oc_pre_root != oc_post_root { + log::error!("❌ Migrations are not idempotent. Selectively remove migrations from Executive until you find the culprit."); + false + } else { + // Exeuction was OK and state root didn't change + true + } + } + Err(e) => { + log::error!( + "❌ Migrations are not idempotent, they failed during the second execution.", + ); + log::debug!("{:?}", e); + false + } + } + } + }; + + // Check weight safety let pre_root = ext.backend.root(); let pov_safety = analyse_pov::<HashingFor<Block>>(proof, *pre_root, command.no_weight_warnings); let ref_time_safety = analyse_ref_time(ref_time_results, command.no_weight_warnings); - - match (pov_safety, ref_time_safety, command.no_weight_warnings) { + let weight_ok = match (pov_safety, ref_time_safety, command.no_weight_warnings) { (_, _, true) => { - log::info!("✅ TryRuntime_on_runtime_upgrade executed without errors") + log::info!("ℹ Skipped checking weight safety"); + true } (WeightSafety::ProbablySafe, WeightSafety::ProbablySafe, _) => { log::info!( target: LOG_TARGET, - "✅ TryRuntime_on_runtime_upgrade executed without errors or weight safety \ - warnings. Please note this does not guarantee a successful runtime upgrade. \ + "✅ No weight safety issues detected. \ + Please note this does not guarantee a successful runtime upgrade. \ Always test your runtime upgrade with recent state, and ensure that the weight usage \ of your migrations will not drastically differ between testing and actual on-chain \ execution." ); + true } _ => { - log::warn!(target: LOG_TARGET, "⚠️ TryRuntime_on_runtime_upgrade executed \ - successfully but with weight safety warnings."); - // Exit with a non-zero exit code to indicate that the runtime upgrade may not be safe. - std::process::exit(1); + log::error!(target: LOG_TARGET, "❌ Weight safety issues detected."); + false } + }; + + if !weight_ok || !idempotency_ok { + log::error!("❌ Issues detected, exiting non-zero. See logs."); + std::process::exit(1); } Ok(()) diff --git a/core/src/lib.rs b/core/src/lib.rs index d1ba19735c3..06e7db0816b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -152,23 +152,22 @@ impl TryFrom<Vec<u8>> for RefTimeInfo { /// Make sure [`LOG_TARGET`] is enabled in logging. pub(crate) fn state_machine_call_with_proof<Block: BlockT, HostFns: HostFunctions>( ext: &TestExternalities<HashingFor<Block>>, + storage_overlay: &mut OverlayedChanges<HashingFor<Block>>, executor: &WasmExecutor<HostFns>, method: &'static str, data: &[u8], mut extensions: Extensions, maybe_export_proof: Option<PathBuf>, -) -> sc_cli::Result<(OverlayedChanges<HashingFor<Block>>, StorageProof, Vec<u8>)> { - let mut changes = Default::default(); - let backend = ext.backend.clone(); - let runtime_code_backend = sp_state_machine::backend::BackendRuntimeCode::new(&backend); - let proving_backend = TrieBackendBuilder::wrap(&backend) +) -> sc_cli::Result<(StorageProof, Vec<u8>)> { + let runtime_code_backend = sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend); + let proving_backend = TrieBackendBuilder::wrap(&ext.backend) .with_recorder(Default::default()) .build(); let runtime_code = runtime_code_backend.runtime_code()?; let encoded_result = StateMachine::new( &proving_backend, - &mut changes, + storage_overlay, executor, method, data, @@ -210,7 +209,7 @@ pub(crate) fn state_machine_call_with_proof<Block: BlockT, HostFns: HostFunction })?; } - Ok((changes, proof, encoded_result)) + Ok((proof, encoded_result)) } /// Converts a [`sp_state_machine::StorageProof`] into a JSON string. diff --git a/core/src/state.rs b/core/src/state.rs index 449da1148ac..e01af37bc55 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -23,7 +23,7 @@ use frame_remote_externalities::{ use parity_scale_codec::Decode; use sc_cli::RuntimeVersion; use sc_executor::{sp_wasm_interface::HostFunctions, WasmExecutor}; -use sp_api::HashT; +use sp_api::{HashT, HeaderT}; use sp_core::{ hexdisplay::HexDisplay, storage::well_known_keys, traits::ReadRuntimeVersion, twox_128, }; @@ -31,9 +31,10 @@ use sp_runtime::{ traits::{BlakeTwo256, Block as BlockT}, DeserializeOwned, }; +use substrate_rpc_client::{ws_client, ChainApi}; use crate::{ - ensure_try_runtime, hash_of, parse, + ensure_try_runtime, hash_of, parse, rpc_err_handler, shared_parameters::{Runtime, SharedParams}, LOG_TARGET, }; @@ -80,6 +81,48 @@ pub struct LiveState { pub child_tree: bool, } +impl LiveState { + /// Return the `at` block hash as a `Hash`, if it exists. + pub fn at<Block: BlockT>(&self) -> sc_cli::Result<Option<<Block>::Hash>> + where + <Block::Hash as FromStr>::Err: Debug, + { + self.at + .clone() + .map(|s| hash_of::<Block>(s.as_str())) + .transpose() + } + + /// Converts this `LiveState` into a `LiveState` for the previous block. + /// + /// Useful for opertations like when you want to execute a block, but also need the state of the + /// block *before* it. + pub async fn to_prev_block_live_state<Block: BlockT>(self) -> sc_cli::Result<LiveState> + where + <Block::Hash as FromStr>::Err: Debug, + { + // We want to execute the block `at`, therefore need the state of the block *before* it. + let at = self.at::<Block>()?; + + // Get the block number requested by the user, or the current block number if they + // didn't specify one. + let rpc = ws_client(&self.uri).await?; + let previous_hash = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, at) + .await + .map_err(rpc_err_handler) + .and_then(|maybe_header| { + maybe_header + .ok_or("header_not_found") + .map(|h| *h.parent_hash()) + })?; + + Ok(LiveState { + at: Some(hex::encode(previous_hash)), + ..self + }) + } +} + /// The source of runtime *state* to use. #[derive(Debug, Clone, clap::Subcommand)] pub enum State { diff --git a/core/tests/create_snapshot.rs b/core/tests/create_snapshot.rs index a6985165af5..03534b5f128 100644 --- a/core/tests/create_snapshot.rs +++ b/core/tests/create_snapshot.rs @@ -25,7 +25,6 @@ use std::{ use assert_cmd::cargo::cargo_bin; use frame_remote_externalities::{Builder, Mode, OfflineConfig, SnapshotConfig}; use node_primitives::{Block, Hash}; -use regex::Regex; use substrate_cli_test_utils as common; use tokio::process::Command; @@ -74,15 +73,10 @@ async fn create_snapshot_works() { let block_hash = common::block_hash(block_number, &ws_url).await.unwrap(); // Try to create a snapshot. - let mut snapshot_creation = create_snapshot(&ws_url, &snap_file_path, block_hash); + let child = create_snapshot(&ws_url, &snap_file_path, block_hash); + let out = child.wait_with_output().await.unwrap(); - let re = Regex::new(r".*writing snapshot of (\d+) bytes to .*").unwrap(); - let matched = - common::wait_for_stream_pattern_match(snapshot_creation.stderr.take().unwrap(), re) - .await; - - // Assert that the snapshot creation succeded. - assert!(matched.is_ok(), "Failed to create snapshot"); + assert!(out.status.success()); let snapshot_is_on_disk = Path::new(&snap_file_path).exists(); assert!(snapshot_is_on_disk, "Snapshot was not written to disk"); diff --git a/core/tests/execute_block.rs b/core/tests/execute_block.rs index 1a3173db32f..93d9b85143d 100644 --- a/core/tests/execute_block.rs +++ b/core/tests/execute_block.rs @@ -60,23 +60,28 @@ async fn execute_block_works() { .unwrap() } - let block_number = 1; + let block_number = 3; let block_hash = common::block_hash(block_number, &ws_url).await.unwrap(); // Try to execute the block. let mut block_execution = execute_block(&ws_url, block_hash); // The execute-block command is actually executing the next block. - let expected_output = format!( - r#".*Block #{} successfully executed"#, - block_number.saturating_add(1) - ); + let expected_output = format!(r#".*Block #{} successfully executed"#, block_number); let re = Regex::new(expected_output.as_str()).unwrap(); let matched = common::wait_for_stream_pattern_match(block_execution.stderr.take().unwrap(), re).await; - // Assert that the block-execution process has executed a block. + // Assert that the block-execution process has executed the expected block. assert!(matched.is_ok()); + + // Assert that the block-execution exited succesfully + assert!(block_execution + .wait_with_output() + .await + .unwrap() + .status + .success()); }) .await } diff --git a/core/tests/on_runtime_upgrade.rs b/core/tests/on_runtime_upgrade.rs new file mode 100644 index 00000000000..642824b91c9 --- /dev/null +++ b/core/tests/on_runtime_upgrade.rs @@ -0,0 +1,173 @@ +// This file is part of try-runtime-cli. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(unix)] + +mod on_runtime_upgrade { + use std::time::Duration; + + use assert_cmd::cargo::cargo_bin; + use substrate_cli_test_utils as common; + use tokio::process::Command; + + fn on_runtime_upgrade( + snap_path: &str, + runtime_path: &str, + extra_args: &[&str], + ) -> tokio::process::Child { + Command::new(cargo_bin("try-runtime")) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .arg(format!("--runtime={}", runtime_path)) + .arg("on-runtime-upgrade") + .args(extra_args) + .args(["snap", format!("--path={}", snap_path).as_str()]) + .kill_on_drop(true) + .spawn() + .unwrap() + } + + #[tokio::test] + async fn ok_works() { + common::run_with_timeout(Duration::from_secs(300), async move { + let project_root = env!("CARGO_MANIFEST_DIR"); + let snap_path = format!("{}/tests/snaps/rococo-bridge-hub.snap", project_root); + let runtime_path = format!( + "{}/tests/runtimes/bridge_hub_rococo_runtime_ok.compact.compressed.wasm", + project_root + ); + dbg!(&runtime_path, &snap_path); + let child = on_runtime_upgrade(snap_path.as_str(), runtime_path.as_str(), &[]); + let out = child.wait_with_output().await.unwrap(); + dbg!(&out); + assert!(out.status.success()); + }) + .await; + } + + #[tokio::test] + async fn weight_issue_fails() { + common::run_with_timeout(Duration::from_secs(300), async move { + let project_root = env!("CARGO_MANIFEST_DIR"); + let snap_path = format!("{}/tests/snaps/rococo-bridge-hub.snap", project_root); + let runtime_path = format!( + "{}/tests/runtimes/bridge_hub_rococo_runtime_WEIGHT_ISSUE.compact.compressed.wasm", + project_root + ); + let child = on_runtime_upgrade(snap_path.as_str(), runtime_path.as_str(), &[]); + let out = child.wait_with_output().await.unwrap(); + assert!(!out.status.success()); + }) + .await; + } + + #[tokio::test] + async fn weight_issue_can_be_ignored() { + common::run_with_timeout(Duration::from_secs(300), async move { + let project_root = env!("CARGO_MANIFEST_DIR"); + let snap_path = format!("{}/tests/snaps/rococo-bridge-hub.snap", project_root); + let runtime_path = format!( + "{}/tests/runtimes/bridge_hub_rococo_runtime_WEIGHT_ISSUE.compact.compressed.wasm", + project_root + ); + + let child = on_runtime_upgrade( + snap_path.as_str(), + runtime_path.as_str(), + &["--no-weight-warnings"], + ); + let out = child.wait_with_output().await.unwrap(); + assert!(out.status.success()); + }) + .await; + } + + #[tokio::test] + async fn not_idempotent_execution_fails() { + common::run_with_timeout(Duration::from_secs(300), async move { + let project_root = env!("CARGO_MANIFEST_DIR"); + let snap_path = format!("{}/tests/snaps/rococo-bridge-hub.snap", project_root); + let runtime_path = format!( + "{}/tests/runtimes/bridge_hub_rococo_runtime_not_idempotent_execution.compact.compressed.wasm", + project_root + ); + + let child = on_runtime_upgrade(snap_path.as_str(), runtime_path.as_str(), &[]); + let out = child.wait_with_output().await.unwrap(); + assert!(!out.status.success()); + }) + .await; + } + + #[tokio::test] + async fn not_idempotent_execution_issue_can_be_ignored() { + common::run_with_timeout(Duration::from_secs(300), async move { + let project_root = env!("CARGO_MANIFEST_DIR"); + let snap_path = format!("{}/tests/snaps/rococo-bridge-hub.snap", project_root); + let runtime_path = format!( + "{}/tests/runtimes/bridge_hub_rococo_runtime_not_idempotent_execution.compact.compressed.wasm", + project_root + ); + + let child = on_runtime_upgrade( + snap_path.as_str(), + runtime_path.as_str(), + &["--no-idempotency-checks"], + ); + let out = child.wait_with_output().await.unwrap(); + assert!(out.status.success()); + }) + .await; + } + + #[tokio::test] + async fn not_idempotent_state_root_fails() { + common::run_with_timeout(Duration::from_secs(300), async move { + let project_root = env!("CARGO_MANIFEST_DIR"); + let snap_path = format!("{}/tests/snaps/rococo-bridge-hub.snap", project_root); + let runtime_path = format!( + "{}/tests/runtimes/bridge_hub_rococo_runtime_not_idempotent_state_root.compact.compressed.wasm", + project_root + ); + + let child = on_runtime_upgrade(snap_path.as_str(), runtime_path.as_str(), &[]); + let out = child.wait_with_output().await.unwrap(); + assert!(!out.status.success()); + }) + .await; + } + + #[tokio::test] + async fn not_idempotent_state_root_issue_can_be_ignored() { + common::run_with_timeout(Duration::from_secs(300), async move { + let project_root = env!("CARGO_MANIFEST_DIR"); + let snap_path = format!("{}/tests/snaps/rococo-bridge-hub.snap", project_root); + let runtime_path = format!( + "{}/tests/runtimes/bridge_hub_rococo_runtime_not_idempotent_state_root.compact.compressed.wasm", + project_root + ); + let child = on_runtime_upgrade( + snap_path.as_str(), + runtime_path.as_str(), + &["--no-idempotency-checks"], + ); + let out = child.wait_with_output().await.unwrap(); + assert!(out.status.success()); + }) + .await; + } +} diff --git a/core/tests/readme.md b/core/tests/readme.md new file mode 100644 index 00000000000..3377d0ae2a1 --- /dev/null +++ b/core/tests/readme.md @@ -0,0 +1,11 @@ + +# tests + +## ./runtimes and ./snaps + +A state snapshot is included in ./snaps, and some runtimes in ./runtimes for use in tests. + +- `bridge_hub_rococo_runtime_OK.compact.compressed.wasm` a runtime with correctly configured migrations +- `bridge_hub_rococo_runtime_WEIGHT_ISSUE.compact.compressed.wasm` a runtime with migrations that would exceed sensible values for a parachain +- `bridge_hub_rococo_runtime_NOT_IDEMPOTENT_EXECUTION.compact.compressed.wasm` a runtime where `try_on_runtime_upgrade` if migrations are executed for a second time +- `bridge_hub_rococo_runtime_NOT_IDEMPOTENT_STATE_ROOT.compact.compressed.wasm` a runtime which will succeed when migrations are executed for a second time, but the state changes are not idempotent diff --git a/core/tests/runtimes/bridge_hub_rococo_runtime_not_idempotent_execution.compact.compressed.wasm b/core/tests/runtimes/bridge_hub_rococo_runtime_not_idempotent_execution.compact.compressed.wasm new file mode 100644 index 00000000000..835cccc0701 Binary files /dev/null and b/core/tests/runtimes/bridge_hub_rococo_runtime_not_idempotent_execution.compact.compressed.wasm differ diff --git a/core/tests/runtimes/bridge_hub_rococo_runtime_not_idempotent_state_root.compact.compressed.wasm b/core/tests/runtimes/bridge_hub_rococo_runtime_not_idempotent_state_root.compact.compressed.wasm new file mode 100644 index 00000000000..c965be9b033 Binary files /dev/null and b/core/tests/runtimes/bridge_hub_rococo_runtime_not_idempotent_state_root.compact.compressed.wasm differ diff --git a/core/tests/runtimes/bridge_hub_rococo_runtime_ok.compact.compressed.wasm b/core/tests/runtimes/bridge_hub_rococo_runtime_ok.compact.compressed.wasm new file mode 100644 index 00000000000..4ff6c81fdb0 Binary files /dev/null and b/core/tests/runtimes/bridge_hub_rococo_runtime_ok.compact.compressed.wasm differ diff --git a/core/tests/runtimes/bridge_hub_rococo_runtime_weight_issue.compact.compressed.wasm b/core/tests/runtimes/bridge_hub_rococo_runtime_weight_issue.compact.compressed.wasm new file mode 100644 index 00000000000..fc03a10bb52 Binary files /dev/null and b/core/tests/runtimes/bridge_hub_rococo_runtime_weight_issue.compact.compressed.wasm differ diff --git a/core/tests/snaps/rococo-bridge-hub.snap b/core/tests/snaps/rococo-bridge-hub.snap new file mode 100644 index 00000000000..bb49d8f068b Binary files /dev/null and b/core/tests/snaps/rococo-bridge-hub.snap differ