├── .github └── workflows │ ├── build-release-binaries.yaml │ ├── rust-checks.yaml │ └── rust-docs.yaml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── cli ├── Cargo.toml └── main.rs ├── core ├── Cargo.toml ├── src │ ├── commands │ │ ├── create_snapshot.rs │ │ ├── execute_block.rs │ │ ├── fast_forward.rs │ │ ├── follow_chain.rs │ │ ├── mod.rs │ │ ├── offchain_worker.rs │ │ └── on_runtime_upgrade │ │ │ ├── mbms.rs │ │ │ └── mod.rs │ ├── common │ │ ├── empty_block │ │ │ ├── inherents │ │ │ │ ├── custom_idps │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── para_parachain.rs │ │ │ │ │ ├── relay_parachains.rs │ │ │ │ │ └── timestamp.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── pre_apply.rs │ │ │ │ └── providers.rs │ │ │ ├── mod.rs │ │ │ └── production.rs │ │ ├── misc_logging.rs │ │ ├── mod.rs │ │ ├── parse.rs │ │ ├── shared_parameters.rs │ │ └── state.rs │ └── lib.rs └── tests │ ├── create_snapshot.rs │ ├── execute_block.rs │ ├── follow_chain.rs │ ├── on_runtime_upgrade.rs │ ├── readme.md │ ├── runtimes │ ├── people_rococo_runtime_different_spec_name.compact.compressed.wasm │ ├── people_rococo_runtime_mbm_double_ok_300b.compact.compressed.wasm │ ├── people_rococo_runtime_mbm_duplicates_ok_80b.compact.compressed.wasm │ ├── people_rococo_runtime_mbm_empty.compact.compressed.wasm │ ├── people_rococo_runtime_mbm_fails.compact.compressed.wasm │ ├── people_rococo_runtime_mbm_post_upgrade_fails.compact.compressed.wasm │ ├── people_rococo_runtime_mbm_pre_upgrade_fails.compact.compressed.wasm │ ├── people_rococo_runtime_no_migrations.compact.compressed.wasm │ ├── people_rococo_runtime_not_idempotent_panic.compact.compressed.wasm │ ├── people_rococo_runtime_not_idempotent_state_root.compact.compressed.wasm │ ├── people_rococo_runtime_ok.compact.compressed.wasm │ ├── people_rococo_runtime_post_upgrade_fail.compact.compressed.wasm │ ├── people_rococo_runtime_post_upgrade_storage_change.compact.compressed.wasm │ ├── people_rococo_runtime_pre_upgrade_fail.compact.compressed.wasm │ ├── people_rococo_runtime_same_spec_version.compact.compressed.wasm │ ├── people_rococo_runtime_weight_max.compact.compressed.wasm │ └── readme.md │ └── snaps │ └── rococo-people.snap ├── rust-toolchain.toml └── rustfmt.toml /.github/workflows/build-release-binaries.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Attach Binaries to Release 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | build_and_upload: 10 | strategy: 11 | matrix: 12 | platform: 13 | - { os: ubuntu-latest, target: x86_64-unknown-linux-musl } 14 | - { os: macos-latest, target: x86_64-apple-darwin } 15 | 16 | runs-on: ${{ matrix.platform.os }} 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: Install Protoc 23 | uses: arduino/setup-protoc@v3 24 | with: 25 | version: "23.2" 26 | repo-token: ${{ secrets.GITHUB_TOKEN }} 27 | 28 | - name: Setup Rust 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | profile: minimal 32 | toolchain: stable 33 | override: true 34 | 35 | - name: Add apple target 36 | if: matrix.platform.os == 'macos-latest' 37 | run: rustup target add x86_64-apple-darwin 38 | 39 | - name: Add musl target 40 | if: matrix.platform.os == 'ubuntu-latest' 41 | run: rustup target add x86_64-unknown-linux-musl 42 | 43 | - name: Install deps for musl build 44 | if: matrix.platform.os == 'ubuntu-latest' 45 | run: | 46 | sudo apt-get update 47 | sudo apt-get install -y 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 48 | sudo ln -s /usr/bin/g++ /usr/bin/musl-g++ 49 | 50 | - name: Build Rust project 51 | run: cargo build --release --target ${{ matrix.platform.target }} 52 | 53 | - name: Upload Binary to Release 54 | uses: actions/upload-release-asset@v1 55 | with: 56 | upload_url: ${{ github.event.release.upload_url }} 57 | asset_path: ./target/${{ matrix.platform.target }}/release/try-runtime 58 | asset_name: try-runtime-${{ matrix.platform.target }} 59 | asset_content_type: application/octet-stream 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | -------------------------------------------------------------------------------- /.github/workflows/rust-checks.yaml: -------------------------------------------------------------------------------- 1 | name: Rust checks 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | concurrency: 10 | group: ${{ github.ref }}-${{ github.workflow }} 11 | cancel-in-progress: true 12 | 13 | env: 14 | RUST_BACKTRACE: 1 15 | # pin nightly to avoid constantly throwing out cache 16 | TOOLCHAIN_FMT: nightly-2024-04-09 17 | 18 | jobs: 19 | doc: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Install Rust stable 24 | uses: dtolnay/rust-toolchain@stable 25 | with: 26 | targets: wasm32-unknown-unknown 27 | - uses: Swatinem/rust-cache@v2 28 | with: 29 | key: check-doc-v0 30 | - name: Install Protoc 31 | uses: arduino/setup-protoc@v1 32 | with: 33 | version: "3.6.1" 34 | - name: cargo doc 35 | run: RUSTFLAGS="-D warnings" cargo doc --locked 36 | 37 | fmt: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v4 41 | - name: Install Rust ${{ env.TOOLCHAIN_FMT }} 42 | uses: dtolnay/rust-toolchain@stable 43 | with: 44 | toolchain: ${{ env.TOOLCHAIN_FMT }} 45 | targets: wasm32-unknown-unknown 46 | components: rustfmt, clippy, rust-src 47 | - uses: Swatinem/rust-cache@v2 48 | with: 49 | key: fmt-v0 50 | - name: cargo fmt 51 | run: cargo +${{ env.TOOLCHAIN_FMT }} fmt --all -- --check 52 | 53 | clippy: 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v4 57 | - name: Install Rust stable 58 | uses: dtolnay/rust-toolchain@stable 59 | with: 60 | toolchain: stable 61 | targets: wasm32-unknown-unknown 62 | components: rustfmt, clippy, rust-src 63 | - uses: Swatinem/rust-cache@v2 64 | with: 65 | key: clippy-v0 66 | - name: Install deps for musl build 67 | run: | 68 | sudo apt-get update 69 | 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 70 | sudo ln -s /usr/bin/g++ /usr/bin/musl-g++ 71 | - name: cargo clippy 72 | uses: actions-rs-plus/clippy-check@v2 73 | with: 74 | toolchain: stable 75 | args: --all-targets --all-features --locked --no-deps -- --deny warnings 76 | 77 | test: 78 | strategy: 79 | matrix: 80 | os: [ubuntu-latest] 81 | toolchain: [stable] 82 | runs-on: ${{ matrix.os }} 83 | env: 84 | RUSTFLAGS: "-Cdebug-assertions=y" 85 | steps: 86 | - uses: actions/checkout@v4 87 | - name: Install Rust ${{ matrix.toolchain }} 88 | uses: dtolnay/rust-toolchain@stable 89 | with: 90 | toolchain: ${{ matrix.toolchain }} 91 | targets: wasm32-unknown-unknown 92 | - uses: Swatinem/rust-cache@v2 93 | - name: Install Protoc 94 | uses: arduino/setup-protoc@v1 95 | with: 96 | version: "3.6.1" 97 | - name: Free disk space 98 | run: | 99 | sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true 100 | sudo rm -rf \ 101 | /usr/share/dotnet /usr/local/lib/android /opt/ghc \ 102 | /usr/local/share/powershell /usr/share/swift /usr/local/.ghcup \ 103 | /usr/lib/jvm || true 104 | sudo apt install aptitude -y >/dev/null 2>&1 105 | sudo aptitude purge aria2 ansible azure-cli shellcheck rpm xorriso zsync \ 106 | esl-erlang firefox gfortran-8 gfortran-9 google-chrome-stable \ 107 | google-cloud-sdk imagemagick \ 108 | libmagickcore-dev libmagickwand-dev libmagic-dev ant ant-optional kubectl \ 109 | mercurial apt-transport-https mono-complete libmysqlclient \ 110 | unixodbc-dev yarn chrpath libssl-dev libxft-dev \ 111 | libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev \ 112 | snmp pollinate libpq-dev postgresql-client powershell ruby-full \ 113 | sphinxsearch subversion mongodb-org azure-cli microsoft-edge-stable \ 114 | -y -f >/dev/null 2>&1 115 | sudo aptitude purge google-cloud-sdk -f -y >/dev/null 2>&1 116 | sudo aptitude purge microsoft-edge-stable -f -y >/dev/null 2>&1 || true 117 | sudo apt purge microsoft-edge-stable -f -y >/dev/null 2>&1 || true 118 | sudo aptitude purge '~n ^mysql' -f -y >/dev/null 2>&1 119 | sudo aptitude purge '~n ^php' -f -y >/dev/null 2>&1 120 | sudo aptitude purge '~n ^dotnet' -f -y >/dev/null 2>&1 121 | sudo apt-get autoremove -y >/dev/null 2>&1 122 | sudo apt-get autoclean -y >/dev/null 2>&1 123 | - name: build try-runtime-cli 124 | # this is required for testing 125 | # build --release or the execution time of the test is too long 126 | run: cargo build --release -p try-runtime-cli 127 | - name: cargo test 128 | run: cargo test --release 129 | - name: Check disk space 130 | run: df . -h 131 | -------------------------------------------------------------------------------- /.github/workflows/rust-docs.yaml: -------------------------------------------------------------------------------- 1 | name: Update rust docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | docs: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Install Protoc 17 | uses: arduino/setup-protoc@v1 18 | with: 19 | version: "3.6.1" 20 | 21 | - uses: actions/checkout@v4 22 | - name: Install Rust Stable 23 | uses: dtolnay/rust-toolchain@stable 24 | with: 25 | toolchain: stable 26 | targets: wasm32-unknown-unknown 27 | components: rust-src 28 | 29 | - name: Check if the README is up to date. 30 | run: | 31 | cargo install cargo-rdme --locked -q 32 | cargo rdme --check --workspace-project try-runtime-cli --readme-path README.md 33 | 34 | - name: Build docs 35 | uses: actions-rs/cargo@v1 36 | with: 37 | command: doc 38 | 39 | - name: Deploy to GitHub Pages 40 | uses: peaceiris/actions-gh-pages@v3 41 | with: 42 | github_token: ${{ secrets.GITHUB_TOKEN }} 43 | publish_dir: ./target/doc 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEs 2 | /.idea 3 | /.vscode 4 | .projectile 5 | 6 | # Rust 7 | .cargo/config 8 | 9 | **/target/ 10 | **/*.rs.bk 11 | 12 | # Substrate 13 | *.log 14 | *.out 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to try-runtime-cli 2 | 3 | Thanks for taking the time to contribute! ❤️ 4 | 5 | ## Versioning and Releases 6 | Versioning for try-runtime-cli presents unique challenges due to its dependence on runtimes. Our approach to versioning is a mix of traditional semantic versioning and custom practices tailored to supporting old polkadot releases. 7 | 8 | ### Semantic Versioning for Master 9 | The latest code on the master branch is maintained to be compatible with polkadot master, and follows a standard Semantic Versioning (SemVer) approach. This means: 10 | 11 | - Major releases (X.0.0) include breaking changes. 12 | - Minor releases (0.X.0) add new features without breaking existing functionality. 13 | - Patch releases (0.0.X) include fixes and minor improvements. 14 | 15 | It's important to note that while we try to guarantee master compatibility with the latest polkadot versions, there's no assurance that older versions will work with any specific runtime and should be considered deprecated. 16 | 17 | ### Tags for Polkadot SDK Releases 18 | In addition to SemVer on master, we maintain Git tags or branches corresponding to every Polkadot SDK release >=v1.0.0. This ensures that: 19 | 20 | - We provide versions of `try-runtime` that are guaranteed to be compatible with specific Polkadot SDK releases. 21 | - In the case of patches or minor changes, we can move or adjust these tags or branches, ensuring continued compatibility with their counterparts in the Substrate repo. 22 | - While this method requires manual maintenance, it offers clarity and ensures compatibility for users working with different runtime versions. 23 | 24 | ### What to Expect as a Contributor 25 | When contributing, consider the following: 26 | 27 | - If your changes are general improvements or fixes and don't tie specifically to a Polkadot SDK release, they will be integrated into the main codebase and subject to semantic versioning. 28 | - If your contribution pertains to compatibility with a specific Polkadot SDK release, it may be integrated into the corresponding tagged branch or version. 29 | - Always indicate in your pull request or issue any specific version considerations or compatibility issues you're aware of. 30 | 31 | ## Tips 32 | 33 | Non-trivial contributions are eligible for DOT tips. To express interest include your Polkadot address in your PR description. 34 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = ["cli", "core"] 5 | 6 | [workspace.package] 7 | version = "0.8.0" 8 | authors = ["Parity Technologies "] 9 | description = "Substrate's programmatic testing framework." 10 | edition = "2021" 11 | license = "Apache-2.0" 12 | homepage = "https://github.com/paritytech/try-runtime-cli" 13 | repository = "https://github.com/paritytech/try-runtime-cli/" 14 | 15 | [workspace.dependencies] 16 | array-bytes = { version = "6.2.3" } 17 | assert_cmd = { version = "2.0.16" } 18 | async-trait = { version = "0.1.83" } 19 | bytesize = { version = "1.3.0" } 20 | clap = { version = "4.5.18" } 21 | env_logger = { version = "0.11.5" } 22 | hex = { version = "0.4.3", default-features = false } 23 | itertools = { version = "0.13.0" } 24 | log = { version = "0.4.22" } 25 | parity-scale-codec = { version = "3.6.12" } 26 | regex = { version = "1.11.0" } 27 | serde = { version = "1.0.210" } 28 | serde_json = { version = "1.0.128" } 29 | strum = "0.26" 30 | strum_macros = "0.26" 31 | tempfile = { version = "3.13.0" } 32 | tokio = { version = "1.40.0" } 33 | zstd = { version = "0.13.2", default-features = false } 34 | 35 | # Polkadot SDK 36 | frame-remote-externalities = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 37 | frame-try-runtime = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 38 | frame-support = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 39 | frame-system = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 40 | 41 | sc-cli = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 42 | sc-executor = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 43 | sc-service = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 44 | 45 | sp-api = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 46 | sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 47 | sp-consensus-babe = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 48 | sp-core = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 49 | sp-externalities = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 50 | sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 51 | sp-io = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 52 | sp-keystore = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 53 | sp-rpc = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 54 | sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 55 | sp-state-machine = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 56 | sp-storage = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 57 | sp-std = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 58 | sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 59 | sp-transaction-storage-proof = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 60 | sp-version = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 61 | sp-weights = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 62 | 63 | substrate-cli-test-utils = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 64 | substrate-rpc-client = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 65 | 66 | polkadot-primitives = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 67 | cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 68 | cumulus-primitives-core = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 69 | cumulus-client-parachain-inherent = { git = "https://github.com/paritytech/polkadot-sdk", rev = "8279d1046cca51a317dec15df5a9b29240545163" } 70 | 71 | # Local 72 | try-runtime-core = { path = "core" } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Try-runtime 4 | 5 | Substrate's programmatic testing framework. 6 | 7 | > As the name suggests, `try-runtime` is a detailed testing framework that gives you a lot of 8 | > control over what is being executed in which environment. It is recommended that user's first 9 | > familiarize themselves with substrate in depth, particularly the execution model. It is 10 | > critical 11 | > to deeply understand how the wasm/client/runtime interactions, and the runtime apis work in 12 | > the 13 | > substrate runtime, before commencing to working with `try-runtime`. 14 | 15 | #### Resources 16 | 17 | Some resources about the above: 18 | 19 | 1. 20 | 2. 21 | 3. 22 | 23 | --- 24 | 25 | ## Background Knowledge 26 | 27 | The basis of all try-runtime commands is the same: connect to a live node, scrape its *state* 28 | and put it inside a [`TestExternalities`], then call into a *specific runtime-api* using the 29 | given state and some *runtime*. 30 | 31 | Alternatively, the state could come from a snapshot file. 32 | 33 | All of the variables in the above statement are made *italic*. Let's look at each of them: 34 | 35 | 1. **State** is the key-value pairs of data that comprise the canonical information that any 36 | blockchain is keeping. A state can be full (all key-value pairs), or be partial (only pairs 37 | related to some pallets/prefixes). Moreover, some keys are special and are not related to 38 | specific pallets, known as [`well_known_keys`] in substrate. The most important of these is 39 | the `:CODE:` key, which contains the code used for execution, when wasm execution is chosen. 40 | 41 | 2. *A runtime-api* call is a call into a function defined in the runtime, *on top of a given 42 | state*. Each subcommand of `try-runtime` utilizes a specific *runtime-api*. 43 | 44 | 3. Finally, the **runtime** is the actual code that is used to execute the aforementioned 45 | runtime-api. Everything in this crate assumes wasm execution, which means the runtime that 46 | you use is the one stored onchain, namely under the `:CODE:` key. 47 | 48 | To recap, a typical try-runtime command does the following: 49 | 50 | 1. Download the state of a live chain, and write to an `externalities`. 51 | 2. Overwrite the `:CODE:` with a given wasm blob 52 | 3. Test some functionality via calling a runtime-api. 53 | 54 | ## Installation 55 | ```bash 56 | # Install latest version (recommended for local development) 57 | cargo install --git https://github.com/paritytech/try-runtime-cli --locked 58 | # Install a specific version (recommended for tools like CI) 59 | cargo install --git https://github.com/paritytech/try-runtime-cli --tag vX.Y.Z --locked 60 | try-runtime --help 61 | try-runtime on-runtime-upgrade --help 62 | ``` 63 | 64 | ## Usage 65 | 66 | To use any of the provided commands, [`SharedParams`] must be provided. The most important of 67 | which being [`SharedParams::runtime`], which specifies which runtime to use. Furthermore, 68 | [`SharedParams::overwrite_state_version`] can be used to alter the state-version (see 69 | for more info). 70 | 71 | Then, the specific command has to be specified. See [`Action`] for more information about each 72 | command's specific customization flags, and assumptions regarding the runtime being used. 73 | 74 | Briefly, this CLI is capable of executing: 75 | 76 | * [`Action::OnRuntimeUpgrade`]: execute all the [`OnRuntimeUpgrade`] hooks. 77 | * [`Action::ExecuteBlock`]: re-execute the given block. 78 | * [`Action::FastForward`]: execute [`OnRuntimeUpgrade`] hooks, then fast-forward the chain a 79 | given number of blocks while checking try-state invarients. 80 | * [`Action::OffchainWorker`]: re-execute the given block's offchain worker code path. 81 | * [`Action::FollowChain`]: continuously execute the blocks of a remote chain on top of a given 82 | runtime. 83 | * [`Action::CreateSnapshot`]: Create a snapshot file from a remote node. 84 | 85 | Finally, to make sure there are no errors regarding this, always run any `try-runtime` command 86 | with `executor=trace` logging targets, which will specify which runtime is being used per api 87 | call. Moreover, `remote-ext`, `try-runtime` and `runtime` logs targets will also be useful. 88 | 89 | ## Spec name check 90 | 91 | A common pitfall is that you might be running some test on top of the state of chain `x`, with 92 | the runtime of chain `y`. To avoid this all commands do a spec-name check before executing 93 | anything by default. This will check the, if any alterations are being made to the `:CODE:`, 94 | then the spec names match. The spec versions are warned, but are not mandated to match. 95 | 96 | > If anything, in most cases, we expect spec-versions to NOT match, because try-runtime is all 97 | > about testing unreleased runtimes. 98 | 99 | ## Note on signature and state-root checks 100 | 101 | All of the commands calling into `TryRuntime_execute_block` ([`Action::ExecuteBlock`] and 102 | [`Action::FollowChain`]) disable both state root and signature checks. This is because in 99% 103 | of the cases, the runtime that is being tested is different from the one that is stored in the 104 | canonical chain state. This implies: 105 | 106 | 1. the state root will NEVER match, because `:CODE:` is different between the two. 107 | 2. replaying all transactions will fail, because the spec-version is part of the transaction 108 | signature. 109 | 110 | ## Best Practices 111 | 112 | Try-runtime is all about battle-testing unreleased runtimes. The following list of suggestions 113 | help developers maximize their testing coverage and make the best use of `try-runtime` features. 114 | 115 | ### Testing Runtime Upgrades 116 | 117 | One of the most powerful abilities of `try-runtime` is using the 118 | [`OnRuntimeUpgrade::pre_upgrade`] and [`OnRuntimeUpgrade::post_upgrade`] hooks to test runtime 119 | upgrades implemented with [`OnRuntimeUpgrade`]. [`OnRuntimeUpgrade`] can be implemented inside 120 | the pallet, or standalone in a runtime to define a migration to execute next runtime upgrade. In 121 | both cases, these methods can be added: 122 | 123 | ```rust 124 | #[cfg(feature = "try-runtime")] 125 | fn pre_upgrade() -> Result, TryRuntimeError> {} 126 | 127 | #[cfg(feature = "try-runtime")] 128 | fn post_upgrade(state: Vec) -> Result<(), TryRuntimeError> {} 129 | ``` 130 | 131 | (The pallet macro syntax will support this simply as a part of `#[pallet::hooks]`). 132 | 133 | These hooks will be called when you execute the [`Action::OnRuntimeUpgrade`] command, before and 134 | after the migration. [`OnRuntimeUpgrade::pre_upgrade`] returns a [`Vec`] that can contain 135 | arbitrary encoded data (usually some pre-upgrade state) which will be passed to 136 | [`OnRuntimeUpgrade::pre_upgrade`] after upgrading and used for post checking. 137 | 138 | ### [`VersionedMigration`] 139 | 140 | It is strongly suggested to use [`VersionedMigration`] when writing custom migrations for 141 | pallets. 142 | 143 | ### State Consistency 144 | 145 | Similarly, each pallet can expose a function in `#[pallet::hooks]` section as follows: 146 | 147 | ```rust 148 | #[cfg(feature = "try-runtime")] 149 | fn try_state(_: BlockNumber) -> Result<(), TryRuntimeError> {} 150 | ``` 151 | 152 | which is called on numerous code paths in the try-runtime tool. These checks should ensure that 153 | the state of the pallet is consistent and correct. See [`TryState`] for more info. 154 | 155 | ### Logging 156 | 157 | It is super helpful to make sure your migration code uses logging (always with a `runtime` log 158 | target prefix, e.g. `runtime::balance`) and state exactly at which stage it is, and what it is 159 | doing. 160 | 161 | ## Examples 162 | 163 | For the following examples, we assume the existence of the following: 164 | 165 | 1. a substrate node compiled with `--features try-runtime`, called `substrate`. This will be the 166 | running node that you connect to, and provide a wasm blob that has try-runtime functionality 167 | enabled. 168 | 2. the `try-runtime` CLI binary on your path. 169 | 170 | ```bash 171 | # this is like your running deployed node. 172 | cargo build --features try-runtime --release && cp target/release/substrate . 173 | ``` 174 | 175 | > The above example is with `substrate`'s `kitchensink-runtime`, but is applicable to any 176 | > substrate-based chain. 177 | 178 | * Run the migrations of a given runtime on top of a live state. 179 | 180 | ```bash 181 | # assuming there's `./substrate --dev --tmp --ws-port 9999` or similar running. 182 | try-runtime \ 183 | --runtime /path-to-substrate/target/release/wbuild/my-runtime.wasm \ 184 | on-runtime-upgrade \ 185 | live --uri ws://localhost:9999 186 | ``` 187 | 188 | * Same as the previous example, but run it at specific block number's state and using the live 189 | polkadot network. This means that this block hash's state should not yet have been pruned by 190 | the node running at `rpc.polkadot.io`. 191 | 192 | ```bash 193 | try-runtime \ 194 | --runtime /path-to-polkadot-runtimes/target/release/wbuild/polkadot-runtime/polkadot-runtime.wasm \ 195 | on-runtime-upgrade \ 196 | live --uri wss://rpc.polkadot.io:443 \ 197 | # replace with your desired block hash! 198 | --at 0xa1b16c1efd889a9f17375ec4dd5c1b4351a2be17fa069564fced10d23b9b3836 199 | ``` 200 | 201 | * Now, let's use a snapshot file. First, we create the snapshot: 202 | 203 | ```bash 204 | try-runtime --runtime existing create-snapshot --uri ws://localhost:9999 my-snapshot.snap 205 | 2022-12-13 10:28:17.516 INFO main remote-ext: since no at is provided, setting it to latest finalized head, 0xe7d0b614dfe89af65b33577aae46a6f958c974bf52f8a5e865a0f4faeb578d22 206 | 2022-12-13 10:28:17.516 INFO main remote-ext: since no prefix is filtered, the data for all pallets will be downloaded 207 | 2022-12-13 10:28:17.550 INFO main remote-ext: writing snapshot of 1611464 bytes to "node-268@latest.snap" 208 | 2022-12-13 10:28:17.551 INFO main remote-ext: initialized state externalities with storage root 0x925e4e95de4c08474fb7f976c4472fa9b8a1091619cd7820a793bf796ee6d932 and state_version V1 209 | ``` 210 | 211 | > Note that the snapshot contains the `existing` runtime, which does not have the correct 212 | > `try-runtime` feature. In the following commands, we still need to overwrite the runtime. 213 | 214 | Then, we can use it to have the same command as before, `on-runtime-upgrade` 215 | 216 | ```bash 217 | try-runtime \ 218 | --runtime /path-to-substrate/target/release/wbuild/my-runtime.wasm \ 219 | on-runtime-upgrade \ 220 | snap -p my-snapshot.snap 221 | ``` 222 | 223 | * Execute the latest finalized block with the given runtime. 224 | 225 | ```bash 226 | try-runtime \ 227 | --runtime /path-to-substrate/target/release/wbuild/my-runtime.wasm \ 228 | execute-block live \ 229 | --uri ws://localhost:9999 230 | ``` 231 | 232 | This can still be customized at a given block with `--at`. If you want to use a snapshot, you 233 | can still use `--block-ws-uri` to provide a node form which the block data can be fetched. 234 | 235 | Moreover, this runs the [`TryState`] hooks as well. The hooks to run can be customized with the 236 | `--try-state`. For example: 237 | 238 | ```bash 239 | try-runtime \ 240 | --runtime /path-to-substrate/target/release/wbuild/my-runtime.wasm \ 241 | execute-block \ 242 | --try-state System,Staking \ 243 | live \ 244 | --uri ws://localhost:9999 \ 245 | --pallet System Staking 246 | ``` 247 | 248 | Will only run the `try-state` of the two given pallets. When running `try-state` against 249 | some real chain data it can take a long time for the command to execute since it has to 250 | query all the key-value pairs. In scenarios like above where we only want to run the 251 | `try-state` for some specific pallets, we can use the `--pallet` option to specify from 252 | which pallets we want to query the state. This will greatly decrease the execution time. 253 | 254 | See [`TryStateSelect`] for more information. 255 | 256 | * Follow our live chain's blocks using `follow-chain`, whilst running the try-state of 3 pallets 257 | in a round robin fashion 258 | 259 | ```bash 260 | try-runtime \ 261 | --runtime /path-to-substrate/target/release/wbuild/my-runtime.wasm \ 262 | follow-chain \ 263 | --uri ws://localhost:9999 \ 264 | --try-state rr-3 265 | ``` 266 | 267 | [`VersionedMigration`]: frame_support::migrations::VersionedMigration 268 | [`OnRuntimeUpgrade`]: frame_support::traits::OnRuntimeUpgrade 269 | [`OnRuntimeUpgrade::pre_upgrade`]: frame_support::traits::OnRuntimeUpgrade::pre_upgrade 270 | [`OnRuntimeUpgrade::post_upgrade`]: frame_support::traits::OnRuntimeUpgrade::post_upgrade 271 | [`TryStateSelect`]: frame_support::traits::TryStateSelect 272 | [`TryState`]: frame_support::traits::TryState 273 | [`TestExternalities`]: sp_state_machine::TestExternalities 274 | [`well_known_keys`]: sp_storage::well_known_keys 275 | [`Action`]: try_runtime_core::commands::Action 276 | [`Action::FollowChain`]: try_runtime_core::commands::Action::FollowChain 277 | [`Action::OnRuntimeUpgrade`]: try_runtime_core::commands::Action::OnRuntimeUpgrade 278 | [`Action::ExecuteBlock`]: try_runtime_core::commands::Action::ExecuteBlock 279 | [`Action::OffchainWorker`]: try_runtime_core::commands::Action::OffchainWorker 280 | [`Action::CreateSnapshot`]: try_runtime_core::commands::Action::CreateSnapshot 281 | [`Action::FastForward`]: try_runtime_core::commands::Action::FastForward 282 | [`SharedParams`]: try_runtime_core::shared_parameters::SharedParams 283 | [`SharedParams::runtime`]: try_runtime_core::common::shared_parameters::SharedParams::runtime 284 | [`SharedParams::overwrite_state_version`]: try_runtime_core::common::shared_parameters::SharedParams::overwrite_state_version 285 | 286 | 287 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "try-runtime-cli" 3 | version.workspace = true 4 | authors.workspace = true 5 | description.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | 11 | [[bin]] 12 | name = "try-runtime" 13 | path = "main.rs" 14 | 15 | [package.metadata.docs.rs] 16 | targets = ["x86_64-unknown-linux-gnu"] 17 | 18 | [dependencies] 19 | clap = { workspace = true, features = ["derive"] } 20 | env_logger = { workspace = true } 21 | parity-scale-codec = { workspace = true, features = ["derive"] } 22 | tokio = { workspace = true, features = ["full"] } 23 | 24 | sp-io = { workspace = true } 25 | sp-core = { workspace = true } 26 | sp-runtime = { workspace = true } 27 | sp-state-machine = { workspace = true } 28 | sp-storage = { workspace = true } 29 | 30 | try-runtime-core = { workspace = true } 31 | 32 | frame-support = { workspace = true } 33 | -------------------------------------------------------------------------------- /cli/main.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | //! # Try-runtime 19 | //! 20 | //! Substrate's programmatic testing framework. 21 | //! 22 | //! > As the name suggests, `try-runtime` is a detailed testing framework that gives you a lot of 23 | //! > control over what is being executed in which environment. It is recommended that user's first 24 | //! > familiarize themselves with substrate in depth, particularly the execution model. It is 25 | //! > critical 26 | //! > to deeply understand how the wasm/client/runtime interactions, and the runtime apis work in 27 | //! > the 28 | //! > substrate runtime, before commencing to working with `try-runtime`. 29 | //! 30 | //! #### Resources 31 | //! 32 | //! Some resources about the above: 33 | //! 34 | //! 1. 35 | //! 2. 36 | //! 3. 37 | //! 38 | //! --- 39 | //! 40 | //! ## Background Knowledge 41 | //! 42 | //! The basis of all try-runtime commands is the same: connect to a live node, scrape its *state* 43 | //! and put it inside a [`TestExternalities`], then call into a *specific runtime-api* using the 44 | //! given state and some *runtime*. 45 | //! 46 | //! Alternatively, the state could come from a snapshot file. 47 | //! 48 | //! All of the variables in the above statement are made *italic*. Let's look at each of them: 49 | //! 50 | //! 1. **State** is the key-value pairs of data that comprise the canonical information that any 51 | //! blockchain is keeping. A state can be full (all key-value pairs), or be partial (only pairs 52 | //! related to some pallets/prefixes). Moreover, some keys are special and are not related to 53 | //! specific pallets, known as [`well_known_keys`] in substrate. The most important of these is 54 | //! the `:CODE:` key, which contains the code used for execution, when wasm execution is chosen. 55 | //! 56 | //! 2. *A runtime-api* call is a call into a function defined in the runtime, *on top of a given 57 | //! state*. Each subcommand of `try-runtime` utilizes a specific *runtime-api*. 58 | //! 59 | //! 3. Finally, the **runtime** is the actual code that is used to execute the aforementioned 60 | //! runtime-api. Everything in this crate assumes wasm execution, which means the runtime that 61 | //! you use is the one stored onchain, namely under the `:CODE:` key. 62 | //! 63 | //! To recap, a typical try-runtime command does the following: 64 | //! 65 | //! 1. Download the state of a live chain, and write to an `externalities`. 66 | //! 2. Overwrite the `:CODE:` with a given wasm blob 67 | //! 3. Test some functionality via calling a runtime-api. 68 | //! 69 | //! ## Installation 70 | 71 | //!```bash 72 | //! # Install latest version (recommended for local development) 73 | //! cargo install --git https://github.com/paritytech/try-runtime-cli --locked 74 | //! # Install a specific version (recommended for tools like CI) 75 | //! cargo install --git https://github.com/paritytech/try-runtime-cli --tag vX.Y.Z --locked 76 | //! try-runtime --help 77 | //! try-runtime on-runtime-upgrade --help 78 | //! ``` 79 | //! 80 | //! ## Usage 81 | //! 82 | //! To use any of the provided commands, [`SharedParams`] must be provided. The most important of 83 | //! which being [`SharedParams::runtime`], which specifies which runtime to use. Furthermore, 84 | //! [`SharedParams::overwrite_state_version`] can be used to alter the state-version (see 85 | //! for more info). 86 | //! 87 | //! Then, the specific command has to be specified. See [`Action`] for more information about each 88 | //! command's specific customization flags, and assumptions regarding the runtime being used. 89 | //! 90 | //! Briefly, this CLI is capable of executing: 91 | //! 92 | //! * [`Action::OnRuntimeUpgrade`]: execute all the [`OnRuntimeUpgrade`] hooks. 93 | //! * [`Action::ExecuteBlock`]: re-execute the given block. 94 | //! * [`Action::FastForward`]: execute [`OnRuntimeUpgrade`] hooks, then fast-forward the chain a 95 | //! given number of blocks while checking try-state invarients. 96 | //! * [`Action::OffchainWorker`]: re-execute the given block's offchain worker code path. 97 | //! * [`Action::FollowChain`]: continuously execute the blocks of a remote chain on top of a given 98 | //! runtime. 99 | //! * [`Action::CreateSnapshot`]: Create a snapshot file from a remote node. 100 | //! 101 | //! Finally, to make sure there are no errors regarding this, always run any `try-runtime` command 102 | //! with `executor=trace` logging targets, which will specify which runtime is being used per api 103 | //! call. Moreover, `remote-ext`, `try-runtime` and `runtime` logs targets will also be useful. 104 | //! 105 | //! ## Spec name check 106 | //! 107 | //! A common pitfall is that you might be running some test on top of the state of chain `x`, with 108 | //! the runtime of chain `y`. To avoid this all commands do a spec-name check before executing 109 | //! anything by default. This will check the, if any alterations are being made to the `:CODE:`, 110 | //! then the spec names match. The spec versions are warned, but are not mandated to match. 111 | //! 112 | //! > If anything, in most cases, we expect spec-versions to NOT match, because try-runtime is all 113 | //! > about testing unreleased runtimes. 114 | //! 115 | //! ## Note on signature and state-root checks 116 | //! 117 | //! All of the commands calling into `TryRuntime_execute_block` ([`Action::ExecuteBlock`] and 118 | //! [`Action::FollowChain`]) disable both state root and signature checks. This is because in 99% 119 | //! of the cases, the runtime that is being tested is different from the one that is stored in the 120 | //! canonical chain state. This implies: 121 | //! 122 | //! 1. the state root will NEVER match, because `:CODE:` is different between the two. 123 | //! 2. replaying all transactions will fail, because the spec-version is part of the transaction 124 | //! signature. 125 | //! 126 | //! ## Best Practices 127 | //! 128 | //! Try-runtime is all about battle-testing unreleased runtimes. The following list of suggestions 129 | //! help developers maximize their testing coverage and make the best use of `try-runtime` features. 130 | //! 131 | //! ### Testing Runtime Upgrades 132 | //! 133 | //! One of the most powerful abilities of `try-runtime` is using the 134 | //! [`OnRuntimeUpgrade::pre_upgrade`] and [`OnRuntimeUpgrade::post_upgrade`] hooks to test runtime 135 | //! upgrades implemented with [`OnRuntimeUpgrade`]. [`OnRuntimeUpgrade`] can be implemented inside 136 | //! the pallet, or standalone in a runtime to define a migration to execute next runtime upgrade. In 137 | //! both cases, these methods can be added: 138 | //! 139 | //! ```ignore 140 | //! #[cfg(feature = "try-runtime")] 141 | //! fn pre_upgrade() -> Result, TryRuntimeError> {} 142 | //! 143 | //! #[cfg(feature = "try-runtime")] 144 | //! fn post_upgrade(state: Vec) -> Result<(), TryRuntimeError> {} 145 | //! ``` 146 | //! 147 | //! (The pallet macro syntax will support this simply as a part of `#[pallet::hooks]`). 148 | //! 149 | //! These hooks will be called when you execute the [`Action::OnRuntimeUpgrade`] command, before and 150 | //! after the migration. [`OnRuntimeUpgrade::pre_upgrade`] returns a [`Vec`] that can contain 151 | //! arbitrary encoded data (usually some pre-upgrade state) which will be passed to 152 | //! [`OnRuntimeUpgrade::pre_upgrade`] after upgrading and used for post checking. 153 | //! 154 | //! ### [`VersionedMigration`] 155 | //! 156 | //! It is strongly suggested to use [`VersionedMigration`] when writing custom migrations for 157 | //! pallets. 158 | //! 159 | //! ### State Consistency 160 | //! 161 | //! Similarly, each pallet can expose a function in `#[pallet::hooks]` section as follows: 162 | //! 163 | //! ```ignore 164 | //! #[cfg(feature = "try-runtime")] 165 | //! fn try_state(_: BlockNumber) -> Result<(), TryRuntimeError> {} 166 | //! ``` 167 | //! 168 | //! which is called on numerous code paths in the try-runtime tool. These checks should ensure that 169 | //! the state of the pallet is consistent and correct. See [`TryState`] for more info. 170 | //! 171 | //! ### Logging 172 | //! 173 | //! It is super helpful to make sure your migration code uses logging (always with a `runtime` log 174 | //! target prefix, e.g. `runtime::balance`) and state exactly at which stage it is, and what it is 175 | //! doing. 176 | //! 177 | //! ## Examples 178 | //! 179 | //! For the following examples, we assume the existence of the following: 180 | //! 181 | //! 1. a substrate node compiled with `--features try-runtime`, called `substrate`. This will be the 182 | //! running node that you connect to, and provide a wasm blob that has try-runtime functionality 183 | //! enabled. 184 | //! 2. the `try-runtime` CLI binary on your path. 185 | //! 186 | //! ```bash 187 | //! # this is like your running deployed node. 188 | //! cargo build --features try-runtime --release && cp target/release/substrate . 189 | //! ``` 190 | //! 191 | //! > The above example is with `substrate`'s `kitchensink-runtime`, but is applicable to any 192 | //! > substrate-based chain. 193 | //! 194 | //! * Run the migrations of a given runtime on top of a live state. 195 | //! 196 | //! ```bash 197 | //! # assuming there's `./substrate --dev --tmp --ws-port 9999` or similar running. 198 | //! try-runtime \ 199 | //! --runtime /path-to-substrate/target/release/wbuild/my-runtime.wasm \ 200 | //! on-runtime-upgrade \ 201 | //! live --uri ws://localhost:9999 202 | //! ``` 203 | //! 204 | //! * Same as the previous example, but run it at specific block number's state and using the live 205 | //! polkadot network. This means that this block hash's state should not yet have been pruned by 206 | //! the node running at `rpc.polkadot.io`. 207 | //! 208 | //! ```bash 209 | //! try-runtime \ 210 | //! --runtime /path-to-polkadot-runtimes/target/release/wbuild/polkadot-runtime/polkadot-runtime.wasm \ 211 | //! on-runtime-upgrade \ 212 | //! live --uri wss://rpc.polkadot.io:443 \ 213 | //! # replace with your desired block hash! 214 | //! --at 0xa1b16c1efd889a9f17375ec4dd5c1b4351a2be17fa069564fced10d23b9b3836 215 | //! ``` 216 | //! 217 | //! * Now, let's use a snapshot file. First, we create the snapshot: 218 | //! 219 | //! ```bash 220 | //! try-runtime --runtime existing create-snapshot --uri ws://localhost:9999 my-snapshot.snap 221 | //! 2022-12-13 10:28:17.516 INFO main remote-ext: since no at is provided, setting it to latest finalized head, 0xe7d0b614dfe89af65b33577aae46a6f958c974bf52f8a5e865a0f4faeb578d22 222 | //! 2022-12-13 10:28:17.516 INFO main remote-ext: since no prefix is filtered, the data for all pallets will be downloaded 223 | //! 2022-12-13 10:28:17.550 INFO main remote-ext: writing snapshot of 1611464 bytes to "node-268@latest.snap" 224 | //! 2022-12-13 10:28:17.551 INFO main remote-ext: initialized state externalities with storage root 0x925e4e95de4c08474fb7f976c4472fa9b8a1091619cd7820a793bf796ee6d932 and state_version V1 225 | //! ``` 226 | //! 227 | //! > Note that the snapshot contains the `existing` runtime, which does not have the correct 228 | //! > `try-runtime` feature. In the following commands, we still need to overwrite the runtime. 229 | //! 230 | //! Then, we can use it to have the same command as before, `on-runtime-upgrade` 231 | //! 232 | //! ```bash 233 | //! try-runtime \ 234 | //! --runtime /path-to-substrate/target/release/wbuild/my-runtime.wasm \ 235 | //! on-runtime-upgrade \ 236 | //! snap -p my-snapshot.snap 237 | //! ``` 238 | //! 239 | //! * Execute the latest finalized block with the given runtime. 240 | //! 241 | //! ```bash 242 | //! try-runtime \ 243 | //! --runtime /path-to-substrate/target/release/wbuild/my-runtime.wasm \ 244 | //! execute-block live \ 245 | //! --uri ws://localhost:9999 246 | //! ``` 247 | //! 248 | //! This can still be customized at a given block with `--at`. If you want to use a snapshot, you 249 | //! can still use `--block-ws-uri` to provide a node form which the block data can be fetched. 250 | //! 251 | //! Moreover, this runs the [`TryState`] hooks as well. The hooks to run can be customized with the 252 | //! `--try-state`. For example: 253 | //! 254 | //! ```bash 255 | //! try-runtime \ 256 | //! --runtime /path-to-substrate/target/release/wbuild/my-runtime.wasm \ 257 | //! execute-block \ 258 | //! --try-state System,Staking \ 259 | //! live \ 260 | //! --uri ws://localhost:9999 \ 261 | //! --pallet System Staking 262 | //! ``` 263 | //! 264 | //! Will only run the `try-state` of the two given pallets. When running `try-state` against 265 | //! some real chain data it can take a long time for the command to execute since it has to 266 | //! query all the key-value pairs. In scenarios like above where we only want to run the 267 | //! `try-state` for some specific pallets, we can use the `--pallet` option to specify from 268 | //! which pallets we want to query the state. This will greatly decrease the execution time. 269 | //! 270 | //! See [`TryStateSelect`] for more information. 271 | //! 272 | //! * Follow our live chain's blocks using `follow-chain`, whilst running the try-state of 3 pallets 273 | //! in a round robin fashion 274 | //! 275 | //! ```bash 276 | //! try-runtime \ 277 | //! --runtime /path-to-substrate/target/release/wbuild/my-runtime.wasm \ 278 | //! follow-chain \ 279 | //! --uri ws://localhost:9999 \ 280 | //! --try-state rr-3 281 | //! ``` 282 | //! 283 | //! [`VersionedMigration`]: frame_support::migrations::VersionedMigration 284 | //! [`OnRuntimeUpgrade`]: frame_support::traits::OnRuntimeUpgrade 285 | //! [`OnRuntimeUpgrade::pre_upgrade`]: frame_support::traits::OnRuntimeUpgrade::pre_upgrade 286 | //! [`OnRuntimeUpgrade::post_upgrade`]: frame_support::traits::OnRuntimeUpgrade::post_upgrade 287 | //! [`TryStateSelect`]: frame_support::traits::TryStateSelect 288 | //! [`TryState`]: frame_support::traits::TryState 289 | //! [`TestExternalities`]: sp_state_machine::TestExternalities 290 | //! [`well_known_keys`]: sp_storage::well_known_keys 291 | //! [`Action`]: try_runtime_core::commands::Action 292 | //! [`Action::FollowChain`]: try_runtime_core::commands::Action::FollowChain 293 | //! [`Action::OnRuntimeUpgrade`]: try_runtime_core::commands::Action::OnRuntimeUpgrade 294 | //! [`Action::ExecuteBlock`]: try_runtime_core::commands::Action::ExecuteBlock 295 | //! [`Action::OffchainWorker`]: try_runtime_core::commands::Action::OffchainWorker 296 | //! [`Action::CreateSnapshot`]: try_runtime_core::commands::Action::CreateSnapshot 297 | //! [`Action::FastForward`]: try_runtime_core::commands::Action::FastForward 298 | //! [`SharedParams`]: try_runtime_core::shared_parameters::SharedParams 299 | //! [`SharedParams::runtime`]: try_runtime_core::common::shared_parameters::SharedParams::runtime 300 | //! [`SharedParams::overwrite_state_version`]: try_runtime_core::common::shared_parameters::SharedParams::overwrite_state_version 301 | 302 | use std::env; 303 | 304 | use clap::Parser; 305 | use sp_runtime::{ 306 | generic::{Block, Header}, 307 | traits::BlakeTwo256, 308 | OpaqueExtrinsic, 309 | }; 310 | use try_runtime_core::commands::TryRuntime; 311 | 312 | fn init_env() { 313 | if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { 314 | env::set_var(env_logger::DEFAULT_FILTER_ENV, "info"); 315 | } 316 | env_logger::init(); 317 | } 318 | 319 | #[tokio::main] 320 | async fn main() { 321 | init_env(); 322 | 323 | let cmd = TryRuntime::parse(); 324 | cmd.run::, OpaqueExtrinsic>, sp_io::SubstrateHostFunctions>() 325 | .await 326 | .unwrap(); 327 | } 328 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "try-runtime-core" 3 | version.workspace = true 4 | authors.workspace = true 5 | description.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | 11 | [package.metadata.docs.rs] 12 | targets = ["x86_64-unknown-linux-gnu"] 13 | 14 | [dependencies] 15 | # Crates.io 16 | tokio = { workspace = true } 17 | async-trait = { workspace = true } 18 | array-bytes = { workspace = true } 19 | polkadot-primitives = { workspace = true } 20 | bytesize = { workspace = true, features = ["serde"] } 21 | clap = { workspace = true, features = ["derive"] } 22 | hex = { workspace = true } 23 | itertools = { workspace = true } 24 | log = { workspace = true } 25 | parity-scale-codec = { workspace = true, features = ["derive"] } 26 | serde = { workspace = true } 27 | serde_json = { workspace = true } 28 | strum = { workspace = true } 29 | strum_macros = { workspace = true } 30 | zstd = { workspace = true } 31 | 32 | frame-remote-externalities = { workspace = true } 33 | frame-try-runtime = { workspace = true, features=["try-runtime"] } 34 | frame-support = { workspace = true } 35 | frame-system = { workspace = true } 36 | 37 | sc-cli = { workspace = true } 38 | sc-executor = { workspace = true } 39 | 40 | sp-api = { workspace = true } 41 | sp-consensus-aura = { workspace = true } 42 | sp-consensus-babe = { workspace = true } 43 | sp-core = { workspace = true } 44 | sp-externalities = { workspace = true } 45 | sp-inherents = { workspace = true } 46 | sp-io = { workspace = true } 47 | sp-keystore = { workspace = true } 48 | sp-rpc = { workspace = true } 49 | sp-runtime = { workspace = true } 50 | sp-state-machine = { workspace = true } 51 | sp-std = { workspace = true } 52 | sp-timestamp = { workspace = true } 53 | sp-transaction-storage-proof = { workspace = true } 54 | sp-version = { workspace = true } 55 | sp-weights = { workspace = true } 56 | 57 | cumulus-primitives-parachain-inherent = { workspace = true } 58 | cumulus-primitives-core = { workspace = true } 59 | cumulus-client-parachain-inherent = { workspace = true } 60 | 61 | substrate-rpc-client = { workspace = true } 62 | paris = "1.5.15" 63 | similar-asserts = "1.6.0" 64 | 65 | [dev-dependencies] 66 | assert_cmd = { workspace = true } 67 | regex = { workspace = true } 68 | tempfile = { workspace = true } 69 | 70 | sc-service = { workspace = true } 71 | substrate-cli-test-utils = { workspace = true, features = ["try-runtime"] } 72 | -------------------------------------------------------------------------------- /core/src/commands/create_snapshot.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use std::{fmt::Debug, str::FromStr}; 19 | 20 | use sc_executor::sp_wasm_interface::HostFunctions; 21 | use sp_runtime::traits::{Block as BlockT, NumberFor}; 22 | use substrate_rpc_client::{ws_client, StateApi}; 23 | 24 | use crate::{ 25 | common::{ 26 | shared_parameters, 27 | state::{build_executor, LiveState, RuntimeChecks, State}, 28 | }, 29 | SharedParams, LOG_TARGET, 30 | }; 31 | 32 | /// Configurations for [`run`]. 33 | #[derive(Debug, Clone, clap::Parser)] 34 | pub struct Command { 35 | /// The source of the snapshot. Must be a remote node. 36 | #[clap(flatten)] 37 | pub from: LiveState, 38 | 39 | /// The snapshot path to write to. 40 | /// 41 | /// If not provided `-@.snap` will be used. 42 | #[clap(index = 1)] 43 | pub snapshot_path: Option, 44 | } 45 | 46 | /// Runs the `create_snapshot` command. 47 | pub async fn run(shared: SharedParams, command: Command) -> sc_cli::Result<()> 48 | where 49 | Block: BlockT + serde::de::DeserializeOwned, 50 | Block::Hash: serde::de::DeserializeOwned, 51 | Block::Header: serde::de::DeserializeOwned, 52 | ::Err: Debug, 53 | NumberFor: FromStr, 54 | as FromStr>::Err: Debug, 55 | HostFns: HostFunctions, 56 | { 57 | let snapshot_path = command.snapshot_path; 58 | if !matches!(shared.runtime, shared_parameters::Runtime::Existing) { 59 | return Err("creating a snapshot is only possible with --runtime existing.".into()); 60 | } 61 | 62 | let path = match snapshot_path { 63 | Some(path) => path, 64 | None => { 65 | let rpc = ws_client(&command.from.uri).await.unwrap(); 66 | let remote_spec = StateApi::::runtime_version(&rpc, None) 67 | .await 68 | .unwrap(); 69 | let path_str = format!( 70 | "{}-{}@{}.snap", 71 | remote_spec.spec_name.to_lowercase(), 72 | remote_spec.spec_version, 73 | command.from.at.clone().unwrap_or("latest".to_owned()) 74 | ); 75 | log::info!(target: LOG_TARGET, "snapshot path not provided (-s), using '{}'", path_str); 76 | path_str 77 | } 78 | }; 79 | 80 | let executor = build_executor::(&shared); 81 | let runtime_checks = RuntimeChecks { 82 | name_matches: false, 83 | version_increases: false, 84 | try_runtime_feature_enabled: false, 85 | }; 86 | let _ = State::Live(command.from) 87 | .to_ext::(&shared, &executor, Some(path.into()), runtime_checks) 88 | .await?; 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /core/src/commands/execute_block.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use std::{fmt::Debug, str::FromStr}; 19 | 20 | use parity_scale_codec::Encode; 21 | use sc_executor::sp_wasm_interface::HostFunctions; 22 | use sp_runtime::{ 23 | generic::SignedBlock, 24 | traits::{Block as BlockT, Header as HeaderT, NumberFor}, 25 | }; 26 | use substrate_rpc_client::{ws_client, ChainApi}; 27 | 28 | use crate::{ 29 | common::state::{ 30 | build_executor, state_machine_call_with_proof, LiveState, RuntimeChecks, State, 31 | }, 32 | full_extensions, rpc_err_handler, SharedParams, LOG_TARGET, 33 | }; 34 | 35 | /// Configurations for [`run`]. 36 | /// 37 | /// This will always call into `TryRuntime_execute_block`, which can optionally skip the state-root 38 | /// check (useful for trying a unreleased runtime), and can execute runtime sanity checks as well. 39 | #[derive(Debug, Clone, clap::Parser)] 40 | pub struct Command { 41 | /// Which try-state targets to execute when running this command. 42 | /// 43 | /// Expected values: 44 | /// - `all` 45 | /// - `none` 46 | /// - A comma separated list of pallets, as per pallet names in `construct_runtime!()` (e.g. 47 | /// `Staking, System`). 48 | /// - `rr-[x]` where `[x]` is a number. Then, the given number of pallets are checked in a 49 | /// round-robin fashion. 50 | #[arg(long, default_value = "all")] 51 | pub try_state: frame_try_runtime::TryStateSelect, 52 | 53 | /// The ws uri from which to fetch the block. 54 | /// 55 | /// This will always fetch the next block of whatever `state` is referring to, because this is 56 | /// the only sensible combination. In other words, if you have the state of block `n`, you 57 | /// should execute block `n+1` on top of it. 58 | /// 59 | /// If `state` is `Live`, this can be ignored and the same uri is used for both. 60 | #[arg( 61 | long, 62 | value_parser = crate::common::parse::url 63 | )] 64 | pub block_ws_uri: Option, 65 | 66 | /// The state type to use. 67 | #[command(subcommand)] 68 | pub state: State, 69 | } 70 | 71 | impl Command { 72 | fn block_ws_uri(&self) -> String { 73 | match (&self.block_ws_uri, &self.state) { 74 | (Some(block_ws_uri), State::Snap { .. }) => block_ws_uri.to_owned(), 75 | (Some(block_ws_uri), State::Live { .. }) => { 76 | log::error!(target: LOG_TARGET, "--block-uri is provided while state type is live, Are you sure you know what you are doing?"); 77 | block_ws_uri.to_owned() 78 | } 79 | (None, State::Live(LiveState { uri, .. })) => uri.clone(), 80 | (None, State::Snap { .. }) => { 81 | panic!("either `--block-uri` must be provided, or state must be `live`"); 82 | } 83 | } 84 | } 85 | } 86 | 87 | // Runs the `execute_block` command. 88 | pub async fn run(shared: SharedParams, command: Command) -> sc_cli::Result<()> 89 | where 90 | Block: BlockT + serde::de::DeserializeOwned, 91 | ::Err: Debug, 92 | Block::Hash: serde::de::DeserializeOwned, 93 | Block::Header: serde::de::DeserializeOwned, 94 | as TryInto>::Error: Debug, 95 | HostFns: HostFunctions, 96 | { 97 | let executor = build_executor::(&shared); 98 | let block_ws_uri = command.block_ws_uri(); 99 | let rpc = ws_client(&block_ws_uri).await?; 100 | 101 | let live_state = match command.state { 102 | State::Live(live_state) => { 103 | // If no --at is provided, get the latest block to replay 104 | if live_state.at.is_some() { 105 | live_state 106 | } else { 107 | let header = 108 | ChainApi::<(), Block::Hash, Block::Header, SignedBlock>::header( 109 | &rpc, None, 110 | ) 111 | .await 112 | .map_err(rpc_err_handler)? 113 | .expect("header exists, block should also exist; qed"); 114 | LiveState { 115 | uri: block_ws_uri, 116 | at: Some(hex::encode(header.hash().encode())), 117 | pallet: Default::default(), 118 | hashed_prefixes: Default::default(), 119 | child_tree: Default::default(), 120 | } 121 | } 122 | } 123 | _ => { 124 | unreachable!("execute block currently only supports Live state") 125 | } 126 | }; 127 | 128 | // The block we want to *execute* at is the block passed by the user 129 | let execute_at = live_state.at::()?; 130 | 131 | let prev_block_live_state = live_state.to_prev_block_live_state::().await?; 132 | 133 | // Get state for the prev block 134 | let runtime_checks = RuntimeChecks { 135 | name_matches: !shared.disable_spec_name_check, 136 | version_increases: false, 137 | try_runtime_feature_enabled: true, 138 | }; 139 | let ext = State::Live(prev_block_live_state) 140 | .to_ext::(&shared, &executor, None, runtime_checks) 141 | .await?; 142 | 143 | // Execute the desired block on top of it 144 | let block = 145 | ChainApi::<(), Block::Hash, Block::Header, SignedBlock>::block(&rpc, execute_at) 146 | .await 147 | .map_err(rpc_err_handler)? 148 | .expect("header exists, block should also exist; qed") 149 | .block; 150 | 151 | // A digest item gets added when the runtime is processing the block, so we need to pop 152 | // the last one to be consistent with what a gossiped block would contain. 153 | let (mut header, extrinsics) = block.deconstruct(); 154 | header.digest_mut().pop(); 155 | let block = Block::new(header, extrinsics); 156 | 157 | // for now, hardcoded for the sake of simplicity. We might customize them one day. 158 | let state_root_check = false; 159 | let signature_check = false; 160 | let payload = ( 161 | block.clone(), 162 | state_root_check, 163 | signature_check, 164 | command.try_state, 165 | ) 166 | .encode(); 167 | 168 | let _ = state_machine_call_with_proof::( 169 | &ext, 170 | &mut Default::default(), 171 | &executor, 172 | "TryRuntime_execute_block", 173 | &payload, 174 | full_extensions(executor.clone()), 175 | shared.export_proof, 176 | )?; 177 | 178 | Ok(()) 179 | } 180 | -------------------------------------------------------------------------------- /core/src/commands/fast_forward.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use std::{fmt::Debug, str::FromStr, sync::Arc, time::Duration}; 19 | 20 | use parity_scale_codec::Encode; 21 | use sc_cli::Result; 22 | use sc_executor::sp_wasm_interface::HostFunctions; 23 | use serde::de::DeserializeOwned; 24 | use sp_core::H256; 25 | use sp_runtime::traits::NumberFor; 26 | use tokio::sync::Mutex; 27 | 28 | use crate::{ 29 | common::{ 30 | empty_block::{inherents::providers::ProviderVariant, production::mine_block}, 31 | state::{build_executor, state_machine_call_with_proof, RuntimeChecks, State}, 32 | }, 33 | BlockT, SharedParams, 34 | }; 35 | 36 | /// Configuration for [`run`]. 37 | #[derive(Debug, Clone, clap::Parser)] 38 | pub struct Command { 39 | /// How many empty blocks should be processed. 40 | #[arg(long)] 41 | pub n_blocks: u64, 42 | 43 | /// The chain blocktime in milliseconds. 44 | #[arg(long)] 45 | pub blocktime: u64, 46 | 47 | /// Which try-state targets to execute when running this command. 48 | /// 49 | /// Expected values: 50 | /// - `all` 51 | /// - `none` 52 | /// - A comma separated list of pallets, as per pallet names in `construct_runtime!()` (e.g. 53 | /// `Staking, System`). 54 | /// - `rr-[x]` where `[x]` is a number. Then, the given number of pallets are checked in a 55 | /// round-robin fashion. 56 | #[arg(long, default_value = "all")] 57 | pub try_state: frame_try_runtime::TryStateSelect, 58 | 59 | /// Whether to run pending migrations before fast-forwarding. 60 | #[arg(long, default_value = "true")] 61 | pub run_migrations: bool, 62 | 63 | /// The state type to use. 64 | #[command(subcommand)] 65 | pub state: State, 66 | } 67 | 68 | pub async fn run(shared: SharedParams, command: Command) -> Result<()> 69 | where 70 | Block: BlockT + DeserializeOwned, 71 | Block::Header: DeserializeOwned, 72 | ::Err: Debug, 73 | NumberFor: FromStr, 74 | as FromStr>::Err: Debug, 75 | HostFns: HostFunctions, 76 | { 77 | let executor = build_executor::(&shared); 78 | let runtime_checks = RuntimeChecks { 79 | name_matches: !shared.disable_spec_name_check, 80 | version_increases: false, 81 | try_runtime_feature_enabled: true, 82 | }; 83 | let ext = command 84 | .state 85 | .to_ext::(&shared, &executor, None, runtime_checks) 86 | .await?; 87 | 88 | if command.run_migrations { 89 | log::info!("Running migrations..."); 90 | state_machine_call_with_proof::( 91 | &ext, 92 | &mut Default::default(), 93 | &executor, 94 | "TryRuntime_on_runtime_upgrade", 95 | command.try_state.encode().as_ref(), 96 | Default::default(), // we don't really need any extensions here. 97 | None, 98 | )?; 99 | } 100 | 101 | log::info!("Fast forwarding {} blocks...", command.n_blocks); 102 | 103 | let inner_ext = Arc::new(Mutex::new(ext.inner_ext)); 104 | let mut parent_header = ext.header.clone(); 105 | let mut parent_block_building_info = None; 106 | let provider_variant = ProviderVariant::Smart(Duration::from_millis(command.blocktime)); 107 | 108 | for _ in 1..=command.n_blocks { 109 | let (next_block_building_info, next_header, _) = mine_block::( 110 | inner_ext.clone(), 111 | &executor, 112 | parent_block_building_info, 113 | parent_header.clone(), 114 | provider_variant, 115 | command.try_state.clone(), 116 | ) 117 | .await?; 118 | 119 | parent_block_building_info = Some(next_block_building_info); 120 | parent_header = next_header; 121 | } 122 | 123 | Ok(()) 124 | } 125 | -------------------------------------------------------------------------------- /core/src/commands/follow_chain.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use std::{fmt::Debug, str::FromStr}; 19 | 20 | use parity_scale_codec::Encode; 21 | use sc_executor::sp_wasm_interface::HostFunctions; 22 | use serde::{de::DeserializeOwned, Serialize}; 23 | use sp_core::H256; 24 | use sp_runtime::{ 25 | generic::SignedBlock, 26 | traits::{Block as BlockT, Header as HeaderT, NumberFor}, 27 | }; 28 | use substrate_rpc_client::{ws_client, ChainApi, FinalizedHeaders, Subscription, WsClient}; 29 | 30 | use crate::{ 31 | common::{ 32 | parse, 33 | state::{build_executor, state_machine_call_with_proof, LiveState, RuntimeChecks, State}, 34 | }, 35 | full_extensions, rpc_err_handler, SharedParams, LOG_TARGET, 36 | }; 37 | 38 | const SUB: &str = "chain_subscribeFinalizedHeads"; 39 | const UN_SUB: &str = "chain_unsubscribeFinalizedHeads"; 40 | 41 | /// Configurations for [`run`]. 42 | #[derive(Debug, Clone, clap::Parser)] 43 | pub struct Command { 44 | /// The url to connect to. 45 | #[arg(short, long, value_parser = parse::url)] 46 | pub uri: String, 47 | 48 | /// If set, then the state root check is enabled. 49 | #[arg(long)] 50 | pub state_root_check: bool, 51 | 52 | /// Which try-state targets to execute when running this command. 53 | /// 54 | /// Expected values: 55 | /// - `all` 56 | /// - `none` 57 | /// - A comma separated list of pallets, as per pallet names in `construct_runtime!()` (e.g. 58 | /// `Staking, System`). 59 | /// - `rr-[x]` where `[x]` is a number. Then, the given number of pallets are checked in a 60 | /// round-robin fashion. 61 | #[arg(long, default_value = "all")] 62 | pub try_state: frame_try_runtime::TryStateSelect, 63 | 64 | /// If present, a single connection to a node will be kept and reused for fetching blocks. 65 | #[arg(long)] 66 | pub keep_connection: bool, 67 | } 68 | 69 | /// Start listening for with `SUB` at `url`. 70 | /// 71 | /// Returns a pair `(client, subscription)` - `subscription` alone will be useless, because it 72 | /// relies on the related alive `client`. 73 | async fn start_subscribing( 74 | url: &str, 75 | ) -> sc_cli::Result<(WsClient, Subscription
)> { 76 | let client = ws_client(url) 77 | .await 78 | .map_err(|e| sc_cli::Error::Application(e.into()))?; 79 | 80 | log::info!(target: LOG_TARGET, "subscribing to {:?} / {:?}", SUB, UN_SUB); 81 | 82 | let sub = ChainApi::<(), (), Header, ()>::subscribe_finalized_heads(&client) 83 | .await 84 | .map_err(|e| sc_cli::Error::Application(e.into()))?; 85 | Ok((client, sub)) 86 | } 87 | 88 | // Runs the `follow_chain` command. 89 | pub async fn run(shared: SharedParams, command: Command) -> sc_cli::Result<()> 90 | where 91 | Block: BlockT + DeserializeOwned, 92 | Block::Header: DeserializeOwned, 93 | ::Err: Debug, 94 | NumberFor: FromStr, 95 | as FromStr>::Err: Debug, 96 | HostFns: HostFunctions, 97 | { 98 | let (rpc, subscription) = start_subscribing::(&command.uri).await?; 99 | let mut finalized_headers: FinalizedHeaders = 100 | FinalizedHeaders::new(&rpc, subscription); 101 | 102 | let mut maybe_state_ext = None; 103 | let executor = build_executor::(&shared); 104 | 105 | while let Some(header) = finalized_headers.next().await { 106 | let hash = header.hash(); 107 | let number = header.number(); 108 | 109 | let block = 110 | ChainApi::<(), Block::Hash, Block::Header, SignedBlock>::block(&rpc, Some(hash)) 111 | .await 112 | .map_err(|e| { 113 | if matches!(e, substrate_rpc_client::Error::ParseError(_)) { 114 | log::error!( 115 | target: LOG_TARGET, 116 | "failed to parse the block format of remote against the local \ 117 | codebase. The block format has changed, and follow-chain cannot run in \ 118 | this case. Try running this command in a branch of your codebase that 119 | has the same block format as the remote chain. For now, we replace the \ 120 | block with an empty one." 121 | ); 122 | } 123 | rpc_err_handler(e) 124 | })? 125 | .expect("if header exists, block should also exist.") 126 | .block; 127 | 128 | log::debug!( 129 | target: LOG_TARGET, 130 | "new block event: {:?} => {:?}, extrinsics: {}", 131 | hash, 132 | number, 133 | block.extrinsics().len() 134 | ); 135 | 136 | // create an ext at the state of this block, whatever is the first subscription event. 137 | if maybe_state_ext.is_none() { 138 | let state = State::Live(LiveState { 139 | uri: command.uri.clone(), 140 | // a bit dodgy, we have to un-parse the has to a string again and re-parse it 141 | // inside. 142 | at: Some(hex::encode(header.parent_hash().encode())), 143 | pallet: vec![], 144 | child_tree: true, 145 | hashed_prefixes: vec![], 146 | }); 147 | let runtime_checks = RuntimeChecks { 148 | name_matches: !shared.disable_spec_name_check, 149 | version_increases: false, 150 | try_runtime_feature_enabled: true, 151 | }; 152 | let ext = state 153 | .to_ext::(&shared, &executor, None, runtime_checks) 154 | .await?; 155 | maybe_state_ext = Some(ext); 156 | } 157 | 158 | let state_ext = maybe_state_ext 159 | .as_mut() 160 | .expect("state_ext either existed or was just created"); 161 | 162 | let mut overlayed_changes = Default::default(); 163 | let result = state_machine_call_with_proof::( 164 | state_ext, 165 | &mut overlayed_changes, 166 | &executor, 167 | "TryRuntime_execute_block", 168 | ( 169 | block, 170 | command.state_root_check, 171 | true, 172 | command.try_state.clone(), 173 | ) 174 | .encode() 175 | .as_ref(), 176 | full_extensions(executor.clone()), 177 | shared 178 | .export_proof 179 | .as_ref() 180 | .map(|path| path.as_path().join(format!("{}.json", number))), 181 | ); 182 | 183 | if let Err(why) = result { 184 | log::error!( 185 | target: LOG_TARGET, 186 | "failed to execute block {:?} due to {:?}", 187 | number, 188 | why 189 | ); 190 | continue; 191 | } 192 | 193 | let storage_changes = overlayed_changes 194 | .drain_storage_changes( 195 | &state_ext.backend, 196 | // Note that in case a block contains a runtime upgrade, state version could 197 | // potentially be incorrect here, this is very niche and would only result in 198 | // unaligned roots, so this use case is ignored for now. 199 | state_ext.state_version, 200 | ) 201 | .unwrap(); 202 | 203 | state_ext.backend.apply_transaction( 204 | storage_changes.transaction_storage_root, 205 | storage_changes.transaction, 206 | ); 207 | 208 | log::info!( 209 | target: LOG_TARGET, 210 | "executed block {}, new storage root {:?}", 211 | number, 212 | state_ext.as_backend().root(), 213 | ); 214 | } 215 | 216 | log::error!(target: LOG_TARGET, "ws subscription must have terminated."); 217 | Ok(()) 218 | } 219 | -------------------------------------------------------------------------------- /core/src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use std::{fmt::Debug, str::FromStr}; 19 | 20 | use sc_executor::sp_wasm_interface::HostFunctions; 21 | use sp_core::H256; 22 | use sp_runtime::{ 23 | traits::{Block as BlockT, NumberFor}, 24 | DeserializeOwned, 25 | }; 26 | 27 | use crate::common::shared_parameters::SharedParams; 28 | 29 | pub mod create_snapshot; 30 | pub mod execute_block; 31 | pub mod fast_forward; 32 | pub mod follow_chain; 33 | pub mod offchain_worker; 34 | pub mod on_runtime_upgrade; 35 | 36 | /// Ready to use, vanilla command combining common actions. 37 | #[derive(Debug, Clone, clap::Parser)] 38 | #[command(author, version, about)] 39 | pub struct TryRuntime { 40 | #[clap(flatten)] 41 | pub shared: SharedParams, 42 | 43 | #[command(subcommand)] 44 | pub action: Action, 45 | } 46 | 47 | impl TryRuntime { 48 | pub async fn run(&self) -> sc_cli::Result<()> 49 | where 50 | Block: BlockT + DeserializeOwned, 51 | Block::Header: DeserializeOwned, 52 | Block::Hash: FromStr, 53 | ::Err: Debug, 54 | as FromStr>::Err: Debug, 55 | as TryInto>::Error: Debug, 56 | NumberFor: FromStr, 57 | HostFns: HostFunctions, 58 | { 59 | self.action.run::(&self.shared).await 60 | } 61 | } 62 | 63 | /// Possible actions of `try-runtime`. 64 | #[derive(Debug, Clone, clap::Subcommand)] 65 | pub enum Action { 66 | /// Execute the migrations of the given runtime 67 | /// 68 | /// This uses a custom runtime api call, namely "TryRuntime_on_runtime_upgrade". The code path 69 | /// only triggers all of the `on_runtime_upgrade` hooks in the runtime, and optionally 70 | /// `try_state`. 71 | /// 72 | /// See [`TryRuntime`] and [`on_runtime_upgrade::Command`] for more information. 73 | OnRuntimeUpgrade(on_runtime_upgrade::Command), 74 | 75 | /// Executes the given block against some state. 76 | /// 77 | /// This uses a custom runtime api call, namely "TryRuntime_execute_block". Some checks, such 78 | /// as state-root and signature checks are always disabled, and additional checks like 79 | /// `try-state` can be enabled. 80 | /// 81 | /// See [`TryRuntime`] and [`execute_block::Command`] for more information. 82 | ExecuteBlock(execute_block::Command), 83 | 84 | /// Executes *the offchain worker hooks* of a given block against some state. 85 | /// 86 | /// This executes the same runtime api as normal block import, namely 87 | /// `OffchainWorkerApi_offchain_worker`. 88 | /// 89 | /// See [`frame_try_runtime::TryRuntime`] and [`offchain_worker::Command`] 90 | /// for more information. 91 | OffchainWorker(offchain_worker::Command), 92 | 93 | /// Follow the given chain's finalized blocks and apply all of its extrinsics. 94 | /// 95 | /// This is essentially repeated calls to [`Action::ExecuteBlock`]. 96 | /// 97 | /// This allows the behavior of a new runtime to be inspected over a long period of time, with 98 | /// realistic transactions coming as input. 99 | /// 100 | /// NOTE: this does NOT execute the offchain worker hooks of mirrored blocks. This might be 101 | /// added in the future. 102 | /// 103 | /// This does not support snapshot states, and can only work with a remote chain. Upon first 104 | /// connections, starts listening for finalized block events. Upon first block notification, it 105 | /// initializes the state from the remote node, and starts applying that block, plus all the 106 | /// blocks that follow, to the same growing state. 107 | /// 108 | /// This can only work if the block format between the remote chain and the new runtime being 109 | /// tested has remained the same, otherwise block decoding might fail. 110 | FollowChain(follow_chain::Command), 111 | 112 | /// Create snapshot files. 113 | /// 114 | /// The `create-snapshot` subcommand facilitates the creation of a snapshot from a node's 115 | /// state. This snapshot can be loaded rapidly into memory from disk, providing an 116 | /// efficient alternative to downloading state from the node for every new command 117 | /// execution. 118 | /// 119 | /// **Usage**: 120 | /// 121 | /// 1. Create a snapshot from a remote node: 122 | /// 123 | /// try-runtime create-snapshot --uri ws://remote-node-uri my_state.snap 124 | /// 125 | /// 2. Utilize the snapshot with `on-runtime-upgrade`: 126 | /// 127 | /// try-runtime --runtime ./path/to/runtime.wasm on-runtime-upgrade snap --path my_state.snap 128 | CreateSnapshot(create_snapshot::Command), 129 | 130 | /// Executes a runtime upgrade (optional), then mines a number of blocks while performing 131 | /// try-state checks. 132 | /// 133 | /// The try-state checks are performed using the `TryRuntime_execute_block` runtime api. 134 | /// 135 | /// See [`TryRuntime`] and [`fast_forward::Command`] for more information. 136 | FastForward(fast_forward::Command), 137 | } 138 | 139 | impl Action { 140 | pub async fn run(&self, shared: &SharedParams) -> sc_cli::Result<()> 141 | where 142 | Block: BlockT + DeserializeOwned, 143 | Block::Header: DeserializeOwned, 144 | Block::Hash: FromStr, 145 | ::Err: Debug, 146 | as FromStr>::Err: Debug, 147 | as TryInto>::Error: Debug, 148 | NumberFor: FromStr, 149 | HostFns: HostFunctions, 150 | { 151 | match &self { 152 | Action::OnRuntimeUpgrade(ref cmd) => { 153 | on_runtime_upgrade::CheckOnRuntimeUpgrade:: { 154 | shared: shared.clone(), 155 | command: cmd.clone(), 156 | _phantom: Default::default(), 157 | } 158 | .run() 159 | .await 160 | } 161 | Action::ExecuteBlock(cmd) => { 162 | execute_block::run::(shared.clone(), cmd.clone()).await 163 | } 164 | Action::OffchainWorker(cmd) => { 165 | offchain_worker::run::(shared.clone(), cmd.clone()).await 166 | } 167 | Action::FollowChain(cmd) => { 168 | follow_chain::run::(shared.clone(), cmd.clone()).await 169 | } 170 | Action::CreateSnapshot(cmd) => { 171 | create_snapshot::run::(shared.clone(), cmd.clone()).await 172 | } 173 | Action::FastForward(cmd) => { 174 | fast_forward::run::(shared.clone(), cmd.clone()).await 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /core/src/commands/offchain_worker.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use std::{fmt::Debug, str::FromStr}; 19 | 20 | use parity_scale_codec::Encode; 21 | use sc_executor::sp_wasm_interface::HostFunctions; 22 | use sp_runtime::traits::{Block as BlockT, NumberFor}; 23 | use substrate_rpc_client::{ws_client, ChainApi}; 24 | 25 | use crate::{ 26 | common::{ 27 | parse, 28 | state::{build_executor, state_machine_call, LiveState, RuntimeChecks, State}, 29 | }, 30 | full_extensions, rpc_err_handler, SharedParams, LOG_TARGET, 31 | }; 32 | 33 | /// Configuration for [`run`]. 34 | #[derive(Debug, Clone, clap::Parser)] 35 | pub struct Command { 36 | /// The ws uri from which to fetch the header. 37 | /// 38 | /// If the `live` state type is being used, then this can be omitted, and is equal to whatever 39 | /// the `state::uri` is. Only use this (with care) when combined with a snapshot. 40 | #[arg( 41 | long, 42 | value_parser = parse::url 43 | )] 44 | pub header_ws_uri: Option, 45 | 46 | /// The state type to use. 47 | #[command(subcommand)] 48 | pub state: State, 49 | } 50 | 51 | impl Command { 52 | fn header_ws_uri(&self) -> String { 53 | match (&self.header_ws_uri, &self.state) { 54 | (Some(header_ws_uri), State::Snap { .. }) => header_ws_uri.to_owned(), 55 | (Some(header_ws_uri), State::Live { .. }) => { 56 | log::error!(target: LOG_TARGET, "--header-uri is provided while state type is live, this will most likely lead to a nonsensical result."); 57 | header_ws_uri.to_owned() 58 | } 59 | (None, State::Live(LiveState { uri, .. })) => uri.clone(), 60 | (None, State::Snap { .. }) => { 61 | panic!("either `--header-uri` must be provided, or state must be `live`"); 62 | } 63 | } 64 | } 65 | } 66 | 67 | // Runs the `offchain_worker` command. 68 | pub async fn run(shared: SharedParams, command: Command) -> sc_cli::Result<()> 69 | where 70 | Block: BlockT + serde::de::DeserializeOwned, 71 | Block::Header: serde::de::DeserializeOwned, 72 | ::Err: Debug, 73 | NumberFor: FromStr, 74 | as FromStr>::Err: Debug, 75 | HostFns: HostFunctions, 76 | { 77 | let executor = build_executor(&shared); 78 | let block_ws_uri = command.header_ws_uri(); 79 | let rpc = ws_client(&block_ws_uri).await?; 80 | 81 | let live_state = match command.state { 82 | State::Live(live_state) => live_state, 83 | _ => { 84 | unreachable!("execute block currently only supports Live state") 85 | } 86 | }; 87 | 88 | // The block we want to *execute* at is the block passed by the user 89 | let execute_at = live_state.at::()?; 90 | 91 | // Get state for the prev block 92 | let prev_block_live_state = live_state.to_prev_block_live_state::().await?; 93 | let runtime_checks = RuntimeChecks { 94 | name_matches: !shared.disable_spec_name_check, 95 | version_increases: false, 96 | try_runtime_feature_enabled: true, 97 | }; 98 | let ext = State::Live(prev_block_live_state) 99 | .to_ext::(&shared, &executor, None, runtime_checks) 100 | .await?; 101 | 102 | let header = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, execute_at) 103 | .await 104 | .map_err(rpc_err_handler) 105 | .map(|maybe_header| maybe_header.ok_or("Header does not exist"))??; 106 | let payload = header.encode(); 107 | 108 | let _ = state_machine_call::( 109 | &ext, 110 | &executor, 111 | "OffchainWorkerApi_offchain_worker", 112 | &payload, 113 | full_extensions(executor.clone()), 114 | )?; 115 | 116 | log::info!(target: LOG_TARGET, "finished execution"); 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /core/src/commands/on_runtime_upgrade/mbms.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Debug, ops::DerefMut, str::FromStr, sync::Arc, time::Duration}; 2 | 3 | use log::Level; 4 | use parity_scale_codec::{Codec, Encode}; 5 | use sc_executor::sp_wasm_interface::HostFunctions; 6 | use sp_core::{twox_128, Hasher, H256}; 7 | use sp_runtime::{ 8 | traits::{Block as BlockT, NumberFor}, 9 | DeserializeOwned, ExtrinsicInclusionMode, 10 | }; 11 | use sp_state_machine::TestExternalities; 12 | use tokio::sync::Mutex; 13 | 14 | use crate::{ 15 | commands::on_runtime_upgrade::Command, 16 | common::{ 17 | empty_block::{ 18 | inherents::providers::ProviderVariant, 19 | production::{core_version, mine_block}, 20 | }, 21 | misc_logging::{basti_log, LogLevelGuard}, 22 | state::{build_executor, state_machine_call_with_proof, RuntimeChecks}, 23 | }, 24 | SharedParams, LOG_TARGET, 25 | }; 26 | 27 | /// Checks multi block migrations (MBMs) for a runtime upgrade. 28 | pub struct MbmChecker { 29 | pub command: Command, 30 | pub shared: SharedParams, 31 | pub runtime_checks: RuntimeChecks, 32 | pub _phantom: core::marker::PhantomData<(Block, HostFns)>, 33 | } 34 | 35 | impl MbmChecker 36 | where 37 | Block: BlockT + DeserializeOwned, 38 | Block::Header: DeserializeOwned, 39 | ::Err: Debug, 40 | NumberFor: FromStr, 41 | as FromStr>::Err: Debug, 42 | HostFns: HostFunctions, 43 | { 44 | pub async fn check_mbms(&self) -> sc_cli::Result<()> { 45 | basti_log( 46 | Level::Info, 47 | &format!( 48 | "🔬 Running Multi-Block-Migrations with checks: {:?}", 49 | self.command.checks 50 | ), 51 | ); 52 | 53 | let executor = build_executor(&self.shared); 54 | let ext = self 55 | .command 56 | .state 57 | .to_ext::(&self.shared, &executor, None, self.runtime_checks) 58 | .await?; 59 | 60 | if core_version::(&ext, &executor)? < 5 { 61 | return Err("Your runtime does not support Multi-Block-Migrations. Please disable the check with `--mbms false` or update your runtime.".into()); 62 | } 63 | 64 | let inner_ext = Arc::new(Mutex::new(ext.inner_ext)); 65 | let mut parent_header = ext.header.clone(); 66 | let mut parent_block_building_info = None; 67 | let provider_variant = 68 | ProviderVariant::Smart(Duration::from_millis(self.command.blocktime)); 69 | let mut n = 0; 70 | 71 | let mut ext_guard = inner_ext.lock().await; 72 | let ext = ext_guard.deref_mut(); 73 | Self::modify_spec_name(ext).await?; 74 | drop(ext_guard); 75 | 76 | // This actually runs the MBMs block by block: 77 | loop { 78 | let _quiet = LogLevelGuard::new(log::LevelFilter::Info); 79 | let (next_block_building_info, next_header, mode) = mine_block::( 80 | inner_ext.clone(), 81 | &executor, 82 | parent_block_building_info, 83 | parent_header.clone(), 84 | provider_variant, 85 | frame_try_runtime::TryStateSelect::None, 86 | ) 87 | .await?; 88 | 89 | parent_block_building_info = Some(next_block_building_info); 90 | parent_header = next_header; 91 | // The first block does not yet have the MBMs enabled. 92 | let first_is_free = n == 0; 93 | 94 | if n > (self.command.mbm_max_blocks + 1) { 95 | // +1 for the MBM init block 96 | log::error!(target: LOG_TARGET, "MBM reached its maximum number of allowed blocks after {} blocks. Increase --mbm-max-blocks if you think this is not a bug.", n); 97 | return Err("MBM max blocks reached".into()); 98 | } else if first_is_free || Self::poll_mbms_ongoing(mode, inner_ext.clone()).await { 99 | n += 1; 100 | log::info!(target: LOG_TARGET, "MBM ongoing for {n} blocks"); 101 | } else { 102 | log::info!(target: LOG_TARGET, "MBM finished after {n} blocks"); 103 | break; 104 | } 105 | } 106 | 107 | let mut ext_guard = inner_ext.lock().await; 108 | let ext = ext_guard.deref_mut(); 109 | log::info!(target: LOG_TARGET, "MBM finished. Executing block one more time."); 110 | 111 | let _ = state_machine_call_with_proof::( 112 | ext, 113 | &mut Default::default(), 114 | &executor, 115 | "TryRuntime_on_runtime_upgrade", 116 | self.command.checks.encode().as_ref(), 117 | Default::default(), // TODO 118 | None, 119 | )?; 120 | 121 | Ok(()) 122 | } 123 | 124 | /// Modify up the spec name in storage such that the `was_upgraded` check will always return 125 | /// true because of changes spec name. 126 | async fn modify_spec_name(ext: &mut TestExternalities) -> sc_cli::Result<()> 127 | where 128 | H: Hasher + 'static, 129 | H::Out: Codec + Ord, 130 | { 131 | let key = [twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat(); 132 | let version = frame_system::LastRuntimeUpgradeInfo { 133 | spec_version: 0.into(), 134 | spec_name: "definitely-something-different".into(), 135 | }; 136 | 137 | ext.execute_with(|| { 138 | sp_io::storage::set(&key, &version.encode()); 139 | }); 140 | ext.commit_all()?; 141 | 142 | Ok(()) 143 | } 144 | 145 | /// Are there any Multi-Block-Migrations ongoing? 146 | async fn poll_mbms_ongoing( 147 | mode: Option, 148 | ext_mutex: std::sync::Arc>>, 149 | ) -> bool 150 | where 151 | H: Hasher + 'static, 152 | H::Out: Codec + Ord, 153 | { 154 | if mode == Some(ExtrinsicInclusionMode::OnlyInherents) { 155 | log::info!(target: LOG_TARGET, "Runtime reports OnlyInherents"); 156 | return true; 157 | } 158 | 159 | let mut ext_guard = ext_mutex.lock().await; 160 | let ext = ext_guard.deref_mut(); 161 | 162 | ext.execute_with(|| { 163 | let mbm_in_progress_key = 164 | [twox_128(b"MultiBlockMigrations"), twox_128(b"Cursor")].concat(); 165 | let mbm_in_progress = sp_io::storage::get(&mbm_in_progress_key).unwrap_or_default(); 166 | 167 | !mbm_in_progress.is_empty() 168 | }) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /core/src/commands/on_runtime_upgrade/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | pub mod mbms; 19 | 20 | use std::{collections::BTreeMap, fmt::Debug, str::FromStr}; 21 | 22 | use bytesize::ByteSize; 23 | use frame_remote_externalities::RemoteExternalities; 24 | use frame_try_runtime::UpgradeCheckSelect; 25 | use log::Level; 26 | use parity_scale_codec::Encode; 27 | use sc_executor::sp_wasm_interface::HostFunctions; 28 | use sp_core::{hexdisplay::HexDisplay, twox_128, Hasher, H256}; 29 | use sp_runtime::{ 30 | traits::{Block as BlockT, HashingFor, NumberFor}, 31 | DeserializeOwned, 32 | }; 33 | use sp_state_machine::{CompactProof, OverlayedChanges, StorageProof}; 34 | 35 | use crate::{ 36 | commands::on_runtime_upgrade::mbms::MbmChecker, 37 | common::{ 38 | misc_logging::{basti_log, LogLevelGuard}, 39 | state::{build_executor, state_machine_call_with_proof, RuntimeChecks, State}, 40 | }, 41 | RefTimeInfo, SharedParams, LOG_TARGET, 42 | }; 43 | 44 | /// Configuration for [`run`]. 45 | #[derive(Debug, Clone, clap::Parser)] 46 | pub struct Command { 47 | /// The state type to use. 48 | #[command(subcommand)] 49 | pub state: State, 50 | 51 | /// Select which optional checks to perform. Selects all when no value is given. 52 | /// 53 | /// - `none`: Perform no checks. 54 | /// - `all`: Perform all checks (default when --checks is present with no value). 55 | /// - `pre-and-post`: Perform pre- and post-upgrade checks (default when the arg is not 56 | /// present). 57 | /// - `try-state`: Perform the try-state checks. 58 | /// 59 | /// Performing any checks will potentially invalidate the measured PoV/Weight. 60 | // NOTE: The clap attributes make it backwards compatible with the previous `--checks` flag. 61 | #[clap(long, 62 | default_value = "pre-and-post", 63 | default_missing_value = "all", 64 | num_args = 0..=1, 65 | verbatim_doc_comment 66 | )] 67 | pub checks: UpgradeCheckSelect, 68 | 69 | /// Whether to disable weight warnings, useful if the runtime is for a relay chain. 70 | #[clap(long, default_value = "false", default_missing_value = "true")] 71 | pub no_weight_warnings: bool, 72 | 73 | /// Whether to skip enforcing that the new runtime `spec_version` is greater or equal to the 74 | /// existing `spec_version`. 75 | #[clap(long, default_value = "false", default_missing_value = "true")] 76 | pub disable_spec_version_check: bool, 77 | 78 | /// Whether to disable migration idempotency checks 79 | #[clap(long, default_value = "false", default_missing_value = "true")] 80 | pub disable_idempotency_checks: bool, 81 | 82 | /// When migrations are detected as not idempotent, enabling this will output a diff of the 83 | /// storage before and after running the same set of migrations the second time. 84 | #[clap(long, default_value = "false", default_missing_value = "true")] 85 | pub print_storage_diff: bool, 86 | 87 | /// Whether or multi-block migrations should be executed to completion after single block 88 | /// migratons are completed. 89 | #[clap(long, default_value = "false", default_missing_value = "true")] 90 | pub disable_mbm_checks: bool, 91 | 92 | /// The maximum duration we expect all MBMs combined to take. 93 | /// 94 | /// This value is just here to ensure that the CLI won't run forever in case of a buggy MBM. 95 | #[clap(long, default_value = "600")] 96 | pub mbm_max_blocks: u32, 97 | 98 | /// The chain blocktime in milliseconds. 99 | #[arg(long)] 100 | pub blocktime: u64, 101 | } 102 | 103 | /// Convenience struct to hold all the generic args and where clauses. 104 | pub(crate) struct CheckOnRuntimeUpgrade { 105 | pub shared: SharedParams, 106 | pub command: Command, 107 | pub _phantom: std::marker::PhantomData<(Block, HostFns)>, 108 | } 109 | 110 | impl + DeserializeOwned, HostFns> CheckOnRuntimeUpgrade 111 | where 112 | Block: BlockT + serde::de::DeserializeOwned, 113 | ::Err: Debug, 114 | Block::Header: serde::de::DeserializeOwned, 115 | NumberFor: FromStr, 116 | as FromStr>::Err: Debug, 117 | HostFns: HostFunctions, 118 | { 119 | // Runs the `on-runtime-upgrade` command. 120 | pub async fn run(&self) -> sc_cli::Result<()> { 121 | let shared = &self.shared; 122 | let command = &self.command; 123 | 124 | let executor = build_executor(shared); 125 | let runtime_checks = RuntimeChecks { 126 | name_matches: !shared.disable_spec_name_check, 127 | version_increases: !command.disable_spec_version_check, 128 | try_runtime_feature_enabled: true, 129 | }; 130 | let mut ext = command 131 | .state 132 | .to_ext::(shared, &executor, None, runtime_checks) 133 | .await?; 134 | 135 | let sync_checks = if command.disable_mbm_checks { 136 | command.checks 137 | } else { 138 | UpgradeCheckSelect::None 139 | }; 140 | 141 | // Run `TryRuntime_on_runtime_upgrade` with the given checks. 142 | basti_log( 143 | Level::Info, 144 | format!( 145 | "🔬 Running TryRuntime_on_runtime_upgrade with checks: {:?}", 146 | sync_checks 147 | ) 148 | .as_str(), 149 | ); 150 | 151 | // Check the Single-Block-Migrations work: 152 | let mut overlayed_changes = Default::default(); 153 | let _ = state_machine_call_with_proof::( 154 | &ext, 155 | &mut overlayed_changes, 156 | &executor, 157 | "TryRuntime_on_runtime_upgrade", 158 | sync_checks.encode().as_ref(), 159 | Default::default(), // we don't really need any extensions here. 160 | shared.export_proof.clone(), 161 | )?; 162 | 163 | let idempotency_ok = self.check_idempotency(&mut ext, &overlayed_changes)?; 164 | let weight_ok = self.check_weight(&ext)?; 165 | 166 | self.check_mbms(runtime_checks).await?; 167 | 168 | if !weight_ok || !idempotency_ok { 169 | return Err("Runtime Upgrade issues detected, exiting non-zero. See logs.".into()); 170 | } 171 | 172 | Ok(()) 173 | } 174 | 175 | /// Check that the migrations are idempotent. 176 | /// 177 | /// Expects the overlayed changes from the first execution of the migrations. 178 | fn check_idempotency( 179 | &self, 180 | ext: &mut RemoteExternalities, 181 | changes: &OverlayedChanges>, 182 | ) -> sc_cli::Result { 183 | if !self.command.disable_idempotency_checks { 184 | basti_log( 185 | Level::Info, 186 | format!( 187 | "🔬 Running TryRuntime_on_runtime_upgrade again to check idempotency: {:?}", 188 | self.command.checks 189 | ) 190 | .as_str(), 191 | ); 192 | let executor = build_executor(&self.shared); 193 | 194 | let before = changes.clone(); 195 | let mut after = changes.clone(); 196 | 197 | // The MBM pallet refuses to interrupt ongoing MBMs, so we need to pretend that it did 198 | // not run yet. We cannot just use a prefious state since the single-block-migrations 199 | // would not be tested for idempotency. 200 | // TODO add switch and guessing logic for the MBM pallet name. 201 | let key = [twox_128(b"MultiBlockMigrations"), twox_128(b"Cursor")].concat(); 202 | after.clear_prefix(&key); 203 | 204 | // Don't print all logs again. 205 | // let _quiet = LogLevelGuard::only_errors(); 206 | match state_machine_call_with_proof::( 207 | ext, 208 | &mut after, 209 | &executor, 210 | "TryRuntime_on_runtime_upgrade", 211 | UpgradeCheckSelect::None.encode().as_ref(), 212 | Default::default(), 213 | self.shared.export_proof.clone(), 214 | ) { 215 | Ok(_) => { 216 | if self.changed(ext, before, after)? { 217 | log::error!("❌ Migrations must behave the same when executed twice. This was not the case as a storage root hash mismatch was detected. Remove migrations one-by-one and re-run until you find the culprit."); 218 | Ok(false) 219 | } else { 220 | log::info!("✅ Migrations are idempotent"); 221 | Ok(true) 222 | } 223 | } 224 | Err(e) => { 225 | log::error!( 226 | "❌ Migrations are not idempotent, they failed during the second execution.", 227 | ); 228 | log::debug!("{:?}", e); 229 | Ok(false) 230 | } 231 | } 232 | } else { 233 | log::info!("ℹ Skipping idempotency check"); 234 | Ok(true) 235 | } 236 | } 237 | 238 | async fn check_mbms(&self, runtime_checks: RuntimeChecks) -> sc_cli::Result<()> { 239 | if self.command.disable_mbm_checks { 240 | log::info!("ℹ Skipping Multi-Block-Migrations"); 241 | return Ok(()); 242 | } 243 | 244 | let checker = MbmChecker:: { 245 | command: self.command.clone(), 246 | shared: self.shared.clone(), 247 | runtime_checks, 248 | _phantom: Default::default(), 249 | }; 250 | 251 | checker.check_mbms().await 252 | } 253 | 254 | /// Check that the migrations don't use more weights than a block. 255 | fn check_weight(&self, ext: &RemoteExternalities) -> sc_cli::Result { 256 | if self.command.no_weight_warnings { 257 | log::info!("ℹ Skipping weight safety check"); 258 | return Ok(true); 259 | } 260 | basti_log( 261 | Level::Info, 262 | "🔬 TryRuntime_on_runtime_upgrade succeeded! Running it again for weight measurements.", 263 | ); 264 | 265 | let executor = build_executor(&self.shared); 266 | let _quiet = LogLevelGuard::only_errors(); 267 | let (proof, encoded_result) = state_machine_call_with_proof::( 268 | ext, 269 | &mut Default::default(), 270 | &executor, 271 | "TryRuntime_on_runtime_upgrade", 272 | UpgradeCheckSelect::None.encode().as_ref(), 273 | Default::default(), 274 | self.shared.export_proof.clone(), 275 | )?; 276 | let ref_time_results = encoded_result.try_into()?; 277 | drop(_quiet); 278 | 279 | let pre_root = ext.backend.root(); 280 | let pov_safety = analyse_pov::>(proof, *pre_root); 281 | let ref_time_safety = analyse_ref_time(ref_time_results); 282 | 283 | match (pov_safety, ref_time_safety) { 284 | (WeightSafety::ProbablySafe, WeightSafety::ProbablySafe) => { 285 | log::info!( 286 | target: LOG_TARGET, 287 | "✅ No weight safety issues detected. \ 288 | Please note this does not guarantee a successful runtime upgrade. \ 289 | Always test your runtime upgrade with recent state, and ensure that the weight usage \ 290 | of your migrations will not drastically differ between testing and actual on-chain \ 291 | execution." 292 | ); 293 | Ok(true) 294 | } 295 | _ => { 296 | log::error!(target: LOG_TARGET, "❌ Weight safety issues detected."); 297 | Ok(false) 298 | } 299 | } 300 | } 301 | 302 | /// Whether any storage was changed. 303 | fn changed( 304 | &self, 305 | ext: &RemoteExternalities, 306 | mut before: OverlayedChanges>, 307 | mut after: OverlayedChanges>, 308 | ) -> sc_cli::Result { 309 | // Events are fine to not be idempotent. 310 | let key = [twox_128(b"System"), twox_128(b"Events")].concat(); 311 | after.clear_prefix(&key); 312 | before.clear_prefix(&key); 313 | let key = [twox_128(b"System"), twox_128(b"EventCount")].concat(); 314 | after.clear_prefix(&key); 315 | before.clear_prefix(&key); 316 | 317 | let (root_before, _) = before.storage_root(&ext.backend, ext.state_version); 318 | let (root_after, _) = after.storage_root(&ext.backend, ext.state_version); 319 | 320 | log::info!( 321 | "Storage root before: 0x{}, after: 0x{}", 322 | hex::encode(root_before), 323 | hex::encode(root_after), 324 | ); 325 | 326 | if root_before == root_after { 327 | return Ok(false); 328 | } 329 | 330 | if self.command.print_storage_diff { 331 | log::info!("Changed storage keys:"); 332 | let changes_before = collect_storage_changes_as_hex::(&before); 333 | let changes_after = collect_storage_changes_as_hex::(&after); 334 | 335 | similar_asserts::assert_eq!(changes_before, changes_after); 336 | Err("Storage changes detected: migrations not idempotent".into()) 337 | } else { 338 | log::error!("Run with --print-storage-diff to see list of changed storage keys."); 339 | Ok(true) 340 | } 341 | } 342 | } 343 | 344 | enum WeightSafety { 345 | ProbablySafe, 346 | PotentiallyUnsafe, 347 | } 348 | 349 | /// The default maximum PoV size in MB. 350 | const DEFAULT_MAX_POV_SIZE: ByteSize = ByteSize::mb(5); 351 | 352 | /// The fraction of the total available ref_time or pov size after which a warning should be logged. 353 | const DEFAULT_WARNING_THRESHOLD: f32 = 0.8; 354 | 355 | /// Analyse the given ref_times and return if there is a potential weight safety issue. 356 | fn analyse_pov(proof: StorageProof, pre_root: H::Out) -> WeightSafety 357 | where 358 | H: Hasher, 359 | { 360 | if proof.is_empty() { 361 | log::info!(target: LOG_TARGET, "Empty PoV detected"); 362 | return WeightSafety::ProbablySafe; 363 | } 364 | 365 | let encoded_proof_size = proof.encoded_size(); 366 | let compact_proof = proof 367 | .clone() 368 | .into_compact_proof::(pre_root) 369 | .map_err(|e| { 370 | log::error!(target: LOG_TARGET, "failed to generate compact proof: {:?}", e); 371 | e 372 | }) 373 | .unwrap_or(CompactProof { 374 | encoded_nodes: Default::default(), 375 | }); 376 | 377 | let compact_proof_size = compact_proof.encoded_size(); 378 | let compressed_compact_proof = zstd::stream::encode_all(&compact_proof.encode()[..], 0) 379 | .map_err(|e| { 380 | log::error!( 381 | target: LOG_TARGET, 382 | "failed to generate compressed proof: {:?}", 383 | e 384 | ); 385 | e 386 | }) 387 | .expect("generating compressed proof should never fail if proof is valid"); 388 | 389 | let proof_nodes = proof.into_nodes(); 390 | log::debug!( 391 | target: LOG_TARGET, 392 | "Proof: 0x{}... / {} nodes", 393 | HexDisplay::from(&proof_nodes.iter().flatten().cloned().take(10).collect::>()), 394 | proof_nodes.len() 395 | ); 396 | log::debug!(target: LOG_TARGET, "Encoded proof size: {}", ByteSize(encoded_proof_size as u64)); 397 | log::debug!(target: LOG_TARGET, "Compact proof size: {}", ByteSize(compact_proof_size as u64),); 398 | log::info!( 399 | target: LOG_TARGET, 400 | "PoV size (zstd-compressed compact proof): {}. For parachains, it's your responsibility \ 401 | to verify that a PoV of this size fits within any relaychain constraints.", 402 | ByteSize(compressed_compact_proof.len() as u64), 403 | ); 404 | if compressed_compact_proof.len() as f32 405 | > DEFAULT_MAX_POV_SIZE.as_u64() as f32 * DEFAULT_WARNING_THRESHOLD 406 | { 407 | log::warn!( 408 | target: LOG_TARGET, 409 | "A PoV size of {} is significant. Most relay chains usually accept PoVs up to {}. \ 410 | Proceed with caution.", 411 | ByteSize(compressed_compact_proof.len() as u64), 412 | DEFAULT_MAX_POV_SIZE, 413 | ); 414 | WeightSafety::PotentiallyUnsafe 415 | } else { 416 | WeightSafety::ProbablySafe 417 | } 418 | } 419 | 420 | /// Analyse the given ref_times and return if there is a potential weight safety issue. 421 | fn analyse_ref_time(ref_time_results: RefTimeInfo) -> WeightSafety { 422 | let RefTimeInfo { used, max } = ref_time_results; 423 | let (used, max) = (used.as_secs_f32(), max.as_secs_f32()); 424 | log::info!( 425 | target: LOG_TARGET, 426 | "Consumed ref_time: {}s ({:.2}% of max {}s)", 427 | used, 428 | used / max * 100.0, 429 | max, 430 | ); 431 | if used >= max * DEFAULT_WARNING_THRESHOLD { 432 | log::warn!( 433 | target: LOG_TARGET, 434 | "Consumed ref_time is >= {}% of the max allowed ref_time. Please ensure the \ 435 | migration is not be too computationally expensive to be fit in a single block.", 436 | DEFAULT_WARNING_THRESHOLD * 100.0, 437 | ); 438 | WeightSafety::PotentiallyUnsafe 439 | } else { 440 | WeightSafety::ProbablySafe 441 | } 442 | } 443 | 444 | fn collect_storage_changes_as_hex( 445 | overlayed_changes: &OverlayedChanges>, 446 | ) -> BTreeMap { 447 | overlayed_changes 448 | .changes() 449 | .map(|(key, entry)| { 450 | ( 451 | HexDisplay::from(key).to_string(), 452 | entry 453 | .clone() 454 | .value() 455 | .map_or_else(|| "".to_string(), hex::encode), 456 | ) 457 | }) 458 | .collect() 459 | } 460 | -------------------------------------------------------------------------------- /core/src/common/empty_block/inherents/custom_idps/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | pub mod para_parachain; 19 | pub mod relay_parachains; 20 | pub mod timestamp; 21 | -------------------------------------------------------------------------------- /core/src/common/empty_block/inherents/custom_idps/para_parachain.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | //! Inherent data provider for the [cumulus parachin inherents](https://github.com/paritytech/polkadot-sdk/blob/master/cumulus/primitives/parachain-inherent/src/lib.rs) 19 | //! for empty block production on top of an existing externalities. 20 | 21 | use std::{ops::DerefMut, sync::Arc}; 22 | 23 | use parity_scale_codec::{Decode, Encode}; 24 | use polkadot_primitives::{BlockNumber, HeadData}; 25 | use sp_consensus_babe::SlotDuration; 26 | use sp_core::twox_128; 27 | use sp_inherents::InherentIdentifier; 28 | use sp_runtime::traits::{Block as BlockT, HashingFor, NumberFor}; 29 | use sp_state_machine::TestExternalities; 30 | use tokio::sync::Mutex; 31 | 32 | /// Get the para id if it exists 33 | pub fn get_para_id(ext: &mut TestExternalities>) -> Option { 34 | let para_id_key = [twox_128(b"ParachainInfo"), twox_128(b"ParachainId")].concat(); 35 | 36 | ext.execute_with(|| sp_io::storage::get(¶_id_key)) 37 | .and_then(|b| -> Option { Decode::decode(&mut &b[..]).ok() }) 38 | } 39 | 40 | /// Get the last relay chain block number if it exists 41 | pub fn get_last_relay_chain_block_number( 42 | ext: &mut TestExternalities>, 43 | ) -> Option { 44 | let last_relay_chain_block_number_key = [ 45 | twox_128(b"ParachainSystem"), 46 | twox_128(b"LastRelayChainBlockNumber"), 47 | ] 48 | .concat(); 49 | 50 | ext.execute_with(|| sp_io::storage::get(&last_relay_chain_block_number_key)) 51 | .and_then(|b| -> Option> { Decode::decode(&mut &b[..]).ok() }) 52 | .map(|n| match n.try_into() { 53 | Ok(block_number) => block_number, 54 | Err(_) => { 55 | panic!("Failed to convert relay chain block number") 56 | } 57 | }) 58 | } 59 | 60 | /// Provides parachain-system pallet inherents. 61 | pub struct InherentDataProvider { 62 | pub timestamp: sp_timestamp::Timestamp, 63 | pub blocktime_millis: u64, 64 | pub parent_header: B::Header, 65 | pub ext_mutex: Arc>>>, 66 | } 67 | 68 | #[async_trait::async_trait] 69 | impl sp_inherents::InherentDataProvider for InherentDataProvider { 70 | async fn provide_inherent_data( 71 | &self, 72 | inherent_data: &mut sp_inherents::InherentData, 73 | ) -> Result<(), sp_inherents::Error> { 74 | let mut ext_guard = self.ext_mutex.lock().await; 75 | let ext = ext_guard.deref_mut(); 76 | let maybe_last_relay_chain_block_number = get_last_relay_chain_block_number::(ext); 77 | let maybe_para_id = get_para_id::(ext); 78 | let (last_relay_chain_block_number, para_id) = 79 | match (maybe_last_relay_chain_block_number, maybe_para_id) { 80 | (Some(last_relay_chain_block_number), Some(para_id)) => { 81 | (last_relay_chain_block_number, para_id) 82 | } 83 | _ => { 84 | log::debug!("Unable to provide para parachains inherent for this chain."); 85 | return Ok(()); 86 | } 87 | }; 88 | 89 | let relay_chain_slot = cumulus_primitives_core::relay_chain::Slot::from_timestamp( 90 | self.timestamp, 91 | SlotDuration::from_millis(self.blocktime_millis), 92 | ) 93 | .encode(); 94 | 95 | let additional_key_values: Vec<(Vec, Vec)> = vec![ 96 | // Insert relay chain slot to pass Aura check 97 | // https://github.com/paritytech/polkadot-sdk/blob/ef114a422291b44f8973739ab7858a29a523e6a2/cumulus/pallets/aura-ext/src/consensus_hook.rs#L69 98 | ( 99 | cumulus_primitives_core::relay_chain::well_known_keys::CURRENT_SLOT.to_vec(), 100 | relay_chain_slot, 101 | ), 102 | // Insert para header info to pass para inherent check 103 | // https://github.com/paritytech/polkadot-sdk/blob/17b56fae2d976a3df87f34076875de8c26da0355/cumulus/pallets/parachain-system/src/lib.rs#L1296 104 | ( 105 | cumulus_primitives_core::relay_chain::well_known_keys::para_head(para_id.into()), 106 | HeadData(self.parent_header.encode()).encode(), 107 | ), 108 | ]; 109 | 110 | cumulus_client_parachain_inherent::MockValidationDataInherentDataProvider { 111 | current_para_block: Default::default(), 112 | current_para_block_head: Default::default(), 113 | relay_offset: last_relay_chain_block_number + 1u32, 114 | relay_blocks_per_para_block: Default::default(), 115 | para_blocks_per_relay_epoch: Default::default(), 116 | relay_randomness_config: (), 117 | xcm_config: cumulus_client_parachain_inherent::MockXcmConfig::default(), 118 | raw_downward_messages: Default::default(), 119 | raw_horizontal_messages: Default::default(), 120 | additional_key_values: Some(additional_key_values), 121 | para_id: para_id.into(), 122 | } 123 | .provide_inherent_data(inherent_data) 124 | .await 125 | .expect("Failed to provide Para Parachain inherent data."); 126 | 127 | Ok(()) 128 | } 129 | 130 | async fn try_handle_error( 131 | &self, 132 | _: &InherentIdentifier, 133 | _: &[u8], 134 | ) -> Option> { 135 | None 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /core/src/common/empty_block/inherents/custom_idps/relay_parachains.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | //! Inherent data provider for the [polkadot parachins inherent](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/primitives/src/v7/mod.rs) 19 | //! for empty block production on top of an existing externalities. 20 | 21 | use sp_inherents::InherentIdentifier; 22 | use sp_runtime::traits::Block as BlockT; 23 | 24 | pub struct InherentDataProvider { 25 | parent_header: B::Header, 26 | } 27 | 28 | impl InherentDataProvider { 29 | pub fn new(parent_header: B::Header) -> Self { 30 | Self { parent_header } 31 | } 32 | } 33 | 34 | #[async_trait::async_trait] 35 | impl sp_inherents::InherentDataProvider for InherentDataProvider { 36 | async fn provide_inherent_data( 37 | &self, 38 | inherent_data: &mut sp_inherents::InherentData, 39 | ) -> Result<(), sp_inherents::Error> { 40 | let para_data = polkadot_primitives::InherentData { 41 | bitfields: Vec::new(), 42 | backed_candidates: Vec::new(), 43 | disputes: Vec::new(), 44 | parent_header: self.parent_header.clone(), 45 | }; 46 | 47 | inherent_data.put_data( 48 | polkadot_primitives::PARACHAINS_INHERENT_IDENTIFIER, 49 | ¶_data, 50 | )?; 51 | 52 | Ok(()) 53 | } 54 | 55 | async fn try_handle_error( 56 | &self, 57 | _: &InherentIdentifier, 58 | _: &[u8], 59 | ) -> Option> { 60 | None 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /core/src/common/empty_block/inherents/custom_idps/timestamp.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | //! Inherent data provider for the timestamp, for empty block production on top of an existing 19 | //! externalities. 20 | 21 | use sp_inherents::{InherentData, InherentIdentifier}; 22 | use sp_runtime::Digest; 23 | use sp_timestamp::{Timestamp, TimestampInherentData}; 24 | 25 | pub struct InherentDataProvider { 26 | pub blocktime_millis: u64, 27 | pub maybe_parent_info: Option<(InherentData, Digest)>, 28 | } 29 | 30 | impl InherentDataProvider { 31 | pub fn timestamp(&self) -> Timestamp { 32 | match &self.maybe_parent_info { 33 | Some((prev_inherent_data, _)) => sp_timestamp::InherentDataProvider::new( 34 | prev_inherent_data 35 | .timestamp_inherent_data() 36 | .unwrap() 37 | .unwrap() 38 | + self.blocktime_millis, 39 | ) 40 | .timestamp(), 41 | None => sp_timestamp::InherentDataProvider::from_system_time().timestamp(), 42 | } 43 | } 44 | } 45 | 46 | #[async_trait::async_trait] 47 | impl sp_inherents::InherentDataProvider for InherentDataProvider { 48 | async fn provide_inherent_data( 49 | &self, 50 | inherent_data: &mut sp_inherents::InherentData, 51 | ) -> Result<(), sp_inherents::Error> { 52 | match &self.maybe_parent_info { 53 | Some((prev_inherent_data, _)) => { 54 | let idp = sp_timestamp::InherentDataProvider::new( 55 | prev_inherent_data 56 | .timestamp_inherent_data() 57 | .unwrap() 58 | .unwrap() 59 | + self.blocktime_millis, 60 | ); 61 | idp.provide_inherent_data(inherent_data) 62 | .await 63 | .expect("Failed to provide timestamp inherent"); 64 | } 65 | None => { 66 | let idp = sp_timestamp::InherentDataProvider::from_system_time(); 67 | idp.provide_inherent_data(inherent_data) 68 | .await 69 | .expect("Failed to provide timestamp inherent"); 70 | } 71 | }; 72 | 73 | Ok(()) 74 | } 75 | 76 | async fn try_handle_error( 77 | &self, 78 | _: &InherentIdentifier, 79 | _: &[u8], 80 | ) -> Option> { 81 | None 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /core/src/common/empty_block/inherents/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | pub mod custom_idps; 19 | pub mod pre_apply; 20 | pub mod providers; 21 | -------------------------------------------------------------------------------- /core/src/common/empty_block/inherents/pre_apply.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use cumulus_primitives_parachain_inherent::MessageQueueChain; 19 | use parity_scale_codec::Encode; 20 | use sp_core::{twox_128, H256}; 21 | use sp_runtime::traits::{Block as BlockT, HashingFor}; 22 | use sp_state_machine::TestExternalities; 23 | 24 | /// Some operations must be performed prior to inherents being applied. 25 | /// 26 | /// This fn sets the last dmq mcq head value to zero to pass [this check](https://github.com/paritytech/polkadot-sdk/blob/ef114a422291b44f8973739ab7858a29a523e6a2/cumulus/pallets/parachain-system/src/lib.rs#L1162) 27 | /// 28 | /// It must be called prior to attempting to apply inherents. 29 | pub fn pre_apply_inherents(ext: &mut TestExternalities>) { 30 | let last_dmq_mqc_head_key = 31 | [twox_128(b"ParachainSystem"), twox_128(b"LastDmqMqcHead")].concat(); 32 | ext.insert( 33 | last_dmq_mqc_head_key.to_vec(), 34 | MessageQueueChain::new(H256::zero()).encode(), 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /core/src/common/empty_block/inherents/providers.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | //! Contains providers for inherents required for empty block production. 19 | 20 | use std::{sync::Arc, time::Duration}; 21 | 22 | use parity_scale_codec::Encode; 23 | use sp_consensus_aura::{Slot, SlotDuration, AURA_ENGINE_ID}; 24 | use sp_consensus_babe::{ 25 | digests::{PreDigest, SecondaryPlainPreDigest}, 26 | BABE_ENGINE_ID, 27 | }; 28 | use sp_inherents::InherentData; 29 | use sp_runtime::{ 30 | traits::{Block as BlockT, HashingFor}, 31 | Digest, DigestItem, 32 | }; 33 | use sp_state_machine::TestExternalities; 34 | use sp_std::prelude::*; 35 | use strum_macros::{Display, EnumIter}; 36 | use tokio::sync::Mutex; 37 | 38 | use crate::common::empty_block::inherents::custom_idps; 39 | 40 | const RELAYCHAIN_BLOCKTIME_MS: u64 = 6000u64; 41 | 42 | /// Trait for providing the inherent data and digest items for block construction. 43 | pub trait InherentProvider { 44 | type Err; 45 | 46 | fn get_inherent_providers_and_pre_digest( 47 | &self, 48 | maybe_parent_info: Option<(InherentData, Digest)>, 49 | parent_header: B::Header, 50 | ext: Arc>>>, 51 | ) -> InherentProviderResult; 52 | } 53 | 54 | // Clippy asks that we abstract the return type because it's so long 55 | type InherentProviderResult = 56 | Result<(Box, Vec), Err>; 57 | 58 | /// Classes of [`InherentProvider`] avaliable. 59 | /// 60 | /// Currently only Smart is implemented. New implementations may be added if Smart is not suitable 61 | /// for some edge cases. 62 | #[derive(Debug, Clone, EnumIter, Display, Copy)] 63 | pub enum ProviderVariant { 64 | /// Smart chain varient will automatically adjust provided inherents based on the given 65 | /// externalities. 66 | /// 67 | /// The blocktime is provided in milliseconds. 68 | Smart(core::time::Duration), 69 | } 70 | 71 | impl InherentProvider for ProviderVariant { 72 | type Err = String; 73 | 74 | fn get_inherent_providers_and_pre_digest( 75 | &self, 76 | maybe_parent_info: Option<(InherentData, Digest)>, 77 | parent_header: B::Header, 78 | ext: Arc>>>, 79 | ) -> InherentProviderResult { 80 | match *self { 81 | ProviderVariant::Smart(blocktime) => { 82 | >::get_inherent_providers_and_pre_digest(&SmartInherentProvider { 83 | blocktime, 84 | }, maybe_parent_info, parent_header, ext) 85 | } 86 | } 87 | } 88 | } 89 | 90 | /// Attempts to provide inherents in a fashion that works for as many chains as possible. 91 | /// 92 | /// It is currently tested for 93 | /// - Polkadot-based relay chains 94 | /// - Polkadot-ecosystem system parachains 95 | /// 96 | /// If it does not work for your Substrate-based chain, [please open an issue](https://github.com/paritytech/try-runtime-cli/issues) 97 | /// and we will look into supporting it. 98 | struct SmartInherentProvider { 99 | blocktime: Duration, 100 | } 101 | 102 | impl InherentProvider for SmartInherentProvider { 103 | type Err = String; 104 | 105 | fn get_inherent_providers_and_pre_digest( 106 | &self, 107 | maybe_parent_info: Option<(InherentData, Digest)>, 108 | parent_header: B::Header, 109 | ext: Arc>>>, 110 | ) -> InherentProviderResult { 111 | let timestamp_idp = custom_idps::timestamp::InherentDataProvider { 112 | blocktime_millis: self.blocktime.as_millis() as u64, 113 | maybe_parent_info, 114 | }; 115 | let para_parachain_idp = custom_idps::para_parachain::InherentDataProvider:: { 116 | blocktime_millis: RELAYCHAIN_BLOCKTIME_MS, 117 | parent_header: parent_header.clone(), 118 | timestamp: timestamp_idp.timestamp(), 119 | ext_mutex: ext, 120 | }; 121 | let relay_parachain_data_idp = 122 | custom_idps::relay_parachains::InherentDataProvider::::new(parent_header); 123 | 124 | let slot = Slot::from_timestamp( 125 | timestamp_idp.timestamp(), 126 | SlotDuration::from_millis(self.blocktime.as_millis() as u64), 127 | ); 128 | let digest = vec![ 129 | DigestItem::PreRuntime( 130 | BABE_ENGINE_ID, 131 | PreDigest::SecondaryPlain(SecondaryPlainPreDigest { 132 | slot, 133 | authority_index: 0, 134 | }) 135 | .encode(), 136 | ), 137 | DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode()), 138 | ]; 139 | 140 | Ok(( 141 | Box::new((timestamp_idp, para_parachain_idp, relay_parachain_data_idp)), 142 | digest, 143 | )) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /core/src/common/empty_block/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod inherents; 2 | pub mod production; 3 | -------------------------------------------------------------------------------- /core/src/common/empty_block/production.rs: -------------------------------------------------------------------------------- 1 | use std::{ops::DerefMut, str::FromStr, sync::Arc}; 2 | 3 | use parity_scale_codec::{Decode, Encode}; 4 | use sc_cli::Result; 5 | use sc_executor::{HostFunctions, WasmExecutor}; 6 | use sp_core::H256; 7 | use sp_inherents::InherentData; 8 | use sp_runtime::{ 9 | traits::{Block as BlockT, HashingFor, Header, NumberFor, One}, 10 | DeserializeOwned, Digest, ExtrinsicInclusionMode, 11 | }; 12 | use sp_state_machine::TestExternalities; 13 | use sp_std::fmt::Debug; 14 | use tokio::sync::Mutex; 15 | 16 | use super::inherents::{pre_apply::pre_apply_inherents, providers::InherentProvider}; 17 | use crate::{ 18 | common::{ 19 | empty_block::inherents::providers::ProviderVariant, misc_logging::LogLevelGuard, 20 | state::state_machine_call, 21 | }, 22 | full_extensions, 23 | }; 24 | 25 | pub async fn mine_block( 26 | ext_mutex: Arc>>>, 27 | executor: &WasmExecutor, 28 | previous_block_building_info: Option<(InherentData, Digest)>, 29 | parent_header: Block::Header, 30 | provider_variant: ProviderVariant, 31 | try_state: frame_try_runtime::TryStateSelect, 32 | ) -> Result<( 33 | (InherentData, Digest), 34 | Block::Header, 35 | Option, 36 | )> 37 | where 38 | Block: BlockT + DeserializeOwned, 39 | Block::Header: DeserializeOwned, 40 | ::Err: Debug, 41 | NumberFor: FromStr, 42 | as FromStr>::Err: Debug, 43 | { 44 | // We are saving state before we overwrite it while producing new block. 45 | let mut ext_guard = ext_mutex.lock().await; 46 | let ext = ext_guard.deref_mut(); 47 | let backend = ext.as_backend(); 48 | drop(ext_guard); 49 | 50 | log::info!( 51 | "Producing new empty block at height {:?}", 52 | *parent_header.number() + One::one() 53 | ); 54 | 55 | // Prevent it from printing all logs twice: 56 | let muffle = LogLevelGuard::only_errors(); 57 | let (next_block, new_block_building_info, mode) = produce_next_block::( 58 | ext_mutex.clone(), 59 | executor, 60 | parent_header.clone(), 61 | provider_variant, 62 | previous_block_building_info, 63 | ) 64 | .await?; 65 | drop(muffle); 66 | 67 | log::info!( 68 | "Produced a new block ({})", 69 | array_bytes::bytes2hex("0x", next_block.header().hash()) 70 | ); 71 | 72 | let mut ext_guard = ext_mutex.lock().await; 73 | let ext = ext_guard.deref_mut(); 74 | 75 | // And now we restore previous state. 76 | ext.backend = backend; 77 | 78 | pre_apply_inherents::(ext); 79 | let state_root_check = true; 80 | let signature_check = true; 81 | let payload = ( 82 | next_block.clone(), 83 | state_root_check, 84 | signature_check, 85 | try_state.clone(), 86 | ) 87 | .encode(); 88 | //call::<(), Block, _>(ext, executor, "TryRuntime_execute_block", &payload).await?; 89 | 90 | if try_state == frame_try_runtime::TryStateSelect::None { 91 | call::<(), Block, _>(ext, executor, "Core_execute_block", &next_block.encode()).await?; 92 | } else { 93 | call::<(), Block, _>(ext, executor, "TryRuntime_execute_block", &payload).await?; 94 | } 95 | 96 | log::info!("Executed the new block"); 97 | 98 | Ok((new_block_building_info, next_block.header().clone(), mode)) 99 | } 100 | 101 | /// Produces next block containing only inherents. 102 | pub async fn produce_next_block( 103 | ext_mutex: Arc>>>, 104 | executor: &WasmExecutor, 105 | parent_header: Block::Header, 106 | chain: ProviderVariant, 107 | previous_block_building_info: Option<(InherentData, Digest)>, 108 | ) -> Result<( 109 | Block, 110 | (InherentData, Digest), 111 | Option, 112 | )> 113 | where 114 | Block: BlockT + DeserializeOwned, 115 | Block::Header: DeserializeOwned, 116 | ::Err: Debug, 117 | NumberFor: FromStr, 118 | as FromStr>::Err: Debug, 119 | { 120 | let (inherent_data_provider, pre_digest) = 121 | >::get_inherent_providers_and_pre_digest( 122 | &chain, 123 | previous_block_building_info, 124 | parent_header.clone(), 125 | ext_mutex.clone(), 126 | )?; 127 | 128 | let mut ext_guard = ext_mutex.lock().await; 129 | let ext = ext_guard.deref_mut(); 130 | 131 | pre_apply_inherents::(ext); 132 | drop(ext_guard); 133 | 134 | let inherent_data = inherent_data_provider 135 | .create_inherent_data() 136 | .await 137 | .map_err(|s| sc_cli::Error::Input(s.to_string()))?; 138 | let digest = Digest { logs: pre_digest }; 139 | 140 | let header = Block::Header::new( 141 | *parent_header.number() + One::one(), 142 | Default::default(), 143 | Default::default(), 144 | parent_header.hash(), 145 | digest.clone(), 146 | ); 147 | 148 | let mut ext_guard = ext_mutex.lock().await; 149 | let ext = ext_guard.deref_mut(); 150 | // Only RA API version 5 supports returning a mode, so need to check. 151 | let mode = if core_version::(ext, executor)? >= 5 { 152 | let mode = call::( 153 | ext, 154 | executor, 155 | "Core_initialize_block", 156 | &header.encode(), 157 | ) 158 | .await?; 159 | Some(mode) 160 | } else { 161 | call::<(), Block, _>(ext, executor, "Core_initialize_block", &header.encode()).await?; 162 | None 163 | }; 164 | 165 | let extrinsics = dry_call::, Block, _>( 166 | ext, 167 | executor, 168 | "BlockBuilder_inherent_extrinsics", 169 | &inherent_data.encode(), 170 | )?; 171 | 172 | for xt in &extrinsics { 173 | call::<(), Block, _>(ext, executor, "BlockBuilder_apply_extrinsic", &xt.encode()).await?; 174 | } 175 | 176 | let header = dry_call::( 177 | ext, 178 | executor, 179 | "BlockBuilder_finalize_block", 180 | &[0u8; 0], 181 | )?; 182 | 183 | call::<(), Block, _>(ext, executor, "BlockBuilder_finalize_block", &[0u8; 0]).await?; 184 | ext.commit_all().unwrap(); 185 | drop(ext_guard); 186 | 187 | Ok(( 188 | Block::new(header, extrinsics), 189 | (inherent_data, digest), 190 | mode, 191 | )) 192 | } 193 | 194 | /// Call `method` with `data` and actually save storage changes to `externalities`. 195 | async fn call( 196 | externalities: &mut TestExternalities>, 197 | executor: &WasmExecutor, 198 | method: &'static str, 199 | data: &[u8], 200 | ) -> Result { 201 | let (mut changes, result) = state_machine_call::( 202 | externalities, 203 | executor, 204 | method, 205 | data, 206 | full_extensions(executor.clone()), 207 | )?; 208 | 209 | let storage_changes = 210 | changes.drain_storage_changes(&externalities.backend, externalities.state_version)?; 211 | 212 | externalities.backend.apply_transaction( 213 | storage_changes.transaction_storage_root, 214 | storage_changes.transaction, 215 | ); 216 | 217 | T::decode(&mut &*result).map_err(|e| sc_cli::Error::Input(format!("{:?}", e))) 218 | } 219 | 220 | pub fn core_version( 221 | externalities: &TestExternalities>, 222 | executor: &WasmExecutor, 223 | ) -> Result { 224 | dry_call::(externalities, executor, "Core_version", &[]) 225 | } 226 | 227 | /// Call `method` with `data` and return the result. `externalities` will not change. 228 | fn dry_call( 229 | externalities: &TestExternalities>, 230 | executor: &WasmExecutor, 231 | method: &'static str, 232 | data: &[u8], 233 | ) -> Result { 234 | let (_, result) = state_machine_call::( 235 | externalities, 236 | executor, 237 | method, 238 | data, 239 | full_extensions(executor.clone()), 240 | )?; 241 | 242 | Ok(::decode(&mut &*result)?) 243 | } 244 | -------------------------------------------------------------------------------- /core/src/common/misc_logging.rs: -------------------------------------------------------------------------------- 1 | use log::{log, Level}; 2 | use paris::formatter::colorize_string; 3 | 4 | use crate::LOG_TARGET; 5 | 6 | fn level_to_color(level: Level) -> &'static str { 7 | match level { 8 | Level::Info => "blue", 9 | Level::Warn => "yellow", 10 | Level::Error => "red", 11 | _ => "white", 12 | } 13 | } 14 | 15 | /// A BIG log that's very difficult to miss. 16 | pub fn basti_log(level: Level, message: &str) { 17 | let color = level_to_color(level); 18 | log!( 19 | target: LOG_TARGET, 20 | level, 21 | "{}", 22 | colorize_string(format!( 23 | "<{}>{}\n\n", 24 | &color, 25 | "-".repeat(message.len()) 26 | )) 27 | ); 28 | log!( 29 | target: LOG_TARGET, 30 | level, 31 | "{}", 32 | colorize_string(format!("<{}>{}\n\n", &color, message)) 33 | ); 34 | log!( 35 | target: LOG_TARGET, 36 | level, 37 | "{}", 38 | colorize_string(format!( 39 | "<{}>{}\n\n", 40 | &color, 41 | "-".repeat(message.len()) 42 | )) 43 | ); 44 | } 45 | 46 | /// Temporarily demote the log level to a specific level and restore on drop. 47 | pub struct LogLevelGuard(log::LevelFilter); 48 | impl LogLevelGuard { 49 | pub fn new(new_level: log::LevelFilter) -> Self { 50 | let old_level = log::max_level(); 51 | log::set_max_level(new_level); 52 | Self(old_level) 53 | } 54 | 55 | /// Only show errors. 56 | pub fn only_errors() -> Self { 57 | Self::new(log::LevelFilter::Error) 58 | } 59 | } 60 | 61 | impl Drop for LogLevelGuard { 62 | fn drop(&mut self) { 63 | log::set_max_level(self.0); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /core/src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod empty_block; 2 | pub mod misc_logging; 3 | pub mod parse; 4 | pub mod shared_parameters; 5 | pub mod state; 6 | -------------------------------------------------------------------------------- /core/src/common/parse.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use sp_version::StateVersion; 19 | 20 | pub(crate) fn hash(block_hash: &str) -> Result { 21 | let (block_hash, offset) = if let Some(block_hash) = block_hash.strip_prefix("0x") { 22 | (block_hash, 2) 23 | } else { 24 | (block_hash, 0) 25 | }; 26 | 27 | if let Some(pos) = block_hash.chars().position(|c| !c.is_ascii_hexdigit()) { 28 | Err(format!( 29 | "Expected block hash, found illegal hex character at position: {}", 30 | offset + pos, 31 | )) 32 | } else { 33 | Ok(block_hash.into()) 34 | } 35 | } 36 | 37 | pub(crate) fn url(s: &str) -> Result { 38 | if s.starts_with("ws://") || s.starts_with("wss://") { 39 | // could use Url crate as well, but lets keep it simple for now. 40 | Ok(s.to_string()) 41 | } else { 42 | Err("not a valid WS(S) url: must start with 'ws://' or 'wss://'") 43 | } 44 | } 45 | 46 | pub(crate) fn state_version(s: &str) -> Result { 47 | s.parse::() 48 | .map_err(|_| ()) 49 | .and_then(StateVersion::try_from) 50 | .map_err(|_| "Invalid state version.") 51 | } 52 | -------------------------------------------------------------------------------- /core/src/common/shared_parameters.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use std::{path::PathBuf, str::FromStr}; 19 | 20 | use sc_cli::{ 21 | WasmExecutionMethod, WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, 22 | DEFAULT_WASM_EXECUTION_METHOD, 23 | }; 24 | use sp_runtime::StateVersion; 25 | 26 | use crate::common::parse; 27 | 28 | /// Shared parameters of the `try-runtime` commands 29 | #[derive(Debug, Clone, clap::Parser)] 30 | #[group(skip)] 31 | pub struct SharedParams { 32 | /// The runtime to use. 33 | /// 34 | /// Must be a path to a wasm blob, compiled with `try-runtime` feature flag. 35 | /// 36 | /// Or, `existing`, indicating that you don't want to overwrite the runtime. This will use 37 | /// whatever comes from the remote node, or the snapshot file. This will most likely not work 38 | /// against a remote node, as no (sane) blockchain should compile its onchain wasm with 39 | /// `try-runtime` feature. 40 | #[arg(long, default_value = "existing")] 41 | pub runtime: Runtime, 42 | 43 | /// Whether to disable enforcing the new runtime `spec_name` matches the existing `spec_name`. 44 | #[clap(long, default_value = "false", default_missing_value = "true")] 45 | pub disable_spec_name_check: bool, 46 | 47 | /// Type of wasm execution used. 48 | #[arg( 49 | long = "wasm-execution", 50 | value_name = "METHOD", 51 | value_enum, 52 | ignore_case = true, 53 | default_value_t = DEFAULT_WASM_EXECUTION_METHOD, 54 | )] 55 | pub wasm_method: WasmExecutionMethod, 56 | 57 | /// The WASM instantiation method to use. 58 | /// 59 | /// Only has an effect when `wasm-execution` is set to `compiled`. 60 | #[arg( 61 | long = "wasm-instantiation-strategy", 62 | value_name = "STRATEGY", 63 | default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, 64 | value_enum, 65 | )] 66 | pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy, 67 | 68 | /// The number of 64KB pages to allocate for Wasm execution. Defaults to 69 | /// [`sc_service::Configuration.default_heap_pages`]. 70 | #[arg(long)] 71 | pub heap_pages: Option, 72 | 73 | /// Path to a file to export the storage proof into (as a JSON). 74 | /// If several blocks are executed, the path is interpreted as a folder 75 | /// where one file per block will be written (named `{block_number}-{block_hash}`). 76 | #[clap(long)] 77 | pub export_proof: Option, 78 | 79 | /// Overwrite the `state_version`. 80 | /// 81 | /// Otherwise `remote-externalities` will automatically set the correct state version. 82 | #[arg(long, value_parser = parse::state_version)] 83 | pub overwrite_state_version: Option, 84 | } 85 | 86 | #[derive(Debug, Clone)] 87 | pub enum Runtime { 88 | /// Use the given path to the wasm binary file. 89 | /// 90 | /// It must have been compiled with `try-runtime`. 91 | Path(PathBuf), 92 | 93 | /// Use the code of the remote node, or the snapshot. 94 | /// 95 | /// In almost all cases, this is not what you want, because the code in the remote node does 96 | /// not have any of the try-runtime custom runtime APIs. 97 | Existing, 98 | } 99 | 100 | impl FromStr for Runtime { 101 | type Err = String; 102 | 103 | fn from_str(s: &str) -> Result { 104 | Ok(match s.to_lowercase().as_ref() { 105 | "existing" => Runtime::Existing, 106 | x => Runtime::Path(x.into()), 107 | }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /core/src/common/state.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use std::{fmt::Debug, path::PathBuf, str::FromStr}; 19 | 20 | use frame_remote_externalities::{ 21 | Builder, Mode, OfflineConfig, OnlineConfig, RemoteExternalities, SnapshotConfig, 22 | }; 23 | use parity_scale_codec::Decode; 24 | use sc_cli::{execution_method_from_cli, RuntimeVersion}; 25 | use sc_executor::{ 26 | sp_wasm_interface::HostFunctions, HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY, 27 | }; 28 | use sp_api::{CallContext, StorageProof}; 29 | use sp_core::{ 30 | hexdisplay::HexDisplay, storage::well_known_keys, traits::ReadRuntimeVersion, twox_128, Hasher, 31 | }; 32 | use sp_externalities::Extensions; 33 | use sp_runtime::{ 34 | traits::{BlakeTwo256, Block as BlockT, HashingFor, Header as HeaderT}, 35 | DeserializeOwned, 36 | }; 37 | use sp_state_machine::{OverlayedChanges, StateMachine, TestExternalities, TrieBackendBuilder}; 38 | use substrate_rpc_client::{ws_client, ChainApi}; 39 | 40 | use crate::{ 41 | common::{ 42 | parse, 43 | shared_parameters::{Runtime, SharedParams}, 44 | }, 45 | hash_of, rpc_err_handler, LOG_TARGET, 46 | }; 47 | 48 | /// A `Live` variant for [`State`] 49 | #[derive(Debug, Clone, clap::Args)] 50 | pub struct LiveState { 51 | /// The url to connect to. 52 | #[arg( 53 | short, 54 | long, 55 | value_parser = parse::url, 56 | )] 57 | pub uri: String, 58 | 59 | /// The block hash at which to fetch the state. 60 | /// 61 | /// If non provided, then the latest finalized head is used. 62 | #[arg( 63 | short, 64 | long, 65 | value_parser = parse::hash, 66 | )] 67 | pub at: Option, 68 | 69 | /// A pallet to scrape. Can be provided multiple times. If empty, entire chain state will 70 | /// be scraped. 71 | /// 72 | /// This is equivalent to passing `xx_hash_64(pallet)` to `--hashed_prefixes`. 73 | #[arg(short, long, num_args = 1..)] 74 | pub pallet: Vec, 75 | 76 | /// Storage entry key prefixes to scrape and inject into the test externalities. Pass as 0x 77 | /// prefixed hex strings. By default, all keys are scraped and included. 78 | #[arg(long = "prefix", value_parser = parse::hash, num_args = 1..)] 79 | pub hashed_prefixes: Vec, 80 | 81 | /// Fetch the child-keys as well. 82 | /// 83 | /// Default is `false`, if specific `--pallets` are specified, `true` otherwise. In other 84 | /// words, if you scrape the whole state the child tree data is included out of the box. 85 | /// Otherwise, it must be enabled explicitly using this flag. 86 | #[arg(long)] 87 | pub child_tree: bool, 88 | } 89 | 90 | impl LiveState { 91 | /// Return the `at` block hash as a `Hash`, if it exists. 92 | pub fn at(&self) -> sc_cli::Result::Hash>> 93 | where 94 | ::Err: Debug, 95 | { 96 | self.at 97 | .clone() 98 | .map(|s| hash_of::(s.as_str())) 99 | .transpose() 100 | } 101 | 102 | /// Converts this `LiveState` into a `LiveState` for the previous block. 103 | /// 104 | /// Useful for opertations like when you want to execute a block, but also need the state of the 105 | /// block *before* it. 106 | pub async fn to_prev_block_live_state(self) -> sc_cli::Result 107 | where 108 | ::Err: Debug, 109 | { 110 | // We want to execute the block `at`, therefore need the state of the block *before* it. 111 | let at = self.at::()?; 112 | 113 | // Get the block number requested by the user, or the current block number if they 114 | // didn't specify one. 115 | let rpc = ws_client(&self.uri).await?; 116 | let previous_hash = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, at) 117 | .await 118 | .map_err(rpc_err_handler) 119 | .and_then(|maybe_header| { 120 | maybe_header 121 | .ok_or("header_not_found") 122 | .map(|h| *h.parent_hash()) 123 | })?; 124 | 125 | Ok(LiveState { 126 | at: Some(hex::encode(previous_hash)), 127 | ..self 128 | }) 129 | } 130 | } 131 | 132 | /// The source of runtime *state* to use. 133 | #[derive(Debug, Clone, clap::Subcommand)] 134 | pub enum State { 135 | /// Use a state snapshot as the source of runtime state. 136 | Snap { 137 | #[clap(short = 'p', long = "path", alias = "snapshot-path")] 138 | path: Option, 139 | }, 140 | 141 | /// Use a live chain as the source of runtime state. 142 | Live(LiveState), 143 | } 144 | 145 | /// Checks to perform on the given runtime, compared to the existing runtime. 146 | #[derive(Debug, Clone, Copy)] 147 | pub struct RuntimeChecks { 148 | /// Enforce the `spec_name`s match 149 | pub name_matches: bool, 150 | /// Enforce the `spec_version` of the given is greater or equal to the existing 151 | /// runtime. 152 | pub version_increases: bool, 153 | /// Enforce that the given runtime is compiled with the try-runtime feature. 154 | pub try_runtime_feature_enabled: bool, 155 | } 156 | 157 | impl State { 158 | /// Create the [`RemoteExternalities`]. 159 | /// 160 | /// This will override the code as it sees fit based on [`Runtime`]. It will also check the 161 | /// spec-version and name. 162 | pub async fn to_ext( 163 | &self, 164 | shared: &SharedParams, 165 | executor: &WasmExecutor, 166 | state_snapshot: Option, 167 | runtime_checks: RuntimeChecks, 168 | ) -> sc_cli::Result> 169 | where 170 | Block::Header: DeserializeOwned, 171 | ::Err: Debug, 172 | { 173 | let builder = match self { 174 | State::Snap { path } => { 175 | let path = path 176 | .as_ref() 177 | .ok_or_else(|| "no snapshot path provided".to_string())?; 178 | 179 | Builder::::new().mode(Mode::Offline(OfflineConfig { 180 | state_snapshot: SnapshotConfig::new(path), 181 | })) 182 | } 183 | State::Live(LiveState { 184 | pallet, 185 | uri, 186 | at, 187 | child_tree, 188 | hashed_prefixes, 189 | }) => { 190 | let at = match at { 191 | Some(at_str) => Some(hash_of::(at_str)?), 192 | None => None, 193 | }; 194 | let hashed_prefixes = hashed_prefixes 195 | .iter() 196 | .map(|p_str| { 197 | hex::decode(p_str).map_err(|e| { 198 | format!( 199 | "Error decoding `hashed_prefixes` hex string entry '{:?}' to bytes: {:?}", 200 | p_str, e 201 | ) 202 | }) 203 | }) 204 | .collect::, _>>()?; 205 | Builder::::new().mode(Mode::Online(OnlineConfig { 206 | at, 207 | transport: uri.to_owned().into(), 208 | state_snapshot, 209 | pallets: pallet.clone(), 210 | child_trie: *child_tree, 211 | hashed_keys: vec![ 212 | // we always download the code, but we almost always won't use it, based on 213 | // `Runtime`. 214 | well_known_keys::CODE.to_vec(), 215 | // we will always download this key, since it helps detect if we should do 216 | // runtime migration or not. 217 | [twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat(), 218 | [twox_128(b"System"), twox_128(b"Number")].concat(), 219 | ], 220 | hashed_prefixes, 221 | })) 222 | } 223 | }; 224 | 225 | // possibly overwrite the state version, should hardly be needed. 226 | let builder = if let Some(state_version) = shared.overwrite_state_version { 227 | log::warn!( 228 | target: LOG_TARGET, 229 | "overwriting state version to {:?}, you better know what you are doing.", 230 | state_version 231 | ); 232 | builder.overwrite_state_version(state_version) 233 | } else { 234 | builder 235 | }; 236 | 237 | // then, we prepare to replace the code based on what the CLI wishes. 238 | let maybe_code_to_overwrite = match shared.runtime { 239 | Runtime::Path(ref path) => Some(std::fs::read(path).map_err(|e| { 240 | format!("error while reading runtime file from {:?}: {:?}", path, e) 241 | })?), 242 | Runtime::Existing => None, 243 | }; 244 | 245 | // build the main ext. 246 | let mut ext = builder.build().await?; 247 | 248 | // actually replace the code if needed. 249 | if let Some(new_code) = maybe_code_to_overwrite { 250 | let original_code = ext 251 | .execute_with(|| sp_io::storage::get(well_known_keys::CODE)) 252 | .expect("':CODE:' is always downloaded in try-runtime-cli; qed"); 253 | 254 | // NOTE: see the impl notes of `read_runtime_version`, the ext is almost not used here, 255 | // only as a backup. 256 | ext.insert(well_known_keys::CODE.to_vec(), new_code.clone()); 257 | let old_version = ::decode( 258 | &mut &*executor 259 | .read_runtime_version(&original_code, &mut ext.ext()) 260 | .unwrap(), 261 | ) 262 | .unwrap(); 263 | let old_code_hash = 264 | HexDisplay::from(BlakeTwo256::hash(&original_code).as_fixed_bytes()).to_string(); 265 | log::info!( 266 | target: LOG_TARGET, 267 | "Original runtime [Name: {:?}] [Version: {:?}] [Code hash: 0x{}...{}]", 268 | old_version.spec_name, 269 | old_version.spec_version, 270 | &old_code_hash[0..4], 271 | &old_code_hash[old_code_hash.len() - 4..], 272 | ); 273 | log::debug!( 274 | target: LOG_TARGET, 275 | "Original runtime full code hash: 0x{:?}", 276 | old_code_hash, 277 | ); 278 | let new_version = ::decode( 279 | &mut &*executor 280 | .read_runtime_version(&new_code, &mut ext.ext()) 281 | .unwrap(), 282 | ) 283 | .unwrap(); 284 | let new_code_hash = 285 | HexDisplay::from(BlakeTwo256::hash(&new_code).as_fixed_bytes()).to_string(); 286 | log::info!( 287 | target: LOG_TARGET, 288 | "New runtime [Name: {:?}] [Version: {:?}] [Code hash: 0x{}...{}]", 289 | new_version.spec_name, 290 | new_version.spec_version, 291 | &new_code_hash[0..4], 292 | &new_code_hash[new_code_hash.len() - 4..], 293 | ); 294 | log::debug!( 295 | target: LOG_TARGET, 296 | "New runtime code hash: 0x{:?}", 297 | new_code_hash 298 | ); 299 | 300 | if runtime_checks.name_matches && new_version.spec_name != old_version.spec_name { 301 | return Err( 302 | "Spec names must match. Use `--disable-spec-name-check` to disable this check." 303 | .into(), 304 | ); 305 | } 306 | 307 | if runtime_checks.version_increases 308 | && new_version.spec_version <= old_version.spec_version 309 | { 310 | return Err(format!("New runtime spec version must be greater than the on-chain runtime spec version: {} <= {}. Use `--disable-spec-version-check` to disable this check.", new_version.spec_version, old_version.spec_version).into()); 311 | } 312 | } 313 | 314 | if runtime_checks.try_runtime_feature_enabled 315 | && !ensure_try_runtime::(executor, &mut ext) 316 | { 317 | return Err("Given runtime is not compiled with the try-runtime feature.".into()); 318 | } 319 | 320 | Ok(ext) 321 | } 322 | } 323 | 324 | /// Build wasm executor by default config. 325 | pub(crate) fn build_executor(shared: &SharedParams) -> WasmExecutor { 326 | let heap_pages = 327 | shared 328 | .heap_pages 329 | .map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |p| HeapAllocStrategy::Static { 330 | extra_pages: p as _, 331 | }); 332 | 333 | WasmExecutor::builder() 334 | .with_execution_method(execution_method_from_cli( 335 | shared.wasm_method, 336 | shared.wasmtime_instantiation_strategy, 337 | )) 338 | .with_onchain_heap_alloc_strategy(heap_pages) 339 | .with_offchain_heap_alloc_strategy(heap_pages) 340 | // There is not that much we can do if someone is using unknown host functions. 341 | // They would need to fork the `cli` to add their custom host functions. 342 | .with_allow_missing_host_functions(true) 343 | .build() 344 | } 345 | 346 | /// Ensure that the given `ext` is compiled with `try-runtime` 347 | fn ensure_try_runtime( 348 | executor: &WasmExecutor, 349 | ext: &mut TestExternalities>, 350 | ) -> bool { 351 | use sp_api::RuntimeApiInfo; 352 | let final_code = ext 353 | .execute_with(|| sp_io::storage::get(well_known_keys::CODE)) 354 | .expect("':CODE:' is always downloaded in try-runtime-cli; qed"); 355 | let final_version = ::decode( 356 | &mut &*executor 357 | .read_runtime_version(&final_code, &mut ext.ext()) 358 | .unwrap(), 359 | ) 360 | .unwrap(); 361 | final_version 362 | .api_version(&>::ID) 363 | .is_some() 364 | } 365 | 366 | /// Execute the given `method` and `data` on top of `ext`, returning the results (encoded) and the 367 | /// state `changes`. 368 | pub(crate) fn state_machine_call( 369 | ext: &TestExternalities>, 370 | executor: &WasmExecutor, 371 | method: &'static str, 372 | data: &[u8], 373 | mut extensions: Extensions, 374 | ) -> sc_cli::Result<(OverlayedChanges>, Vec)> { 375 | let mut changes = Default::default(); 376 | let encoded_result = StateMachine::new( 377 | &ext.backend, 378 | &mut changes, 379 | executor, 380 | method, 381 | data, 382 | &mut extensions, 383 | &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, 384 | CallContext::Offchain, 385 | ) 386 | .execute() 387 | .map_err(|e| format!("failed to execute '{}': {}", method, e)) 388 | .map_err::(Into::into)?; 389 | 390 | Ok((changes, encoded_result)) 391 | } 392 | 393 | /// Same as [`state_machine_call`], but it also computes and returns the storage proof and ref time 394 | /// information. 395 | /// 396 | /// Make sure [`LOG_TARGET`] is enabled in logging. 397 | pub(crate) fn state_machine_call_with_proof( 398 | ext: &TestExternalities>, 399 | storage_overlay: &mut OverlayedChanges>, 400 | executor: &WasmExecutor, 401 | method: &'static str, 402 | data: &[u8], 403 | mut extensions: Extensions, 404 | maybe_export_proof: Option, 405 | ) -> sc_cli::Result<(StorageProof, Vec)> { 406 | let runtime_code_backend = sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend); 407 | let proving_backend = TrieBackendBuilder::wrap(&ext.backend) 408 | .with_recorder(Default::default()) 409 | .build(); 410 | let runtime_code = runtime_code_backend.runtime_code()?; 411 | 412 | let encoded_result = StateMachine::new( 413 | &proving_backend, 414 | storage_overlay, 415 | executor, 416 | method, 417 | data, 418 | &mut extensions, 419 | &runtime_code, 420 | CallContext::Offchain, 421 | ) 422 | .execute() 423 | .map_err(|e| format!("failed to execute {}: {}", method, e)) 424 | .map_err::(Into::into)?; 425 | 426 | let proof = proving_backend 427 | .extract_proof() 428 | .expect("A recorder was set and thus, a storage proof can be extracted; qed"); 429 | 430 | if let Some(path) = maybe_export_proof { 431 | let mut file = std::fs::File::create(&path).map_err(|e| { 432 | log::error!( 433 | target: LOG_TARGET, 434 | "Failed to create file {}: {:?}", 435 | path.to_string_lossy(), 436 | e 437 | ); 438 | e 439 | })?; 440 | 441 | log::info!(target: LOG_TARGET, "Writing storage proof to {}", path.to_string_lossy()); 442 | 443 | use std::io::Write as _; 444 | file.write_all(storage_proof_to_raw_json(&proof).as_bytes()) 445 | .map_err(|e| { 446 | log::error!( 447 | target: LOG_TARGET, 448 | "Failed to write storage proof to {}: {:?}", 449 | path.to_string_lossy(), 450 | e 451 | ); 452 | e 453 | })?; 454 | } 455 | 456 | Ok((proof, encoded_result)) 457 | } 458 | 459 | /// Converts a [`sp_state_machine::StorageProof`] into a JSON string. 460 | fn storage_proof_to_raw_json(storage_proof: &sp_state_machine::StorageProof) -> String { 461 | serde_json::Value::Object( 462 | storage_proof 463 | .to_memory_db::() 464 | .drain() 465 | .iter() 466 | .map(|(key, (value, _n))| { 467 | ( 468 | format!("0x{}", hex::encode(key.as_bytes())), 469 | serde_json::Value::String(format!("0x{}", hex::encode(value))), 470 | ) 471 | }) 472 | .collect(), 473 | ) 474 | .to_string() 475 | } 476 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use std::{fmt::Debug, str::FromStr, time::Duration}; 19 | 20 | use common::shared_parameters::SharedParams; 21 | use parity_scale_codec::DecodeAll; 22 | use sc_executor::{sp_wasm_interface::HostFunctions, WasmExecutor}; 23 | use sp_core::{ 24 | offchain::{ 25 | testing::{TestOffchainExt, TestTransactionPoolExt}, 26 | OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, 27 | }, 28 | traits::ReadRuntimeVersionExt, 29 | }; 30 | use sp_externalities::Extensions; 31 | use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; 32 | use sp_runtime::traits::Block as BlockT; 33 | use sp_weights::Weight; 34 | 35 | pub mod commands; 36 | pub mod common; 37 | 38 | pub(crate) const LOG_TARGET: &str = "try-runtime::cli"; 39 | 40 | /// Get the hash type of the generic `Block` from a `hash_str`. 41 | pub(crate) fn hash_of(hash_str: &str) -> sc_cli::Result 42 | where 43 | ::Err: Debug, 44 | { 45 | hash_str 46 | .parse::<::Hash>() 47 | .map_err(|e| format!("Could not parse block hash: {:?}", e).into()) 48 | } 49 | 50 | pub struct RefTimeInfo { 51 | pub used: Duration, 52 | pub max: Duration, 53 | } 54 | 55 | impl TryFrom> for RefTimeInfo { 56 | type Error = String; 57 | 58 | /// try_from Vec encoded as (Weight, Weight) tuple 59 | fn try_from(value: Vec) -> Result { 60 | let (weight_used, weight_max) = <(Weight, Weight)>::decode_all(&mut &*value) 61 | .map_err(|e| format!("failed to decode weight: {:?}", e))?; 62 | 63 | Ok(RefTimeInfo { 64 | // 1000 picoseconds == 1 nanosecond 65 | used: Duration::from_nanos(weight_used.ref_time() / 1000), 66 | max: Duration::from_nanos(weight_max.ref_time() / 1000), 67 | }) 68 | } 69 | } 70 | 71 | /// Build all extensions that we typically use. 72 | pub(crate) fn full_extensions(wasm_executor: WasmExecutor) -> Extensions { 73 | let mut extensions = Extensions::default(); 74 | let (offchain, _offchain_state) = TestOffchainExt::new(); 75 | let (pool, _pool_state) = TestTransactionPoolExt::new(); 76 | let keystore = MemoryKeystore::new(); 77 | extensions.register(OffchainDbExt::new(offchain.clone())); 78 | extensions.register(OffchainWorkerExt::new(offchain)); 79 | extensions.register(KeystoreExt::new(keystore)); 80 | extensions.register(TransactionPoolExt::new(pool)); 81 | extensions.register(ReadRuntimeVersionExt::new(wasm_executor)); 82 | 83 | extensions 84 | } 85 | 86 | pub(crate) fn rpc_err_handler(error: impl Debug) -> &'static str { 87 | log::error!(target: LOG_TARGET, "rpc error: {:?}", error); 88 | "rpc error." 89 | } 90 | -------------------------------------------------------------------------------- /core/tests/create_snapshot.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | #![cfg(unix)] 19 | 20 | use std::{ 21 | path::{Path, PathBuf}, 22 | time::Duration, 23 | }; 24 | 25 | use assert_cmd::cargo::cargo_bin; 26 | use frame_remote_externalities::{Builder, Mode, OfflineConfig, SnapshotConfig}; 27 | use sp_runtime::{ 28 | generic::{Block, Header}, 29 | traits::BlakeTwo256, 30 | OpaqueExtrinsic, 31 | }; 32 | use substrate_cli_test_utils as common; 33 | use tokio::process::Command; 34 | 35 | #[tokio::test] 36 | async fn create_snapshot_works() { 37 | let port = 45789; 38 | let ws_url = format!("ws://localhost:{}", port); 39 | 40 | // Spawn a dev node. 41 | let _ = std::thread::spawn(move || { 42 | match common::start_node_inline(vec![ 43 | "--no-hardware-benchmarks", 44 | "--dev", 45 | format!("--rpc-port={}", port).as_str(), 46 | ]) { 47 | Ok(_) => {} 48 | Err(e) => { 49 | panic!("Node exited with error: {}", e); 50 | } 51 | } 52 | }); 53 | // Wait some time to ensure the node is warmed up. 54 | std::thread::sleep(Duration::from_secs(90)); 55 | 56 | // Run the command with tokio 57 | let temp_dir = tempfile::Builder::new() 58 | .prefix("try-runtime-cli-test-dir") 59 | .tempdir() 60 | .expect("Failed to create a tempdir"); 61 | let snap_file_path = temp_dir.path().join("snapshot.snap"); 62 | 63 | common::run_with_timeout(Duration::from_secs(60), async move { 64 | fn create_snapshot( 65 | ws_url: &str, 66 | snap_file: &PathBuf, 67 | at: sp_core::H256, 68 | ) -> tokio::process::Child { 69 | Command::new(cargo_bin("try-runtime")) 70 | .stdout(std::process::Stdio::piped()) 71 | .stderr(std::process::Stdio::piped()) 72 | .arg("--runtime=existing") 73 | .args(["create-snapshot", format!("--uri={}", ws_url).as_str()]) 74 | .arg(snap_file) 75 | .args(["--at", format!("{:?}", at).as_str()]) 76 | .kill_on_drop(true) 77 | .spawn() 78 | .unwrap() 79 | } 80 | let block_number = 2; 81 | let block_hash = common::block_hash(block_number, &ws_url).await.unwrap(); 82 | 83 | // Try to create a snapshot. 84 | let child = create_snapshot(&ws_url, &snap_file_path, block_hash); 85 | let out = child.wait_with_output().await.unwrap(); 86 | 87 | assert!(out.status.success()); 88 | 89 | let snapshot_is_on_disk = Path::new(&snap_file_path).exists(); 90 | assert!(snapshot_is_on_disk, "Snapshot was not written to disk"); 91 | 92 | // Try and load the snapshot we have created by running `create-snapshot`. 93 | let snapshot_loading_result = 94 | Builder::, OpaqueExtrinsic>>::new() 95 | .mode(Mode::Offline(OfflineConfig { 96 | state_snapshot: SnapshotConfig { 97 | path: snap_file_path, 98 | }, 99 | })) 100 | .build() 101 | .await; 102 | 103 | assert!( 104 | snapshot_loading_result.is_ok(), 105 | "Snapshot couldn't be loaded: {:?}", 106 | snapshot_loading_result.err().unwrap() 107 | ); 108 | }) 109 | .await; 110 | } 111 | -------------------------------------------------------------------------------- /core/tests/execute_block.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | #![cfg(unix)] 19 | 20 | use std::time::Duration; 21 | 22 | use assert_cmd::cargo::cargo_bin; 23 | use regex::Regex; 24 | use substrate_cli_test_utils as common; 25 | use tokio::process::Command; 26 | 27 | #[tokio::test] 28 | async fn execute_block_works() { 29 | let port = 45789; 30 | 31 | // Spawn a dev node. 32 | let _ = std::thread::spawn(move || { 33 | match common::start_node_inline(vec![ 34 | "--no-hardware-benchmarks", 35 | "--dev", 36 | format!("--rpc-port={}", port).as_str(), 37 | ]) { 38 | Ok(_) => {} 39 | Err(e) => { 40 | panic!("Node exited with error: {}", e); 41 | } 42 | } 43 | }); 44 | // Wait some time to ensure the node is warmed up. 45 | std::thread::sleep(Duration::from_secs(90)); 46 | 47 | // Test passing --at 48 | common::run_with_timeout(Duration::from_secs(60), async move { 49 | let ws_url = format!("ws://localhost:{}", port); 50 | 51 | fn execute_block(ws_url: &str, at: sp_core::H256) -> tokio::process::Child { 52 | Command::new(cargo_bin("try-runtime")) 53 | .stdout(std::process::Stdio::piped()) 54 | .stderr(std::process::Stdio::piped()) 55 | .arg("--runtime=existing") 56 | .args(["execute-block"]) 57 | .args(["live", format!("--uri={}", ws_url).as_str()]) 58 | .args(["--at", format!("{:?}", at).as_str()]) 59 | .kill_on_drop(true) 60 | .spawn() 61 | .unwrap() 62 | } 63 | 64 | let block_number = 3; 65 | let block_hash = common::block_hash(block_number, &ws_url).await.unwrap(); 66 | 67 | // Try to execute the block. 68 | let mut block_execution = execute_block(&ws_url, block_hash); 69 | 70 | // The execute-block command is actually executing the next block. 71 | let expected_output = format!(r#".*Block #{} successfully executed"#, block_number); 72 | let re = Regex::new(expected_output.as_str()).unwrap(); 73 | let matched = 74 | common::wait_for_stream_pattern_match(block_execution.stderr.take().unwrap(), re).await; 75 | 76 | // Assert that the block-execution process has executed the expected block. 77 | assert!(matched.is_ok()); 78 | 79 | // Assert that the block-execution exited succesfully 80 | assert!(block_execution 81 | .wait_with_output() 82 | .await 83 | .unwrap() 84 | .status 85 | .success()); 86 | }) 87 | .await; 88 | 89 | // Test not passing --at 90 | common::run_with_timeout(Duration::from_secs(60), async move { 91 | let ws_url = format!("ws://localhost:{}", port); 92 | 93 | fn execute_block(ws_url: &str) -> tokio::process::Child { 94 | Command::new(cargo_bin("try-runtime")) 95 | .stdout(std::process::Stdio::piped()) 96 | .stderr(std::process::Stdio::piped()) 97 | .arg("--runtime=existing") 98 | .args(["execute-block"]) 99 | .args(["live", format!("--uri={}", ws_url).as_str()]) 100 | .kill_on_drop(true) 101 | .spawn() 102 | .unwrap() 103 | } 104 | 105 | // Try to execute the block. 106 | let mut block_execution = execute_block(&ws_url); 107 | let expected_output = r".*Block #(\d+) successfully executed"; 108 | let re = Regex::new(expected_output).unwrap(); 109 | let matched = 110 | common::wait_for_stream_pattern_match(block_execution.stderr.take().unwrap(), re).await; 111 | 112 | // Assert that the block-execution process has executed a block. 113 | assert!(matched.is_ok()); 114 | 115 | // Assert that the block-execution exited succesfully 116 | assert!(block_execution 117 | .wait_with_output() 118 | .await 119 | .unwrap() 120 | .status 121 | .success()); 122 | }) 123 | .await 124 | } 125 | -------------------------------------------------------------------------------- /core/tests/follow_chain.rs: -------------------------------------------------------------------------------- 1 | // This file is part of try-runtime-cli. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | #![cfg(unix)] 19 | 20 | use std::time::Duration; 21 | 22 | use assert_cmd::cargo::cargo_bin; 23 | use regex::Regex; 24 | use substrate_cli_test_utils as common; 25 | use tokio::process::Command; 26 | 27 | #[tokio::test] 28 | async fn follow_chain_works() { 29 | let port = 45789; 30 | let ws_url = format!("ws://localhost:{}", port); 31 | 32 | // Spawn a dev node. 33 | let _ = std::thread::spawn(move || { 34 | match common::start_node_inline(vec![ 35 | "--no-hardware-benchmarks", 36 | "--dev", 37 | format!("--rpc-port={}", port).as_str(), 38 | ]) { 39 | Ok(_) => {} 40 | Err(e) => { 41 | panic!("Node exited with error: {}", e); 42 | } 43 | } 44 | }); 45 | // Wait some time to ensure the node is warmed up. 46 | std::thread::sleep(Duration::from_secs(90)); 47 | 48 | common::run_with_timeout(Duration::from_secs(60), async move { 49 | fn start_follow(ws_url: &str) -> tokio::process::Child { 50 | Command::new(cargo_bin("try-runtime")) 51 | .stdout(std::process::Stdio::piped()) 52 | .stderr(std::process::Stdio::piped()) 53 | .arg("--runtime=existing") 54 | .args(["follow-chain", format!("--uri={}", ws_url).as_str()]) 55 | .kill_on_drop(true) 56 | .spawn() 57 | .unwrap() 58 | } 59 | 60 | // Kick off the follow-chain process and wait for it to process at least 3 blocks. 61 | let mut follow = start_follow(&ws_url); 62 | let re = Regex::new(r".*executed block ([3-9]|[1-9]\d+).*").unwrap(); 63 | let matched = 64 | common::wait_for_stream_pattern_match(follow.stderr.take().unwrap(), re).await; 65 | 66 | // Assert that the follow-chain process has followed at least 3 blocks. 67 | assert!(matched.is_ok()); 68 | }) 69 | .await 70 | } 71 | -------------------------------------------------------------------------------- /core/tests/on_runtime_upgrade.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | mod on_runtime_upgrade { 4 | use std::{path::PathBuf, time::Duration}; 5 | 6 | use assert_cmd::cargo::cargo_bin; 7 | use substrate_cli_test_utils as common; 8 | use tokio::process::Command; 9 | 10 | struct TestConfig { 11 | snap_path: String, 12 | runtime_path: String, 13 | command_extra_args: Vec, 14 | sub_command_extra_args: Vec, 15 | } 16 | 17 | impl TestConfig { 18 | fn new(snap_name: &str, runtime_name: &str) -> Self { 19 | let project_root = env!("CARGO_MANIFEST_DIR"); 20 | Self { 21 | snap_path: format!("{}/tests/snaps/{}.snap", project_root, snap_name), 22 | runtime_path: format!( 23 | "{}/tests/runtimes/{}.compact.compressed.wasm", 24 | project_root, runtime_name 25 | ), 26 | command_extra_args: Vec::new(), 27 | sub_command_extra_args: Vec::new(), 28 | } 29 | } 30 | 31 | fn with_command_args(mut self, args: &[&str]) -> Self { 32 | self.command_extra_args = args.iter().map(|&s| s.to_string()).collect(); 33 | self 34 | } 35 | 36 | fn with_sub_command_args(mut self, args: &[&str]) -> Self { 37 | self.sub_command_extra_args = args.iter().map(|&s| s.to_string()).collect(); 38 | self 39 | } 40 | } 41 | 42 | async fn run_test(config: TestConfig, expected_success: bool) { 43 | common::run_with_timeout(Duration::from_secs(300), async move { 44 | let child = on_runtime_upgrade(&config); 45 | let out = child.wait_with_output().await.unwrap(); 46 | if expected_success { 47 | assert_ok(out); 48 | } else { 49 | assert_err(out); 50 | } 51 | }) 52 | .await; 53 | } 54 | 55 | fn on_runtime_upgrade(config: &TestConfig) -> tokio::process::Child { 56 | let path = cargo_bin("try-runtime"); 57 | assert!( 58 | path.exists(), 59 | "try-runtime binary not found at path: {}", 60 | path.display() 61 | ); 62 | 63 | Command::new(path) 64 | .stdout(std::process::Stdio::piped()) 65 | .stderr(std::process::Stdio::piped()) 66 | .arg(format!("--runtime={}", config.runtime_path)) 67 | .args(&config.command_extra_args) 68 | .arg("on-runtime-upgrade") 69 | .arg("--blocktime=6000") 70 | .args(&config.sub_command_extra_args) 71 | .args(["snap", format!("--path={}", config.snap_path).as_str()]) 72 | .kill_on_drop(true) 73 | .spawn() 74 | .unwrap() 75 | } 76 | 77 | fn assert_ok(out: std::process::Output) { 78 | if !out.status.success() { 79 | panic!( 80 | "Command failed with status: {}\nstdout: {}\nstderr: {}", 81 | out.status, 82 | String::from_utf8_lossy(&out.stdout), 83 | String::from_utf8_lossy(&out.stderr) 84 | ); 85 | } 86 | } 87 | 88 | fn assert_err(out: std::process::Output) { 89 | if out.status.success() { 90 | panic!( 91 | "Command succeeded when it should have failed\nstdout: {}\nstderr: {}", 92 | String::from_utf8_lossy(&out.stdout), 93 | String::from_utf8_lossy(&out.stderr) 94 | ); 95 | } 96 | } 97 | 98 | #[test] 99 | fn precondition_snap_path_exists() { 100 | let config = TestConfig::new("rococo-people", "people_rococo_runtime_ok"); 101 | let snap = PathBuf::from(&config.snap_path); 102 | let project = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 103 | 104 | assert!( 105 | snap.exists(), 106 | "Snap file not found at path: {}", 107 | snap.display() 108 | ); 109 | assert!( 110 | project.exists(), 111 | "Project directory not found at path: {}", 112 | project.display() 113 | ); 114 | } 115 | 116 | #[tokio::test] 117 | async fn ok_works() { 118 | run_test( 119 | TestConfig::new("rococo-people", "people_rococo_runtime_ok"), 120 | true, 121 | ) 122 | .await; 123 | } 124 | 125 | #[tokio::test] 126 | async fn weight_max_fails() { 127 | run_test( 128 | TestConfig::new("rococo-people", "people_rococo_runtime_weight_max"), 129 | false, 130 | ) 131 | .await; 132 | } 133 | 134 | #[tokio::test] 135 | async fn weight_max_can_be_ignored() { 136 | run_test( 137 | TestConfig::new("rococo-people", "people_rococo_runtime_weight_max") 138 | .with_sub_command_args(&["--no-weight-warnings"]), 139 | true, 140 | ) 141 | .await; 142 | } 143 | 144 | #[tokio::test] 145 | async fn pre_upgrade_fail_fails() { 146 | run_test( 147 | TestConfig::new("rococo-people", "people_rococo_runtime_pre_upgrade_fail"), 148 | false, 149 | ) 150 | .await; 151 | } 152 | 153 | #[tokio::test] 154 | async fn pre_upgrade_fail_pre_and_postfails() { 155 | run_test( 156 | TestConfig::new("rococo-people", "people_rococo_runtime_pre_upgrade_fail") 157 | .with_sub_command_args(&["--checks=pre-and-post"]), 158 | false, 159 | ) 160 | .await; 161 | } 162 | 163 | #[tokio::test] 164 | async fn pre_upgrade_fail_can_be_ignored() { 165 | run_test( 166 | TestConfig::new("rococo-people", "people_rococo_runtime_pre_upgrade_fail") 167 | .with_sub_command_args(&["--checks=none"]), 168 | true, 169 | ) 170 | .await; 171 | } 172 | 173 | #[tokio::test] 174 | async fn post_upgrade_fail_fails() { 175 | run_test( 176 | TestConfig::new("rococo-people", "people_rococo_runtime_post_upgrade_fail"), 177 | false, 178 | ) 179 | .await; 180 | } 181 | 182 | #[tokio::test] 183 | async fn post_upgrade_fail_pre_and_postfails() { 184 | run_test( 185 | TestConfig::new("rococo-people", "people_rococo_runtime_post_upgrade_fail") 186 | .with_sub_command_args(&["--checks=pre-and-post"]), 187 | false, 188 | ) 189 | .await; 190 | } 191 | 192 | #[tokio::test] 193 | async fn post_upgrade_fail_can_be_ignored() { 194 | run_test( 195 | TestConfig::new("rococo-people", "people_rococo_runtime_post_upgrade_fail") 196 | .with_sub_command_args(&["--checks=none"]), 197 | true, 198 | ) 199 | .await; 200 | } 201 | 202 | #[tokio::test] 203 | async fn post_upgrade_storage_change_fails() { 204 | run_test( 205 | TestConfig::new( 206 | "rococo-people", 207 | "people_rococo_runtime_post_upgrade_storage_change", 208 | ), 209 | false, 210 | ) 211 | .await; 212 | } 213 | 214 | #[tokio::test] 215 | async fn not_idempotent_execution_fails() { 216 | run_test( 217 | TestConfig::new( 218 | "rococo-people", 219 | "people_rococo_runtime_not_idempotent_panic", 220 | ), 221 | false, 222 | ) 223 | .await; 224 | } 225 | 226 | /// If a Migration panics on second execution than it cannot be ignored. This is something that 227 | /// also should not be ignored. 228 | #[tokio::test] 229 | async fn not_idempotent_execution_issue_canot_be_ignored() { 230 | run_test( 231 | TestConfig::new( 232 | "rococo-people", 233 | "people_rococo_runtime_not_idempotent_panic", 234 | ) 235 | .with_sub_command_args(&["--disable-idempotency-checks"]), 236 | false, 237 | ) 238 | .await; 239 | } 240 | 241 | #[tokio::test] 242 | async fn not_idempotent_state_root_fails() { 243 | run_test( 244 | TestConfig::new( 245 | "rococo-people", 246 | "people_rococo_runtime_not_idempotent_state_root", 247 | ), 248 | false, 249 | ) 250 | .await; 251 | } 252 | 253 | #[tokio::test] 254 | async fn not_idempotent_state_root_issue_can_be_ignored() { 255 | run_test( 256 | TestConfig::new( 257 | "rococo-people", 258 | "people_rococo_runtime_not_idempotent_state_root", 259 | ) 260 | .with_sub_command_args(&["--disable-idempotency-checks"]), 261 | true, 262 | ) 263 | .await; 264 | } 265 | 266 | #[tokio::test] 267 | async fn non_matching_spec_name_fails() { 268 | run_test( 269 | TestConfig::new("rococo-people", "people_rococo_runtime_different_spec_name"), 270 | false, 271 | ) 272 | .await; 273 | } 274 | 275 | #[tokio::test] 276 | async fn non_matching_spec_name_can_be_ignored() { 277 | run_test( 278 | TestConfig::new("rococo-people", "people_rococo_runtime_different_spec_name") 279 | .with_command_args(&["--disable-spec-name-check"]), 280 | true, 281 | ) 282 | .await; 283 | } 284 | 285 | #[tokio::test] 286 | async fn non_incrementing_spec_version_fails() { 287 | run_test( 288 | TestConfig::new("rococo-people", "people_rococo_runtime_same_spec_version"), 289 | false, 290 | ) 291 | .await; 292 | } 293 | 294 | #[tokio::test] 295 | async fn non_incrementing_spec_version_can_be_ignored() { 296 | run_test( 297 | TestConfig::new("rococo-people", "people_rococo_runtime_same_spec_version") 298 | .with_sub_command_args(&["--disable-spec-version-check"]), 299 | true, 300 | ) 301 | .await; 302 | } 303 | 304 | /// Two migrations, one taking 100 blocks and another one taking 200. 305 | #[tokio::test] 306 | async fn mbm_double_ok_300b_works() { 307 | run_test( 308 | TestConfig::new("rococo-people", "people_rococo_runtime_mbm_double_ok_300b"), 309 | true, 310 | ) 311 | .await; 312 | } 313 | 314 | /// 300 block migrations works since we give it 300 blocks. 315 | #[tokio::test] 316 | async fn mbm_double_ok_300b_with_300b_works() { 317 | run_test( 318 | TestConfig::new("rococo-people", "people_rococo_runtime_mbm_double_ok_300b") 319 | .with_sub_command_args(&["--mbm-max-blocks=300"]), 320 | true, 321 | ) 322 | .await; 323 | } 324 | 325 | /// 300 block migrations fails since we only give it 299 blocks. 326 | #[tokio::test] 327 | async fn mbm_double_ok_300b_too_few_blocks_errors() { 328 | run_test( 329 | TestConfig::new("rococo-people", "people_rococo_runtime_mbm_double_ok_300b") 330 | .with_sub_command_args(&["--mbm-max-blocks=299"]), 331 | false, 332 | ) 333 | .await; 334 | } 335 | 336 | /// The same MBM configured multiple times, with other ones in between. 337 | #[tokio::test] 338 | async fn mbm_double_ok_80b_duplicates_works() { 339 | run_test( 340 | TestConfig::new( 341 | "rococo-people", 342 | "people_rococo_runtime_mbm_duplicates_ok_80b", 343 | ), 344 | true, 345 | ) 346 | .await; 347 | } 348 | 349 | // TODO check that it does not modify storage on success 350 | #[tokio::test] 351 | async fn mbm_pre_upgrade_fail_fails() { 352 | run_test( 353 | TestConfig::new( 354 | "rococo-people", 355 | "people_rococo_runtime_mbm_pre_upgrade_fails", 356 | ), 357 | false, 358 | ) 359 | .await; 360 | } 361 | 362 | #[tokio::test] 363 | async fn mbm_post_upgrade_fail_fails() { 364 | run_test( 365 | TestConfig::new( 366 | "rococo-people", 367 | "people_rococo_runtime_mbm_post_upgrade_fails", 368 | ), 369 | false, 370 | ) 371 | .await; 372 | } 373 | 374 | #[tokio::test] 375 | async fn mbm_fail_fails() { 376 | run_test( 377 | TestConfig::new("rococo-people", "people_rococo_runtime_mbm_fails"), 378 | false, 379 | ) 380 | .await; 381 | } 382 | 383 | #[tokio::test] 384 | async fn mbm_fail_ignore_mbms() { 385 | run_test( 386 | TestConfig::new("rococo-people", "people_rococo_runtime_mbm_fails") 387 | .with_sub_command_args(&["--disable-mbms-checks"]), 388 | false, 389 | ) 390 | .await; 391 | } 392 | 393 | #[tokio::test] 394 | async fn mbm_empty_works() { 395 | run_test( 396 | TestConfig::new("rococo-people", "people_rococo_runtime_mbm_empty"), 397 | true, 398 | ) 399 | .await; 400 | } 401 | 402 | /*#[tokio::test] 403 | async fn no_migrations_works() { 404 | run_test( 405 | TestConfig::new("rococo-people", "people_rococo_runtime_no_migrations") 406 | .with_sub_command_args(&["--disable-spec-version-check"]), 407 | true 408 | ).await; 409 | }*/ 410 | } 411 | -------------------------------------------------------------------------------- /core/tests/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # tests 3 | 4 | ## ./runtimes and ./snaps 5 | 6 | A state snapshot is included in ./snaps, and some runtimes in ./runtimes for use in tests. 7 | 8 | - `bridge_hub_rococo_runtime_OK.compact.compressed.wasm` a runtime with correctly configured migrations 9 | - `bridge_hub_rococo_runtime_WEIGHT_ISSUE.compact.compressed.wasm` a runtime with migrations that would exceed sensible values for a parachain 10 | - `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 11 | - `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 12 | -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_different_spec_name.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_different_spec_name.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_mbm_double_ok_300b.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_mbm_double_ok_300b.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_mbm_duplicates_ok_80b.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_mbm_duplicates_ok_80b.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_mbm_empty.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_mbm_empty.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_mbm_fails.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_mbm_fails.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_mbm_post_upgrade_fails.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_mbm_post_upgrade_fails.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_mbm_pre_upgrade_fails.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_mbm_pre_upgrade_fails.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_no_migrations.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_no_migrations.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_not_idempotent_panic.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_not_idempotent_panic.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_not_idempotent_state_root.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_not_idempotent_state_root.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_ok.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_ok.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_post_upgrade_fail.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_post_upgrade_fail.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_post_upgrade_storage_change.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_post_upgrade_storage_change.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_pre_upgrade_fail.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_pre_upgrade_fail.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_same_spec_version.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_same_spec_version.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/people_rococo_runtime_weight_max.compact.compressed.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/runtimes/people_rococo_runtime_weight_max.compact.compressed.wasm -------------------------------------------------------------------------------- /core/tests/runtimes/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Test Runtimes 3 | 4 | Some runtimes to use in tests. 5 | 6 | ## Generation Instructions 7 | 8 | ### No Migrations 9 | 10 | Just pass an empty Migrations tuple to executive. 11 | 12 | ### Bad Spec Name 13 | 14 | Set the `spec_name` to something other than what's in the on-chain runtime. 15 | 16 | ### Non-Incrementing Spec Version 17 | 18 | Set the `spec_version` to less than or equal to the current on-chain runtime version. 19 | 20 | ### Not Idempotent Execution 21 | 22 | Add a migration that is non-idempotent. E.g. 23 | 24 | ```rust 25 | pub struct NonIdempotentExceptionMigration; 26 | 27 | impl frame_support::traits::OnRuntimeUpgrade for NonIdempotentExceptionMigration { 28 | fn on_runtime_upgrade() -> Weight { 29 | let key = sp_core::blake2_128(b"some_random_seed"); 30 | if frame_support::storage::unhashed::get(&key[..]).unwrap_or(false) { 31 | panic!("exception"); 32 | }; 33 | frame_support::storage::unhashed::put::(&key[..], &true); 34 | 35 | ::DbWeight::get().writes(1) 36 | } 37 | } 38 | ``` 39 | 40 | ### Not Idempotent State Root 41 | 42 | Add a migration that is non-idempotnent w.r.t the state root. E.g. 43 | 44 | ```rust 45 | pub struct NonIdempotentStateRootMigration; 46 | 47 | impl frame_support::traits::OnRuntimeUpgrade for NonIdempotentStateRootMigration { 48 | fn on_runtime_upgrade() -> Weight { 49 | let key = sp_core::blake2_128(b"some_random_seed"); 50 | let cur = frame_support::storage::unhashed::get(&key[..]).unwrap_or(0); 51 | frame_support::storage::unhashed::put::(&key[..], &(cur + 1u32)); 52 | ::DbWeight::get().writes(1) 53 | } 54 | } 55 | ``` 56 | 57 | ### Weight Issue 58 | 59 | Add a migration that is overweight. E.g. 60 | 61 | ```rust 62 | pub struct OverweightMigration; 63 | 64 | impl frame_support::traits::OnRuntimeUpgrade for OverweightMigration { 65 | fn on_runtime_upgrade() -> Weight { 66 | ::BlockWeights::get().max_block 67 | } 68 | } 69 | ``` 70 | 71 | ### MBMs 72 | 73 | ```rust 74 | use frame_support::pallet_prelude::Get; 75 | pub struct ExampleMbm(core::marker::PhantomData); 76 | 77 | impl> frame_support::migrations::SteppedMigration for ExampleMbm { 78 | type Cursor = u32; 79 | type Identifier = u32; 80 | 81 | fn id() -> Self::Identifier { 82 | T::get() 83 | } 84 | 85 | fn step( 86 | cursor: Option, 87 | _meter: &mut frame_support::weights::WeightMeter, 88 | ) -> Result, frame_support::migrations::SteppedMigrationError> { 89 | let cursor = cursor.unwrap_or(0); 90 | log::error!("Migrating #{} with cursor: {}", T::get(), cursor) ; 91 | 92 | if cursor < T::get() { 93 | Ok(Some(cursor + 1)) 94 | } else { 95 | Ok(None) 96 | } 97 | } 98 | } 99 | ``` 100 | -------------------------------------------------------------------------------- /core/tests/snaps/rococo-people.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/try-runtime-cli/b45be7dfce89fd881be27171d3ea114ef19d832c/core/tests/snaps/rococo-people.snap -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = [ 4 | "rust-src", 5 | "rustc-dev", 6 | ] 7 | targets = [ "wasm32-unknown-unknown" ] 8 | profile = "default" 9 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | 3 | # Imports 4 | imports_granularity = "Crate" 5 | group_imports = "StdExternalCrate" 6 | reorder_imports = true 7 | 8 | # Consistency 9 | newline_style = "Unix" 10 | 11 | # Format comments 12 | comment_width = 100 13 | wrap_comments = true 14 | --------------------------------------------------------------------------------