diff --git a/.github/workflows/auto-add-parachain-issues.yml b/.github/workflows/auto-add-parachain-issues.yml new file mode 100644 index 0000000000000000000000000000000000000000..6b5222b6ff74147b063d913ec0dcdec299a6fcea --- /dev/null +++ b/.github/workflows/auto-add-parachain-issues.yml @@ -0,0 +1,30 @@ +# If there are new issues related to the async backing feature, +# add it to the parachain team's board and set a custom "meta" field. + +name: Add selected issues to Parachain team board +on: + issues: + types: + - labeled + +jobs: + add-parachain-issues: + if: github.event.label.name == 'T16-async_backing' + runs-on: ubuntu-latest + steps: + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@v2.1.0 + with: + app_id: ${{ secrets.PROJECT_APP_ID }} + private_key: ${{ secrets.PROJECT_APP_KEY }} + - name: Sync issues + uses: paritytech/github-issue-sync@v0.3.2 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PROJECT_TOKEN: ${{ steps.generate_token.outputs.token }} + project: 119 # Parachain team board + project_field: 'meta' + project_value: 'async backing' + labels: | + T16-async_backing diff --git a/.github/workflows/fmt-check.yml b/.github/workflows/fmt-check.yml index efcf278c46e83630a54fae3de01d0c9e19304dee..324c9bfff7a54cf594726328097f88bb23c9d951 100644 --- a/.github/workflows/fmt-check.yml +++ b/.github/workflows/fmt-check.yml @@ -15,7 +15,7 @@ jobs: os: ["ubuntu-latest"] runs-on: ${{ matrix.os }} container: - image: docker.io/paritytech/ci-unified:bullseye-1.75.0-2024-01-22-v20240109 + image: docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/release-30_publish_release_draft.yml b/.github/workflows/release-30_publish_release_draft.yml index 12891ef70af36b4e848c68e292f9ba2881ee20d2..430b1e26646758ef40218760fbd20f3a448f45f3 100644 --- a/.github/workflows/release-30_publish_release_draft.yml +++ b/.github/workflows/release-30_publish_release_draft.yml @@ -42,7 +42,6 @@ jobs: URL=https://github.com/chevdor/tera-cli/releases/download/v0.2.4/tera-cli_linux_amd64.deb wget $URL -O tera.deb sudo dpkg -i tera.deb - tera --version - name: Download artifacts uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 @@ -70,7 +69,7 @@ jobs: export REF1=$(get_latest_release_tag) if [[ -z "${{ inputs.version }}" ]]; then - export REF2="${{ github.ref }}" + export REF2="${{ github.ref_name }}" else export REF2="${{ inputs.version }}" fi @@ -79,10 +78,6 @@ jobs: ./scripts/release/build-changelogs.sh - echo "Checking the folder state" - pwd - ls -la scripts/release - - name: Archive artifact context.json uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: @@ -151,5 +146,5 @@ jobs: access_token: ${{ secrets.RELEASENOTES_MATRIX_V2_ACCESS_TOKEN }} server: m.parity.io message: | - **New version of polkadot tagged**: ${{ github.ref }}
+ **New version of polkadot tagged**: ${{ github.ref_name }}
Draft release created: ${{ needs.publish-release-draft.outputs.release_url }} diff --git a/.github/workflows/review-bot.yml b/.github/workflows/review-bot.yml index 5b036115b2386c366b2f1e78e9ce1dc7d526eedd..f1401406ae47afd3230cc163f35df0e3bcbac7b7 100644 --- a/.github/workflows/review-bot.yml +++ b/.github/workflows/review-bot.yml @@ -5,28 +5,41 @@ on: - Review-Trigger types: - completed + workflow_dispatch: + inputs: + pr-number: + description: "Number of the PR to evaluate" + required: true + type: number jobs: review-approvals: runs-on: ubuntu-latest environment: master steps: + - name: Generate token + id: app_token + uses: actions/create-github-app-token@v1.9.3 + with: + app-id: ${{ secrets.REVIEW_APP_ID }} + private-key: ${{ secrets.REVIEW_APP_KEY }} - name: Extract content of artifact + if: ${{ !inputs.pr-number }} id: number - uses: Bullrich/extract-text-from-artifact@v1.0.0 + uses: Bullrich/extract-text-from-artifact@v1.0.1 with: artifact-name: pr_number - - name: Generate token - id: app_token - uses: tibdex/github-app-token@v1 - with: - app_id: ${{ secrets.REVIEW_APP_ID }} - private_key: ${{ secrets.REVIEW_APP_KEY }} - name: "Evaluates PR reviews and assigns reviewers" uses: paritytech/review-bot@v2.4.0 with: repo-token: ${{ steps.app_token.outputs.token }} team-token: ${{ steps.app_token.outputs.token }} checks-token: ${{ steps.app_token.outputs.token }} - pr-number: ${{ steps.number.outputs.content }} + # This is extracted from the triggering event + pr-number: ${{ inputs.pr-number || steps.number.outputs.content }} request-reviewers: true + - name: Log payload + if: ${{ failure() || runner.debug }} + run: echo "::debug::$payload" + env: + payload: ${{ toJson(github.event) }} diff --git a/.github/workflows/review-trigger.yml b/.github/workflows/review-trigger.yml index 8b23dd30bb29ad7879543c064c3eb711cc87895d..ec4a62afc0c780fcb13e7bc73228bb2e77e6a582 100644 --- a/.github/workflows/review-trigger.yml +++ b/.github/workflows/review-trigger.yml @@ -21,6 +21,43 @@ jobs: - name: Skip merge queue if: ${{ contains(github.ref, 'gh-readonly-queue') }} run: exit 0 + - name: Get PR data + id: comments + run: | + echo "bodies=$(gh pr view ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --json comments --jq '[.comments[].body]')" >> "$GITHUB_OUTPUT" + echo "reviews=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews --jq '[.[].state]')" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ github.token }} + - name: Fail when author pushes new code + # Require new reviews when the author is pushing and he is not a member + if: | + contains(fromJson(steps.comments.outputs.reviews), 'APPROVED') && + github.event_name == 'pull_request_target' && + github.event.action == 'synchronize' && + github.event.sender.login == github.event.pull_request.user.login && + github.event.pull_request.author_association != 'CONTRIBUTOR' && + github.event.pull_request.author_association != 'MEMBER' + run: | + echo "User's association is ${{ github.event.pull_request.author_association }}" + # We get the list of reviewers who approved the PR + REVIEWERS=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews \ + --jq '{reviewers: [.[] | select(.state == "APPROVED") | .user.login]}') + + # We request them to review again + echo $REVIEWERS | gh api --method POST repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/requested_reviewers --input - + + echo "::error::Project needs to be reviewed again" + exit 1 + env: + GH_TOKEN: ${{ github.token }} + - name: Comment requirements + # If the previous step failed and github-actions hasn't commented yet we comment instructions + if: failure() && !contains(fromJson(steps.comments.outputs.bodies), 'Review required! Latest push from author must always be reviewed') + run: | + gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --body "Review required! Latest push from author must always be reviewed" + env: + GH_TOKEN: ${{ github.token }} + COMMENTS: ${{ steps.comments.outputs.users }} - name: Get PR number env: PR_NUMBER: ${{ github.event.pull_request.number }} @@ -28,7 +65,7 @@ jobs: echo "Saving PR number: $PR_NUMBER" mkdir -p ./pr echo $PR_NUMBER > ./pr/pr_number - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 name: Save PR number with: name: pr_number diff --git a/.github/workflows/sync-templates.yml b/.github/workflows/sync-templates.yml new file mode 100644 index 0000000000000000000000000000000000000000..3617d6c34a3e342e1abd52acaffbf3a8c61ec43b --- /dev/null +++ b/.github/workflows/sync-templates.yml @@ -0,0 +1,159 @@ +name: Synchronize templates + + +# This job is used to keep the repository templates up-to-date. +# The code of the templates exist inside the monorepo, and upon releases we synchronize the repositories: +# - https://github.com/paritytech/polkadot-sdk-minimal-template +# - https://github.com/paritytech/polkadot-sdk-parachain-template +# - https://github.com/paritytech/polkadot-sdk-solochain-template +# +# The job moves the template code out of the monorepo, +# replaces any references to the monorepo workspace using psvm and toml-cli, +# checks that it builds successfully, +# and commits and pushes the result to each respective repository. +# If the build fails, a PR is created instead for manual inspection. + + +on: + # A manual dispatch for now - automatic on releases later. + workflow_dispatch: + inputs: + crate_release_version: + description: 'A release version to use, e.g. 1.9.0' + required: true + + +jobs: + sync-templates: + runs-on: ubuntu-latest + environment: master + strategy: + fail-fast: false + matrix: + template: ["minimal", "solochain", "parachain"] + env: + template-path: "polkadot-sdk-${{ matrix.template }}-template" + steps: + + # 1. Prerequisites. + + - name: Configure git identity + run: | + git config --global user.name "Template Bot" + git config --global user.email "163342540+paritytech-polkadotsdk-templatebot[bot]@users.noreply.github.com" + - uses: actions/checkout@v3 + with: + path: polkadot-sdk + ref: "release-crates-io-v${{ github.event.inputs.crate_release_version }}" + - name: Generate a token for the template repository + id: app_token + uses: actions/create-github-app-token@v1.9.3 + with: + owner: "paritytech" + repositories: "polkadot-sdk-${{ matrix.template }}-template" + app-id: ${{ secrets.TEMPLATE_APP_ID }} + private-key: ${{ secrets.TEMPLATE_APP_KEY }} + - uses: actions/checkout@v3 + with: + repository: "paritytech/polkadot-sdk-${{ matrix.template }}-template" + path: "${{ env.template-path }}" + token: ${{ steps.app_token.outputs.token }} + - name: Install toml-cli + run: cargo install --git https://github.com/gnprice/toml-cli --rev ea69e9d2ca4f0f858110dc7a5ae28bcb918c07fb # v0.2.3 + - name: Install Polkadot SDK Version Manager + run: cargo install --git https://github.com/paritytech/psvm psvm + - name: Rust compilation prerequisites + run: | + sudo apt update + sudo apt install -y \ + protobuf-compiler + rustup target add wasm32-unknown-unknown + rustup component add rustfmt clippy rust-src + + # 2. Yanking the template out of the monorepo workspace. + + - name: Use psvm to replace git references with released creates. + run: find . -type f -name 'Cargo.toml' -exec psvm -o -v ${{ github.event.inputs.crate_release_version }} -p {} \; + working-directory: polkadot-sdk/templates/${{ matrix.template }}/ + - name: Create a new workspace Cargo.toml + run: | + cat << EOF > Cargo.toml + [workspace.package] + license = "MIT-0" + authors = ["Parity Technologies "] + homepage = "https://substrate.io" + + [workspace] + members = [ + "node", + "pallets/template", + "runtime", + ] + resolver = "2" + EOF + shell: bash + working-directory: polkadot-sdk/templates/${{ matrix.template }}/ + - name: Update workspace configuration + run: | + set -euo pipefail + # toml-cli has no overwrite functionality yet, so we use temporary files. + # We cannot pipe the output straight to the same file while the CLI still reads and processes it. + + toml set templates/${{ matrix.template }}/Cargo.toml 'workspace.package.repository' "https://github.com/paritytech/polkadot-sdk-${{ matrix.template }}-template.git" > Cargo.temp + mv Cargo.temp ./templates/${{ matrix.template }}/Cargo.toml + + toml set templates/${{ matrix.template }}/Cargo.toml 'workspace.package.edition' "$(toml get --raw Cargo.toml 'workspace.package.edition')" > Cargo.temp + mv Cargo.temp ./templates/${{ matrix.template }}/Cargo.toml + + toml get Cargo.toml 'workspace.lints' --output-toml >> ./templates/${{ matrix.template }}/Cargo.toml + + toml get Cargo.toml 'workspace.dependencies' --output-toml >> ./templates/${{ matrix.template }}/Cargo.toml + working-directory: polkadot-sdk + - name: Print the result Cargo.tomls for debugging + if: runner.debug == '1' + run: find . -type f -name 'Cargo.toml' -exec cat {} \; + working-directory: polkadot-sdk/templates/${{ matrix.template }}/ + + - name: Clean the destination repository + run: rm -rf ./* + working-directory: "${{ env.template-path }}" + - name: Copy over the new changes + run: | + cp -r polkadot-sdk/templates/${{ matrix.template }}/* "${{ env.template-path }}/" + + # 3. Verify the build. Push the changes or create a PR. + + # We've run into out-of-disk error when compiling in the next step, so we free up some space this way. + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # 1.3.1 + with: + android: true # This alone is a 12 GB save. + # We disable the rest because it caused some problems. (they're enabled by default) + # The Android removal is enough. + dotnet: false + haskell: false + large-packages: false + swap-storage: false + + - name: Check if it compiles + id: check-compilation + run: cargo check && cargo test + working-directory: "${{ env.template-path }}" + timeout-minutes: 90 + - name: Create PR on failure + if: failure() && steps.check-compilation.outcome == 'failure' + uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 # v5 + with: + path: "${{ env.template-path }}" + token: ${{ steps.app_token.outputs.token }} + add-paths: | + ./* + title: "[Don't merge] Update the ${{ matrix.template }} template" + body: "The template has NOT been successfully built and needs to be inspected." + branch: "update-template/${{ github.event_name }}" + - name: Push changes + run: | + git add -A . + git commit --allow-empty -m "Update template triggered by ${{ github.event_name }}" + git push + working-directory: "${{ env.template-path }}" diff --git a/.github/workflows/test-github-actions.yml b/.github/workflows/test-github-actions.yml new file mode 100644 index 0000000000000000000000000000000000000000..c8ce49cb462b07b135d33158408958aa88a2aa21 --- /dev/null +++ b/.github/workflows/test-github-actions.yml @@ -0,0 +1,46 @@ +name: test-github-actions + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + merge_group: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + CARGO_NET_GIT_FETCH_WITH_CLI: true + +jobs: + test-linux-stable-int: + runs-on: arc-runners-polkadot-sdk + timeout-minutes: 30 + container: + image: "docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408" + env: + RUSTFLAGS: "-C debug-assertions -D warnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" + # Ensure we run the UI tests. + RUN_UI_TESTS: 1 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: script + run: WASM_BUILD_NO_COLOR=1 time cargo test -p staging-node-cli --release --locked -- --ignored + quick-benchmarks: + runs-on: arc-runners-polkadot-sdk + timeout-minutes: 30 + container: + image: "docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408" + env: + RUSTFLAGS: "-C debug-assertions -D warnings" + RUST_BACKTRACE: "full" + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: script + run: time cargo run --locked --release -p staging-node-cli --bin substrate-node --features runtime-benchmarks --quiet -- benchmark pallet --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 --quiet diff --git a/.gitlab/pipeline/check.yml b/.gitlab/pipeline/check.yml index 89b2c00db9b2b13187a562987e00abcb232e0e32..6fb8a97fe95821886c416d97224fb21fd0f2897b 100644 --- a/.gitlab/pipeline/check.yml +++ b/.gitlab/pipeline/check.yml @@ -132,7 +132,6 @@ check-runtime-migration-westend: WASM: "westend_runtime.compact.compressed.wasm" URI: "wss://westend-try-runtime-node.parity-chains.parity.io:443" SUBCOMMAND_EXTRA_ARGS: "--no-weight-warnings" - allow_failure: true check-runtime-migration-rococo: stage: check diff --git a/.gitlab/pipeline/publish.yml b/.gitlab/pipeline/publish.yml index d8f5d5832291f7afced292d3b0fdeb6238de26a8..68712610ad2361601af3763485d1ab3e6c158682 100644 --- a/.gitlab/pipeline/publish.yml +++ b/.gitlab/pipeline/publish.yml @@ -74,6 +74,8 @@ publish-subsystem-benchmarks: artifacts: true - job: subsystem-benchmark-availability-distribution artifacts: true + - job: subsystem-benchmark-approval-voting + artifacts: true - job: publish-rustdoc artifacts: false script: @@ -115,6 +117,8 @@ trigger_workflow: artifacts: true - job: subsystem-benchmark-availability-distribution artifacts: true + - job: subsystem-benchmark-approval-voting + artifacts: true script: - echo "Triggering workflow" - > diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index 1d6efd7b9fd1a91c3c49aa26faa9263216e9cb4e..c17a3ce35eaf54896e7d96e6cfefbb7edf53ced7 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -511,7 +511,7 @@ test-syscalls: fi allow_failure: false # this rarely triggers in practice -subsystem-benchmark-availability-recovery: +.subsystem-benchmark-template: stage: test artifacts: name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" @@ -523,26 +523,26 @@ subsystem-benchmark-availability-recovery: - .docker-env - .common-refs - .run-immediately - script: - - cargo bench -p polkadot-availability-recovery --bench availability-recovery-regression-bench --features subsystem-benchmarks tags: - benchmark + +subsystem-benchmark-availability-recovery: + extends: + - .subsystem-benchmark-template + script: + - cargo bench -p polkadot-availability-recovery --bench availability-recovery-regression-bench --features subsystem-benchmarks allow_failure: true subsystem-benchmark-availability-distribution: - stage: test - artifacts: - name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" - when: always - expire_in: 1 hour - paths: - - charts/ extends: - - .docker-env - - .common-refs - - .run-immediately + - .subsystem-benchmark-template script: - cargo bench -p polkadot-availability-distribution --bench availability-distribution-regression-bench --features subsystem-benchmarks - tags: - - benchmark + allow_failure: true + +subsystem-benchmark-approval-voting: + extends: + - .subsystem-benchmark-template + script: + - cargo bench -p polkadot-node-core-approval-voting --bench approval-voting-regression-bench --features subsystem-benchmarks allow_failure: true diff --git a/.gitlab/pipeline/zombienet/bridges.yml b/.gitlab/pipeline/zombienet/bridges.yml index 4278f59b1e9a2e33f32bf255436d6af5d31b30fb..9d7a8b9311934a148e855caf7c4315d8a281aed1 100644 --- a/.gitlab/pipeline/zombienet/bridges.yml +++ b/.gitlab/pipeline/zombienet/bridges.yml @@ -55,9 +55,9 @@ zombienet-bridges-0001-asset-transfer-works: - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0001-asset-transfer --docker - echo "Done" -zombienet-bridges-0002-mandatory-headers-synced-while-idle: +zombienet-bridges-0002-free-headers-synced-while-idle: extends: - .zombienet-bridges-common script: - - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0002-mandatory-headers-synced-while-idle --docker + - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0002-free-headers-synced-while-idle --docker - echo "Done" diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index ba05c709a27b163386e36f680812e0cf24f10277..6b72075c513b73c075d1dc10c90d0461bf0e1a82 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -161,6 +161,8 @@ zombienet-polkadot-functional-0011-async-backing-6-seconds-rate: zombienet-polkadot-elastic-scaling-0001-basic-3cores-6s-blocks: extends: - .zombienet-polkadot-common + variables: + FORCED_INFRA_INSTANCE: "spot-iops" script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/elastic_scaling" diff --git a/Cargo.lock b/Cargo.lock index 61bc895e706ed3643c7bc6a9f5f73e25b2830c32..1fe4012070a02e3e85aaed5167680d78c03d2ca0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,15 +42,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" -[[package]] -name = "aead" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "aead" version = "0.5.2" @@ -61,18 +52,6 @@ dependencies = [ "generic-array 0.14.7", ] -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher 0.3.0", - "cpufeatures", - "opaque-debug 0.3.0", -] - [[package]] name = "aes" version = "0.8.3" @@ -84,31 +63,17 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "aes-gcm" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" -dependencies = [ - "aead 0.4.3", - "aes 0.7.5", - "cipher 0.3.0", - "ctr 0.7.0", - "ghash 0.4.4", - "subtle 2.5.0", -] - [[package]] name = "aes-gcm" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ - "aead 0.5.2", - "aes 0.8.3", + "aead", + "aes", "cipher 0.4.4", - "ctr 0.9.2", - "ghash 0.5.0", + "ctr", + "ghash", "subtle 2.5.0", ] @@ -809,6 +774,7 @@ dependencies = [ "parachains-common", "rococo-emulated-chain", "sp-core", + "staging-xcm", "testnet-parachains-constants", ] @@ -819,17 +785,22 @@ dependencies = [ "assert_matches", "asset-hub-rococo-runtime", "asset-test-utils", + "cumulus-pallet-parachain-system", "emulated-integration-tests-common", "frame-support", "pallet-asset-conversion", "pallet-assets", "pallet-balances", "pallet-message-queue", + "pallet-treasury", + "pallet-utility", "pallet-xcm", "parachains-common", "parity-scale-codec", "penpal-runtime", + "polkadot-runtime-common", "rococo-runtime", + "rococo-runtime-constants", "rococo-system-emulated-network", "sp-runtime", "staging-xcm", @@ -865,6 +836,7 @@ dependencies = [ "hex-literal", "log", "pallet-asset-conversion", + "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-assets", "pallet-aura", @@ -927,6 +899,7 @@ dependencies = [ "frame-support", "parachains-common", "sp-core", + "staging-xcm", "testnet-parachains-constants", "westend-emulated-chain", ] @@ -988,6 +961,7 @@ dependencies = [ "hex-literal", "log", "pallet-asset-conversion", + "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-assets", "pallet-aura", @@ -2144,6 +2118,7 @@ dependencies = [ "static_assertions", "substrate-wasm-builder", "testnet-parachains-constants", + "tuplex", ] [[package]] @@ -2213,7 +2188,6 @@ dependencies = [ "pallet-message-queue", "pallet-xcm", "parachains-common", - "parity-scale-codec", "rococo-westend-system-emulated-network", "sp-runtime", "staging-xcm", @@ -2303,6 +2277,7 @@ dependencies = [ "static_assertions", "substrate-wasm-builder", "testnet-parachains-constants", + "tuplex", "westend-runtime-constants", ] @@ -2341,6 +2316,7 @@ dependencies = [ "staging-xcm", "staging-xcm-builder", "static_assertions", + "tuplex", ] [[package]] @@ -2529,18 +2505,6 @@ dependencies = [ "keystream", ] -[[package]] -name = "chacha20" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" -dependencies = [ - "cfg-if", - "cipher 0.3.0", - "cpufeatures", - "zeroize", -] - [[package]] name = "chacha20" version = "0.9.1" @@ -2554,14 +2518,14 @@ dependencies = [ [[package]] name = "chacha20poly1305" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead 0.4.3", - "chacha20 0.8.2", - "cipher 0.3.0", - "poly1305 0.7.2", + "aead", + "chacha20", + "cipher 0.4.4", + "poly1305", "zeroize", ] @@ -2641,15 +2605,6 @@ dependencies = [ "generic-array 0.14.7", ] -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "cipher" version = "0.4.4" @@ -2658,6 +2613,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -2828,6 +2784,36 @@ dependencies = [ "testnet-parachains-constants", ] +[[package]] +name = "collectives-westend-integration-tests" +version = "1.0.0" +dependencies = [ + "assert_matches", + "asset-hub-westend-runtime", + "collectives-westend-runtime", + "cumulus-pallet-parachain-system", + "cumulus-pallet-xcmp-queue", + "emulated-integration-tests-common", + "frame-support", + "pallet-asset-rate", + "pallet-assets", + "pallet-balances", + "pallet-message-queue", + "pallet-treasury", + "pallet-utility", + "pallet-xcm", + "parachains-common", + "parity-scale-codec", + "polkadot-runtime-common", + "sp-runtime", + "staging-xcm", + "staging-xcm-executor", + "testnet-parachains-constants", + "westend-runtime", + "westend-runtime-constants", + "westend-system-emulated-network", +] + [[package]] name = "collectives-westend-runtime" version = "3.0.0" @@ -3635,15 +3621,6 @@ dependencies = [ "subtle 2.5.0", ] -[[package]] -name = "ctr" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" -dependencies = [ - "cipher 0.3.0", -] - [[package]] name = "ctr" version = "0.9.2" @@ -6354,16 +6331,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "ghash" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" -dependencies = [ - "opaque-debug 0.3.0", - "polyval 0.5.3", -] - [[package]] name = "ghash" version = "0.5.0" @@ -6371,7 +6338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ "opaque-debug 0.3.0", - "polyval 0.6.1", + "polyval", ] [[package]] @@ -7346,6 +7313,7 @@ dependencies = [ "node-primitives", "pallet-alliance", "pallet-asset-conversion", + "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", @@ -7535,9 +7503,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libflate" @@ -8506,6 +8474,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "minimal-template" +version = "0.0.0" +dependencies = [ + "docify", + "minimal-template-node", + "minimal-template-runtime", + "pallet-minimal-template", + "polkadot-sdk-docs", + "polkadot-sdk-frame", + "simple-mermaid", +] + [[package]] name = "minimal-template-node" version = "0.0.0" @@ -9497,6 +9478,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "log", "pallet-assets", "pallet-balances", "parity-scale-codec", @@ -9510,6 +9492,27 @@ dependencies = [ "sp-std 14.0.0", ] +[[package]] +name = "pallet-asset-conversion-ops" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-asset-conversion", + "pallet-assets", + "pallet-balances", + "parity-scale-codec", + "primitive-types", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", +] + [[package]] name = "pallet-asset-conversion-tx-payment" version = "10.0.0" @@ -9569,7 +9572,7 @@ dependencies = [ [[package]] name = "pallet-assets" -version = "29.0.0" +version = "29.1.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9934,7 +9937,9 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "log", "parity-scale-codec", + "pretty_assertions", "scale-info", "sp-api", "sp-arithmetic", @@ -11713,7 +11718,6 @@ dependencies = [ "polkadot-primitives", "polkadot-runtime-common", "scale-info", - "sp-core", "sp-io", "sp-runtime", "sp-std 14.0.0", @@ -11967,7 +11971,7 @@ checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" dependencies = [ "bitcoin_hashes 0.13.0", "rand 0.8.5", - "rand_core 0.6.4", + "rand_core 0.5.1", "serde", "unicode-normalization", ] @@ -12954,6 +12958,7 @@ dependencies = [ "polkadot-overseer", "polkadot-primitives", "polkadot-primitives-test-helpers", + "polkadot-subsystem-bench", "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", @@ -13239,7 +13244,6 @@ dependencies = [ "slotmap", "sp-core", "sp-maybe-compressed-blob", - "sp-wasm-interface 20.0.0", "tempfile", "test-parachain-adder", "test-parachain-halt", @@ -13276,7 +13280,6 @@ name = "polkadot-node-core-pvf-common" version = "7.0.0" dependencies = [ "assert_matches", - "cfg-if", "cpu-time", "futures", "landlock", @@ -14286,7 +14289,6 @@ dependencies = [ name = "polkadot-test-runtime" version = "1.0.0" dependencies = [ - "bitvec", "frame-election-provider-support", "frame-executive", "frame-support", @@ -14311,16 +14313,12 @@ dependencies = [ "pallet-vesting", "pallet-xcm", "parity-scale-codec", - "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-parachains", - "rustc-hex", "scale-info", "serde", - "serde_derive", "serde_json", - "smallvec", "sp-api", "sp-authority-discovery", "sp-block-builder", @@ -14509,17 +14507,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "poly1305" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" -dependencies = [ - "cpufeatures", - "opaque-debug 0.3.0", - "universal-hash 0.4.0", -] - [[package]] name = "poly1305" version = "0.8.0" @@ -14528,19 +14515,7 @@ checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", "opaque-debug 0.3.0", - "universal-hash 0.5.1", -] - -[[package]] -name = "polyval" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug 0.3.0", - "universal-hash 0.4.0", + "universal-hash", ] [[package]] @@ -14552,7 +14527,7 @@ dependencies = [ "cfg-if", "cpufeatures", "opaque-debug 0.3.0", - "universal-hash 0.5.1", + "universal-hash", ] [[package]] @@ -15770,6 +15745,7 @@ dependencies = [ "pallet-multisig", "pallet-nis", "pallet-offences", + "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", @@ -17400,6 +17376,7 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", + "schnellru", "serde", "serde_json", "sp-api", @@ -17829,7 +17806,7 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" dependencies = [ - "aead 0.5.2", + "aead", "arrayref", "arrayvec 0.7.4", "curve25519-dalek 4.1.2", @@ -18481,7 +18458,7 @@ dependencies = [ "bip39", "blake2-rfc", "bs58 0.5.0", - "chacha20 0.9.1", + "chacha20", "crossbeam-queue", "derive_more", "ed25519-zebra 4.0.3", @@ -18503,7 +18480,7 @@ dependencies = [ "num-traits", "pbkdf2", "pin-project", - "poly1305 0.8.0", + "poly1305", "rand 0.8.5", "rand_chacha 0.3.1", "ruzstd", @@ -18566,16 +18543,16 @@ checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" [[package]] name = "snow" -version = "0.9.3" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9d1425eb528a21de2755c75af4c9b5d57f50a0d4c3b7f1828a4cd03f8ba155" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" dependencies = [ - "aes-gcm 0.9.2", + "aes-gcm", "blake2 0.10.6", "chacha20poly1305", "curve25519-dalek 4.1.2", "rand_core 0.6.4", - "ring 0.16.20", + "ring 0.17.7", "rustc_version 0.4.0", "sha2 0.10.7", "subtle 2.5.0", @@ -19839,7 +19816,7 @@ dependencies = [ name = "sp-statement-store" version = "10.0.0" dependencies = [ - "aes-gcm 0.10.3", + "aes-gcm", "curve25519-dalek 4.1.2", "ed25519-dalek 2.1.0", "hkdf", @@ -21209,11 +21186,8 @@ version = "1.0.0" dependencies = [ "frame-support", "polkadot-primitives", - "polkadot-runtime-common", "smallvec", - "sp-core", "sp-runtime", - "sp-weights", ] [[package]] @@ -21991,6 +21965,12 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tuplex" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "676ac81d5454c4dcf37955d34fa8626ede3490f744b86ca14a7b90168d2a08aa" + [[package]] name = "twox-hash" version = "1.6.3" @@ -22078,16 +22058,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" -[[package]] -name = "universal-hash" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" -dependencies = [ - "generic-array 0.14.7", - "subtle 2.5.0", -] - [[package]] name = "universal-hash" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 460c49f7f37c2d43e021e4bfa8f4263ac1ea3063..1d3f3d8e9ecd137493e72370734378fac9b19a87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,6 +103,7 @@ members = [ "cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend", "cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo", "cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend", + "cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend", "cumulus/parachains/integration-tests/emulated/tests/people/people-rococo", "cumulus/parachains/integration-tests/emulated/tests/people/people-westend", "cumulus/parachains/pallets/collective-content", @@ -300,6 +301,7 @@ members = [ "substrate/frame", "substrate/frame/alliance", "substrate/frame/asset-conversion", + "substrate/frame/asset-conversion/ops", "substrate/frame/asset-rate", "substrate/frame/assets", "substrate/frame/atomic-swap", @@ -511,6 +513,7 @@ members = [ "substrate/utils/substrate-bip39", "substrate/utils/wasm-builder", + "templates/minimal", "templates/minimal/node", "templates/minimal/pallets/template", "templates/minimal/runtime", diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index 67b91a16a302d6214830241082b21c407b04c6d1..74049031afe63cf0d2bc95193541a2b1303a1bbf 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -16,6 +16,7 @@ hash-db = { version = "0.16.0", default-features = false } log = { workspace = true } scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } static_assertions = { version = "1.1", optional = true } +tuplex = { version = "0.1", default-features = false } # Bridge dependencies @@ -82,6 +83,7 @@ std = [ "sp-runtime/std", "sp-std/std", "sp-trie/std", + "tuplex/std", "xcm-builder/std", "xcm/std", ] diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs new file mode 100644 index 0000000000000000000000000000000000000000..2c152aef68226aee36e791a882b5859427a9a33d --- /dev/null +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -0,0 +1,680 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Transaction extension that rejects bridge-related transactions, that include +//! obsolete (duplicated) data or do not pass some additional pallet-specific +//! checks. + +use crate::{ + extensions::refund_relayer_extension::RefundableParachainId, + messages_call_ext::MessagesCallSubType, +}; +use bp_relayers::ExplicitOrAccountParams; +use bp_runtime::Parachain; +use pallet_bridge_grandpa::{ + BridgedBlockNumber, CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, +}; +use pallet_bridge_parachains::{ + CallSubType as ParachainsCallSubtype, SubmitParachainHeadsHelper, SubmitParachainHeadsInfo, +}; +use pallet_bridge_relayers::Pallet as RelayersPallet; +use sp_runtime::{ + traits::{Get, PhantomData, UniqueSaturatedInto}, + transaction_validity::{TransactionPriority, TransactionValidity, ValidTransactionBuilder}, +}; + +/// A duplication of the `FilterCall` trait. +/// +/// We need this trait in order to be able to implement it for the messages pallet, +/// since the implementation is done outside of the pallet crate. +pub trait BridgeRuntimeFilterCall { + /// Data that may be passed from the validate to `post_dispatch`. + type ToPostDispatch; + /// Called during validation. Needs to checks whether a runtime call, submitted + /// by the `who` is valid. `who` may be `None` if transaction is not signed + /// by a regular account. + fn validate(who: &AccountId, call: &Call) -> (Self::ToPostDispatch, TransactionValidity); + /// Called after transaction is dispatched. + fn post_dispatch(_who: &AccountId, _has_failed: bool, _to_post_dispatch: Self::ToPostDispatch) { + } +} + +/// Wrapper for the bridge GRANDPA pallet that checks calls for obsolete submissions +/// and also boosts transaction priority if it has submitted by registered relayer. +/// The boost is computed as +/// `(BundledHeaderNumber - 1 - BestFinalizedHeaderNumber) * Priority::get()`. +/// The boost is only applied if submitter has active registration in the relayers +/// pallet. +pub struct CheckAndBoostBridgeGrandpaTransactions( + PhantomData<(T, I, Priority, SlashAccount)>, +); + +impl, SlashAccount: Get> + BridgeRuntimeFilterCall + for CheckAndBoostBridgeGrandpaTransactions +where + T: pallet_bridge_relayers::Config + pallet_bridge_grandpa::Config, + T::RuntimeCall: GrandpaCallSubType, +{ + // bridged header number, bundled in transaction + type ToPostDispatch = Option>; + + fn validate( + who: &T::AccountId, + call: &T::RuntimeCall, + ) -> (Self::ToPostDispatch, TransactionValidity) { + match GrandpaCallSubType::::check_obsolete_submit_finality_proof(call) { + Ok(Some(our_tx)) => { + let to_post_dispatch = Some(our_tx.base.block_number); + let total_priority_boost = + compute_priority_boost::(who, our_tx.improved_by); + ( + to_post_dispatch, + ValidTransactionBuilder::default().priority(total_priority_boost).build(), + ) + }, + Ok(None) => (None, ValidTransactionBuilder::default().build()), + Err(e) => (None, Err(e)), + } + } + + fn post_dispatch( + relayer: &T::AccountId, + has_failed: bool, + bundled_block_number: Self::ToPostDispatch, + ) { + // we are only interested in associated pallet submissions + let Some(bundled_block_number) = bundled_block_number else { return }; + // we are only interested in failed or unneeded transactions + let has_failed = + has_failed || !SubmitFinalityProofHelper::::was_successful(bundled_block_number); + + if !has_failed { + return + } + + // let's slash registered relayer + RelayersPallet::::slash_and_deregister( + relayer, + ExplicitOrAccountParams::Explicit(SlashAccount::get()), + ); + } +} + +/// Wrapper for the bridge parachains pallet that checks calls for obsolete submissions +/// and also boosts transaction priority if it has submitted by registered relayer. +/// The boost is computed as +/// `(BundledHeaderNumber - 1 - BestKnownHeaderNumber) * Priority::get()`. +/// The boost is only applied if submitter has active registration in the relayers +/// pallet. +pub struct CheckAndBoostBridgeParachainsTransactions( + PhantomData<(T, RefPara, Priority, SlashAccount)>, +); + +impl, SlashAccount: Get> + BridgeRuntimeFilterCall + for CheckAndBoostBridgeParachainsTransactions +where + T: pallet_bridge_relayers::Config + pallet_bridge_parachains::Config, + RefPara: RefundableParachainId, + T::RuntimeCall: ParachainsCallSubtype, +{ + // bridged header number, bundled in transaction + type ToPostDispatch = Option; + + fn validate( + who: &T::AccountId, + call: &T::RuntimeCall, + ) -> (Self::ToPostDispatch, TransactionValidity) { + match ParachainsCallSubtype::::check_obsolete_submit_parachain_heads( + call, + ) { + Ok(Some(our_tx)) if our_tx.base.para_id.0 == RefPara::BridgedChain::PARACHAIN_ID => { + let to_post_dispatch = Some(our_tx.base); + let total_priority_boost = + compute_priority_boost::(&who, our_tx.improved_by); + ( + to_post_dispatch, + ValidTransactionBuilder::default().priority(total_priority_boost).build(), + ) + }, + Ok(_) => (None, ValidTransactionBuilder::default().build()), + Err(e) => (None, Err(e)), + } + } + + fn post_dispatch(relayer: &T::AccountId, has_failed: bool, maybe_update: Self::ToPostDispatch) { + // we are only interested in associated pallet submissions + let Some(update) = maybe_update else { return }; + // we are only interested in failed or unneeded transactions + let has_failed = has_failed || + !SubmitParachainHeadsHelper::::was_successful(&update); + + if !has_failed { + return + } + + // let's slash registered relayer + RelayersPallet::::slash_and_deregister( + relayer, + ExplicitOrAccountParams::Explicit(SlashAccount::get()), + ); + } +} + +impl BridgeRuntimeFilterCall + for pallet_bridge_grandpa::Pallet +where + T: pallet_bridge_grandpa::Config, + T::RuntimeCall: GrandpaCallSubType, +{ + type ToPostDispatch = (); + fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) { + ( + (), + GrandpaCallSubType::::check_obsolete_submit_finality_proof(call) + .and_then(|_| ValidTransactionBuilder::default().build()), + ) + } +} + +impl BridgeRuntimeFilterCall + for pallet_bridge_parachains::Pallet +where + T: pallet_bridge_parachains::Config, + T::RuntimeCall: ParachainsCallSubtype, +{ + type ToPostDispatch = (); + fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) { + ( + (), + ParachainsCallSubtype::::check_obsolete_submit_parachain_heads(call) + .and_then(|_| ValidTransactionBuilder::default().build()), + ) + } +} + +impl, I: 'static> + BridgeRuntimeFilterCall for pallet_bridge_messages::Pallet +where + T::RuntimeCall: MessagesCallSubType, +{ + type ToPostDispatch = (); + /// Validate messages in order to avoid "mining" messages delivery and delivery confirmation + /// transactions, that are delivering outdated messages/confirmations. Without this validation, + /// even honest relayers may lose their funds if there are multiple relays running and + /// submitting the same messages/confirmations. + fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) { + ((), call.check_obsolete_call()) + } +} + +/// Computes priority boost that improved known header by `improved_by` +fn compute_priority_boost( + relayer: &T::AccountId, + improved_by: N, +) -> TransactionPriority +where + T: pallet_bridge_relayers::Config, + N: UniqueSaturatedInto, + Priority: Get, +{ + // we only boost priority if relayer has staked required balance + let is_relayer_registration_active = RelayersPallet::::is_registration_active(relayer); + // if tx improves by just one, there's no need to bump its priority + let improved_by: TransactionPriority = improved_by.unique_saturated_into().saturating_sub(1); + // if relayer is registered, for every skipped header we improve by `Priority` + let boost_per_header = if is_relayer_registration_active { Priority::get() } else { 0 }; + improved_by.saturating_mul(boost_per_header) +} + +/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension. +/// +/// ## Example +/// +/// ```nocompile +/// generate_bridge_reject_obsolete_headers_and_messages!{ +/// Call, AccountId +/// BridgeRococoGrandpa, BridgeRococoMessages, +/// BridgeRococoParachains +/// } +/// ``` +/// +/// The goal of this extension is to avoid "mining" transactions that provide outdated bridged +/// headers and messages. Without that extension, even honest relayers may lose their funds if +/// there are multiple relays running and submitting the same information. +#[macro_export] +macro_rules! generate_bridge_reject_obsolete_headers_and_messages { + ($call:ty, $account_id:ty, $($filter_call:ty),*) => { + #[derive(Clone, codec::Decode, Default, codec::Encode, Eq, PartialEq, sp_runtime::RuntimeDebug, scale_info::TypeInfo)] + pub struct BridgeRejectObsoleteHeadersAndMessages; + impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteHeadersAndMessages { + const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages"; + type AccountId = $account_id; + type Call = $call; + type AdditionalSigned = (); + type Pre = ( + $account_id, + ( $( + <$filter_call as $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall< + $account_id, + $call, + >>::ToPostDispatch, + )* ), + ); + + fn additional_signed(&self) -> sp_std::result::Result< + (), + sp_runtime::transaction_validity::TransactionValidityError, + > { + Ok(()) + } + + #[allow(unused_variables)] + fn validate( + &self, + who: &Self::AccountId, + call: &Self::Call, + _info: &sp_runtime::traits::DispatchInfoOf, + _len: usize, + ) -> sp_runtime::transaction_validity::TransactionValidity { + let tx_validity = sp_runtime::transaction_validity::ValidTransaction::default(); + let to_prepare = (); + $( + let (from_validate, call_filter_validity) = < + $filter_call as + $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall< + Self::AccountId, + $call, + >>::validate(&who, call); + let tx_validity = tx_validity.combine_with(call_filter_validity?); + )* + Ok(tx_validity) + } + + #[allow(unused_variables)] + fn pre_dispatch( + self, + relayer: &Self::AccountId, + call: &Self::Call, + info: &sp_runtime::traits::DispatchInfoOf, + len: usize, + ) -> Result { + use tuplex::PushBack; + let to_post_dispatch = (); + $( + let (from_validate, call_filter_validity) = < + $filter_call as + $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall< + $account_id, + $call, + >>::validate(&relayer, call); + let _ = call_filter_validity?; + let to_post_dispatch = to_post_dispatch.push_back(from_validate); + )* + Ok((relayer.clone(), to_post_dispatch)) + } + + #[allow(unused_variables)] + fn post_dispatch( + to_post_dispatch: Option, + info: &sp_runtime::traits::DispatchInfoOf, + post_info: &sp_runtime::traits::PostDispatchInfoOf, + len: usize, + result: &sp_runtime::DispatchResult, + ) -> Result<(), sp_runtime::transaction_validity::TransactionValidityError> { + use tuplex::PopFront; + let Some((relayer, to_post_dispatch)) = to_post_dispatch else { return Ok(()) }; + let has_failed = result.is_err(); + $( + let (item, to_post_dispatch) = to_post_dispatch.pop_front(); + < + $filter_call as + $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall< + $account_id, + $call, + >>::post_dispatch(&relayer, has_failed, item); + )* + Ok(()) + } + } + }; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + extensions::refund_relayer_extension::{ + tests::{ + initialize_environment, relayer_account_at_this_chain, + submit_parachain_head_call_ex, submit_relay_header_call_ex, + }, + RefundableParachain, + }, + mock::*, + }; + use bp_polkadot_core::parachains::ParaId; + use bp_runtime::HeaderId; + use frame_support::{assert_err, assert_ok}; + use sp_runtime::{ + traits::{ConstU64, SignedExtension}, + transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, + DispatchError, + }; + + pub struct MockCall { + data: u32, + } + + impl sp_runtime::traits::Dispatchable for MockCall { + type RuntimeOrigin = u64; + type Config = (); + type Info = (); + type PostInfo = (); + + fn dispatch( + self, + _origin: Self::RuntimeOrigin, + ) -> sp_runtime::DispatchResultWithInfo { + unimplemented!() + } + } + + pub struct FirstFilterCall; + impl FirstFilterCall { + fn post_dispatch_called_with(success: bool) { + frame_support::storage::unhashed::put(&[1], &success); + } + + fn verify_post_dispatch_called_with(success: bool) { + assert_eq!(frame_support::storage::unhashed::get::(&[1]), Some(success)); + } + } + + impl BridgeRuntimeFilterCall for FirstFilterCall { + type ToPostDispatch = u64; + fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) { + if call.data <= 1 { + return (1, InvalidTransaction::Custom(1).into()) + } + + (1, Ok(ValidTransaction { priority: 1, ..Default::default() })) + } + + fn post_dispatch(_who: &u64, has_failed: bool, to_post_dispatch: Self::ToPostDispatch) { + Self::post_dispatch_called_with(!has_failed); + assert_eq!(to_post_dispatch, 1); + } + } + + pub struct SecondFilterCall; + + impl SecondFilterCall { + fn post_dispatch_called_with(success: bool) { + frame_support::storage::unhashed::put(&[2], &success); + } + + fn verify_post_dispatch_called_with(success: bool) { + assert_eq!(frame_support::storage::unhashed::get::(&[2]), Some(success)); + } + } + + impl BridgeRuntimeFilterCall for SecondFilterCall { + type ToPostDispatch = u64; + fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) { + if call.data <= 2 { + return (2, InvalidTransaction::Custom(2).into()) + } + + (2, Ok(ValidTransaction { priority: 2, ..Default::default() })) + } + + fn post_dispatch(_who: &u64, has_failed: bool, to_post_dispatch: Self::ToPostDispatch) { + Self::post_dispatch_called_with(!has_failed); + assert_eq!(to_post_dispatch, 2); + } + } + + #[test] + fn test_generated_obsolete_extension() { + generate_bridge_reject_obsolete_headers_and_messages!( + MockCall, + u64, + FirstFilterCall, + SecondFilterCall + ); + + run_test(|| { + assert_err!( + BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 1 }, &(), 0), + InvalidTransaction::Custom(1) + ); + assert_err!( + BridgeRejectObsoleteHeadersAndMessages.pre_dispatch( + &42, + &MockCall { data: 1 }, + &(), + 0 + ), + InvalidTransaction::Custom(1) + ); + + assert_err!( + BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 2 }, &(), 0), + InvalidTransaction::Custom(2) + ); + assert_err!( + BridgeRejectObsoleteHeadersAndMessages.pre_dispatch( + &42, + &MockCall { data: 2 }, + &(), + 0 + ), + InvalidTransaction::Custom(2) + ); + + assert_eq!( + BridgeRejectObsoleteHeadersAndMessages + .validate(&42, &MockCall { data: 3 }, &(), 0) + .unwrap(), + ValidTransaction { priority: 3, ..Default::default() }, + ); + assert_eq!( + BridgeRejectObsoleteHeadersAndMessages + .pre_dispatch(&42, &MockCall { data: 3 }, &(), 0) + .unwrap(), + (42, (1, 2)), + ); + + // when post_dispatch is called with `Ok(())`, it is propagated to all "nested" + // extensions + assert_ok!(BridgeRejectObsoleteHeadersAndMessages::post_dispatch( + Some((0, (1, 2))), + &(), + &(), + 0, + &Ok(()) + )); + FirstFilterCall::verify_post_dispatch_called_with(true); + SecondFilterCall::verify_post_dispatch_called_with(true); + + // when post_dispatch is called with `Err(())`, it is propagated to all "nested" + // extensions + assert_ok!(BridgeRejectObsoleteHeadersAndMessages::post_dispatch( + Some((0, (1, 2))), + &(), + &(), + 0, + &Err(DispatchError::BadOrigin) + )); + FirstFilterCall::verify_post_dispatch_called_with(false); + SecondFilterCall::verify_post_dispatch_called_with(false); + }); + } + + frame_support::parameter_types! { + pub SlashDestination: ThisChainAccountId = 42; + } + + type BridgeGrandpaWrapper = + CheckAndBoostBridgeGrandpaTransactions, SlashDestination>; + + #[test] + fn grandpa_wrapper_does_not_boost_extensions_for_unregistered_relayer() { + run_test(|| { + initialize_environment(100, 100, 100); + + let priority_boost = BridgeGrandpaWrapper::validate( + &relayer_account_at_this_chain(), + &submit_relay_header_call_ex(200), + ) + .1 + .unwrap() + .priority; + assert_eq!(priority_boost, 0); + }) + } + + #[test] + fn grandpa_wrapper_boosts_extensions_for_registered_relayer() { + run_test(|| { + initialize_environment(100, 100, 100); + BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) + .unwrap(); + + let priority_boost = BridgeGrandpaWrapper::validate( + &relayer_account_at_this_chain(), + &submit_relay_header_call_ex(200), + ) + .1 + .unwrap() + .priority; + assert_eq!(priority_boost, 99_000); + }) + } + + #[test] + fn grandpa_wrapper_slashes_registered_relayer_if_transaction_fails() { + run_test(|| { + initialize_environment(100, 100, 100); + BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) + .unwrap(); + + assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + BridgeGrandpaWrapper::post_dispatch(&relayer_account_at_this_chain(), true, Some(150)); + assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + }) + } + + #[test] + fn grandpa_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() { + run_test(|| { + initialize_environment(100, 100, 100); + BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) + .unwrap(); + + assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + BridgeGrandpaWrapper::post_dispatch(&relayer_account_at_this_chain(), false, Some(100)); + assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + }) + } + + type BridgeParachainsWrapper = CheckAndBoostBridgeParachainsTransactions< + TestRuntime, + RefundableParachain<(), BridgedUnderlyingParachain>, + ConstU64<1_000>, + SlashDestination, + >; + + #[test] + fn parachains_wrapper_does_not_boost_extensions_for_unregistered_relayer() { + run_test(|| { + initialize_environment(100, 100, 100); + + let priority_boost = BridgeParachainsWrapper::validate( + &relayer_account_at_this_chain(), + &submit_parachain_head_call_ex(200), + ) + .1 + .unwrap() + .priority; + assert_eq!(priority_boost, 0); + }) + } + + #[test] + fn parachains_wrapper_boosts_extensions_for_registered_relayer() { + run_test(|| { + initialize_environment(100, 100, 100); + BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) + .unwrap(); + + let priority_boost = BridgeParachainsWrapper::validate( + &relayer_account_at_this_chain(), + &submit_parachain_head_call_ex(200), + ) + .1 + .unwrap() + .priority; + assert_eq!(priority_boost, 99_000); + }) + } + + #[test] + fn parachains_wrapper_slashes_registered_relayer_if_transaction_fails() { + run_test(|| { + initialize_environment(100, 100, 100); + BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) + .unwrap(); + + assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + BridgeParachainsWrapper::post_dispatch( + &relayer_account_at_this_chain(), + true, + Some(SubmitParachainHeadsInfo { + at_relay_block: HeaderId(150, Default::default()), + para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), + para_head_hash: [150u8; 32].into(), + is_free_execution_expected: false, + }), + ); + assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + }) + } + + #[test] + fn parachains_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() { + run_test(|| { + initialize_environment(100, 100, 100); + BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) + .unwrap(); + + assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + BridgeParachainsWrapper::post_dispatch( + &relayer_account_at_this_chain(), + false, + Some(SubmitParachainHeadsInfo { + at_relay_block: HeaderId(100, Default::default()), + para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), + para_head_hash: [100u8; 32].into(), + is_free_execution_expected: false, + }), + ); + assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + }) + } +} diff --git a/bridges/bin/runtime-common/src/extensions/mod.rs b/bridges/bin/runtime-common/src/extensions/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..3f1b506aaae3ef66fe6f44379258356a2074464c --- /dev/null +++ b/bridges/bin/runtime-common/src/extensions/mod.rs @@ -0,0 +1,21 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Bridge-specific transaction extensions. + +pub mod check_obsolete_extension; +pub mod priority_calculator; +pub mod refund_relayer_extension; diff --git a/bridges/bin/runtime-common/src/extensions/priority_calculator.rs b/bridges/bin/runtime-common/src/extensions/priority_calculator.rs new file mode 100644 index 0000000000000000000000000000000000000000..92810290f95e77a7fdc04cafaa1e6ab290e1661a --- /dev/null +++ b/bridges/bin/runtime-common/src/extensions/priority_calculator.rs @@ -0,0 +1,407 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Bridge transaction priority calculator. +//! +//! We want to prioritize message delivery transactions with more messages over +//! transactions with less messages. That's because we reject delivery transactions +//! if it contains already delivered message. And if some transaction delivers +//! single message with nonce `N`, then the transaction with nonces `N..=N+100` will +//! be rejected. This can lower bridge throughput down to one message per block. + +use frame_support::traits::Get; +use sp_runtime::transaction_validity::TransactionPriority; + +// reexport everything from `integrity_tests` module +#[allow(unused_imports)] +pub use integrity_tests::*; + +/// We'll deal with different bridge items here - messages, headers, ... +/// To avoid being too verbose with generic code, let's just define a separate alias. +pub type ItemCount = u64; + +/// Compute priority boost for transaction that brings given number of bridge +/// items (messages, headers, ...), when every additional item adds `PriorityBoostPerItem` +/// to transaction priority. +pub fn compute_priority_boost(n_items: ItemCount) -> TransactionPriority +where + PriorityBoostPerItem: Get, +{ + // we don't want any boost for transaction with single (additional) item => minus one + PriorityBoostPerItem::get().saturating_mul(n_items.saturating_sub(1)) +} + +#[cfg(not(feature = "integrity-test"))] +mod integrity_tests {} + +#[cfg(feature = "integrity-test")] +mod integrity_tests { + use super::{compute_priority_boost, ItemCount}; + use crate::extensions::refund_relayer_extension::RefundableParachainId; + + use bp_messages::MessageNonce; + use bp_runtime::PreComputedSize; + use frame_support::{ + dispatch::{DispatchClass, DispatchInfo, Pays, PostDispatchInfo}, + traits::Get, + }; + use pallet_transaction_payment::OnChargeTransaction; + use sp_runtime::{ + traits::{Dispatchable, UniqueSaturatedInto, Zero}, + transaction_validity::TransactionPriority, + FixedPointOperand, SaturatedConversion, Saturating, + }; + + type BalanceOf = + <::OnChargeTransaction as OnChargeTransaction< + T, + >>::Balance; + + /// Ensures that the value of `PriorityBoostPerItem` matches the value of + /// `tip_boost_per_item`. + /// + /// We want two transactions, `TX1` with `N` items and `TX2` with `N+1` items, have almost + /// the same priority if we'll add `tip_boost_per_item` tip to the `TX1`. We want to be sure + /// that if we add plain `PriorityBoostPerItem` priority to `TX1`, the priority will be close + /// to `TX2` as well. + fn ensure_priority_boost_is_sane( + param_name: &str, + max_items: ItemCount, + tip_boost_per_item: Balance, + estimate_priority: impl Fn(ItemCount, Balance) -> TransactionPriority, + ) where + PriorityBoostPerItem: Get, + ItemCount: UniqueSaturatedInto, + Balance: FixedPointOperand + Zero, + { + let priority_boost_per_item = PriorityBoostPerItem::get(); + for n_items in 1..=max_items { + let base_priority = estimate_priority(n_items, Zero::zero()); + let priority_boost = compute_priority_boost::(n_items); + let priority_with_boost = base_priority + .checked_add(priority_boost) + .expect("priority overflow: try lowering `max_items` or `tip_boost_per_item`?"); + + let tip = tip_boost_per_item.saturating_mul((n_items - 1).unique_saturated_into()); + let priority_with_tip = estimate_priority(1, tip); + + const ERROR_MARGIN: TransactionPriority = 5; // 5% + if priority_with_boost.abs_diff(priority_with_tip).saturating_mul(100) / + priority_with_tip > + ERROR_MARGIN + { + panic!( + "The {param_name} value ({}) must be fixed to: {}", + priority_boost_per_item, + compute_priority_boost_per_item( + max_items, + tip_boost_per_item, + estimate_priority + ), + ); + } + } + } + + /// Compute priority boost that we give to bridge transaction for every + /// additional bridge item. + #[cfg(feature = "integrity-test")] + fn compute_priority_boost_per_item( + max_items: ItemCount, + tip_boost_per_item: Balance, + estimate_priority: impl Fn(ItemCount, Balance) -> TransactionPriority, + ) -> TransactionPriority + where + ItemCount: UniqueSaturatedInto, + Balance: FixedPointOperand + Zero, + { + // estimate priority of transaction that delivers one item and has large tip + let small_with_tip_priority = + estimate_priority(1, tip_boost_per_item.saturating_mul(max_items.saturated_into())); + // estimate priority of transaction that delivers maximal number of items, but has no tip + let large_without_tip_priority = estimate_priority(max_items, Zero::zero()); + + small_with_tip_priority + .saturating_sub(large_without_tip_priority) + .saturating_div(max_items - 1) + } + + /// Computations, specific to bridge relay chains transactions. + pub mod per_relay_header { + use super::*; + + use bp_header_chain::{ + max_expected_submit_finality_proof_arguments_size, ChainWithGrandpa, + }; + use pallet_bridge_grandpa::WeightInfoExt; + + /// Ensures that the value of `PriorityBoostPerHeader` matches the value of + /// `tip_boost_per_header`. + /// + /// We want two transactions, `TX1` with `N` headers and `TX2` with `N+1` headers, have + /// almost the same priority if we'll add `tip_boost_per_header` tip to the `TX1`. We want + /// to be sure that if we add plain `PriorityBoostPerHeader` priority to `TX1`, the priority + /// will be close to `TX2` as well. + pub fn ensure_priority_boost_is_sane( + tip_boost_per_header: BalanceOf, + ) where + Runtime: + pallet_transaction_payment::Config + pallet_bridge_grandpa::Config, + GrandpaInstance: 'static, + PriorityBoostPerHeader: Get, + Runtime::RuntimeCall: Dispatchable, + BalanceOf: Send + Sync + FixedPointOperand, + { + // the meaning of `max_items` here is different when comparing with message + // transactions - with messages we have a strict limit on maximal number of + // messages we can fit into a single transaction. With headers, current best + // header may be improved by any "number of items". But this number is only + // used to verify priority boost, so it should be fine to select this arbitrary + // value - it SHALL NOT affect any value, it just adds more tests for the value. + let maximal_improved_by = 4_096; + super::ensure_priority_boost_is_sane::>( + "PriorityBoostPerRelayHeader", + maximal_improved_by, + tip_boost_per_header, + |_n_headers, tip| { + estimate_relay_header_submit_transaction_priority::( + tip, + ) + }, + ); + } + + /// Estimate relay header delivery transaction priority. + #[cfg(feature = "integrity-test")] + fn estimate_relay_header_submit_transaction_priority( + tip: BalanceOf, + ) -> TransactionPriority + where + Runtime: + pallet_transaction_payment::Config + pallet_bridge_grandpa::Config, + GrandpaInstance: 'static, + Runtime::RuntimeCall: Dispatchable, + BalanceOf: Send + Sync + FixedPointOperand, + { + // just an estimation of extra transaction bytes that are added to every transaction + // (including signature, signed extensions extra and etc + in our case it includes + // all call arguments except the proof itself) + let base_tx_size = 512; + // let's say we are relaying largest relay chain headers + let tx_call_size = max_expected_submit_finality_proof_arguments_size::< + Runtime::BridgedChain, + >(true, Runtime::BridgedChain::MAX_AUTHORITIES_COUNT * 2 / 3 + 1); + + // finally we are able to estimate transaction size and weight + let transaction_size = base_tx_size.saturating_add(tx_call_size); + let transaction_weight = Runtime::WeightInfo::submit_finality_proof_weight( + Runtime::BridgedChain::MAX_AUTHORITIES_COUNT * 2 / 3 + 1, + Runtime::BridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY, + ); + + pallet_transaction_payment::ChargeTransactionPayment::::get_priority( + &DispatchInfo { + weight: transaction_weight, + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }, + transaction_size as _, + tip, + Zero::zero(), + ) + } + } + + /// Computations, specific to bridge parachains transactions. + pub mod per_parachain_header { + use super::*; + + use bp_runtime::Parachain; + use pallet_bridge_parachains::WeightInfoExt; + + /// Ensures that the value of `PriorityBoostPerHeader` matches the value of + /// `tip_boost_per_header`. + /// + /// We want two transactions, `TX1` with `N` headers and `TX2` with `N+1` headers, have + /// almost the same priority if we'll add `tip_boost_per_header` tip to the `TX1`. We want + /// to be sure that if we add plain `PriorityBoostPerHeader` priority to `TX1`, the priority + /// will be close to `TX2` as well. + pub fn ensure_priority_boost_is_sane( + tip_boost_per_header: BalanceOf, + ) where + Runtime: pallet_transaction_payment::Config + + pallet_bridge_parachains::Config, + RefundableParachain: RefundableParachainId, + PriorityBoostPerHeader: Get, + Runtime::RuntimeCall: Dispatchable, + BalanceOf: Send + Sync + FixedPointOperand, + { + // the meaning of `max_items` here is different when comparing with message + // transactions - with messages we have a strict limit on maximal number of + // messages we can fit into a single transaction. With headers, current best + // header may be improved by any "number of items". But this number is only + // used to verify priority boost, so it should be fine to select this arbitrary + // value - it SHALL NOT affect any value, it just adds more tests for the value. + let maximal_improved_by = 4_096; + super::ensure_priority_boost_is_sane::>( + "PriorityBoostPerParachainHeader", + maximal_improved_by, + tip_boost_per_header, + |_n_headers, tip| { + estimate_parachain_header_submit_transaction_priority::< + Runtime, + RefundableParachain, + >(tip) + }, + ); + } + + /// Estimate parachain header delivery transaction priority. + #[cfg(feature = "integrity-test")] + fn estimate_parachain_header_submit_transaction_priority( + tip: BalanceOf, + ) -> TransactionPriority + where + Runtime: pallet_transaction_payment::Config + + pallet_bridge_parachains::Config, + RefundableParachain: RefundableParachainId, + Runtime::RuntimeCall: Dispatchable, + BalanceOf: Send + Sync + FixedPointOperand, + { + // just an estimation of extra transaction bytes that are added to every transaction + // (including signature, signed extensions extra and etc + in our case it includes + // all call arguments except the proof itself) + let base_tx_size = 512; + // let's say we are relaying largest parachain headers and proof takes some more bytes + let tx_call_size = >::WeightInfo::expected_extra_storage_proof_size() + .saturating_add(RefundableParachain::BridgedChain::MAX_HEADER_SIZE); + + // finally we are able to estimate transaction size and weight + let transaction_size = base_tx_size.saturating_add(tx_call_size); + let transaction_weight = >::WeightInfo::submit_parachain_heads_weight( + Runtime::DbWeight::get(), + &PreComputedSize(transaction_size as _), + // just one parachain - all other submissions won't receive any boost + 1, + ); + + pallet_transaction_payment::ChargeTransactionPayment::::get_priority( + &DispatchInfo { + weight: transaction_weight, + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }, + transaction_size as _, + tip, + Zero::zero(), + ) + } + } + + /// Computations, specific to bridge messages transactions. + pub mod per_message { + use super::*; + + use pallet_bridge_messages::WeightInfoExt; + + /// Ensures that the value of `PriorityBoostPerMessage` matches the value of + /// `tip_boost_per_message`. + /// + /// We want two transactions, `TX1` with `N` messages and `TX2` with `N+1` messages, have + /// almost the same priority if we'll add `tip_boost_per_message` tip to the `TX1`. We want + /// to be sure that if we add plain `PriorityBoostPerMessage` priority to `TX1`, the + /// priority will be close to `TX2` as well. + pub fn ensure_priority_boost_is_sane( + tip_boost_per_message: BalanceOf, + ) where + Runtime: pallet_transaction_payment::Config + + pallet_bridge_messages::Config, + MessagesInstance: 'static, + PriorityBoostPerMessage: Get, + Runtime::RuntimeCall: Dispatchable, + BalanceOf: Send + Sync + FixedPointOperand, + { + let maximal_messages_in_delivery_transaction = + Runtime::MaxUnconfirmedMessagesAtInboundLane::get(); + super::ensure_priority_boost_is_sane::>( + "PriorityBoostPerMessage", + maximal_messages_in_delivery_transaction, + tip_boost_per_message, + |n_messages, tip| { + estimate_message_delivery_transaction_priority::( + n_messages, tip, + ) + }, + ); + } + + /// Estimate message delivery transaction priority. + #[cfg(feature = "integrity-test")] + fn estimate_message_delivery_transaction_priority( + messages: MessageNonce, + tip: BalanceOf, + ) -> TransactionPriority + where + Runtime: pallet_transaction_payment::Config + + pallet_bridge_messages::Config, + MessagesInstance: 'static, + Runtime::RuntimeCall: Dispatchable, + BalanceOf: Send + Sync + FixedPointOperand, + { + // just an estimation of extra transaction bytes that are added to every transaction + // (including signature, signed extensions extra and etc + in our case it includes + // all call arguments except the proof itself) + let base_tx_size = 512; + // let's say we are relaying similar small messages and for every message we add more + // trie nodes to the proof (x0.5 because we expect some nodes to be reused) + let estimated_message_size = 512; + // let's say all our messages have the same dispatch weight + let estimated_message_dispatch_weight = + Runtime::WeightInfo::message_dispatch_weight(estimated_message_size); + // messages proof argument size is (for every message) messages size + some additional + // trie nodes. Some of them are reused by different messages, so let's take 2/3 of + // default "overhead" constant + let messages_proof_size = Runtime::WeightInfo::expected_extra_storage_proof_size() + .saturating_mul(2) + .saturating_div(3) + .saturating_add(estimated_message_size) + .saturating_mul(messages as _); + + // finally we are able to estimate transaction size and weight + let transaction_size = base_tx_size.saturating_add(messages_proof_size); + let transaction_weight = Runtime::WeightInfo::receive_messages_proof_weight( + &PreComputedSize(transaction_size as _), + messages as _, + estimated_message_dispatch_weight.saturating_mul(messages), + ); + + pallet_transaction_payment::ChargeTransactionPayment::::get_priority( + &DispatchInfo { + weight: transaction_weight, + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }, + transaction_size as _, + tip, + Zero::zero(), + ) + } + } +} diff --git a/bridges/bin/runtime-common/src/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs similarity index 84% rename from bridges/bin/runtime-common/src/refund_relayer_extension.rs rename to bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 455392a0a277f3520cd7f58150f12e7420d36014..5aa7f1c095d540a4ee5050aeb7d694c98b744683 100644 --- a/bridges/bin/runtime-common/src/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -23,8 +23,8 @@ use crate::messages_call_ext::{ CallHelper as MessagesCallHelper, CallInfo as MessagesCallInfo, MessagesCallSubType, }; use bp_messages::{LaneId, MessageNonce}; -use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; -use bp_runtime::{Chain, Parachain, ParachainIdOf, RangeInclusiveExt, StaticStrProvider}; +use bp_relayers::{ExplicitOrAccountParams, RewardsAccountOwner, RewardsAccountParams}; +use bp_runtime::{Parachain, RangeInclusiveExt, StaticStrProvider}; use codec::{Codec, Decode, Encode}; use frame_support::{ dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo}, @@ -33,8 +33,7 @@ use frame_support::{ CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use pallet_bridge_grandpa::{ - CallSubType as GrandpaCallSubType, Config as GrandpaConfig, SubmitFinalityProofHelper, - SubmitFinalityProofInfo, + CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, SubmitFinalityProofInfo, }; use pallet_bridge_messages::Config as MessagesConfig; use pallet_bridge_parachains::{ @@ -66,20 +65,9 @@ type CallOf = ::RuntimeCall; /// coming from this parachain. pub trait RefundableParachainId { /// The instance of the bridge parachains pallet. - type Instance; + type Instance: 'static; /// The parachain Id. - type Id: Get; -} - -/// Default implementation of `RefundableParachainId`. -pub struct DefaultRefundableParachainId(PhantomData<(Instance, Id)>); - -impl RefundableParachainId for DefaultRefundableParachainId -where - Id: Get, -{ - type Instance = Instance; - type Id = Id; + type BridgedChain: Parachain; } /// Implementation of `RefundableParachainId` for `trait Parachain`. @@ -87,10 +75,11 @@ pub struct RefundableParachain(PhantomData<(Instance, Para)>); impl RefundableParachainId for RefundableParachain where + Instance: 'static, Para: Parachain, { type Instance = Instance; - type Id = ParachainIdOf; + type BridgedChain = Para; } /// Trait identifying a bridged messages lane. A relayer might be refunded for delivering messages @@ -242,17 +231,10 @@ pub enum RelayerAccountAction { /// Everything common among our refund signed extensions. pub trait RefundSignedExtension: 'static + Clone + Codec + sp_std::fmt::Debug + Default + Eq + PartialEq + Send + Sync + TypeInfo -where - >::BridgedChain: - Chain, { /// This chain runtime. - type Runtime: UtilityConfig> - + GrandpaConfig - + MessagesConfig<::Instance> + type Runtime: MessagesConfig<::Instance> + RelayersConfig; - /// Grandpa pallet reference. - type GrandpaInstance: 'static; /// Messages pallet and lane reference. type Msgs: RefundableMessagesLaneId; /// Refund amount calculator. @@ -276,11 +258,13 @@ where call: &CallOf, ) -> Result<&CallOf, TransactionValidityError>; - /// Called from post-dispatch and shall perform additional checks (apart from relay - /// chain finality and messages transaction finality) of given call result. + /// Called from post-dispatch and shall perform additional checks (apart from messages + /// transaction success) of given call result. fn additional_call_result_check( relayer: &AccountIdOf, call_info: &CallInfo, + extra_weight: &mut Weight, + extra_size: &mut u32, ) -> bool; /// Given post-dispatch information, analyze the outcome of relayer call and return @@ -348,35 +332,6 @@ where return slash_relayer_if_delivery_result } - // check if relay chain state has been updated - if let Some(finality_proof_info) = call_info.submit_finality_proof_info() { - if !SubmitFinalityProofHelper::::was_successful( - finality_proof_info.block_number, - ) { - // we only refund relayer if all calls have updated chain state - log::trace!( - target: "runtime::bridge", - "{} via {:?}: relayer {:?} has submitted invalid relay chain finality proof", - Self::Id::STR, - ::Id::get(), - relayer, - ); - return slash_relayer_if_delivery_result - } - - // there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll` - // transaction. If relay chain header is mandatory, the GRANDPA pallet returns - // `Pays::No`, because such transaction is mandatory for operating the bridge. But - // `utility.batchAll` transaction always requires payment. But in both cases we'll - // refund relayer - either explicitly here, or using `Pays::No` if he's choosing - // to submit dedicated transaction. - - // submitter has means to include extra weight/bytes in the `submit_finality_proof` - // call, so let's subtract extra weight/size to avoid refunding for this extra stuff - extra_weight = finality_proof_info.extra_weight; - extra_size = finality_proof_info.extra_size; - } - // Check if the `ReceiveMessagesProof` call delivered at least some of the messages that // it contained. If this happens, we consider the transaction "helpful" and refund it. let msgs_call_info = call_info.messages_call_info(); @@ -391,8 +346,13 @@ where return slash_relayer_if_delivery_result } - // do additional check - if !Self::additional_call_result_check(&relayer, &call_info) { + // do additional checks + if !Self::additional_call_result_check( + &relayer, + &call_info, + &mut extra_weight, + &mut extra_size, + ) { return slash_relayer_if_delivery_result } @@ -468,18 +428,11 @@ where RuntimeDebugNoBound, TypeInfo, )] -pub struct RefundSignedExtensionAdapter(T) -where - >::BridgedChain: - Chain; +pub struct RefundSignedExtensionAdapter(T); impl SignedExtension for RefundSignedExtensionAdapter where - >::BridgedChain: - Chain, CallOf: Dispatchable - + IsSubType, T::Runtime>> - + GrandpaCallSubType + MessagesCallSubType::Instance>, { const IDENTIFIER: &'static str = T::Id::STR; @@ -520,8 +473,9 @@ where } // compute priority boost - let priority_boost = - crate::priority_calculator::compute_priority_boost::(bundled_messages); + let priority_boost = crate::extensions::priority_calculator::compute_priority_boost::< + T::Priority, + >(bundled_messages); let valid_transaction = ValidTransactionBuilder::default().priority(priority_boost); log::trace!( @@ -588,7 +542,10 @@ where ); }, RelayerAccountAction::Slash(relayer, slash_account) => - RelayersPallet::::slash_and_deregister(&relayer, slash_account), + RelayersPallet::::slash_and_deregister( + &relayer, + ExplicitOrAccountParams::Params(slash_account), + ), } Ok(()) @@ -640,6 +597,14 @@ impl RefundSignedExtension for RefundBridgedParachainMessages where Self: 'static + Send + Sync, + RefundBridgedGrandpaMessages< + Runtime, + Runtime::BridgesGrandpaPalletInstance, + Msgs, + Refund, + Priority, + Id, + >: 'static + Send + Sync, Runtime: UtilityConfig> + BoundedBridgeGrandpaConfig + ParachainsConfig @@ -657,7 +622,6 @@ where + MessagesCallSubType, { type Runtime = Runtime; - type GrandpaInstance = Runtime::BridgesGrandpaPalletInstance; type Msgs = Msgs; type Refund = Refund; type Priority = Priority; @@ -683,7 +647,7 @@ where let para_finality_call = calls .next() .transpose()? - .and_then(|c| c.submit_parachain_heads_info_for(Para::Id::get())); + .and_then(|c| c.submit_parachain_heads_info_for(Para::BridgedChain::PARACHAIN_ID)); let relay_finality_call = calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info()); @@ -707,7 +671,26 @@ where Ok(call) } - fn additional_call_result_check(relayer: &Runtime::AccountId, call_info: &CallInfo) -> bool { + fn additional_call_result_check( + relayer: &Runtime::AccountId, + call_info: &CallInfo, + extra_weight: &mut Weight, + extra_size: &mut u32, + ) -> bool { + // check if relay chain state has been updated + let is_grandpa_call_successful = + RefundBridgedGrandpaMessages::< + Runtime, + Runtime::BridgesGrandpaPalletInstance, + Msgs, + Refund, + Priority, + Id, + >::additional_call_result_check(relayer, call_info, extra_weight, extra_size); + if !is_grandpa_call_successful { + return false + } + // check if parachain state has been updated if let Some(para_proof_info) = call_info.submit_parachain_heads_info() { if !SubmitParachainHeadsHelper::::was_successful( @@ -718,7 +701,7 @@ where target: "runtime::bridge", "{} from parachain {} via {:?}: relayer {:?} has submitted invalid parachain finality proof", Id::STR, - Para::Id::get(), + Para::BridgedChain::PARACHAIN_ID, Msgs::Id::get(), relayer, ); @@ -790,7 +773,6 @@ where + MessagesCallSubType, { type Runtime = Runtime; - type GrandpaInstance = GrandpaInstance; type Msgs = Msgs; type Refund = Refund; type Priority = Priority; @@ -832,13 +814,125 @@ where Ok(call) } - fn additional_call_result_check(_relayer: &Runtime::AccountId, _call_info: &CallInfo) -> bool { + fn additional_call_result_check( + relayer: &Runtime::AccountId, + call_info: &CallInfo, + extra_weight: &mut Weight, + extra_size: &mut u32, + ) -> bool { + // check if relay chain state has been updated + if let Some(finality_proof_info) = call_info.submit_finality_proof_info() { + if !SubmitFinalityProofHelper::::was_successful( + finality_proof_info.block_number, + ) { + // we only refund relayer if all calls have updated chain state + log::trace!( + target: "runtime::bridge", + "{} via {:?}: relayer {:?} has submitted invalid relay chain finality proof", + Self::Id::STR, + ::Id::get(), + relayer, + ); + return false + } + + // there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll` + // transaction. If relay chain header is mandatory, the GRANDPA pallet returns + // `Pays::No`, because such transaction is mandatory for operating the bridge. But + // `utility.batchAll` transaction always requires payment. But in both cases we'll + // refund relayer - either explicitly here, or using `Pays::No` if he's choosing + // to submit dedicated transaction. + + // submitter has means to include extra weight/bytes in the `submit_finality_proof` + // call, so let's subtract extra weight/size to avoid refunding for this extra stuff + *extra_weight = (*extra_weight).saturating_add(finality_proof_info.extra_weight); + *extra_size = (*extra_size).saturating_add(finality_proof_info.extra_size); + } + + true + } +} + +/// Transaction extension that refunds a relayer for standalone messages delivery and confirmation +/// transactions. Finality transactions are not refunded. +#[derive( + DefaultNoBound, + CloneNoBound, + Decode, + Encode, + EqNoBound, + PartialEqNoBound, + RuntimeDebugNoBound, + TypeInfo, +)] +#[scale_info(skip_type_params(Runtime, GrandpaInstance, Msgs, Refund, Priority, Id))] +pub struct RefundBridgedMessages( + PhantomData<( + // runtime with `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed + Runtime, + // implementation of `RefundableMessagesLaneId` trait, which specifies the instance of + // the used `pallet-bridge-messages` pallet and the lane within this pallet + Msgs, + // implementation of the `RefundCalculator` trait, that is used to compute refund that + // we give to relayer for his transaction + Refund, + // getter for per-message `TransactionPriority` boost that we give to message + // delivery transactions + Priority, + // the runtime-unique identifier of this signed extension + Id, + )>, +); + +impl RefundSignedExtension + for RefundBridgedMessages +where + Self: 'static + Send + Sync, + Runtime: MessagesConfig + RelayersConfig, + Msgs: RefundableMessagesLaneId, + Refund: RefundCalculator, + Priority: Get, + Id: StaticStrProvider, + CallOf: Dispatchable + + MessagesCallSubType, +{ + type Runtime = Runtime; + type Msgs = Msgs; + type Refund = Refund; + type Priority = Priority; + type Id = Id; + + fn expand_call(call: &CallOf) -> Vec<&CallOf> { + vec![call] + } + + fn parse_and_check_for_obsolete_call( + call: &CallOf, + ) -> Result, TransactionValidityError> { + let call = Self::check_obsolete_parsed_call(call)?; + Ok(call.call_info_for(Msgs::Id::get()).map(CallInfo::Msgs)) + } + + fn check_obsolete_parsed_call( + call: &CallOf, + ) -> Result<&CallOf, TransactionValidityError> { + call.check_obsolete_call()?; + Ok(call) + } + + fn additional_call_result_check( + _relayer: &Runtime::AccountId, + _call_info: &CallInfo, + _extra_weight: &mut Weight, + _extra_size: &mut u32, + ) -> bool { + // everything is checked by the `RefundTransactionExtension` true } } #[cfg(test)] -mod tests { +pub(crate) mod tests { use super::*; use crate::{ messages::{ @@ -850,6 +944,7 @@ mod tests { }, mock::*, }; + use bp_header_chain::StoredHeaderDataBuilder; use bp_messages::{ DeliveredMessages, InboundLaneData, MessageNonce, MessagesOperatingMode, OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState, @@ -875,7 +970,6 @@ mod tests { }; parameter_types! { - TestParachain: u32 = 1000; pub TestLaneId: LaneId = TEST_LANE_ID; pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( TEST_LANE_ID, @@ -891,6 +985,14 @@ mod tests { bp_runtime::generate_static_str_provider!(TestExtension); + type TestMessagesExtensionProvider = RefundBridgedMessages< + TestRuntime, + RefundableMessagesLane<(), TestLaneId>, + ActualFeeRefund, + ConstU64<1>, + StrTestExtension, + >; + type TestMessagesExtension = RefundSignedExtensionAdapter; type TestGrandpaExtensionProvider = RefundBridgedGrandpaMessages< TestRuntime, (), @@ -902,7 +1004,7 @@ mod tests { type TestGrandpaExtension = RefundSignedExtensionAdapter; type TestExtensionProvider = RefundBridgedParachainMessages< TestRuntime, - DefaultRefundableParachainId<(), TestParachain>, + RefundableParachain<(), BridgedUnderlyingParachain>, RefundableMessagesLane<(), TestLaneId>, ActualFeeRefund, ConstU64<1>, @@ -926,7 +1028,7 @@ mod tests { TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get()) } - fn relayer_account_at_this_chain() -> ThisChainAccountId { + pub fn relayer_account_at_this_chain() -> ThisChainAccountId { 0 } @@ -934,7 +1036,7 @@ mod tests { 0 } - fn initialize_environment( + pub fn initialize_environment( best_relay_header_number: RelayBlockNumber, parachain_head_at_relay_header_number: RelayBlockNumber, best_message: MessageNonce, @@ -945,8 +1047,12 @@ mod tests { StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(), ); pallet_bridge_grandpa::BestFinalized::::put(best_relay_header); + pallet_bridge_grandpa::ImportedHeaders::::insert( + best_relay_header.hash(), + bp_test_utils::test_header::(0).build(), + ); - let para_id = ParaId(TestParachain::get()); + let para_id = ParaId(BridgedUnderlyingParachain::PARACHAIN_ID); let para_info = ParaInfo { best_head_hash: BestParaHeadHash { at_relay_block_number: parachain_head_at_relay_header_number, @@ -990,7 +1096,7 @@ mod tests { }) } - fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall { + pub fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall { let relay_header = BridgedChainHeader::new( relay_header_number, Default::default(), @@ -1004,6 +1110,7 @@ mod tests { finality_target: Box::new(relay_header), justification: relay_justification, current_set_id: TEST_GRANDPA_SET_ID, + is_free_execution_expected: false, }) } @@ -1013,10 +1120,24 @@ mod tests { RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads { at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()), parachains: vec![( - ParaId(TestParachain::get()), + ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), + [parachain_head_at_relay_header_number as u8; 32].into(), + )], + parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] }, + }) + } + + pub fn submit_parachain_head_call_ex( + parachain_head_at_relay_header_number: RelayBlockNumber, + ) -> RuntimeCall { + RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads_ex { + at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()), + parachains: vec![( + ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), [parachain_head_at_relay_header_number as u8; 32].into(), )], parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] }, + is_free_execution_expected: false, }) } @@ -1147,7 +1268,7 @@ mod tests { RuntimeCall::Utility(UtilityCall::batch_all { calls: vec![ submit_relay_header_call_ex(relay_header_number), - submit_parachain_head_call(parachain_head_at_relay_header_number), + submit_parachain_head_call_ex(parachain_head_at_relay_header_number), message_delivery_call(best_message), ], }) @@ -1175,7 +1296,7 @@ mod tests { RuntimeCall::Utility(UtilityCall::batch_all { calls: vec![ submit_relay_header_call_ex(relay_header_number), - submit_parachain_head_call(parachain_head_at_relay_header_number), + submit_parachain_head_call_ex(parachain_head_at_relay_header_number), message_confirmation_call(best_message), ], }) @@ -1190,11 +1311,14 @@ mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_mandatory: false, + is_free_execution_expected: false, }, SubmitParachainHeadsInfo { - at_relay_block_number: 200, - para_id: ParaId(TestParachain::get()), + at_relay_block: HeaderId(200, [0u8; 32].into()), + para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), para_head_hash: [200u8; 32].into(), + is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo { base: BaseMessagesProofInfo { @@ -1227,11 +1351,14 @@ mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_mandatory: false, + is_free_execution_expected: false, }, SubmitParachainHeadsInfo { - at_relay_block_number: 200, - para_id: ParaId(TestParachain::get()), + at_relay_block: HeaderId(200, [0u8; 32].into()), + para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), para_head_hash: [200u8; 32].into(), + is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo( BaseMessagesProofInfo { @@ -1260,6 +1387,8 @@ mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_mandatory: false, + is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo { base: BaseMessagesProofInfo { @@ -1292,6 +1421,8 @@ mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_mandatory: false, + is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo( BaseMessagesProofInfo { @@ -1316,9 +1447,10 @@ mod tests { relayer: relayer_account_at_this_chain(), call_info: CallInfo::ParachainFinalityAndMsgs( SubmitParachainHeadsInfo { - at_relay_block_number: 200, - para_id: ParaId(TestParachain::get()), + at_relay_block: HeaderId(200, [0u8; 32].into()), + para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), para_head_hash: [200u8; 32].into(), + is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo { base: BaseMessagesProofInfo { @@ -1340,9 +1472,10 @@ mod tests { relayer: relayer_account_at_this_chain(), call_info: CallInfo::ParachainFinalityAndMsgs( SubmitParachainHeadsInfo { - at_relay_block_number: 200, - para_id: ParaId(TestParachain::get()), + at_relay_block: HeaderId(200, [0u8; 32].into()), + para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), para_head_hash: [200u8; 32].into(), + is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo( BaseMessagesProofInfo { @@ -1417,8 +1550,14 @@ mod tests { extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0) } - fn run_validate_ignore_priority(call: RuntimeCall) -> TransactionValidity { - run_validate(call).map(|mut tx| { + fn run_messages_validate(call: RuntimeCall) -> TransactionValidity { + let extension: TestMessagesExtension = + RefundSignedExtensionAdapter(RefundBridgedMessages(PhantomData)); + extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0) + } + + fn ignore_priority(tx: TransactionValidity) -> TransactionValidity { + tx.map(|mut tx| { tx.priority = 0; tx }) @@ -1440,6 +1579,14 @@ mod tests { extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0) } + fn run_messages_pre_dispatch( + call: RuntimeCall, + ) -> Result>, TransactionValidityError> { + let extension: TestMessagesExtension = + RefundSignedExtensionAdapter(RefundBridgedMessages(PhantomData)); + extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0) + } + fn dispatch_info() -> DispatchInfo { DispatchInfo { weight: Weight::from_parts( @@ -1498,40 +1645,48 @@ mod tests { Balances::set_balance(&relayer_account_at_this_chain(), ExistentialDeposit::get()); // message delivery is failing - assert_eq!(run_validate(message_delivery_call(200)), Ok(Default::default()),); - assert_eq!( - run_validate(parachain_finality_and_delivery_batch_call(200, 200)), - Ok(Default::default()), - ); - assert_eq!( - run_validate(all_finality_and_delivery_batch_call(200, 200, 200)), - Ok(Default::default()), - ); + let fns = [run_validate, run_grandpa_validate, run_messages_validate]; + for f in fns { + assert_eq!(f(message_delivery_call(200)), Ok(Default::default()),); + assert_eq!( + f(parachain_finality_and_delivery_batch_call(200, 200)), + Ok(Default::default()), + ); + assert_eq!( + f(all_finality_and_delivery_batch_call(200, 200, 200)), + Ok(Default::default()), + ); + assert_eq!( + f(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Ok(Default::default()), + ); + } + + // message confirmation validation is passing assert_eq!( - run_validate(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + ignore_priority(run_validate(message_confirmation_call(200))), Ok(Default::default()), ); - // message confirmation validation is passing assert_eq!( - run_validate_ignore_priority(message_confirmation_call(200)), + ignore_priority(run_messages_validate(message_confirmation_call(200))), Ok(Default::default()), ); assert_eq!( - run_validate_ignore_priority(parachain_finality_and_confirmation_batch_call( + ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call( 200, 200 - )), + ))), Ok(Default::default()), ); assert_eq!( - run_validate_ignore_priority(all_finality_and_confirmation_batch_call( + ignore_priority(run_validate(all_finality_and_confirmation_batch_call( 200, 200, 200 - )), + ))), Ok(Default::default()), ); assert_eq!( - run_validate_ignore_priority(all_finality_and_confirmation_batch_call_ex( + ignore_priority(run_validate(all_finality_and_confirmation_batch_call_ex( 200, 200, 200 - )), + ))), Ok(Default::default()), ); }); @@ -1545,25 +1700,28 @@ mod tests { BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) .unwrap(); - let priority_of_100_messages_delivery = - run_validate(message_delivery_call(200)).unwrap().priority; - let priority_of_200_messages_delivery = - run_validate(message_delivery_call(300)).unwrap().priority; - assert!( - priority_of_200_messages_delivery > priority_of_100_messages_delivery, - "Invalid priorities: {} for 200 messages vs {} for 100 messages", - priority_of_200_messages_delivery, - priority_of_100_messages_delivery, - ); + let fns = [run_validate, run_grandpa_validate, run_messages_validate]; + for f in fns { + let priority_of_100_messages_delivery = + f(message_delivery_call(200)).unwrap().priority; + let priority_of_200_messages_delivery = + f(message_delivery_call(300)).unwrap().priority; + assert!( + priority_of_200_messages_delivery > priority_of_100_messages_delivery, + "Invalid priorities: {} for 200 messages vs {} for 100 messages", + priority_of_200_messages_delivery, + priority_of_100_messages_delivery, + ); - let priority_of_100_messages_confirmation = - run_validate(message_confirmation_call(200)).unwrap().priority; - let priority_of_200_messages_confirmation = - run_validate(message_confirmation_call(300)).unwrap().priority; - assert_eq!( - priority_of_100_messages_confirmation, - priority_of_200_messages_confirmation - ); + let priority_of_100_messages_confirmation = + f(message_confirmation_call(200)).unwrap().priority; + let priority_of_200_messages_confirmation = + f(message_confirmation_call(300)).unwrap().priority; + assert_eq!( + priority_of_100_messages_confirmation, + priority_of_200_messages_confirmation + ); + } }); } @@ -1575,23 +1733,24 @@ mod tests { BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) .unwrap(); - let priority_of_max_messages_delivery = run_validate(message_delivery_call( - 100 + MaxUnconfirmedMessagesAtInboundLane::get(), - )) - .unwrap() - .priority; - let priority_of_more_than_max_messages_delivery = run_validate(message_delivery_call( - 100 + MaxUnconfirmedMessagesAtInboundLane::get() + 1, - )) - .unwrap() - .priority; - - assert!( - priority_of_max_messages_delivery > priority_of_more_than_max_messages_delivery, - "Invalid priorities: {} for MAX messages vs {} for MAX+1 messages", - priority_of_max_messages_delivery, - priority_of_more_than_max_messages_delivery, - ); + let fns = [run_validate, run_grandpa_validate, run_messages_validate]; + for f in fns { + let priority_of_max_messages_delivery = + f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get())) + .unwrap() + .priority; + let priority_of_more_than_max_messages_delivery = + f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get() + 1)) + .unwrap() + .priority; + + assert!( + priority_of_max_messages_delivery > priority_of_more_than_max_messages_delivery, + "Invalid priorities: {} for MAX messages vs {} for MAX+1 messages", + priority_of_max_messages_delivery, + priority_of_more_than_max_messages_delivery, + ); + } }); } @@ -1601,45 +1760,54 @@ mod tests { initialize_environment(100, 100, 100); assert_eq!( - run_validate_ignore_priority(message_delivery_call(200)), + ignore_priority(run_validate(message_delivery_call(200))), + Ok(ValidTransaction::default()), + ); + assert_eq!( + ignore_priority(run_validate(message_confirmation_call(200))), + Ok(ValidTransaction::default()), + ); + + assert_eq!( + ignore_priority(run_messages_validate(message_delivery_call(200))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(message_confirmation_call(200)), + ignore_priority(run_messages_validate(message_confirmation_call(200))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(parachain_finality_and_delivery_batch_call(200, 200)), + ignore_priority(run_validate(parachain_finality_and_delivery_batch_call(200, 200))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(parachain_finality_and_confirmation_batch_call( + ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call( 200, 200 - )), + ))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(all_finality_and_delivery_batch_call(200, 200, 200)), + ignore_priority(run_validate(all_finality_and_delivery_batch_call(200, 200, 200))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(all_finality_and_delivery_batch_call_ex( + ignore_priority(run_validate(all_finality_and_delivery_batch_call_ex( 200, 200, 200 - )), + ))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(all_finality_and_confirmation_batch_call( + ignore_priority(run_validate(all_finality_and_confirmation_batch_call( 200, 200, 200 - )), + ))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(all_finality_and_confirmation_batch_call_ex( + ignore_priority(run_validate(all_finality_and_confirmation_batch_call_ex( 200, 200, 200 - )), + ))), Ok(ValidTransaction::default()), ); }); @@ -1929,8 +2097,11 @@ mod tests { RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads { at_relay_block: (100, RelayBlockHash::default()), parachains: vec![ - (ParaId(TestParachain::get()), [1u8; 32].into()), - (ParaId(TestParachain::get() + 1), [1u8; 32].into()), + (ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), [1u8; 32].into()), + ( + ParaId(BridgedUnderlyingParachain::PARACHAIN_ID + 1), + [1u8; 32].into(), + ), ], parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] }, }), @@ -2314,6 +2485,148 @@ mod tests { }); } + #[test] + fn messages_ext_only_parses_standalone_transactions() { + run_test(|| { + initialize_environment(100, 100, 100); + + // relay + parachain + message delivery calls batch is ignored + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_delivery_batch_call(200, 200, 200) + ), + Ok(None), + ); + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_delivery_batch_call_ex(200, 200, 200) + ), + Ok(None), + ); + + // relay + parachain + message confirmation calls batch is ignored + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_confirmation_batch_call(200, 200, 200) + ), + Ok(None), + ); + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_confirmation_batch_call_ex(200, 200, 200) + ), + Ok(None), + ); + + // parachain + message delivery call batch is ignored + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + ¶chain_finality_and_delivery_batch_call(200, 200) + ), + Ok(None), + ); + + // parachain + message confirmation call batch is ignored + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + ¶chain_finality_and_confirmation_batch_call(200, 200) + ), + Ok(None), + ); + + // relay + message delivery call batch is ignored + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_delivery_batch_call(200, 200) + ), + Ok(None), + ); + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_delivery_batch_call_ex(200, 200) + ), + Ok(None), + ); + + // relay + message confirmation call batch is ignored + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_confirmation_batch_call(200, 200) + ), + Ok(None), + ); + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_confirmation_batch_call_ex(200, 200) + ), + Ok(None), + ); + + // message delivery call batch is accepted + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &message_delivery_call(200) + ), + Ok(Some(delivery_pre_dispatch_data().call_info)), + ); + + // message confirmation call batch is accepted + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &message_confirmation_call(200) + ), + Ok(Some(confirmation_pre_dispatch_data().call_info)), + ); + }); + } + + #[test] + fn messages_ext_rejects_calls_with_obsolete_messages() { + run_test(|| { + initialize_environment(100, 100, 100); + + assert_eq!( + run_messages_pre_dispatch(message_delivery_call(100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); + assert_eq!( + run_messages_pre_dispatch(message_confirmation_call(100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); + + assert_eq!( + run_messages_validate(message_delivery_call(100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); + assert_eq!( + run_messages_validate(message_confirmation_call(100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); + }); + } + + #[test] + fn messages_ext_accepts_calls_with_new_messages() { + run_test(|| { + initialize_environment(100, 100, 100); + + assert_eq!( + run_messages_pre_dispatch(message_delivery_call(200)), + Ok(Some(delivery_pre_dispatch_data())), + ); + assert_eq!( + run_messages_pre_dispatch(message_confirmation_call(200)), + Ok(Some(confirmation_pre_dispatch_data())), + ); + + assert_eq!(run_messages_validate(message_delivery_call(200)), Ok(Default::default()),); + assert_eq!( + run_messages_validate(message_confirmation_call(200)), + Ok(Default::default()), + ); + }); + } + #[test] fn grandpa_ext_only_parses_valid_batches() { run_test(|| { diff --git a/bridges/bin/runtime-common/src/lib.rs b/bridges/bin/runtime-common/src/lib.rs index 2722f6f1c6d14f09ab215f8f020f2c449eda4d4b..5679acd6006ccb8540f940f0f90363f902d643f7 100644 --- a/bridges/bin/runtime-common/src/lib.rs +++ b/bridges/bin/runtime-common/src/lib.rs @@ -19,11 +19,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -use crate::messages_call_ext::MessagesCallSubType; -use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType; -use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype; -use sp_runtime::transaction_validity::TransactionValidity; - +pub mod extensions; pub mod messages; pub mod messages_api; pub mod messages_benchmarking; @@ -31,8 +27,6 @@ pub mod messages_call_ext; pub mod messages_generation; pub mod messages_xcm_extension; pub mod parachains_benchmarking; -pub mod priority_calculator; -pub mod refund_relayer_extension; mod mock; @@ -40,184 +34,3 @@ mod mock; pub mod integrity; const LOG_TARGET_BRIDGE_DISPATCH: &str = "runtime::bridge-dispatch"; - -/// A duplication of the `FilterCall` trait. -/// -/// We need this trait in order to be able to implement it for the messages pallet, -/// since the implementation is done outside of the pallet crate. -pub trait BridgeRuntimeFilterCall { - /// Checks if a runtime call is valid. - fn validate(call: &Call) -> TransactionValidity; -} - -impl BridgeRuntimeFilterCall for pallet_bridge_grandpa::Pallet -where - T: pallet_bridge_grandpa::Config, - T::RuntimeCall: GrandpaCallSubType, -{ - fn validate(call: &T::RuntimeCall) -> TransactionValidity { - GrandpaCallSubType::::check_obsolete_submit_finality_proof(call) - } -} - -impl BridgeRuntimeFilterCall - for pallet_bridge_parachains::Pallet -where - T: pallet_bridge_parachains::Config, - T::RuntimeCall: ParachainsCallSubtype, -{ - fn validate(call: &T::RuntimeCall) -> TransactionValidity { - ParachainsCallSubtype::::check_obsolete_submit_parachain_heads(call) - } -} - -impl, I: 'static> BridgeRuntimeFilterCall - for pallet_bridge_messages::Pallet -where - T::RuntimeCall: MessagesCallSubType, -{ - /// Validate messages in order to avoid "mining" messages delivery and delivery confirmation - /// transactions, that are delivering outdated messages/confirmations. Without this validation, - /// even honest relayers may lose their funds if there are multiple relays running and - /// submitting the same messages/confirmations. - fn validate(call: &T::RuntimeCall) -> TransactionValidity { - call.check_obsolete_call() - } -} - -/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension. -/// -/// ## Example -/// -/// ```nocompile -/// generate_bridge_reject_obsolete_headers_and_messages!{ -/// Call, AccountId -/// BridgeRococoGrandpa, BridgeRococoMessages, -/// BridgeRococoParachains -/// } -/// ``` -/// -/// The goal of this extension is to avoid "mining" transactions that provide outdated bridged -/// headers and messages. Without that extension, even honest relayers may lose their funds if -/// there are multiple relays running and submitting the same information. -#[macro_export] -macro_rules! generate_bridge_reject_obsolete_headers_and_messages { - ($call:ty, $account_id:ty, $($filter_call:ty),*) => { - #[derive(Clone, codec::Decode, Default, codec::Encode, Eq, PartialEq, sp_runtime::RuntimeDebug, scale_info::TypeInfo)] - pub struct BridgeRejectObsoleteHeadersAndMessages; - impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteHeadersAndMessages { - const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages"; - type AccountId = $account_id; - type Call = $call; - type AdditionalSigned = (); - type Pre = (); - - fn additional_signed(&self) -> sp_std::result::Result< - (), - sp_runtime::transaction_validity::TransactionValidityError, - > { - Ok(()) - } - - fn validate( - &self, - _who: &Self::AccountId, - call: &Self::Call, - _info: &sp_runtime::traits::DispatchInfoOf, - _len: usize, - ) -> sp_runtime::transaction_validity::TransactionValidity { - let valid = sp_runtime::transaction_validity::ValidTransaction::default(); - $( - let valid = valid - .combine_with(<$filter_call as $crate::BridgeRuntimeFilterCall<$call>>::validate(call)?); - )* - Ok(valid) - } - - fn pre_dispatch( - self, - who: &Self::AccountId, - call: &Self::Call, - info: &sp_runtime::traits::DispatchInfoOf, - len: usize, - ) -> Result { - self.validate(who, call, info, len).map(drop) - } - } - }; -} - -#[cfg(test)] -mod tests { - use crate::BridgeRuntimeFilterCall; - use frame_support::{assert_err, assert_ok}; - use sp_runtime::{ - traits::SignedExtension, - transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, - }; - - pub struct MockCall { - data: u32, - } - - impl sp_runtime::traits::Dispatchable for MockCall { - type RuntimeOrigin = (); - type Config = (); - type Info = (); - type PostInfo = (); - - fn dispatch( - self, - _origin: Self::RuntimeOrigin, - ) -> sp_runtime::DispatchResultWithInfo { - unimplemented!() - } - } - - struct FirstFilterCall; - impl BridgeRuntimeFilterCall for FirstFilterCall { - fn validate(call: &MockCall) -> TransactionValidity { - if call.data <= 1 { - return InvalidTransaction::Custom(1).into() - } - - Ok(ValidTransaction { priority: 1, ..Default::default() }) - } - } - - struct SecondFilterCall; - impl BridgeRuntimeFilterCall for SecondFilterCall { - fn validate(call: &MockCall) -> TransactionValidity { - if call.data <= 2 { - return InvalidTransaction::Custom(2).into() - } - - Ok(ValidTransaction { priority: 2, ..Default::default() }) - } - } - - #[test] - fn test() { - generate_bridge_reject_obsolete_headers_and_messages!( - MockCall, - (), - FirstFilterCall, - SecondFilterCall - ); - - assert_err!( - BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0), - InvalidTransaction::Custom(1) - ); - - assert_err!( - BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0), - InvalidTransaction::Custom(2) - ); - - assert_ok!( - BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0), - ValidTransaction { priority: 3, ..Default::default() } - ) - } -} diff --git a/bridges/bin/runtime-common/src/messages.rs b/bridges/bin/runtime-common/src/messages.rs index 4aca53f3b98361b1a5f7d5dc89dc72ec0bc1323c..0fe9935dbdb6dfc776977ff8cfbad87d3eee9f6e 100644 --- a/bridges/bin/runtime-common/src/messages.rs +++ b/bridges/bin/runtime-common/src/messages.rs @@ -35,7 +35,7 @@ use frame_support::{traits::Get, weights::Weight}; use hash_db::Hasher; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; -use sp_std::{convert::TryFrom, marker::PhantomData, vec::Vec}; +use sp_std::{marker::PhantomData, vec::Vec}; /// Bidirectional message bridge. pub trait MessageBridge { diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs index ad71cd0d456d827d3757433d214f7ea794406fca..e323f1edfc71da8c84fe8cabb977da85ce4d303e 100644 --- a/bridges/bin/runtime-common/src/mock.rs +++ b/bridges/bin/runtime-common/src/mock.rs @@ -183,7 +183,8 @@ impl pallet_transaction_payment::Config for TestRuntime { impl pallet_bridge_grandpa::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type BridgedChain = BridgedUnderlyingChain; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; + type MaxFreeHeadersPerBlock = ConstU32<4>; + type FreeHeadersInterval = ConstU32<1_024>; type HeadersToKeep = ConstU32<8>; type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight; } @@ -406,6 +407,7 @@ impl Chain for BridgedUnderlyingParachain { impl Parachain for BridgedUnderlyingParachain { const PARACHAIN_ID: u32 = 42; + const MAX_HEADER_SIZE: u32 = 1_024; } /// The other, bridged chain, used in tests. diff --git a/bridges/bin/runtime-common/src/priority_calculator.rs b/bridges/bin/runtime-common/src/priority_calculator.rs deleted file mode 100644 index 5035553f508dfea94a0cb5ddf9b916dd7d9b4ea5..0000000000000000000000000000000000000000 --- a/bridges/bin/runtime-common/src/priority_calculator.rs +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Parity Bridges Common. - -// Parity Bridges Common is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Bridges Common is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Bridges Common. If not, see . - -//! Bridge transaction priority calculator. -//! -//! We want to prioritize message delivery transactions with more messages over -//! transactions with less messages. That's because we reject delivery transactions -//! if it contains already delivered message. And if some transaction delivers -//! single message with nonce `N`, then the transaction with nonces `N..=N+100` will -//! be rejected. This can lower bridge throughput down to one message per block. - -use bp_messages::MessageNonce; -use frame_support::traits::Get; -use sp_runtime::transaction_validity::TransactionPriority; - -// reexport everything from `integrity_tests` module -#[allow(unused_imports)] -pub use integrity_tests::*; - -/// Compute priority boost for message delivery transaction that delivers -/// given number of messages. -pub fn compute_priority_boost( - messages: MessageNonce, -) -> TransactionPriority -where - PriorityBoostPerMessage: Get, -{ - // we don't want any boost for transaction with single message => minus one - PriorityBoostPerMessage::get().saturating_mul(messages.saturating_sub(1)) -} - -#[cfg(not(feature = "integrity-test"))] -mod integrity_tests {} - -#[cfg(feature = "integrity-test")] -mod integrity_tests { - use super::compute_priority_boost; - - use bp_messages::MessageNonce; - use bp_runtime::PreComputedSize; - use frame_support::{ - dispatch::{DispatchClass, DispatchInfo, Pays, PostDispatchInfo}, - traits::Get, - }; - use pallet_bridge_messages::WeightInfoExt; - use pallet_transaction_payment::OnChargeTransaction; - use sp_runtime::{ - traits::{Dispatchable, UniqueSaturatedInto, Zero}, - transaction_validity::TransactionPriority, - FixedPointOperand, SaturatedConversion, Saturating, - }; - - type BalanceOf = - <::OnChargeTransaction as OnChargeTransaction< - T, - >>::Balance; - - /// Ensures that the value of `PriorityBoostPerMessage` matches the value of - /// `tip_boost_per_message`. - /// - /// We want two transactions, `TX1` with `N` messages and `TX2` with `N+1` messages, have almost - /// the same priority if we'll add `tip_boost_per_message` tip to the `TX1`. We want to be sure - /// that if we add plain `PriorityBoostPerMessage` priority to `TX1`, the priority will be close - /// to `TX2` as well. - pub fn ensure_priority_boost_is_sane( - tip_boost_per_message: BalanceOf, - ) where - Runtime: - pallet_transaction_payment::Config + pallet_bridge_messages::Config, - MessagesInstance: 'static, - PriorityBoostPerMessage: Get, - Runtime::RuntimeCall: Dispatchable, - BalanceOf: Send + Sync + FixedPointOperand, - { - let priority_boost_per_message = PriorityBoostPerMessage::get(); - let maximal_messages_in_delivery_transaction = - Runtime::MaxUnconfirmedMessagesAtInboundLane::get(); - for messages in 1..=maximal_messages_in_delivery_transaction { - let base_priority = estimate_message_delivery_transaction_priority::< - Runtime, - MessagesInstance, - >(messages, Zero::zero()); - let priority_boost = compute_priority_boost::(messages); - let priority_with_boost = base_priority + priority_boost; - - let tip = tip_boost_per_message.saturating_mul((messages - 1).unique_saturated_into()); - let priority_with_tip = - estimate_message_delivery_transaction_priority::(1, tip); - - const ERROR_MARGIN: TransactionPriority = 5; // 5% - if priority_with_boost.abs_diff(priority_with_tip).saturating_mul(100) / - priority_with_tip > - ERROR_MARGIN - { - panic!( - "The PriorityBoostPerMessage value ({}) must be fixed to: {}", - priority_boost_per_message, - compute_priority_boost_per_message::( - tip_boost_per_message - ), - ); - } - } - } - - /// Compute priority boost that we give to message delivery transaction for additional message. - #[cfg(feature = "integrity-test")] - fn compute_priority_boost_per_message( - tip_boost_per_message: BalanceOf, - ) -> TransactionPriority - where - Runtime: - pallet_transaction_payment::Config + pallet_bridge_messages::Config, - MessagesInstance: 'static, - Runtime::RuntimeCall: Dispatchable, - BalanceOf: Send + Sync + FixedPointOperand, - { - // estimate priority of transaction that delivers one message and has large tip - let maximal_messages_in_delivery_transaction = - Runtime::MaxUnconfirmedMessagesAtInboundLane::get(); - let small_with_tip_priority = - estimate_message_delivery_transaction_priority::( - 1, - tip_boost_per_message - .saturating_mul(maximal_messages_in_delivery_transaction.saturated_into()), - ); - // estimate priority of transaction that delivers maximal number of messages, but has no tip - let large_without_tip_priority = estimate_message_delivery_transaction_priority::< - Runtime, - MessagesInstance, - >(maximal_messages_in_delivery_transaction, Zero::zero()); - - small_with_tip_priority - .saturating_sub(large_without_tip_priority) - .saturating_div(maximal_messages_in_delivery_transaction - 1) - } - - /// Estimate message delivery transaction priority. - #[cfg(feature = "integrity-test")] - fn estimate_message_delivery_transaction_priority( - messages: MessageNonce, - tip: BalanceOf, - ) -> TransactionPriority - where - Runtime: - pallet_transaction_payment::Config + pallet_bridge_messages::Config, - MessagesInstance: 'static, - Runtime::RuntimeCall: Dispatchable, - BalanceOf: Send + Sync + FixedPointOperand, - { - // just an estimation of extra transaction bytes that are added to every transaction - // (including signature, signed extensions extra and etc + in our case it includes - // all call arguments except the proof itself) - let base_tx_size = 512; - // let's say we are relaying similar small messages and for every message we add more trie - // nodes to the proof (x0.5 because we expect some nodes to be reused) - let estimated_message_size = 512; - // let's say all our messages have the same dispatch weight - let estimated_message_dispatch_weight = - Runtime::WeightInfo::message_dispatch_weight(estimated_message_size); - // messages proof argument size is (for every message) messages size + some additional - // trie nodes. Some of them are reused by different messages, so let's take 2/3 of default - // "overhead" constant - let messages_proof_size = Runtime::WeightInfo::expected_extra_storage_proof_size() - .saturating_mul(2) - .saturating_div(3) - .saturating_add(estimated_message_size) - .saturating_mul(messages as _); - - // finally we are able to estimate transaction size and weight - let transaction_size = base_tx_size.saturating_add(messages_proof_size); - let transaction_weight = Runtime::WeightInfo::receive_messages_proof_weight( - &PreComputedSize(transaction_size as _), - messages as _, - estimated_message_dispatch_weight.saturating_mul(messages), - ); - - pallet_transaction_payment::ChargeTransactionPayment::::get_priority( - &DispatchInfo { - weight: transaction_weight, - class: DispatchClass::Normal, - pays_fee: Pays::Yes, - }, - transaction_size as _, - tip, - Zero::zero(), - ) - } -} diff --git a/bridges/chains/chain-bridge-hub-cumulus/src/lib.rs b/bridges/chains/chain-bridge-hub-cumulus/src/lib.rs index c49aa4b856397d28746d017fd8333ae3ad10655e..a5c90ceba111e0c8a095f7e96e6d4a8dba92d183 100644 --- a/bridges/chains/chain-bridge-hub-cumulus/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-cumulus/src/lib.rs @@ -39,6 +39,9 @@ use frame_support::{ use frame_system::limits; use sp_std::time::Duration; +/// Maximal bridge hub header size. +pub const MAX_BRIDGE_HUB_HEADER_SIZE: u32 = 4_096; + /// Average block interval in Cumulus-based parachains. /// /// Corresponds to the `MILLISECS_PER_BLOCK` from `parachains_common` crate. diff --git a/bridges/chains/chain-bridge-hub-kusama/src/lib.rs b/bridges/chains/chain-bridge-hub-kusama/src/lib.rs index 576e3dbee80d0babbdb7c0bbdfc420c5a636b68b..ef3ef4ab7b7a9bc111218e3c53091ac232f34721 100644 --- a/bridges/chains/chain-bridge-hub-kusama/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-kusama/src/lib.rs @@ -62,6 +62,7 @@ impl Chain for BridgeHubKusama { impl Parachain for BridgeHubKusama { const PARACHAIN_ID: u32 = BRIDGE_HUB_KUSAMA_PARACHAIN_ID; + const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE; } impl ChainWithMessages for BridgeHubKusama { diff --git a/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs b/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs index 6db389c92994d74fb0d8176509cd81d64b806df2..9db71af928e5df01170cf4ab8bf5f20cd72f7610 100644 --- a/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs @@ -59,6 +59,7 @@ impl Chain for BridgeHubPolkadot { impl Parachain for BridgeHubPolkadot { const PARACHAIN_ID: u32 = BRIDGE_HUB_POLKADOT_PARACHAIN_ID; + const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE; } impl ChainWithMessages for BridgeHubPolkadot { diff --git a/bridges/chains/chain-bridge-hub-rococo/src/lib.rs b/bridges/chains/chain-bridge-hub-rococo/src/lib.rs index abce872d7ba35cf24b013aa26b4b1f1d796b5785..d7097f01c5316a58851f400a86b98eda3d7e8bcc 100644 --- a/bridges/chains/chain-bridge-hub-rococo/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-rococo/src/lib.rs @@ -59,6 +59,7 @@ impl Chain for BridgeHubRococo { impl Parachain for BridgeHubRococo { const PARACHAIN_ID: u32 = BRIDGE_HUB_ROCOCO_PARACHAIN_ID; + const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE; } impl ChainWithMessages for BridgeHubRococo { @@ -103,9 +104,9 @@ frame_support::parameter_types! { /// Transaction fee that is paid at the Rococo BridgeHub for delivering single inbound message. /// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`) - pub const BridgeHubRococoBaseDeliveryFeeInRocs: u128 = 5_651_581_649; + pub const BridgeHubRococoBaseDeliveryFeeInRocs: u128 = 314_037_860; /// Transaction fee that is paid at the Rococo BridgeHub for delivering single outbound message confirmation. /// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`) - pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 5_380_901_781; + pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 57_414_813; } diff --git a/bridges/chains/chain-bridge-hub-westend/src/lib.rs b/bridges/chains/chain-bridge-hub-westend/src/lib.rs index 4af895cc6d328bdb350fa95b0e0a74f0cc731b04..800f290d7bfa41cec4139e80a7dc9ea8962a6da5 100644 --- a/bridges/chains/chain-bridge-hub-westend/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-westend/src/lib.rs @@ -58,6 +58,7 @@ impl Chain for BridgeHubWestend { impl Parachain for BridgeHubWestend { const PARACHAIN_ID: u32 = BRIDGE_HUB_WESTEND_PARACHAIN_ID; + const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE; } impl ChainWithMessages for BridgeHubWestend { @@ -93,10 +94,10 @@ frame_support::parameter_types! { pub const BridgeHubWestendBaseXcmFeeInWnds: u128 = 17_756_830_000; /// Transaction fee that is paid at the Westend BridgeHub for delivering single inbound message. - /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`) - pub const BridgeHubWestendBaseDeliveryFeeInWnds: u128 = 1_695_489_961_344; + /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_standalone_message_delivery_transaction` + `33%`) + pub const BridgeHubWestendBaseDeliveryFeeInWnds: u128 = 94_211_536_452; /// Transaction fee that is paid at the Westend BridgeHub for delivering single outbound message confirmation. - /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`) - pub const BridgeHubWestendBaseConfirmationFeeInWnds: u128 = 1_618_309_961_344; + /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_standalone_message_confirmation_transaction` + `33%`) + pub const BridgeHubWestendBaseConfirmationFeeInWnds: u128 = 17_224_486_452; } diff --git a/bridges/chains/chain-kusama/src/lib.rs b/bridges/chains/chain-kusama/src/lib.rs index a81004afe8127b556211d0207d2bc1f9ecc02955..fd7172c5869d468ff534e54f9ef6278cf86a88ed 100644 --- a/bridges/chains/chain-kusama/src/lib.rs +++ b/bridges/chains/chain-kusama/src/lib.rs @@ -67,6 +67,8 @@ pub const PARAS_PALLET_NAME: &str = "Paras"; /// Name of the With-Kusama GRANDPA pallet instance that is deployed at bridged chains. pub const WITH_KUSAMA_GRANDPA_PALLET_NAME: &str = "BridgeKusamaGrandpa"; +/// Name of the With-Kusama parachains pallet instance that is deployed at bridged chains. +pub const WITH_KUSAMA_BRIDGE_PARACHAINS_PALLET_NAME: &str = "BridgeKusamaParachains"; /// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Polkadot /// parachains. diff --git a/bridges/chains/chain-polkadot/src/lib.rs b/bridges/chains/chain-polkadot/src/lib.rs index 00d35783a9b61844bab7701fdb60711125447ca3..a8cac0467d574e9355a8fe9ba2e7c2378019349d 100644 --- a/bridges/chains/chain-polkadot/src/lib.rs +++ b/bridges/chains/chain-polkadot/src/lib.rs @@ -69,6 +69,8 @@ pub const PARAS_PALLET_NAME: &str = "Paras"; /// Name of the With-Polkadot GRANDPA pallet instance that is deployed at bridged chains. pub const WITH_POLKADOT_GRANDPA_PALLET_NAME: &str = "BridgePolkadotGrandpa"; +/// Name of the With-Polkadot parachains pallet instance that is deployed at bridged chains. +pub const WITH_POLKADOT_BRIDGE_PARACHAINS_PALLET_NAME: &str = "BridgePolkadotParachains"; /// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Polkadot /// parachains. diff --git a/bridges/chains/chain-rococo/src/lib.rs b/bridges/chains/chain-rococo/src/lib.rs index 2385dd2cbb250181ce5f46aef9f1e76f8fd010d2..b290fe71c829d08130556a2b061c0d63f0787d4c 100644 --- a/bridges/chains/chain-rococo/src/lib.rs +++ b/bridges/chains/chain-rococo/src/lib.rs @@ -67,6 +67,8 @@ pub const PARAS_PALLET_NAME: &str = "Paras"; /// Name of the With-Rococo GRANDPA pallet instance that is deployed at bridged chains. pub const WITH_ROCOCO_GRANDPA_PALLET_NAME: &str = "BridgeRococoGrandpa"; +/// Name of the With-Rococo parachains pallet instance that is deployed at bridged chains. +pub const WITH_ROCOCO_BRIDGE_PARACHAINS_PALLET_NAME: &str = "BridgeRococoParachains"; /// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Rococo /// parachains. diff --git a/bridges/chains/chain-westend/src/lib.rs b/bridges/chains/chain-westend/src/lib.rs index b344b7f4bf93392c08502446513a9ae39296b512..ef451f7de0a9640bc1a278e1c712bbb099193ceb 100644 --- a/bridges/chains/chain-westend/src/lib.rs +++ b/bridges/chains/chain-westend/src/lib.rs @@ -67,6 +67,8 @@ pub const PARAS_PALLET_NAME: &str = "Paras"; /// Name of the With-Westend GRANDPA pallet instance that is deployed at bridged chains. pub const WITH_WESTEND_GRANDPA_PALLET_NAME: &str = "BridgeWestendGrandpa"; +/// Name of the With-Westend parachains pallet instance that is deployed at bridged chains. +pub const WITH_WESTEND_BRIDGE_PARACHAINS_PALLET_NAME: &str = "BridgeWestendParachains"; /// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Westend /// parachains. diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index 4a7ebb3cc8d42d7cb9d97d5c6990bb33658416bd..6fa62ec0cff498e9c33cf3a1ccf7bc76c7b7279e 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -15,20 +15,24 @@ // along with Parity Bridges Common. If not, see . use crate::{ - weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, CurrentAuthoritySet, Error, - Pallet, + weights::WeightInfo, BestFinalized, BridgedBlockNumber, BridgedHeader, Config, + CurrentAuthoritySet, Error, FreeHeadersRemaining, Pallet, }; use bp_header_chain::{ justification::GrandpaJustification, max_expected_submit_finality_proof_arguments_size, ChainWithGrandpa, GrandpaConsensusLogReader, }; -use bp_runtime::{BlockNumberOf, OwnedBridgeModule}; +use bp_runtime::{BlockNumberOf, Chain, OwnedBridgeModule}; use codec::Encode; -use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight}; +use frame_support::{ + dispatch::CallableCallFor, + traits::{Get, IsSubType}, + weights::Weight, +}; use sp_consensus_grandpa::SetId; use sp_runtime::{ - traits::{Header, Zero}, - transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, + traits::{CheckedSub, Header, Zero}, + transaction_validity::{InvalidTransaction, TransactionValidityError}, RuntimeDebug, SaturatedConversion, }; @@ -40,6 +44,11 @@ pub struct SubmitFinalityProofInfo { /// An identifier of the validators set that has signed the submitted justification. /// It might be `None` if deprecated version of the `submit_finality_proof` is used. pub current_set_id: Option, + /// If `true`, then the call proves new **mandatory** header. + pub is_mandatory: bool, + /// If `true`, then the call must be free (assuming that everything else is valid) to + /// be treated as valid. + pub is_free_execution_expected: bool, /// Extra weight that we assume is included in the call. /// /// We have some assumptions about headers and justifications of the bridged chain. @@ -54,6 +63,16 @@ pub struct SubmitFinalityProofInfo { pub extra_size: u32, } +/// Verified `SubmitFinalityProofInfo`. +#[derive(Copy, Clone, PartialEq, RuntimeDebug)] +pub struct VerifiedSubmitFinalityProofInfo { + /// Base call information. + pub base: SubmitFinalityProofInfo, + /// A difference between bundled bridged header and best bridged header known to us + /// before the call. + pub improved_by: N, +} + impl SubmitFinalityProofInfo { /// Returns `true` if call size/weight is below our estimations for regular calls. pub fn fits_limits(&self) -> bool { @@ -67,14 +86,86 @@ pub struct SubmitFinalityProofHelper, I: 'static> { } impl, I: 'static> SubmitFinalityProofHelper { + /// Returns `true` if we may fit more free headers into the current block. If `false` is + /// returned, the call will be paid even if `is_free_execution_expected` has been set + /// to `true`. + pub fn has_free_header_slots() -> bool { + // `unwrap_or(u32::MAX)` means that if `FreeHeadersRemaining` is `None`, we may accept + // this header for free. That is a small cheat - it is `None` if executed outside of + // transaction (e.g. during block initialization). Normal relayer would never submit + // such calls, but if he did, that is not our problem. During normal transactions, + // the `FreeHeadersRemaining` is always `Some(_)`. + let free_headers_remaining = FreeHeadersRemaining::::get().unwrap_or(u32::MAX); + free_headers_remaining > 0 + } + + /// Check that the: (1) GRANDPA head provided by the `SubmitFinalityProof` is better than the + /// best one we know (2) if `current_set_id` matches the current authority set id, if specified + /// and (3) whether transaction MAY be free for the submitter if `is_free_execution_expected` + /// is `true`. + /// + /// Returns number of headers between the current best finalized header, known to the pallet + /// and the bundled header. + pub fn check_obsolete_from_extension( + call_info: &SubmitFinalityProofInfo>, + ) -> Result, Error> { + // do basic checks first + let improved_by = Self::check_obsolete(call_info.block_number, call_info.current_set_id)?; + + // if submitter has NOT specified that it wants free execution, then we are done + if !call_info.is_free_execution_expected { + return Ok(improved_by); + } + + // else - if we can not accept more free headers, "reject" the transaction + if !Self::has_free_header_slots() { + log::trace!( + target: crate::LOG_TARGET, + "Cannot accept free {:?} header {:?}. No more free slots remaining", + T::BridgedChain::ID, + call_info.block_number, + ); + + return Err(Error::::FreeHeadersLimitExceded); + } + + // ensure that the `improved_by` is larger than the configured free interval + if !call_info.is_mandatory { + if let Some(free_headers_interval) = T::FreeHeadersInterval::get() { + if improved_by < free_headers_interval.into() { + log::trace!( + target: crate::LOG_TARGET, + "Cannot accept free {:?} header {:?}. Too small difference \ + between submitted headers: {:?} vs {}", + T::BridgedChain::ID, + call_info.block_number, + improved_by, + free_headers_interval, + ); + + return Err(Error::::BelowFreeHeaderInterval); + } + } + } + + // we do not check whether the header matches free submission criteria here - it is the + // relayer responsibility to check that + + Ok(improved_by) + } + /// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best /// one we know. Additionally, checks if `current_set_id` matches the current authority set - /// id, if specified. + /// id, if specified. This method is called by the call code and the transaction extension, + /// so it does not check the free execution. + /// + /// Returns number of headers between the current best finalized header, known to the pallet + /// and the bundled header. pub fn check_obsolete( finality_target: BlockNumberOf, current_set_id: Option, - ) -> Result<(), Error> { - let best_finalized = crate::BestFinalized::::get().ok_or_else(|| { + ) -> Result, Error> { + let best_finalized = BestFinalized::::get().ok_or_else(|| { log::trace!( target: crate::LOG_TARGET, "Cannot finalize header {:?} because pallet is not yet initialized", @@ -83,16 +174,19 @@ impl, I: 'static> SubmitFinalityProofHelper { >::NotInitialized })?; - if best_finalized.number() >= finality_target { - log::trace!( - target: crate::LOG_TARGET, - "Cannot finalize obsolete header: bundled {:?}, best {:?}", - finality_target, - best_finalized, - ); + let improved_by = match finality_target.checked_sub(&best_finalized.number()) { + Some(improved_by) if improved_by > Zero::zero() => improved_by, + _ => { + log::trace!( + target: crate::LOG_TARGET, + "Cannot finalize obsolete header: bundled {:?}, best {:?}", + finality_target, + best_finalized, + ); - return Err(Error::::OldHeader) - } + return Err(Error::::OldHeader) + }, + }; if let Some(current_set_id) = current_set_id { let actual_set_id = >::get().set_id; @@ -108,12 +202,12 @@ impl, I: 'static> SubmitFinalityProofHelper { } } - Ok(()) + Ok(improved_by) } /// Check if the `SubmitFinalityProof` was successfully executed. pub fn was_successful(finality_target: BlockNumberOf) -> bool { - match crate::BestFinalized::::get() { + match BestFinalized::::get() { Some(best_finalized) => best_finalized.number() == finality_target, None => false, } @@ -135,17 +229,20 @@ pub trait CallSubType, I: 'static>: finality_target, justification, None, + false, )) } else if let Some(crate::Call::::submit_finality_proof_ex { finality_target, justification, current_set_id, + is_free_execution_expected, }) = self.is_sub_type() { return Some(submit_finality_proof_info_from_args::( finality_target, justification, Some(*current_set_id), + *is_free_execution_expected, )) } @@ -155,26 +252,36 @@ pub trait CallSubType, I: 'static>: /// Validate Grandpa headers in order to avoid "mining" transactions that provide outdated /// bridged chain headers. Without this validation, even honest relayers may lose their funds /// if there are multiple relays running and submitting the same information. - fn check_obsolete_submit_finality_proof(&self) -> TransactionValidity + /// + /// Returns `Ok(None)` if the call is not the `submit_finality_proof` call of our pallet. + /// Returns `Ok(Some(_))` if the call is the `submit_finality_proof` call of our pallet and + /// we believe the call brings header that improves the pallet state. + /// Returns `Err(_)` if the call is the `submit_finality_proof` call of our pallet and we + /// believe that the call will fail. + fn check_obsolete_submit_finality_proof( + &self, + ) -> Result< + Option>>, + TransactionValidityError, + > where Self: Sized, { - let finality_target = match self.submit_finality_proof_info() { + let call_info = match self.submit_finality_proof_info() { Some(finality_proof) => finality_proof, - _ => return Ok(ValidTransaction::default()), + _ => return Ok(None), }; if Pallet::::ensure_not_halted().is_err() { - return InvalidTransaction::Call.into() + return Err(InvalidTransaction::Call.into()) } - match SubmitFinalityProofHelper::::check_obsolete( - finality_target.block_number, - finality_target.current_set_id, - ) { - Ok(_) => Ok(ValidTransaction::default()), - Err(Error::::OldHeader) => InvalidTransaction::Stale.into(), - Err(_) => InvalidTransaction::Call.into(), + let result = SubmitFinalityProofHelper::::check_obsolete_from_extension(&call_info); + match result { + Ok(improved_by) => + Ok(Some(VerifiedSubmitFinalityProofInfo { base: call_info, improved_by })), + Err(Error::::OldHeader) => Err(InvalidTransaction::Stale.into()), + Err(_) => Err(InvalidTransaction::Call.into()), } } } @@ -189,6 +296,7 @@ pub(crate) fn submit_finality_proof_info_from_args, I: 'static>( finality_target: &BridgedHeader, justification: &GrandpaJustification>, current_set_id: Option, + is_free_execution_expected: bool, ) -> SubmitFinalityProofInfo> { let block_number = *finality_target.number(); @@ -230,16 +338,26 @@ pub(crate) fn submit_finality_proof_info_from_args, I: 'static>( ); let extra_size = actual_call_size.saturating_sub(max_expected_call_size); - SubmitFinalityProofInfo { block_number, current_set_id, extra_weight, extra_size } + SubmitFinalityProofInfo { + block_number, + current_set_id, + is_mandatory: is_mandatory_finality_target, + is_free_execution_expected, + extra_weight, + extra_size, + } } #[cfg(test)] mod tests { use crate::{ call_ext::CallSubType, - mock::{run_test, test_header, RuntimeCall, TestBridgedChain, TestNumber, TestRuntime}, - BestFinalized, Config, CurrentAuthoritySet, PalletOperatingMode, StoredAuthoritySet, - SubmitFinalityProofInfo, WeightInfo, + mock::{ + run_test, test_header, FreeHeadersInterval, RuntimeCall, TestBridgedChain, TestNumber, + TestRuntime, + }, + BestFinalized, Config, CurrentAuthoritySet, FreeHeadersRemaining, PalletOperatingMode, + StoredAuthoritySet, SubmitFinalityProofInfo, WeightInfo, }; use bp_header_chain::ChainWithGrandpa; use bp_runtime::{BasicOperatingMode, HeaderId}; @@ -247,6 +365,7 @@ mod tests { make_default_justification, make_justification_for_header, JustificationGeneratorParams, TEST_GRANDPA_SET_ID, }; + use codec::Encode; use frame_support::weights::Weight; use sp_runtime::{testing::DigestItem, traits::Header as _, SaturatedConversion}; @@ -256,6 +375,7 @@ mod tests { justification: make_default_justification(&test_header(num)), // not initialized => zero current_set_id: 0, + is_free_execution_expected: false, }; RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( bridge_grandpa_call, @@ -311,6 +431,121 @@ mod tests { }); } + #[test] + fn extension_rejects_new_header_if_free_execution_is_requested_and_free_submissions_are_not_accepted( + ) { + run_test(|| { + let bridge_grandpa_call = crate::Call::::submit_finality_proof_ex { + finality_target: Box::new(test_header(10 + FreeHeadersInterval::get() as u64)), + justification: make_default_justification(&test_header( + 10 + FreeHeadersInterval::get() as u64, + )), + current_set_id: 0, + is_free_execution_expected: true, + }; + sync_to_header_10(); + + // when we can accept free headers => Ok + FreeHeadersRemaining::::put(2); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + ),) + .is_ok()); + + // when we can NOT accept free headers => Err + FreeHeadersRemaining::::put(0); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + ),) + .is_err()); + + // when called outside of transaction => Ok + FreeHeadersRemaining::::kill(); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call, + ),) + .is_ok()); + }) + } + + #[test] + fn extension_rejects_new_header_if_free_execution_is_requested_and_improved_by_is_below_expected( + ) { + run_test(|| { + let bridge_grandpa_call = crate::Call::::submit_finality_proof_ex { + finality_target: Box::new(test_header(100)), + justification: make_default_justification(&test_header(100)), + current_set_id: 0, + is_free_execution_expected: true, + }; + sync_to_header_10(); + + // when `improved_by` is less than the free interval + BestFinalized::::put(HeaderId( + 100 - FreeHeadersInterval::get() as u64 + 1, + sp_core::H256::default(), + )); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + ),) + .is_err()); + + // when `improved_by` is equal to the free interval + BestFinalized::::put(HeaderId( + 100 - FreeHeadersInterval::get() as u64, + sp_core::H256::default(), + )); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + ),) + .is_ok()); + + // when `improved_by` is larger than the free interval + BestFinalized::::put(HeaderId( + 100 - FreeHeadersInterval::get() as u64 - 1, + sp_core::H256::default(), + )); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + ),) + .is_ok()); + + // when `improved_by` is less than the free interval BUT it is a mandatory header + let mut mandatory_header = test_header(100); + let consensus_log = sp_consensus_grandpa::ConsensusLog::::ScheduledChange( + sp_consensus_grandpa::ScheduledChange { + next_authorities: bp_test_utils::authority_list(), + delay: 0, + }, + ); + mandatory_header.digest = sp_runtime::Digest { + logs: vec![DigestItem::Consensus( + sp_consensus_grandpa::GRANDPA_ENGINE_ID, + consensus_log.encode(), + )], + }; + let justification = make_justification_for_header(JustificationGeneratorParams { + header: mandatory_header.clone(), + set_id: 1, + ..Default::default() + }); + let bridge_grandpa_call = crate::Call::::submit_finality_proof_ex { + finality_target: Box::new(mandatory_header), + justification, + current_set_id: 0, + is_free_execution_expected: true, + }; + BestFinalized::::put(HeaderId( + 100 - FreeHeadersInterval::get() as u64 + 1, + sp_core::H256::default(), + )); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + ),) + .is_ok()); + }) + } + #[test] fn extension_accepts_new_header() { run_test(|| { @@ -336,6 +571,8 @@ mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_mandatory: false, + is_free_execution_expected: false, }) ); @@ -345,6 +582,7 @@ mod tests { finality_target: Box::new(test_header(42)), justification: make_default_justification(&test_header(42)), current_set_id: 777, + is_free_execution_expected: false, }); assert_eq!( deprecated_call.submit_finality_proof_info(), @@ -353,6 +591,8 @@ mod tests { current_set_id: Some(777), extra_weight: Weight::zero(), extra_size: 0, + is_mandatory: false, + is_free_execution_expected: false, }) ); } @@ -370,6 +610,7 @@ mod tests { finality_target: Box::new(small_finality_target), justification: small_justification, current_set_id: TEST_GRANDPA_SET_ID, + is_free_execution_expected: false, }); assert_eq!(small_call.submit_finality_proof_info().unwrap().extra_size, 0); @@ -387,6 +628,7 @@ mod tests { finality_target: Box::new(large_finality_target), justification: large_justification, current_set_id: TEST_GRANDPA_SET_ID, + is_free_execution_expected: false, }); assert_ne!(large_call.submit_finality_proof_info().unwrap().extra_size, 0); } @@ -406,6 +648,7 @@ mod tests { finality_target: Box::new(finality_target.clone()), justification, current_set_id: TEST_GRANDPA_SET_ID, + is_free_execution_expected: false, }); assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, Weight::zero()); @@ -420,7 +663,52 @@ mod tests { finality_target: Box::new(finality_target), justification, current_set_id: TEST_GRANDPA_SET_ID, + is_free_execution_expected: false, }); assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight); } + + #[test] + fn check_obsolete_submit_finality_proof_returns_correct_improved_by() { + run_test(|| { + fn make_call(number: u64) -> RuntimeCall { + RuntimeCall::Grandpa(crate::Call::::submit_finality_proof_ex { + finality_target: Box::new(test_header(number)), + justification: make_default_justification(&test_header(number)), + current_set_id: 0, + is_free_execution_expected: false, + }) + } + + sync_to_header_10(); + + // when the difference between headers is 1 + assert_eq!( + RuntimeCall::check_obsolete_submit_finality_proof(&make_call(11)) + .unwrap() + .unwrap() + .improved_by, + 1, + ); + + // when the difference between headers is 2 + assert_eq!( + RuntimeCall::check_obsolete_submit_finality_proof(&make_call(12)) + .unwrap() + .unwrap() + .improved_by, + 2, + ); + }) + } + + #[test] + fn check_obsolete_submit_finality_proof_ignores_other_calls() { + run_test(|| { + let call = + RuntimeCall::System(frame_system::Call::::remark { remark: vec![42] }); + + assert_eq!(RuntimeCall::check_obsolete_submit_finality_proof(&call), Ok(None)); + }) + } } diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index 9e095651ef81da1e5418d7532ae56ae0fb8ef564..efcbfb1654b34e08e07d3bda4e32ee587a0ba51c 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -44,11 +44,12 @@ use bp_header_chain::{ }; use bp_runtime::{BlockNumberOf, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule}; use frame_support::{dispatch::PostDispatchInfo, ensure, DefaultNoBound}; +use sp_consensus_grandpa::SetId; use sp_runtime::{ traits::{Header as HeaderT, Zero}, SaturatedConversion, }; -use sp_std::{boxed::Box, convert::TryInto, prelude::*}; +use sp_std::{boxed::Box, prelude::*}; mod call_ext; #[cfg(test)] @@ -57,6 +58,7 @@ mod storage_types; /// Module, containing weights for this pallet. pub mod weights; +pub mod weights_ext; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; @@ -65,6 +67,7 @@ pub mod benchmarking; pub use call_ext::*; pub use pallet::*; pub use weights::WeightInfo; +pub use weights_ext::WeightInfoExt; /// The target that will be used when publishing logs related to this pallet. pub const LOG_TARGET: &str = "runtime::bridge-grandpa"; @@ -101,17 +104,31 @@ pub mod pallet { /// The chain we are bridging to here. type BridgedChain: ChainWithGrandpa; - /// Maximal number of "free" mandatory header transactions per block. + /// Maximal number of "free" header transactions per block. /// /// To be able to track the bridged chain, the pallet requires all headers that are /// changing GRANDPA authorities set at the bridged chain (we call them mandatory). - /// So it is a common good deed to submit mandatory headers to the pallet. However, if the - /// bridged chain gets compromised, its validators may generate as many mandatory headers - /// as they want. And they may fill the whole block (at this chain) for free. This constants - /// limits number of calls that we may refund in a single block. All calls above this - /// limit are accepted, but are not refunded. + /// So it is a common good deed to submit mandatory headers to the pallet. + /// + /// The pallet may be configured (see `[Self::FreeHeadersInterval]`) to import some + /// non-mandatory headers for free as well. It also may be treated as a common good + /// deed, because it may help to reduce bridge fees - this cost may be deducted from + /// bridge fees, paid by message senders. + /// + /// However, if the bridged chain gets compromised, its validators may generate as many + /// "free" headers as they want. And they may fill the whole block (at this chain) for + /// free. This constants limits number of calls that we may refund in a single block. + /// All calls above this limit are accepted, but are not refunded. + #[pallet::constant] + type MaxFreeHeadersPerBlock: Get; + + /// The distance between bridged chain headers, that may be submitted for free. The + /// first free header is header number zero, the next one is header number + /// `FreeHeadersInterval::get()` or any of its descendant if that header has not + /// been submitted. In other words, interval between free headers should be at least + /// `FreeHeadersInterval`. #[pallet::constant] - type MaxFreeMandatoryHeadersPerBlock: Get; + type FreeHeadersInterval: Get>; /// Maximal number of finalized headers to keep in the storage. /// @@ -124,7 +141,7 @@ pub mod pallet { type HeadersToKeep: Get; /// Weights gathered through benchmarking. - type WeightInfo: WeightInfo; + type WeightInfo: WeightInfoExt; } #[pallet::pallet] @@ -133,12 +150,12 @@ pub mod pallet { #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { - FreeMandatoryHeadersRemaining::::put(T::MaxFreeMandatoryHeadersPerBlock::get()); + FreeHeadersRemaining::::put(T::MaxFreeHeadersPerBlock::get()); Weight::zero() } fn on_finalize(_n: BlockNumberFor) { - FreeMandatoryHeadersRemaining::::kill(); + FreeHeadersRemaining::::kill(); } } @@ -155,7 +172,7 @@ pub mod pallet { /// `submit_finality_proof_ex` instead. Semantically, this call is an equivalent of the /// `submit_finality_proof_ex` call without current authority set id check. #[pallet::call_index(0)] - #[pallet::weight(::submit_finality_proof( + #[pallet::weight(T::WeightInfo::submit_finality_proof_weight( justification.commit.precommits.len().saturated_into(), justification.votes_ancestries.len().saturated_into(), ))] @@ -175,6 +192,8 @@ pub mod pallet { // the `submit_finality_proof_ex` also reads this value, but it is done from the // cache, so we don't treat it as an additional db access >::get().set_id, + // cannot enforce free execution using this call + false, ) } @@ -250,8 +269,14 @@ pub mod pallet { /// - verification is not optimized or invalid; /// /// - header contains forced authorities set change or change with non-zero delay. + /// + /// The `is_free_execution_expected` parameter is not really used inside the call. It is + /// used by the transaction extension, which should be registered at the runtime level. If + /// this parameter is `true`, the transaction will be treated as invalid, if the call won't + /// be executed for free. If transaction extension is not used by the runtime, this + /// parameter is not used at all. #[pallet::call_index(4)] - #[pallet::weight(::submit_finality_proof( + #[pallet::weight(T::WeightInfo::submit_finality_proof_weight( justification.commit.precommits.len().saturated_into(), justification.votes_ancestries.len().saturated_into(), ))] @@ -260,6 +285,7 @@ pub mod pallet { finality_target: Box>, justification: GrandpaJustification>, current_set_id: sp_consensus_grandpa::SetId, + _is_free_execution_expected: bool, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; ensure_signed(origin)?; @@ -273,7 +299,8 @@ pub mod pallet { // it checks whether the `number` is better than the current best block number // and whether the `current_set_id` matches the best known set id - SubmitFinalityProofHelper::::check_obsolete(number, Some(current_set_id))?; + let improved_by = + SubmitFinalityProofHelper::::check_obsolete(number, Some(current_set_id))?; let authority_set = >::get(); let unused_proof_size = authority_set.unused_proof_size(); @@ -283,23 +310,16 @@ pub mod pallet { let maybe_new_authority_set = try_enact_authority_change::(&finality_target, set_id)?; - let may_refund_call_fee = maybe_new_authority_set.is_some() && - // if we have seen too many mandatory headers in this block, we don't want to refund - Self::free_mandatory_headers_remaining() > 0 && - // if arguments out of expected bounds, we don't want to refund - submit_finality_proof_info_from_args::(&finality_target, &justification, Some(current_set_id)) - .fits_limits(); + let may_refund_call_fee = may_refund_call_fee::( + &finality_target, + &justification, + current_set_id, + improved_by, + ); if may_refund_call_fee { - FreeMandatoryHeadersRemaining::::mutate(|count| { - *count = count.saturating_sub(1) - }); + on_free_header_imported::(); } insert_header::(*finality_target, hash); - log::info!( - target: LOG_TARGET, - "Successfully imported finalized header with hash {:?}!", - hash - ); // mandatory header is a header that changes authorities set. The pallet can't go // further without importing this header. So every bridge MUST import mandatory headers. @@ -311,6 +331,13 @@ pub mod pallet { // to pay for the transaction. let pays_fee = if may_refund_call_fee { Pays::No } else { Pays::Yes }; + log::info!( + target: LOG_TARGET, + "Successfully imported finalized header with hash {:?}! Free: {}", + hash, + if may_refund_call_fee { "Yes" } else { "No" }, + ); + // the proof size component of the call weight assumes that there are // `MaxBridgedAuthorities` in the `CurrentAuthoritySet` (we use `MaxEncodedLen` // estimation). But if their number is lower, then we may "refund" some `proof_size`, @@ -335,20 +362,18 @@ pub mod pallet { } } - /// Number mandatory headers that we may accept in the current block for free (returning - /// `Pays::No`). + /// Number of free header submissions that we may yet accept in the current block. /// - /// If the `FreeMandatoryHeadersRemaining` hits zero, all following mandatory headers in the + /// If the `FreeHeadersRemaining` hits zero, all following mandatory headers in the /// current block are accepted with fee (`Pays::Yes` is returned). /// - /// The `FreeMandatoryHeadersRemaining` is an ephemeral value that is set to - /// `MaxFreeMandatoryHeadersPerBlock` at each block initialization and is killed on block + /// The `FreeHeadersRemaining` is an ephemeral value that is set to + /// `MaxFreeHeadersPerBlock` at each block initialization and is killed on block /// finalization. So it never ends up in the storage trie. #[pallet::storage] #[pallet::whitelist_storage] - #[pallet::getter(fn free_mandatory_headers_remaining)] - pub(super) type FreeMandatoryHeadersRemaining, I: 'static = ()> = - StorageValue<_, u32, ValueQuery>; + pub type FreeHeadersRemaining, I: 'static = ()> = + StorageValue<_, u32, OptionQuery>; /// Hash of the header used to bootstrap the pallet. #[pallet::storage] @@ -473,6 +498,68 @@ pub mod pallet { /// The `current_set_id` argument of the `submit_finality_proof_ex` doesn't match /// the id of the current set, known to the pallet. InvalidAuthoritySetId, + /// The submitter wanted free execution, but we can't fit more free transactions + /// to the block. + FreeHeadersLimitExceded, + /// The submitter wanted free execution, but the difference between best known and + /// bundled header numbers is below the `FreeHeadersInterval`. + BelowFreeHeaderInterval, + } + + /// Called when new free header is imported. + pub fn on_free_header_imported, I: 'static>() { + FreeHeadersRemaining::::mutate(|count| { + *count = match *count { + None => None, + // the signed extension expects that `None` means outside of block + // execution - i.e. when transaction is validated from the transaction pool, + // so use `saturating_sub` and don't go from `Some(0)`->`None` + Some(count) => Some(count.saturating_sub(1)), + } + }); + } + + /// Return true if we may refund transaction cost to the submitter. In other words, + /// this transaction is considered as common good deed w.r.t to pallet configuration. + fn may_refund_call_fee, I: 'static>( + finality_target: &BridgedHeader, + justification: &GrandpaJustification>, + current_set_id: SetId, + improved_by: BridgedBlockNumber, + ) -> bool { + // if we have refunded too much at this block => not refunding + if FreeHeadersRemaining::::get().unwrap_or(0) == 0 { + return false; + } + + // if size/weight of call is larger than expected => not refunding + let call_info = submit_finality_proof_info_from_args::( + &finality_target, + &justification, + Some(current_set_id), + // this function is called from the transaction body and we do not want + // to do MAY-be-free-executed checks here - they had to be done in the + // transaction extension before + false, + ); + if !call_info.fits_limits() { + return false; + } + + // if that's a mandatory header => refund + if call_info.is_mandatory { + return true; + } + + // if configuration allows free non-mandatory headers and the header + // matches criteria => refund + if let Some(free_headers_interval) = T::FreeHeadersInterval::get() { + if improved_by >= free_headers_interval.into() { + return true; + } + } + + false } /// Check the given header for a GRANDPA scheduled authority set change. If a change @@ -692,8 +779,8 @@ pub fn initialize_for_benchmarks, I: 'static>(header: BridgedHeader mod tests { use super::*; use crate::mock::{ - run_test, test_header, RuntimeEvent as TestEvent, RuntimeOrigin, System, TestBridgedChain, - TestHeader, TestNumber, TestRuntime, MAX_BRIDGED_AUTHORITIES, + run_test, test_header, FreeHeadersInterval, RuntimeEvent as TestEvent, RuntimeOrigin, + System, TestBridgedChain, TestHeader, TestNumber, TestRuntime, MAX_BRIDGED_AUTHORITIES, }; use bp_header_chain::BridgeGrandpaCall; use bp_runtime::BasicOperatingMode; @@ -747,6 +834,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ) } @@ -766,6 +854,7 @@ mod tests { Box::new(header), justification, set_id, + false, ) } @@ -794,6 +883,7 @@ mod tests { Box::new(header), justification, set_id, + false, ) } @@ -1009,6 +1099,7 @@ mod tests { Box::new(header.clone()), justification.clone(), TEST_GRANDPA_SET_ID, + false, ), >::InvalidJustification ); @@ -1018,6 +1109,7 @@ mod tests { Box::new(header), justification, next_set_id, + false, ), >::InvalidAuthoritySetId ); @@ -1039,6 +1131,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ), >::InvalidJustification ); @@ -1069,6 +1162,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ), >::InvalidAuthoritySet ); @@ -1108,6 +1202,7 @@ mod tests { Box::new(header.clone()), justification.clone(), TEST_GRANDPA_SET_ID, + false, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No); @@ -1171,6 +1266,7 @@ mod tests { Box::new(header.clone()), justification, TEST_GRANDPA_SET_ID, + false, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); @@ -1203,6 +1299,7 @@ mod tests { Box::new(header.clone()), justification, TEST_GRANDPA_SET_ID, + false, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); @@ -1233,6 +1330,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ), >::UnsupportedScheduledChange ); @@ -1259,6 +1357,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ), >::UnsupportedScheduledChange ); @@ -1285,6 +1384,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ), >::TooManyAuthoritiesInSet ); @@ -1350,12 +1450,13 @@ mod tests { Box::new(header), invalid_justification, TEST_GRANDPA_SET_ID, + false, ) }; initialize_substrate_bridge(); - for _ in 0..::MaxFreeMandatoryHeadersPerBlock::get() + 1 { + for _ in 0..::MaxFreeHeadersPerBlock::get() + 1 { assert_err!(submit_invalid_request(), >::InvalidJustification); } @@ -1423,6 +1524,64 @@ mod tests { }) } + #[test] + fn may_import_non_mandatory_header_for_free() { + run_test(|| { + initialize_substrate_bridge(); + + // set best finalized to `100` + const BEST: u8 = 12; + fn reset_best() { + BestFinalized::::set(Some(HeaderId( + BEST as _, + Default::default(), + ))); + } + + // non-mandatory header is imported with fee + reset_best(); + let non_free_header_number = BEST + FreeHeadersInterval::get() as u8 - 1; + let result = submit_finality_proof(non_free_header_number); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + + // non-mandatory free header is imported without fee + reset_best(); + let free_header_number = BEST + FreeHeadersInterval::get() as u8; + let result = submit_finality_proof(free_header_number); + assert_eq!(result.unwrap().pays_fee, Pays::No); + + // another non-mandatory free header is imported without fee + let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 2; + let result = submit_finality_proof(free_header_number); + assert_eq!(result.unwrap().pays_fee, Pays::No); + + // now the rate limiter starts charging fees even for free headers + let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 3; + let result = submit_finality_proof(free_header_number); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + + // check that we can import for free if `improved_by` is larger + // than the free interval + next_block(); + reset_best(); + let free_header_number = FreeHeadersInterval::get() as u8 + 42; + let result = submit_finality_proof(free_header_number); + assert_eq!(result.unwrap().pays_fee, Pays::No); + + // check that the rate limiter shares the counter between mandatory + // and free non-mandatory headers + next_block(); + reset_best(); + let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 4; + let result = submit_finality_proof(free_header_number); + assert_eq!(result.unwrap().pays_fee, Pays::No); + let result = submit_mandatory_finality_proof(free_header_number + 1, 1); + assert_eq!(result.expect("call failed").pays_fee, Pays::No); + let result = submit_mandatory_finality_proof(free_header_number + 2, 2); + assert_eq!(result.expect("call failed").pays_fee, Pays::Yes); + }); + } + #[test] fn should_prune_headers_over_headers_to_keep_parameter() { run_test(|| { @@ -1519,9 +1678,23 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ), DispatchError::BadOrigin, ); }) } + + #[test] + fn on_free_header_imported_never_sets_to_none() { + run_test(|| { + FreeHeadersRemaining::::set(Some(2)); + on_free_header_imported::(); + assert_eq!(FreeHeadersRemaining::::get(), Some(1)); + on_free_header_imported::(); + assert_eq!(FreeHeadersRemaining::::get(), Some(0)); + on_free_header_imported::(); + assert_eq!(FreeHeadersRemaining::::get(), Some(0)); + }) + } } diff --git a/bridges/modules/grandpa/src/mock.rs b/bridges/modules/grandpa/src/mock.rs index e689e520c92ffcb230a83f7a728722a688729417..27df9d9c78f540d0d73f74c6a86ba19af30d4b6b 100644 --- a/bridges/modules/grandpa/src/mock.rs +++ b/bridges/modules/grandpa/src/mock.rs @@ -48,14 +48,16 @@ impl frame_system::Config for TestRuntime { } parameter_types! { - pub const MaxFreeMandatoryHeadersPerBlock: u32 = 2; + pub const MaxFreeHeadersPerBlock: u32 = 2; + pub const FreeHeadersInterval: u32 = 32; pub const HeadersToKeep: u32 = 5; } impl grandpa::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type BridgedChain = TestBridgedChain; - type MaxFreeMandatoryHeadersPerBlock = MaxFreeMandatoryHeadersPerBlock; + type MaxFreeHeadersPerBlock = MaxFreeHeadersPerBlock; + type FreeHeadersInterval = FreeHeadersInterval; type HeadersToKeep = HeadersToKeep; type WeightInfo = (); } diff --git a/bridges/modules/grandpa/src/weights_ext.rs b/bridges/modules/grandpa/src/weights_ext.rs new file mode 100644 index 0000000000000000000000000000000000000000..66edea6fb6a64cfa530bd48b0dfd1762af9c545f --- /dev/null +++ b/bridges/modules/grandpa/src/weights_ext.rs @@ -0,0 +1,58 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Weight-related utilities. + +use crate::weights::{BridgeWeight, WeightInfo}; + +use frame_support::weights::Weight; + +/// Extended weight info. +pub trait WeightInfoExt: WeightInfo { + // Our configuration assumes that the runtime has special signed extensions used to: + // + // 1) boost priority of `submit_finality_proof` transactions; + // + // 2) slash relayer if he submits an invalid transaction. + // + // We read and update storage values of other pallets (`pallet-bridge-relayers` and + // balances/assets pallet). So we need to add this weight to the weight of our call. + // Hence two following methods. + + /// Extra weight that is added to the `submit_finality_proof` call weight by signed extensions + /// that are declared at runtime level. + fn submit_finality_proof_overhead_from_runtime() -> Weight; + + // Functions that are directly mapped to extrinsics weights. + + /// Weight of message delivery extrinsic. + fn submit_finality_proof_weight(precommits_len: u32, votes_ancestries_len: u32) -> Weight { + let base_weight = Self::submit_finality_proof(precommits_len, votes_ancestries_len); + base_weight.saturating_add(Self::submit_finality_proof_overhead_from_runtime()) + } +} + +impl WeightInfoExt for BridgeWeight { + fn submit_finality_proof_overhead_from_runtime() -> Weight { + Weight::zero() + } +} + +impl WeightInfoExt for () { + fn submit_finality_proof_overhead_from_runtime() -> Weight { + Weight::zero() + } +} diff --git a/bridges/modules/parachains/src/call_ext.rs b/bridges/modules/parachains/src/call_ext.rs index da91a40a2322393ee715bf1c61840e4b18df23b8..fe6b319205d41491ce2df36d8a1d112eb37f94b4 100644 --- a/bridges/modules/parachains/src/call_ext.rs +++ b/bridges/modules/parachains/src/call_ext.rs @@ -14,25 +14,45 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use crate::{Config, Pallet, RelayBlockNumber}; +use crate::{Config, GrandpaPalletOf, Pallet, RelayBlockHash, RelayBlockNumber}; +use bp_header_chain::HeaderChain; use bp_parachains::BestParaHeadHash; use bp_polkadot_core::parachains::{ParaHash, ParaId}; -use bp_runtime::OwnedBridgeModule; -use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; +use bp_runtime::{HeaderId, OwnedBridgeModule}; +use frame_support::{ + dispatch::CallableCallFor, + traits::{Get, IsSubType}, +}; +use pallet_bridge_grandpa::SubmitFinalityProofHelper; use sp_runtime::{ - transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, + traits::Zero, + transaction_validity::{InvalidTransaction, TransactionValidityError}, RuntimeDebug, }; /// Info about a `SubmitParachainHeads` call which tries to update a single parachain. #[derive(PartialEq, RuntimeDebug)] pub struct SubmitParachainHeadsInfo { - /// Number of the finalized relay block that has been used to prove parachain finality. - pub at_relay_block_number: RelayBlockNumber, + /// Number and hash of the finalized relay block that has been used to prove parachain + /// finality. + pub at_relay_block: HeaderId, /// Parachain identifier. pub para_id: ParaId, /// Hash of the bundled parachain head. pub para_head_hash: ParaHash, + /// If `true`, then the call must be free (assuming that everything else is valid) to + /// be treated as valid. + pub is_free_execution_expected: bool, +} + +/// Verified `SubmitParachainHeadsInfo`. +#[derive(PartialEq, RuntimeDebug)] +pub struct VerifiedSubmitParachainHeadsInfo { + /// Base call information. + pub base: SubmitParachainHeadsInfo, + /// A difference between bundled bridged relay chain header and relay chain header number + /// used to prove best bridged parachain header, known to us before the call. + pub improved_by: RelayBlockNumber, } /// Helper struct that provides methods for working with the `SubmitParachainHeads` call. @@ -41,40 +61,117 @@ pub struct SubmitParachainHeadsHelper, I: 'static> { } impl, I: 'static> SubmitParachainHeadsHelper { - /// Check if the para head provided by the `SubmitParachainHeads` is better than the best one - /// we know. - pub fn is_obsolete(update: &SubmitParachainHeadsInfo) -> bool { - let stored_best_head = match crate::ParasInfo::::get(update.para_id) { - Some(stored_best_head) => stored_best_head, - None => return false, + /// Check that is called from signed extension and takes the `is_free_execution_expected` + /// into account. + pub fn check_obsolete_from_extension( + update: &SubmitParachainHeadsInfo, + ) -> Result { + // first do all base checks + let improved_by = Self::check_obsolete(update)?; + + // if we don't expect free execution - no more checks + if !update.is_free_execution_expected { + return Ok(improved_by); + } + + // reject if no more free slots remaining in the block + if !SubmitFinalityProofHelper::::has_free_header_slots() + { + log::trace!( + target: crate::LOG_TARGET, + "The free parachain {:?} head can't be updated: no more free slots \ + left in the block.", + update.para_id, + ); + + return Err(InvalidTransaction::Call.into()); + } + + // if free headers interval is not configured and call is expected to execute + // for free => it is a relayer error, it should've been able to detect that. + let free_headers_interval = match T::FreeHeadersInterval::get() { + Some(free_headers_interval) => free_headers_interval, + None => return Ok(improved_by), }; - if stored_best_head.best_head_hash.at_relay_block_number >= update.at_relay_block_number { + // reject if we are importing parachain headers too often + if improved_by < free_headers_interval { log::trace!( target: crate::LOG_TARGET, - "The parachain head can't be updated. The parachain head for {:?} \ - was already updated at better relay chain block {} >= {}.", + "The free parachain {:?} head can't be updated: it improves previous + best head by {} while at least {} is expected.", update.para_id, - stored_best_head.best_head_hash.at_relay_block_number, - update.at_relay_block_number + improved_by, + free_headers_interval, ); - return true + + return Err(InvalidTransaction::Stale.into()); } - if stored_best_head.best_head_hash.head_hash == update.para_head_hash { + Ok(improved_by) + } + + /// Check if the para head provided by the `SubmitParachainHeads` is better than the best one + /// we know. + pub fn check_obsolete( + update: &SubmitParachainHeadsInfo, + ) -> Result { + // check if we know better parachain head already + let improved_by = match crate::ParasInfo::::get(update.para_id) { + Some(stored_best_head) => { + let improved_by = match update + .at_relay_block + .0 + .checked_sub(stored_best_head.best_head_hash.at_relay_block_number) + { + Some(improved_by) if improved_by > Zero::zero() => improved_by, + _ => { + log::trace!( + target: crate::LOG_TARGET, + "The parachain head can't be updated. The parachain head for {:?} \ + was already updated at better relay chain block {} >= {}.", + update.para_id, + stored_best_head.best_head_hash.at_relay_block_number, + update.at_relay_block.0 + ); + return Err(InvalidTransaction::Stale.into()) + }, + }; + + if stored_best_head.best_head_hash.head_hash == update.para_head_hash { + log::trace!( + target: crate::LOG_TARGET, + "The parachain head can't be updated. The parachain head hash for {:?} \ + was already updated to {} at block {} < {}.", + update.para_id, + update.para_head_hash, + stored_best_head.best_head_hash.at_relay_block_number, + update.at_relay_block.0 + ); + return Err(InvalidTransaction::Stale.into()) + } + + improved_by + }, + None => RelayBlockNumber::MAX, + }; + + // let's check if our chain had no reorgs and we still know the relay chain header + // used to craft the proof + if GrandpaPalletOf::::finalized_header_state_root(update.at_relay_block.1).is_none() { log::trace!( target: crate::LOG_TARGET, - "The parachain head can't be updated. The parachain head hash for {:?} \ - was already updated to {} at block {} < {}.", + "The parachain {:?} head can't be updated. Relay chain header {}/{} used to create \ + parachain proof is missing from the storage.", update.para_id, - update.para_head_hash, - stored_best_head.best_head_hash.at_relay_block_number, - update.at_relay_block_number + update.at_relay_block.0, + update.at_relay_block.1, ); - return true + + return Err(InvalidTransaction::Call.into()) } - false + Ok(improved_by) } /// Check if the `SubmitParachainHeads` was successfully executed. @@ -83,7 +180,7 @@ impl, I: 'static> SubmitParachainHeadsHelper { Some(stored_best_head) => stored_best_head.best_head_hash == BestParaHeadHash { - at_relay_block_number: update.at_relay_block_number, + at_relay_block_number: update.at_relay_block.0, head_hash: update.para_head_hash, }, None => false, @@ -98,22 +195,36 @@ pub trait CallSubType, I: 'static>: /// Create a new instance of `SubmitParachainHeadsInfo` from a `SubmitParachainHeads` call with /// one single parachain entry. fn one_entry_submit_parachain_heads_info(&self) -> Option { - if let Some(crate::Call::::submit_parachain_heads { - ref at_relay_block, - ref parachains, - .. - }) = self.is_sub_type() - { - if let &[(para_id, para_head_hash)] = parachains.as_slice() { - return Some(SubmitParachainHeadsInfo { - at_relay_block_number: at_relay_block.0, + match self.is_sub_type() { + Some(crate::Call::::submit_parachain_heads { + ref at_relay_block, + ref parachains, + .. + }) => match ¶chains[..] { + &[(para_id, para_head_hash)] => Some(SubmitParachainHeadsInfo { + at_relay_block: HeaderId(at_relay_block.0, at_relay_block.1), para_id, para_head_hash, - }) - } + is_free_execution_expected: false, + }), + _ => None, + }, + Some(crate::Call::::submit_parachain_heads_ex { + ref at_relay_block, + ref parachains, + is_free_execution_expected, + .. + }) => match ¶chains[..] { + &[(para_id, para_head_hash)] => Some(SubmitParachainHeadsInfo { + at_relay_block: HeaderId(at_relay_block.0, at_relay_block.1), + para_id, + para_head_hash, + is_free_execution_expected: *is_free_execution_expected, + }), + _ => None, + }, + _ => None, } - - None } /// Create a new instance of `SubmitParachainHeadsInfo` from a `SubmitParachainHeads` call with @@ -133,24 +244,23 @@ pub trait CallSubType, I: 'static>: /// block production, or "eat" significant portion of block production time literally /// for nothing. In addition, the single-parachain-head-per-transaction is how the /// pallet will be used in our environment. - fn check_obsolete_submit_parachain_heads(&self) -> TransactionValidity + fn check_obsolete_submit_parachain_heads( + &self, + ) -> Result, TransactionValidityError> where Self: Sized, { let update = match self.one_entry_submit_parachain_heads_info() { Some(update) => update, - None => return Ok(ValidTransaction::default()), + None => return Ok(None), }; if Pallet::::ensure_not_halted().is_err() { - return InvalidTransaction::Call.into() + return Err(InvalidTransaction::Call.into()) } - if SubmitParachainHeadsHelper::::is_obsolete(&update) { - return InvalidTransaction::Stale.into() - } - - Ok(ValidTransaction::default()) + SubmitParachainHeadsHelper::::check_obsolete_from_extension(&update) + .map(|improved_by| Some(VerifiedSubmitParachainHeadsInfo { base: update, improved_by })) } } @@ -164,9 +274,10 @@ where #[cfg(test)] mod tests { use crate::{ - mock::{run_test, RuntimeCall, TestRuntime}, - CallSubType, PalletOperatingMode, ParaInfo, ParasInfo, RelayBlockNumber, + mock::{run_test, FreeHeadersInterval, RuntimeCall, TestRuntime}, + CallSubType, PalletOperatingMode, ParaInfo, ParasInfo, RelayBlockHash, RelayBlockNumber, }; + use bp_header_chain::StoredHeaderData; use bp_parachains::BestParaHeadHash; use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; use bp_runtime::BasicOperatingMode; @@ -175,15 +286,37 @@ mod tests { num: RelayBlockNumber, parachains: Vec<(ParaId, ParaHash)>, ) -> bool { - RuntimeCall::Parachains(crate::Call::::submit_parachain_heads { - at_relay_block: (num, Default::default()), + RuntimeCall::Parachains(crate::Call::::submit_parachain_heads_ex { + at_relay_block: (num, [num as u8; 32].into()), + parachains, + parachain_heads_proof: ParaHeadsProof { storage_proof: Vec::new() }, + is_free_execution_expected: false, + }) + .check_obsolete_submit_parachain_heads() + .is_ok() + } + + fn validate_free_submit_parachain_heads( + num: RelayBlockNumber, + parachains: Vec<(ParaId, ParaHash)>, + ) -> bool { + RuntimeCall::Parachains(crate::Call::::submit_parachain_heads_ex { + at_relay_block: (num, [num as u8; 32].into()), parachains, parachain_heads_proof: ParaHeadsProof { storage_proof: Vec::new() }, + is_free_execution_expected: true, }) .check_obsolete_submit_parachain_heads() .is_ok() } + fn insert_relay_block(num: RelayBlockNumber) { + pallet_bridge_grandpa::ImportedHeaders::::insert( + RelayBlockHash::from([num as u8; 32]), + StoredHeaderData { number: num, state_root: RelayBlockHash::from([10u8; 32]) }, + ); + } + fn sync_to_relay_header_10() { ParasInfo::::insert( ParaId(1), @@ -244,6 +377,7 @@ mod tests { // when current best finalized is #10 and we're trying to import header#15 => tx is // accepted sync_to_relay_header_10(); + insert_relay_block(15); assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())])); }); } @@ -260,4 +394,65 @@ mod tests { )); }); } + + #[test] + fn extension_rejects_initial_parachain_head_if_missing_relay_chain_header() { + run_test(|| { + // when relay chain header is unknown => "obsolete" + assert!(!validate_submit_parachain_heads(10, vec![(ParaId(1), [1u8; 32].into())])); + // when relay chain header is unknown => "ok" + insert_relay_block(10); + assert!(validate_submit_parachain_heads(10, vec![(ParaId(1), [1u8; 32].into())])); + }); + } + + #[test] + fn extension_rejects_free_parachain_head_if_missing_relay_chain_header() { + run_test(|| { + sync_to_relay_header_10(); + // when relay chain header is unknown => "obsolete" + assert!(!validate_submit_parachain_heads(15, vec![(ParaId(2), [15u8; 32].into())])); + // when relay chain header is unknown => "ok" + insert_relay_block(15); + assert!(validate_submit_parachain_heads(15, vec![(ParaId(2), [15u8; 32].into())])); + }); + } + + #[test] + fn extension_rejects_free_parachain_head_if_no_free_slots_remaining() { + run_test(|| { + // when current best finalized is #10 and we're trying to import header#15 => tx should + // be accepted + sync_to_relay_header_10(); + insert_relay_block(15); + // ... but since we have specified `is_free_execution_expected = true`, it'll be + // rejected + assert!(!validate_free_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())])); + // ... if we have specify `is_free_execution_expected = false`, it'll be accepted + assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())])); + }); + } + + #[test] + fn extension_rejects_free_parachain_head_if_improves_by_is_below_expected() { + run_test(|| { + // when current best finalized is #10 and we're trying to import header#15 => tx should + // be accepted + sync_to_relay_header_10(); + insert_relay_block(10 + FreeHeadersInterval::get() - 1); + insert_relay_block(10 + FreeHeadersInterval::get()); + // try to submit at 10 + FreeHeadersInterval::get() - 1 => failure + let relay_header = 10 + FreeHeadersInterval::get() - 1; + assert!(!validate_free_submit_parachain_heads( + relay_header, + vec![(ParaId(1), [2u8; 32].into())] + )); + // try to submit at 10 + FreeHeadersInterval::get() => ok + let relay_header = 10 + FreeHeadersInterval::get(); + assert!(validate_free_submit_parachain_heads( + relay_header, + vec![(ParaId(1), [2u8; 32].into())] + )); + }); + } } diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 1363a637604d1202ffc4bf799bf7ced180d9fe53..61e04aed3770dcaa9cb611dc754aad21325e1b39 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -32,6 +32,7 @@ use bp_parachains::{parachain_head_storage_key_at_source, ParaInfo, ParaStoredHe use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; use bp_runtime::{Chain, HashOf, HeaderId, HeaderIdOf, Parachain, StorageProofError}; use frame_support::{dispatch::PostDispatchInfo, DefaultNoBound}; +use pallet_bridge_grandpa::SubmitFinalityProofHelper; use sp_std::{marker::PhantomData, vec::Vec}; #[cfg(feature = "runtime-benchmarks")] @@ -92,7 +93,8 @@ pub mod pallet { BoundedStorageValue<>::MaxParaHeadDataSize, ParaStoredHeaderData>; /// Weight info of the given parachains pallet. pub type WeightInfoOf = >::WeightInfo; - type GrandpaPalletOf = + /// Bridge GRANDPA pallet that is used to verify parachain proofs. + pub type GrandpaPalletOf = pallet_bridge_grandpa::Pallet>::BridgesGrandpaPalletInstance>; #[pallet::event] @@ -192,6 +194,21 @@ pub mod pallet { /// /// The GRANDPA pallet instance must be configured to import headers of relay chain that /// we're interested in. + /// + /// The associated GRANDPA pallet is also used to configure free parachain heads + /// submissions. The parachain head submission will be free if: + /// + /// 1) the submission contains exactly one parachain head update that succeeds; + /// + /// 2) the difference between relay chain block numbers, used to prove new parachain head + /// and previous best parachain head is larger than the `FreeHeadersInterval`, configured + /// at the associated GRANDPA pallet; + /// + /// 3) there are slots for free submissions, remaining at the block. This is also configured + /// at the associated GRANDPA pallet using `MaxFreeHeadersPerBlock` parameter. + /// + /// First parachain head submission is also free for the submitted, if free submissions + /// are yet accepted to this block. type BridgesGrandpaPalletInstance: 'static; /// Name of the original `paras` pallet in the `construct_runtime!()` call at the bridged @@ -335,10 +352,83 @@ pub mod pallet { at_relay_block: (RelayBlockNumber, RelayBlockHash), parachains: Vec<(ParaId, ParaHash)>, parachain_heads_proof: ParaHeadsProof, + ) -> DispatchResultWithPostInfo { + Self::submit_parachain_heads_ex( + origin, + at_relay_block, + parachains, + parachain_heads_proof, + false, + ) + } + + /// Change `PalletOwner`. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::call_index(1)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { + >::set_owner(origin, new_owner) + } + + /// Halt or resume all pallet operations. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::call_index(2)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + operating_mode: BasicOperatingMode, + ) -> DispatchResult { + >::set_operating_mode(origin, operating_mode) + } + + /// Submit proof of one or several parachain heads. + /// + /// The proof is supposed to be proof of some `Heads` entries from the + /// `polkadot-runtime-parachains::paras` pallet instance, deployed at the bridged chain. + /// The proof is supposed to be crafted at the `relay_header_hash` that must already be + /// imported by corresponding GRANDPA pallet at this chain. + /// + /// The call fails if: + /// + /// - the pallet is halted; + /// + /// - the relay chain block `at_relay_block` is not imported by the associated bridge + /// GRANDPA pallet. + /// + /// The call may succeed, but some heads may not be updated e.g. because pallet knows + /// better head or it isn't tracked by the pallet. + /// + /// The `is_free_execution_expected` parameter is not really used inside the call. It is + /// used by the transaction extension, which should be registered at the runtime level. If + /// this parameter is `true`, the transaction will be treated as invalid, if the call won't + /// be executed for free. If transaction extension is not used by the runtime, this + /// parameter is not used at all. + #[pallet::call_index(3)] + #[pallet::weight(WeightInfoOf::::submit_parachain_heads_weight( + T::DbWeight::get(), + parachain_heads_proof, + parachains.len() as _, + ))] + pub fn submit_parachain_heads_ex( + origin: OriginFor, + at_relay_block: (RelayBlockNumber, RelayBlockHash), + parachains: Vec<(ParaId, ParaHash)>, + parachain_heads_proof: ParaHeadsProof, + _is_free_execution_expected: bool, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; ensure_signed(origin)?; + let total_parachains = parachains.len(); + let free_headers_interval = + T::FreeHeadersInterval::get().unwrap_or(RelayBlockNumber::MAX); + // the pallet allows two kind of free submissions + // 1) if distance between all parachain heads is gte than the [`T::FreeHeadersInterval`] + // 2) if all heads are the first heads of their parachains + let mut free_parachain_heads = 0; + // we'll need relay chain header to verify that parachains heads are always increasing. let (relay_block_number, relay_block_hash) = at_relay_block; let relay_block = pallet_bridge_grandpa::ImportedHeaders::< @@ -358,6 +448,7 @@ pub mod pallet { parachains.len() as _, ); + let mut is_updated_something = false; let mut storage = GrandpaPalletOf::::storage_proof_checker( relay_block_hash, parachain_heads_proof.storage_proof, @@ -414,6 +505,7 @@ pub mod pallet { } // convert from parachain head into stored parachain head data + let parachain_head_size = parachain_head.0.len(); let parachain_head_data = match T::ParaStoredHeaderDataBuilder::try_build(parachain, ¶chain_head) { Some(parachain_head_data) => parachain_head_data, @@ -430,13 +522,30 @@ pub mod pallet { let update_result: Result<_, ()> = ParasInfo::::try_mutate(parachain, |stored_best_head| { + let is_free = parachain_head_size < + T::ParaStoredHeaderDataBuilder::max_free_head_size() as usize && + match stored_best_head { + Some(ref best_head) + if at_relay_block.0.saturating_sub( + best_head.best_head_hash.at_relay_block_number, + ) >= free_headers_interval => + true, + Some(_) => false, + None => true, + }; let artifacts = Pallet::::update_parachain_head( parachain, stored_best_head.take(), - relay_block_number, + HeaderId(relay_block_number, relay_block_hash), parachain_head_data, parachain_head_hash, )?; + + is_updated_something = true; + if is_free { + free_parachain_heads = free_parachain_heads + 1; + } + *stored_best_head = Some(artifacts.best_head); Ok(artifacts.prune_happened) }); @@ -467,28 +576,21 @@ pub mod pallet { Error::::HeaderChainStorageProof(HeaderChainError::StorageProof(e)) })?; - Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) - } - - /// Change `PalletOwner`. - /// - /// May only be called either by root, or by `PalletOwner`. - #[pallet::call_index(1)] - #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] - pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { - >::set_owner(origin, new_owner) - } + // check if we allow this submission for free + let is_free = total_parachains == 1 + && free_parachain_heads == total_parachains + && SubmitFinalityProofHelper::::has_free_header_slots(); + let pays_fee = if is_free { + log::trace!(target: LOG_TARGET, "Parachain heads update transaction is free"); + pallet_bridge_grandpa::on_free_header_imported::( + ); + Pays::No + } else { + log::trace!(target: LOG_TARGET, "Parachain heads update transaction is paid"); + Pays::Yes + }; - /// Halt or resume all pallet operations. - /// - /// May only be called either by root, or by `PalletOwner`. - #[pallet::call_index(2)] - #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] - pub fn set_operating_mode( - origin: OriginFor, - operating_mode: BasicOperatingMode, - ) -> DispatchResult { - >::set_operating_mode(origin, operating_mode) + Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee }) } } @@ -545,18 +647,20 @@ pub mod pallet { pub(super) fn update_parachain_head( parachain: ParaId, stored_best_head: Option, - new_at_relay_block_number: RelayBlockNumber, + new_at_relay_block: HeaderId, new_head_data: ParaStoredHeaderData, new_head_hash: ParaHash, ) -> Result { // check if head has been already updated at better relay chain block. Without this // check, we may import heads in random order let update = SubmitParachainHeadsInfo { - at_relay_block_number: new_at_relay_block_number, + at_relay_block: new_at_relay_block, para_id: parachain, para_head_hash: new_head_hash, + // doesn't actually matter here + is_free_execution_expected: false, }; - if SubmitParachainHeadsHelper::::is_obsolete(&update) { + if SubmitParachainHeadsHelper::::check_obsolete(&update).is_err() { Self::deposit_event(Event::RejectedObsoleteParachainHead { parachain, parachain_head_hash: new_head_hash, @@ -596,7 +700,7 @@ pub mod pallet { ImportedParaHashes::::try_get(parachain, next_imported_hash_position); let updated_best_para_head = ParaInfo { best_head_hash: BestParaHeadHash { - at_relay_block_number: new_at_relay_block_number, + at_relay_block_number: new_at_relay_block.0, head_hash: new_head_hash, }, next_imported_hash_position: (next_imported_hash_position + 1) % @@ -610,9 +714,10 @@ pub mod pallet { ImportedParaHeads::::insert(parachain, new_head_hash, updated_head_data); log::trace!( target: LOG_TARGET, - "Updated head of parachain {:?} to {}", + "Updated head of parachain {:?} to {} at relay block {}", parachain, new_head_hash, + new_at_relay_block.0, ); // remove old head @@ -696,14 +801,28 @@ impl, I: 'static, C: Parachain> HeaderChain pub fn initialize_for_benchmarks, I: 'static, PC: Parachain>( header: HeaderOf, ) { + use bp_runtime::HeaderIdProvider; + use sp_runtime::traits::Header; + + let relay_head = + pallet_bridge_grandpa::BridgedHeader::::new( + 0, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ); let parachain = ParaId(PC::PARACHAIN_ID); let parachain_head = ParaHead(header.encode()); let updated_head_data = T::ParaStoredHeaderDataBuilder::try_build(parachain, ¶chain_head) .expect("failed to build stored parachain head in benchmarks"); + pallet_bridge_grandpa::initialize_for_benchmarks::( + relay_head.clone(), + ); Pallet::::update_parachain_head( parachain, None, - 0, + relay_head.id(), updated_head_data, parachain_head.hash(), ) @@ -714,9 +833,9 @@ pub fn initialize_for_benchmarks, I: 'static, PC: Parachain::DbWeight; pub(crate) fn initialize(state_root: RelayBlockHash) -> RelayBlockHash { + pallet_bridge_grandpa::FreeHeadersRemaining::::set(Some(100)); pallet_bridge_grandpa::Pallet::::initialize( RuntimeOrigin::root(), bp_header_chain::InitializationData { @@ -770,10 +891,6 @@ pub(crate) mod tests { num: RelayBlockNumber, state_root: RelayBlockHash, ) -> (ParaHash, GrandpaJustification) { - pallet_bridge_grandpa::Pallet::::on_initialize( - 0, - ); - let header = test_relay_header(num, state_root); let hash = header.hash(); let justification = make_default_justification(&header); @@ -783,6 +900,7 @@ pub(crate) mod tests { Box::new(header), justification.clone(), TEST_GRANDPA_SET_ID, + false, ) ); @@ -908,7 +1026,7 @@ pub(crate) mod tests { run_test(|| { initialize(state_root); - // we're trying to update heads of parachains 1, 2 and 3 + // we're trying to update heads of parachains 1 and 3 let expected_weight = WeightInfo::submit_parachain_heads_weight(DbWeight::get(), &proof, 2); let result = Pallet::::submit_parachain_heads( @@ -918,9 +1036,10 @@ pub(crate) mod tests { proof, ); assert_ok!(result); + assert_eq!(result.expect("checked above").pays_fee, Pays::Yes); assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight)); - // but only 1 and 2 are updated, because proof is missing head of parachain#2 + // 1 and 3 are updated, because proof is missing head of parachain#2 assert_eq!(ParasInfo::::get(ParaId(1)), Some(initial_best_head(1))); assert_eq!(ParasInfo::::get(ParaId(2)), None); assert_eq!( @@ -989,7 +1108,9 @@ pub(crate) mod tests { run_test(|| { // start with relay block #0 and import head#5 of parachain#1 initialize(state_root_5); - assert_ok!(import_parachain_1_head(0, state_root_5, parachains_5, proof_5)); + let result = import_parachain_1_head(0, state_root_5, parachains_5, proof_5); + // first parachain head is imported for free + assert_eq!(result.unwrap().pays_fee, Pays::No); assert_eq!( ParasInfo::::get(ParaId(1)), Some(ParaInfo { @@ -1024,7 +1145,9 @@ pub(crate) mod tests { // import head#10 of parachain#1 at relay block #1 let (relay_1_hash, justification) = proceed(1, state_root_10); - assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10)); + let result = import_parachain_1_head(1, state_root_10, parachains_10, proof_10); + // second parachain head is imported for fee + assert_eq!(result.unwrap().pays_fee, Pays::Yes); assert_eq!( ParasInfo::::get(ParaId(1)), Some(ParaInfo { @@ -1647,4 +1770,143 @@ pub(crate) mod tests { ); }) } + + #[test] + fn may_be_free_for_submitting_filtered_heads() { + run_test(|| { + let (state_root, proof, parachains) = + prepare_parachain_heads_proof::(vec![(2, head_data(2, 5))]); + // start with relay block #0 and import head#5 of parachain#2 + initialize(state_root); + // first submission is free + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (0, test_relay_header(0, state_root).hash()), + parachains.clone(), + proof.clone(), + ); + assert_eq!(result.unwrap().pays_fee, Pays::No); + // next submission is NOT free, because we haven't updated anything + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (0, test_relay_header(0, state_root).hash()), + parachains, + proof, + ); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + // then we submit new head, proved at relay block `FreeHeadersInterval - 1` => Pays::Yes + let (state_root, proof, parachains) = prepare_parachain_heads_proof::< + RegularParachainHeader, + >(vec![(2, head_data(2, 50))]); + let relay_block_number = FreeHeadersInterval::get() - 1; + proceed(relay_block_number, state_root); + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (relay_block_number, test_relay_header(relay_block_number, state_root).hash()), + parachains, + proof, + ); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + // then we submit new head, proved after `FreeHeadersInterval` => Pays::No + let (state_root, proof, parachains) = prepare_parachain_heads_proof::< + RegularParachainHeader, + >(vec![(2, head_data(2, 100))]); + let relay_block_number = relay_block_number + FreeHeadersInterval::get(); + proceed(relay_block_number, state_root); + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (relay_block_number, test_relay_header(relay_block_number, state_root).hash()), + parachains, + proof, + ); + assert_eq!(result.unwrap().pays_fee, Pays::No); + // then we submit new BIG head, proved after `FreeHeadersInterval` => Pays::Yes + // then we submit new head, proved after `FreeHeadersInterval` => Pays::No + let mut large_head = head_data(2, 100); + large_head.0.extend(&[42u8; BigParachain::MAX_HEADER_SIZE as _]); + let (state_root, proof, parachains) = + prepare_parachain_heads_proof::(vec![(2, large_head)]); + let relay_block_number = relay_block_number + FreeHeadersInterval::get(); + proceed(relay_block_number, state_root); + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (relay_block_number, test_relay_header(relay_block_number, state_root).hash()), + parachains, + proof, + ); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + }) + } + + #[test] + fn grandpa_and_parachain_pallets_share_free_headers_counter() { + run_test(|| { + initialize(Default::default()); + // set free headers limit to `4` + let mut free_headers_remaining = 4; + pallet_bridge_grandpa::FreeHeadersRemaining::::set( + Some(free_headers_remaining), + ); + // import free GRANDPA and parachain headers + let mut relay_block_number = 0; + for i in 0..2 { + // import free GRANDPA header + let (state_root, proof, parachains) = prepare_parachain_heads_proof::< + RegularParachainHeader, + >(vec![(2, head_data(2, 5 + i))]); + relay_block_number = relay_block_number + FreeHeadersInterval::get(); + proceed(relay_block_number, state_root); + assert_eq!( + pallet_bridge_grandpa::FreeHeadersRemaining::< + TestRuntime, + BridgesGrandpaPalletInstance, + >::get(), + Some(free_headers_remaining - 1), + ); + free_headers_remaining = free_headers_remaining - 1; + // import free parachain header + assert_ok!(Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (relay_block_number, test_relay_header(relay_block_number, state_root).hash()), + parachains, + proof, + ),); + assert_eq!( + pallet_bridge_grandpa::FreeHeadersRemaining::< + TestRuntime, + BridgesGrandpaPalletInstance, + >::get(), + Some(free_headers_remaining - 1), + ); + free_headers_remaining = free_headers_remaining - 1; + } + // try to import free GRANDPA header => non-free execution + let (state_root, proof, parachains) = + prepare_parachain_heads_proof::(vec![(2, head_data(2, 7))]); + relay_block_number = relay_block_number + FreeHeadersInterval::get(); + let result = pallet_bridge_grandpa::Pallet::::submit_finality_proof_ex( + RuntimeOrigin::signed(1), + Box::new(test_relay_header(relay_block_number, state_root)), + make_default_justification(&test_relay_header(relay_block_number, state_root)), + TEST_GRANDPA_SET_ID, + false, + ); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + // try to import free parachain header => non-free execution + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (relay_block_number, test_relay_header(relay_block_number, state_root).hash()), + parachains, + proof, + ); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + assert_eq!( + pallet_bridge_grandpa::FreeHeadersRemaining::< + TestRuntime, + BridgesGrandpaPalletInstance, + >::get(), + Some(0), + ); + }); + } } diff --git a/bridges/modules/parachains/src/mock.rs b/bridges/modules/parachains/src/mock.rs index d9cbabf850ec99ee13baa0f8bfc013b1192bd000..dbb62845392d5fd2f408744f4f8a2321ec4bd34d 100644 --- a/bridges/modules/parachains/src/mock.rs +++ b/bridges/modules/parachains/src/mock.rs @@ -70,6 +70,7 @@ impl Chain for Parachain1 { impl Parachain for Parachain1 { const PARACHAIN_ID: u32 = 1; + const MAX_HEADER_SIZE: u32 = 1_024; } pub struct Parachain2; @@ -96,6 +97,7 @@ impl Chain for Parachain2 { impl Parachain for Parachain2 { const PARACHAIN_ID: u32 = 2; + const MAX_HEADER_SIZE: u32 = 1_024; } pub struct Parachain3; @@ -122,6 +124,7 @@ impl Chain for Parachain3 { impl Parachain for Parachain3 { const PARACHAIN_ID: u32 = 3; + const MAX_HEADER_SIZE: u32 = 1_024; } // this parachain is using u128 as block number and stored head data size exceeds limit @@ -149,6 +152,7 @@ impl Chain for BigParachain { impl Parachain for BigParachain { const PARACHAIN_ID: u32 = 4; + const MAX_HEADER_SIZE: u32 = 2_048; } construct_runtime! { @@ -168,12 +172,14 @@ impl frame_system::Config for TestRuntime { parameter_types! { pub const HeadersToKeep: u32 = 5; + pub const FreeHeadersInterval: u32 = 15; } impl pallet_bridge_grandpa::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type BridgedChain = TestBridgedChain; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<2>; + type MaxFreeHeadersPerBlock = ConstU32<2>; + type FreeHeadersInterval = FreeHeadersInterval; type HeadersToKeep = HeadersToKeep; type WeightInfo = (); } @@ -181,7 +187,8 @@ impl pallet_bridge_grandpa::Config for TestRun impl pallet_bridge_grandpa::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type BridgedChain = TestBridgedChain; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<2>; + type MaxFreeHeadersPerBlock = ConstU32<2>; + type FreeHeadersInterval = FreeHeadersInterval; type HeadersToKeep = HeadersToKeep; type WeightInfo = (); } diff --git a/bridges/modules/parachains/src/weights_ext.rs b/bridges/modules/parachains/src/weights_ext.rs index 393086a85690fcc2846c1708bc788e1d67a61d66..64dad625de08b3fd0cd96c255ee80fafa8df2be9 100644 --- a/bridges/modules/parachains/src/weights_ext.rs +++ b/bridges/modules/parachains/src/weights_ext.rs @@ -36,6 +36,20 @@ pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024; /// Extended weight info. pub trait WeightInfoExt: WeightInfo { + // Our configuration assumes that the runtime has special signed extensions used to: + // + // 1) boost priority of `submit_parachain_heads` transactions; + // + // 2) slash relayer if he submits an invalid transaction. + // + // We read and update storage values of other pallets (`pallet-bridge-relayers` and + // balances/assets pallet). So we need to add this weight to the weight of our call. + // Hence two following methods. + + /// Extra weight that is added to the `submit_finality_proof` call weight by signed extensions + /// that are declared at runtime level. + fn submit_parachain_heads_overhead_from_runtime() -> Weight; + /// Storage proof overhead, that is included in every storage proof. /// /// The relayer would pay some extra fee for additional proof bytes, since they mean @@ -65,7 +79,10 @@ pub trait WeightInfoExt: WeightInfo { let pruning_weight = Self::parachain_head_pruning_weight(db_weight).saturating_mul(parachains_count as u64); - base_weight.saturating_add(proof_size_overhead).saturating_add(pruning_weight) + base_weight + .saturating_add(proof_size_overhead) + .saturating_add(pruning_weight) + .saturating_add(Self::submit_parachain_heads_overhead_from_runtime()) } /// Returns weight of single parachain head storage update. @@ -95,12 +112,20 @@ pub trait WeightInfoExt: WeightInfo { } impl WeightInfoExt for () { + fn submit_parachain_heads_overhead_from_runtime() -> Weight { + Weight::zero() + } + fn expected_extra_storage_proof_size() -> u32 { EXTRA_STORAGE_PROOF_SIZE } } impl WeightInfoExt for BridgeWeight { + fn submit_parachain_heads_overhead_from_runtime() -> Weight { + Weight::zero() + } + fn expected_extra_storage_proof_size() -> u32 { EXTRA_STORAGE_PROOF_SIZE } diff --git a/bridges/modules/relayers/src/benchmarking.rs b/bridges/modules/relayers/src/benchmarking.rs index 00c3814a4c38d9bf0f18b70c0eedc75c239b8ad0..ca312d44edfddd286eae1715655d538b6b00f070 100644 --- a/bridges/modules/relayers/src/benchmarking.rs +++ b/bridges/modules/relayers/src/benchmarking.rs @@ -106,7 +106,7 @@ benchmarks! { let slash_destination = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain); T::prepare_rewards_account(slash_destination, Zero::zero()); }: { - crate::Pallet::::slash_and_deregister(&relayer, slash_destination) + crate::Pallet::::slash_and_deregister(&relayer, slash_destination.into()) } verify { assert!(!crate::Pallet::::is_registration_active(&relayer)); diff --git a/bridges/modules/relayers/src/lib.rs b/bridges/modules/relayers/src/lib.rs index ce66c9df48e01c829a55d03ffe4437c41f006a6b..7a3a0f9ea94cbe5768bf6ee8c850355193ea44f0 100644 --- a/bridges/modules/relayers/src/lib.rs +++ b/bridges/modules/relayers/src/lib.rs @@ -21,7 +21,8 @@ #![warn(missing_docs)] use bp_relayers::{ - PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, + ExplicitOrAccountParams, PaymentProcedure, Registration, RelayerRewardsKeyProvider, + RewardsAccountParams, StakeAndSlash, }; use bp_runtime::StorageDoubleMapKeyProvider; use frame_support::fail; @@ -242,7 +243,7 @@ pub mod pallet { /// It may fail inside, but error is swallowed and we only log it. pub fn slash_and_deregister( relayer: &T::AccountId, - slash_destination: RewardsAccountParams, + slash_destination: ExplicitOrAccountParams, ) { let registration = match RegisteredRelayers::::take(relayer) { Some(registration) => registration, @@ -259,7 +260,7 @@ pub mod pallet { match T::StakeAndSlash::repatriate_reserved( relayer, - slash_destination, + slash_destination.clone(), registration.stake, ) { Ok(failed_to_slash) if failed_to_slash.is_zero() => { diff --git a/bridges/modules/relayers/src/stake_adapter.rs b/bridges/modules/relayers/src/stake_adapter.rs index 88af9b1877bfe85614f081ee66dbb28586b1d34b..7ba90d91dfd94e49bf0ff6ee8fcc06f80e287c41 100644 --- a/bridges/modules/relayers/src/stake_adapter.rs +++ b/bridges/modules/relayers/src/stake_adapter.rs @@ -17,7 +17,7 @@ //! Code that allows `NamedReservableCurrency` to be used as a `StakeAndSlash` //! mechanism of the relayers pallet. -use bp_relayers::{PayRewardFromAccount, RewardsAccountParams, StakeAndSlash}; +use bp_relayers::{ExplicitOrAccountParams, PayRewardFromAccount, StakeAndSlash}; use codec::Codec; use frame_support::traits::{tokens::BalanceStatus, NamedReservableCurrency}; use sp_runtime::{traits::Get, DispatchError, DispatchResult}; @@ -55,11 +55,14 @@ where fn repatriate_reserved( relayer: &AccountId, - beneficiary: RewardsAccountParams, + beneficiary: ExplicitOrAccountParams, amount: Currency::Balance, ) -> Result { - let beneficiary_account = - PayRewardFromAccount::<(), AccountId>::rewards_account(beneficiary); + let beneficiary_account = match beneficiary { + ExplicitOrAccountParams::Explicit(account) => account, + ExplicitOrAccountParams::Params(params) => + PayRewardFromAccount::<(), AccountId>::rewards_account(params), + }; Currency::repatriate_reserved_named( &ReserveId::get(), relayer, @@ -134,7 +137,11 @@ mod tests { Balances::mint_into(&beneficiary_account, expected_balance).unwrap(); assert_eq!( - TestStakeAndSlash::repatriate_reserved(&1, beneficiary, test_stake()), + TestStakeAndSlash::repatriate_reserved( + &1, + ExplicitOrAccountParams::Params(beneficiary), + test_stake() + ), Ok(test_stake()) ); assert_eq!(Balances::free_balance(1), 0); @@ -146,7 +153,11 @@ mod tests { Balances::mint_into(&2, test_stake() * 2).unwrap(); TestStakeAndSlash::reserve(&2, test_stake() / 3).unwrap(); assert_eq!( - TestStakeAndSlash::repatriate_reserved(&2, beneficiary, test_stake()), + TestStakeAndSlash::repatriate_reserved( + &2, + ExplicitOrAccountParams::Params(beneficiary), + test_stake() + ), Ok(test_stake() - test_stake() / 3) ); assert_eq!(Balances::free_balance(2), test_stake() * 2 - test_stake() / 3); @@ -158,7 +169,11 @@ mod tests { Balances::mint_into(&3, test_stake() * 2).unwrap(); TestStakeAndSlash::reserve(&3, test_stake()).unwrap(); assert_eq!( - TestStakeAndSlash::repatriate_reserved(&3, beneficiary, test_stake()), + TestStakeAndSlash::repatriate_reserved( + &3, + ExplicitOrAccountParams::Params(beneficiary), + test_stake() + ), Ok(0) ); assert_eq!(Balances::free_balance(3), test_stake()); @@ -176,7 +191,12 @@ mod tests { Balances::mint_into(&3, test_stake() * 2).unwrap(); TestStakeAndSlash::reserve(&3, test_stake()).unwrap(); - assert!(TestStakeAndSlash::repatriate_reserved(&3, beneficiary, test_stake()).is_err()); + assert!(TestStakeAndSlash::repatriate_reserved( + &3, + ExplicitOrAccountParams::Params(beneficiary), + test_stake() + ) + .is_err()); assert_eq!(Balances::free_balance(3), test_stake()); assert_eq!(Balances::reserved_balance(3), test_stake()); assert_eq!(Balances::free_balance(beneficiary_account), 0); diff --git a/bridges/modules/xcm-bridge-hub-router/src/lib.rs b/bridges/modules/xcm-bridge-hub-router/src/lib.rs index 5d0be41b1b5588e3ddc8c6306c9bf83ec29d6056..ece72ac8494b763035b7ebfead71e3e280bf4b5c 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/lib.rs @@ -191,6 +191,10 @@ pub mod pallet { impl, I: 'static> Pallet { /// Called when new message is sent (queued to local outbound XCM queue) over the bridge. pub(crate) fn on_message_sent_to_bridge(message_size: u32) { + log::trace!( + target: LOG_TARGET, + "on_message_sent_to_bridge - message_size: {message_size:?}", + ); let _ = Bridge::::try_mutate(|bridge| { let is_channel_with_bridge_hub_congested = T::WithBridgeHubChannel::is_congested(); let is_bridge_congested = bridge.is_congested; @@ -238,14 +242,16 @@ impl, I: 'static> ExporterFor for Pallet { remote_location: &InteriorLocation, message: &Xcm<()>, ) -> Option<(Location, Option)> { + log::trace!( + target: LOG_TARGET, + "exporter_for - network: {network:?}, remote_location: {remote_location:?}, msg: {message:?}", + ); // ensure that the message is sent to the expected bridged network (if specified). if let Some(bridged_network) = T::BridgedNetworkId::get() { if *network != bridged_network { log::trace!( target: LOG_TARGET, - "Router with bridged_network_id {:?} does not support bridging to network {:?}!", - bridged_network, - network, + "Router with bridged_network_id {bridged_network:?} does not support bridging to network {network:?}!", ); return None } @@ -300,7 +306,7 @@ impl, I: 'static> ExporterFor for Pallet { log::info!( target: LOG_TARGET, - "Going to send message to {:?} ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}", + "Validate send message to {:?} ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}", (network, remote_location), message_size, fee, @@ -321,40 +327,59 @@ impl, I: 'static> SendXcm for Pallet { dest: &mut Option, xcm: &mut Option>, ) -> SendResult { - // `dest` and `xcm` are required here - let dest_ref = dest.as_ref().ok_or(SendError::MissingArgument)?; - let xcm_ref = xcm.as_ref().ok_or(SendError::MissingArgument)?; - - // we won't have an access to `dest` and `xcm` in the `deliver` method, so precompute - // everything required here - let message_size = xcm_ref.encoded_size() as _; - - // bridge doesn't support oversized/overweight messages now. So it is better to drop such - // messages here than at the bridge hub. Let's check the message size. - if message_size > HARD_MESSAGE_SIZE_LIMIT { - return Err(SendError::ExceedsMaxMessageSize) - } + log::trace!(target: LOG_TARGET, "validate - msg: {xcm:?}, destination: {dest:?}"); + + // In case of success, the `ViaBridgeHubExporter` can modify XCM instructions and consume + // `dest` / `xcm`, so we retain the clone of original message and the destination for later + // `DestinationVersion` validation. + let xcm_to_dest_clone = xcm.clone(); + let dest_clone = dest.clone(); + + // First, use the inner exporter to validate the destination to determine if it is even + // routable. If it is not, return an error. If it is, then the XCM is extended with + // instructions to pay the message fee at the sibling/child bridge hub. The cost will + // include both the cost of (1) delivery to the sibling bridge hub (returned by + // `Config::ToBridgeHubSender`) and (2) delivery to the bridged bridge hub (returned by + // `Self::exporter_for`). + match ViaBridgeHubExporter::::validate(dest, xcm) { + Ok((ticket, cost)) => { + // If the ticket is ok, it means we are routing with this router, so we need to + // apply more validations to the cloned `dest` and `xcm`, which are required here. + let xcm_to_dest_clone = xcm_to_dest_clone.ok_or(SendError::MissingArgument)?; + let dest_clone = dest_clone.ok_or(SendError::MissingArgument)?; + + // We won't have access to `dest` and `xcm` in the `deliver` method, so we need to + // precompute everything required here. However, `dest` and `xcm` were consumed by + // `ViaBridgeHubExporter`, so we need to use their clones. + let message_size = xcm_to_dest_clone.encoded_size() as _; + + // The bridge doesn't support oversized or overweight messages. Therefore, it's + // better to drop such messages here rather than at the bridge hub. Let's check the + // message size." + if message_size > HARD_MESSAGE_SIZE_LIMIT { + return Err(SendError::ExceedsMaxMessageSize) + } - // We need to ensure that the known `dest`'s XCM version can comprehend the current `xcm` - // program. This may seem like an additional, unnecessary check, but it is not. A similar - // check is probably performed by the `ViaBridgeHubExporter`, which attempts to send a - // versioned message to the sibling bridge hub. However, the local bridge hub may have a - // higher XCM version than the remote `dest`. Once again, it is better to discard such - // messages here than at the bridge hub (e.g., to avoid losing funds). - let destination_version = T::DestinationVersion::get_version_for(dest_ref) - .ok_or(SendError::DestinationUnsupported)?; - let _ = VersionedXcm::from(xcm_ref.clone()) - .into_version(destination_version) - .map_err(|()| SendError::DestinationUnsupported)?; - - // just use exporter to validate destination and insert instructions to pay message fee - // at the sibling/child bridge hub - // - // the cost will include both cost of: (1) to-sibling bridge hub delivery (returned by - // the `Config::ToBridgeHubSender`) and (2) to-bridged bridge hub delivery (returned by - // `Self::exporter_for`) - ViaBridgeHubExporter::::validate(dest, xcm) - .map(|(ticket, cost)| ((message_size, ticket), cost)) + // We need to ensure that the known `dest`'s XCM version can comprehend the current + // `xcm` program. This may seem like an additional, unnecessary check, but it is + // not. A similar check is probably performed by the `ViaBridgeHubExporter`, which + // attempts to send a versioned message to the sibling bridge hub. However, the + // local bridge hub may have a higher XCM version than the remote `dest`. Once + // again, it is better to discard such messages here than at the bridge hub (e.g., + // to avoid losing funds). + let destination_version = T::DestinationVersion::get_version_for(&dest_clone) + .ok_or(SendError::DestinationUnsupported)?; + let _ = VersionedXcm::from(xcm_to_dest_clone) + .into_version(destination_version) + .map_err(|()| SendError::DestinationUnsupported)?; + + Ok(((message_size, ticket), cost)) + }, + Err(e) => { + log::trace!(target: LOG_TARGET, "validate - ViaBridgeHubExporter - error: {e:?}"); + Err(e) + }, + } } fn deliver(ticket: Self::Ticket) -> Result { @@ -366,6 +391,7 @@ impl, I: 'static> SendXcm for Pallet { // increase delivery fee factor if required Self::on_message_sent_to_bridge(message_size); + log::trace!(target: LOG_TARGET, "deliver - message sent, xcm_hash: {xcm_hash:?}"); Ok(xcm_hash) } } @@ -444,24 +470,51 @@ mod tests { #[test] fn not_applicable_if_destination_is_within_other_network() { run_test(|| { + // unroutable dest + let dest = Location::new(2, [GlobalConsensus(ByGenesis([0; 32])), Parachain(1000)]); + let xcm: Xcm<()> = vec![ClearOrigin].into(); + + // check that router does not consume when `NotApplicable` + let mut xcm_wrapper = Some(xcm.clone()); assert_eq!( - send_xcm::( - Location::new(2, [GlobalConsensus(Rococo), Parachain(1000)]), - vec![].into(), - ), + XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut xcm_wrapper), Err(SendError::NotApplicable), ); + // XCM is NOT consumed and untouched + assert_eq!(Some(xcm.clone()), xcm_wrapper); + + // check the full `send_xcm` + assert_eq!(send_xcm::(dest, xcm,), Err(SendError::NotApplicable),); }); } #[test] fn exceeds_max_message_size_if_size_is_above_hard_limit() { run_test(|| { + // routable dest with XCM version + let dest = + Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]); + // oversized XCM + let xcm: Xcm<()> = vec![ClearOrigin; HARD_MESSAGE_SIZE_LIMIT as usize].into(); + + // dest is routable with the inner router + assert_ok!(ViaBridgeHubExporter::::validate( + &mut Some(dest.clone()), + &mut Some(xcm.clone()) + )); + + // check for oversized message + let mut xcm_wrapper = Some(xcm.clone()); + assert_eq!( + XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut xcm_wrapper), + Err(SendError::ExceedsMaxMessageSize), + ); + // XCM is consumed by the inner router + assert!(xcm_wrapper.is_none()); + + // check the full `send_xcm` assert_eq!( - send_xcm::( - Location::new(2, [GlobalConsensus(Rococo), Parachain(1000)]), - vec![ClearOrigin; HARD_MESSAGE_SIZE_LIMIT as usize].into(), - ), + send_xcm::(dest, xcm,), Err(SendError::ExceedsMaxMessageSize), ); }); @@ -470,11 +523,28 @@ mod tests { #[test] fn destination_unsupported_if_wrap_version_fails() { run_test(|| { + // routable dest but we don't know XCM version + let dest = UnknownXcmVersionForRoutableLocation::get(); + let xcm: Xcm<()> = vec![ClearOrigin].into(); + + // dest is routable with the inner router + assert_ok!(ViaBridgeHubExporter::::validate( + &mut Some(dest.clone()), + &mut Some(xcm.clone()) + )); + + // check that it does not pass XCM version check + let mut xcm_wrapper = Some(xcm.clone()); + assert_eq!( + XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut xcm_wrapper), + Err(SendError::DestinationUnsupported), + ); + // XCM is consumed by the inner router + assert!(xcm_wrapper.is_none()); + + // check the full `send_xcm` assert_eq!( - send_xcm::( - UnknownXcmVersionLocation::get(), - vec![ClearOrigin].into(), - ), + send_xcm::(dest, xcm,), Err(SendError::DestinationUnsupported), ); }); diff --git a/bridges/modules/xcm-bridge-hub-router/src/mock.rs b/bridges/modules/xcm-bridge-hub-router/src/mock.rs index 54e10966d51b23e7be5010b39cb9cb7d6a3b0118..20c86d1da9a2e315c9465b7e8fe0633401a3a3d4 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/mock.rs @@ -61,7 +61,7 @@ parameter_types! { Some((BridgeFeeAsset::get(), BASE_FEE).into()) ) ]; - pub UnknownXcmVersionLocation: Location = Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(9999)]); + pub UnknownXcmVersionForRoutableLocation: Location = Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(9999)]); } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] @@ -76,7 +76,7 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { type BridgedNetworkId = BridgedNetworkId; type Bridges = NetworkExportTable; type DestinationVersion = - LatestOrNoneForLocationVersionChecker>; + LatestOrNoneForLocationVersionChecker>; type BridgeHubOrigin = EnsureRoot; type ToBridgeHubSender = TestToBridgeHubSender; diff --git a/bridges/primitives/header-chain/src/lib.rs b/bridges/primitives/header-chain/src/lib.rs index 98fb9ff83d8335fc04fbce7f9e566c73d15752a8..ad496012c6a3f95d636a2c1ae52fcb5f4ec5434d 100644 --- a/bridges/primitives/header-chain/src/lib.rs +++ b/bridges/primitives/header-chain/src/lib.rs @@ -32,7 +32,9 @@ use core::{clone::Clone, cmp::Eq, default::Default, fmt::Debug}; use frame_support::PalletError; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; -use sp_consensus_grandpa::{AuthorityList, ConsensusLog, SetId, GRANDPA_ENGINE_ID}; +use sp_consensus_grandpa::{ + AuthorityList, ConsensusLog, ScheduledChange, SetId, GRANDPA_ENGINE_ID, +}; use sp_runtime::{traits::Header as HeaderT, Digest, RuntimeDebug}; use sp_std::{boxed::Box, vec::Vec}; @@ -147,24 +149,23 @@ pub struct GrandpaConsensusLogReader(sp_std::marker::PhantomData impl GrandpaConsensusLogReader { /// Find and return scheduled (regular) change digest item. - pub fn find_scheduled_change( - digest: &Digest, - ) -> Option> { + pub fn find_scheduled_change(digest: &Digest) -> Option> { + use sp_runtime::generic::OpaqueDigestItemId; + let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID); + + let filter_log = |log: ConsensusLog| match log { + ConsensusLog::ScheduledChange(change) => Some(change), + _ => None, + }; + // find the first consensus digest with the right ID which converts to // the right kind of consensus log. - digest - .convert_first(|log| log.consensus_try_to(&GRANDPA_ENGINE_ID)) - .and_then(|log| match log { - ConsensusLog::ScheduledChange(change) => Some(change), - _ => None, - }) + digest.convert_first(|l| l.try_to(id).and_then(filter_log)) } /// Find and return forced change digest item. Or light client can't do anything /// with forced changes, so we can't accept header with the forced change digest. - pub fn find_forced_change( - digest: &Digest, - ) -> Option<(Number, sp_consensus_grandpa::ScheduledChange)> { + pub fn find_forced_change(digest: &Digest) -> Option<(Number, ScheduledChange)> { // find the first consensus digest with the right ID which converts to // the right kind of consensus log. digest @@ -346,7 +347,7 @@ mod tests { use super::*; use bp_runtime::ChainId; use frame_support::weights::Weight; - use sp_runtime::{testing::H256, traits::BlakeTwo256, MultiSignature}; + use sp_runtime::{testing::H256, traits::BlakeTwo256, DigestItem, MultiSignature}; struct TestChain; @@ -385,4 +386,35 @@ mod tests { max_expected_submit_finality_proof_arguments_size::(false, 100), ); } + + #[test] + fn find_scheduled_change_works() { + let scheduled_change = ScheduledChange { next_authorities: vec![], delay: 0 }; + + // first + let mut digest = Digest::default(); + digest.push(DigestItem::Consensus( + GRANDPA_ENGINE_ID, + ConsensusLog::ScheduledChange(scheduled_change.clone()).encode(), + )); + assert_eq!( + GrandpaConsensusLogReader::find_scheduled_change(&digest), + Some(scheduled_change.clone()) + ); + + // not first + let mut digest = Digest::default(); + digest.push(DigestItem::Consensus( + GRANDPA_ENGINE_ID, + ConsensusLog::::OnDisabled(0).encode(), + )); + digest.push(DigestItem::Consensus( + GRANDPA_ENGINE_ID, + ConsensusLog::ScheduledChange(scheduled_change.clone()).encode(), + )); + assert_eq!( + GrandpaConsensusLogReader::find_scheduled_change(&digest), + Some(scheduled_change.clone()) + ); + } } diff --git a/bridges/primitives/parachains/src/lib.rs b/bridges/primitives/parachains/src/lib.rs index 692bbd99ecef38535bb65a18dac09a77f1f1eca2..142c6e9b08923fdd2934fb7f3b9c2d12788fc8b9 100644 --- a/bridges/primitives/parachains/src/lib.rs +++ b/bridges/primitives/parachains/src/lib.rs @@ -116,6 +116,10 @@ impl ParaStoredHeaderData { /// Stored parachain head data builder. pub trait ParaStoredHeaderDataBuilder { + /// Maximal parachain head size that we may accept for free. All heads above + /// this limit are submitted for a regular fee. + fn max_free_head_size() -> u32; + /// Return number of parachains that are supported by this builder. fn supported_parachains() -> u32; @@ -127,6 +131,10 @@ pub trait ParaStoredHeaderDataBuilder { pub struct SingleParaStoredHeaderDataBuilder(PhantomData); impl ParaStoredHeaderDataBuilder for SingleParaStoredHeaderDataBuilder { + fn max_free_head_size() -> u32 { + C::MAX_HEADER_SIZE + } + fn supported_parachains() -> u32 { 1 } @@ -147,6 +155,17 @@ impl ParaStoredHeaderDataBuilder for SingleParaStoredHeaderDataBui #[impl_trait_for_tuples::impl_for_tuples(1, 30)] #[tuple_types_custom_trait_bound(Parachain)] impl ParaStoredHeaderDataBuilder for C { + fn max_free_head_size() -> u32 { + let mut result = 0_u32; + for_tuples!( #( + result = sp_std::cmp::max( + result, + SingleParaStoredHeaderDataBuilder::::max_free_head_size(), + ); + )* ); + result + } + fn supported_parachains() -> u32 { let mut result = 0; for_tuples!( #( diff --git a/bridges/primitives/relayers/src/lib.rs b/bridges/primitives/relayers/src/lib.rs index c808c437b54cbaaa5813067a6413fe7189336ee6..2a9ef6a8e1e9aba999ea90045447f7a87fb3813b 100644 --- a/bridges/primitives/relayers/src/lib.rs +++ b/bridges/primitives/relayers/src/lib.rs @@ -19,7 +19,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -pub use registration::{Registration, StakeAndSlash}; +pub use registration::{ExplicitOrAccountParams, Registration, StakeAndSlash}; use bp_messages::LaneId; use bp_runtime::{ChainId, StorageDoubleMapKeyProvider}; diff --git a/bridges/primitives/relayers/src/registration.rs b/bridges/primitives/relayers/src/registration.rs index bc2d0d127aefec3c6982b17915202cc0f87984f2..9d9b7e4812201390c6831ff020c12c8cc995c17a 100644 --- a/bridges/primitives/relayers/src/registration.rs +++ b/bridges/primitives/relayers/src/registration.rs @@ -21,7 +21,7 @@ //! required finality proofs). This extension boosts priority of message delivery //! transactions, based on the number of bundled messages. So transaction with more //! messages has larger priority than the transaction with less messages. -//! See `bridge_runtime_common::priority_calculator` for details; +//! See `bridge_runtime_common::extensions::priority_calculator` for details; //! //! This encourages relayers to include more messages to their delivery transactions. //! At the same time, we are not verifying storage proofs before boosting @@ -46,6 +46,21 @@ use sp_runtime::{ DispatchError, DispatchResult, }; +/// Either explicit account reference or `RewardsAccountParams`. +#[derive(Clone, Debug)] +pub enum ExplicitOrAccountParams { + /// Explicit account reference. + Explicit(AccountId), + /// Account, referenced using `RewardsAccountParams`. + Params(RewardsAccountParams), +} + +impl From for ExplicitOrAccountParams { + fn from(params: RewardsAccountParams) -> Self { + ExplicitOrAccountParams::Params(params) + } +} + /// Relayer registration. #[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] pub struct Registration { @@ -90,7 +105,7 @@ pub trait StakeAndSlash { /// Returns `Ok(_)` with non-zero balance if we have failed to repatriate some portion of stake. fn repatriate_reserved( relayer: &AccountId, - beneficiary: RewardsAccountParams, + beneficiary: ExplicitOrAccountParams, amount: Balance, ) -> Result; } @@ -113,7 +128,7 @@ where fn repatriate_reserved( _relayer: &AccountId, - _beneficiary: RewardsAccountParams, + _beneficiary: ExplicitOrAccountParams, _amount: Balance, ) -> Result { Ok(Zero::zero()) diff --git a/bridges/primitives/runtime/src/chain.rs b/bridges/primitives/runtime/src/chain.rs index 4ec5a001a99ecad21617ed0afc57d3edac383d0d..369386e41b0cf9f2d911ca40fc9e6ccfb3de6e52 100644 --- a/bridges/primitives/runtime/src/chain.rs +++ b/bridges/primitives/runtime/src/chain.rs @@ -26,7 +26,7 @@ use sp_runtime::{ }, FixedPointOperand, }; -use sp_std::{convert::TryFrom, fmt::Debug, hash::Hash, str::FromStr, vec, vec::Vec}; +use sp_std::{fmt::Debug, hash::Hash, str::FromStr, vec, vec::Vec}; /// Chain call, that is either SCALE-encoded, or decoded. #[derive(Debug, Clone, PartialEq)] @@ -236,6 +236,12 @@ where pub trait Parachain: Chain { /// Parachain identifier. const PARACHAIN_ID: u32; + /// Maximal size of the parachain header. + /// + /// This isn't a strict limit. The relayer may submit larger headers and the + /// pallet will accept the call. The limit is only used to compute whether + /// the refund can be made. + const MAX_HEADER_SIZE: u32; } impl Parachain for T @@ -244,6 +250,8 @@ where ::Chain: Parachain, { const PARACHAIN_ID: u32 = <::Chain as Parachain>::PARACHAIN_ID; + const MAX_HEADER_SIZE: u32 = + <::Chain as Parachain>::MAX_HEADER_SIZE; } /// Adapter for `Get` to access `PARACHAIN_ID` from `trait Parachain` @@ -306,6 +314,11 @@ macro_rules! decl_bridge_finality_runtime_apis { pub const []: &str = stringify!([<$chain:camel FinalityApi_best_finalized>]); + /// Name of the `FinalityApi::free_headers_interval` runtime method. + pub const []: &str = + stringify!([<$chain:camel FinalityApi_free_headers_interval>]); + + $( /// Name of the `FinalityApi::accepted__finality_proofs` /// runtime method. @@ -322,6 +335,13 @@ macro_rules! decl_bridge_finality_runtime_apis { /// Returns number and hash of the best finalized header known to the bridge module. fn best_finalized() -> Option>; + /// Returns free headers interval, if it is configured in the runtime. + /// The caller expects that if his transaction improves best known header + /// at least by the free_headers_interval`, it will be fee-free. + /// + /// See [`pallet_bridge_grandpa::Config::FreeHeadersInterval`] for details. + fn free_headers_interval() -> Option; + $( /// Returns the justifications accepted in the current block. fn []( diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs index c9c5c9412913b0470024e9e1473e5d69ff184f25..5daba0351ad48f0ae39b870990b6f5ccea1bec1e 100644 --- a/bridges/primitives/runtime/src/lib.rs +++ b/bridges/primitives/runtime/src/lib.rs @@ -31,7 +31,7 @@ use sp_runtime::{ traits::{BadOrigin, Header as HeaderT, UniqueSaturatedInto}, RuntimeDebug, }; -use sp_std::{convert::TryFrom, fmt::Debug, ops::RangeInclusive, vec, vec::Vec}; +use sp_std::{fmt::Debug, ops::RangeInclusive, vec, vec::Vec}; pub use chain::{ AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain, EncodedOrDecodedCall, HashOf, diff --git a/bridges/relays/client-substrate/src/chain.rs b/bridges/relays/client-substrate/src/chain.rs index 2aba5f5674d97b2bcf8b3e2633c36b6bee52d98a..40269fe64c879249e9f0ed5ffe070d9fc606bdb6 100644 --- a/bridges/relays/client-substrate/src/chain.rs +++ b/bridges/relays/client-substrate/src/chain.rs @@ -46,6 +46,12 @@ pub trait Chain: ChainBase + Clone { /// Keep in mind that this method is normally provided by the other chain, which is /// bridged with this chain. const BEST_FINALIZED_HEADER_ID_METHOD: &'static str; + /// Name of the runtime API method that is returning interval between source chain + /// headers that may be submitted for free to the target chain. + /// + /// Keep in mind that this method is normally provided by the other chain, which is + /// bridged with this chain. + const FREE_HEADERS_INTERVAL_METHOD: &'static str; /// Average block interval. /// @@ -75,6 +81,9 @@ pub trait ChainWithRuntimeVersion: Chain { pub trait RelayChain: Chain { /// Name of the `runtime_parachains::paras` pallet in the runtime of this chain. const PARAS_PALLET_NAME: &'static str; + /// Name of the `pallet-bridge-parachains`, deployed at the **bridged** chain to sync + /// parachains of **this** chain. + const WITH_CHAIN_BRIDGE_PARACHAINS_PALLET_NAME: &'static str; } /// Substrate-based chain that is using direct GRANDPA finality from minimal relay-client point of diff --git a/bridges/relays/client-substrate/src/test_chain.rs b/bridges/relays/client-substrate/src/test_chain.rs index 77240d15884f4512458772b16f14f27b44f57f39..cfd241c022a269da799e8e03c4398566d98a14a0 100644 --- a/bridges/relays/client-substrate/src/test_chain.rs +++ b/bridges/relays/client-substrate/src/test_chain.rs @@ -56,6 +56,7 @@ impl bp_runtime::Chain for TestChain { impl Chain for TestChain { const NAME: &'static str = "Test"; const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = "TestMethod"; + const FREE_HEADERS_INTERVAL_METHOD: &'static str = "TestMethod"; const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(0); type SignedBlock = sp_runtime::generic::SignedBlock< @@ -110,6 +111,7 @@ impl bp_runtime::Chain for TestParachainBase { impl bp_runtime::Parachain for TestParachainBase { const PARACHAIN_ID: u32 = 1000; + const MAX_HEADER_SIZE: u32 = 1_024; } /// Parachain that may be used in tests. @@ -123,6 +125,7 @@ impl bp_runtime::UnderlyingChainProvider for TestParachain { impl Chain for TestParachain { const NAME: &'static str = "TestParachain"; const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = "TestParachainMethod"; + const FREE_HEADERS_INTERVAL_METHOD: &'static str = "TestParachainMethod"; const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(0); type SignedBlock = sp_runtime::generic::SignedBlock< diff --git a/bridges/relays/finality/README.md b/bridges/relays/finality/README.md index 92e765cea0e505be7854e17a9b91df520bba32b0..89b9d1399584a8575f2ae47cda7c8ee064314697 100644 --- a/bridges/relays/finality/README.md +++ b/bridges/relays/finality/README.md @@ -33,7 +33,9 @@ node. The transaction is then tracked by the relay until it is mined and finaliz The main entrypoint for the crate is the [`run` function](./src/finality_loop.rs), which takes source and target clients and [`FinalitySyncParams`](./src/finality_loop.rs) parameters. The most important parameter is the `only_mandatory_headers` - it is set to `true`, the relay will only submit mandatory headers. Since transactions -with mandatory headers are fee-free, the cost of running such relay is zero (in terms of fees). +with mandatory headers are fee-free, the cost of running such relay is zero (in terms of fees). If a similar, +`only_free_headers` parameter, is set to `true`, then free headers (if configured in the runtime) are also +relayed. ## Finality Relay Metrics diff --git a/bridges/relays/finality/src/finality_loop.rs b/bridges/relays/finality/src/finality_loop.rs index e31d8a708122db84c4c87f257edee7ee4ba616bb..8b3def868a453703600850a463cf2f07988811df 100644 --- a/bridges/relays/finality/src/finality_loop.rs +++ b/bridges/relays/finality/src/finality_loop.rs @@ -29,7 +29,7 @@ use crate::{ use async_trait::async_trait; use backoff::{backoff::Backoff, ExponentialBackoff}; use futures::{future::Fuse, select, Future, FutureExt}; -use num_traits::Saturating; +use num_traits::{Saturating, Zero}; use relay_utils::{ metrics::MetricsParams, relay_loop::Client as RelayClient, retry_backoff, FailedClient, HeaderId, MaybeConnectionError, TrackedTransactionStatus, TransactionTracker, @@ -39,6 +39,17 @@ use std::{ time::{Duration, Instant}, }; +/// Type of headers that we relay. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum HeadersToRelay { + /// Relay all headers. + All, + /// Relay only mandatory headers. + Mandatory, + /// Relay only free (including mandatory) headers. + Free, +} + /// Finality proof synchronization loop parameters. #[derive(Debug, Clone)] pub struct FinalitySyncParams { @@ -63,7 +74,7 @@ pub struct FinalitySyncParams { /// Timeout before we treat our transactions as lost and restart the whole sync process. pub stall_timeout: Duration, /// If true, only mandatory headers are relayed. - pub only_mandatory_headers: bool, + pub headers_to_relay: HeadersToRelay, } /// Source client used in finality synchronization loop. @@ -90,11 +101,16 @@ pub trait TargetClient: RelayClient { &self, ) -> Result, Self::Error>; + /// Get free source headers submission interval, if it is configured in the + /// target runtime. + async fn free_source_headers_interval(&self) -> Result, Self::Error>; + /// Submit header finality proof. async fn submit_finality_proof( &self, header: P::Header, proof: P::FinalityProof, + is_free_execution_expected: bool, ) -> Result; } @@ -104,9 +120,13 @@ pub fn metrics_prefix() -> String { format!("{}_to_{}_Sync", P::SOURCE_NAME, P::TARGET_NAME) } +/// Finality sync information. pub struct SyncInfo { + /// Best finalized header at the source client. pub best_number_at_source: P::Number, + /// Best source header, known to the target client. pub best_number_at_target: P::Number, + /// Whether the target client follows the same fork as the source client do. pub is_using_same_fork: bool, } @@ -183,6 +203,7 @@ impl Transaction Result { let header_number = header.number(); log::debug!( @@ -193,7 +214,9 @@ impl Transaction, TC: TargetClient

> Finality pub async fn select_header_to_submit( &mut self, info: &SyncInfo

, + free_headers_interval: Option, ) -> Result>, Error> { // to see that the loop is progressing log::trace!( @@ -302,9 +326,15 @@ impl, TC: TargetClient

> Finality ); // read missing headers - let selector = JustifiedHeaderSelector::new::(&self.source_client, info).await?; + let selector = JustifiedHeaderSelector::new::( + &self.source_client, + info, + self.sync_params.headers_to_relay, + free_headers_interval, + ) + .await?; // if we see that the header schedules GRANDPA change, we need to submit it - if self.sync_params.only_mandatory_headers { + if self.sync_params.headers_to_relay == HeadersToRelay::Mandatory { return Ok(selector.select_mandatory()) } @@ -312,7 +342,12 @@ impl, TC: TargetClient

> Finality // => even if we have already selected some header and its persistent finality proof, // we may try to select better header by reading non-persistent proofs from the stream self.finality_proofs_buf.fill(&mut self.finality_proofs_stream); - let maybe_justified_header = selector.select(&self.finality_proofs_buf); + let maybe_justified_header = selector.select( + info, + self.sync_params.headers_to_relay, + free_headers_interval, + &self.finality_proofs_buf, + ); // remove obsolete 'recent' finality proofs + keep its size under certain limit let oldest_finality_proof_to_keep = maybe_justified_header @@ -329,6 +364,7 @@ impl, TC: TargetClient

> Finality pub async fn run_iteration( &mut self, + free_headers_interval: Option, ) -> Result< Option>, Error, @@ -345,12 +381,16 @@ impl, TC: TargetClient

> Finality } // submit new header if we have something new - match self.select_header_to_submit(&info).await? { + match self.select_header_to_submit(&info, free_headers_interval).await? { Some(header) => { - let transaction = - Transaction::submit(&self.target_client, header.header, header.proof) - .await - .map_err(Error::Target)?; + let transaction = Transaction::submit( + &self.target_client, + header.header, + header.proof, + self.sync_params.headers_to_relay == HeadersToRelay::Free, + ) + .await + .map_err(Error::Target)?; self.best_submitted_number = Some(transaction.header_number); Ok(Some(transaction)) }, @@ -378,9 +418,11 @@ impl, TC: TargetClient

> Finality let exit_signal = exit_signal.fuse(); futures::pin_mut!(exit_signal, proof_submission_tx_tracker); + let free_headers_interval = free_headers_interval(&self.target_client).await?; + loop { // run loop iteration - let next_tick = match self.run_iteration().await { + let next_tick = match self.run_iteration(free_headers_interval).await { Ok(Some(tx)) => { proof_submission_tx_tracker .set(tx.track::(self.target_client.clone()).fuse()); @@ -433,6 +475,52 @@ impl, TC: TargetClient

> Finality } } +async fn free_headers_interval( + target_client: &impl TargetClient

, +) -> Result, FailedClient> { + match target_client.free_source_headers_interval().await { + Ok(Some(free_headers_interval)) if !free_headers_interval.is_zero() => { + log::trace!( + target: "bridge", + "Free headers interval for {} headers at {} is: {:?}", + P::SOURCE_NAME, + P::TARGET_NAME, + free_headers_interval, + ); + Ok(Some(free_headers_interval)) + }, + Ok(Some(_free_headers_interval)) => { + log::trace!( + target: "bridge", + "Free headers interval for {} headers at {} is zero. Not submitting any free headers", + P::SOURCE_NAME, + P::TARGET_NAME, + ); + Ok(None) + }, + Ok(None) => { + log::trace!( + target: "bridge", + "Free headers interval for {} headers at {} is None. Not submitting any free headers", + P::SOURCE_NAME, + P::TARGET_NAME, + ); + + Ok(None) + }, + Err(e) => { + log::error!( + target: "bridge", + "Failed to read free headers interval for {} headers at {}: {:?}", + P::SOURCE_NAME, + P::TARGET_NAME, + e, + ); + Err(FailedClient::Target) + }, + } +} + /// Run finality proofs synchronization loop. pub async fn run( source_client: impl SourceClient

, @@ -509,7 +597,7 @@ mod tests { tick: Duration::from_secs(0), recent_finality_proofs_limit: 1024, stall_timeout: Duration::from_secs(1), - only_mandatory_headers: false, + headers_to_relay: HeadersToRelay::All, } } @@ -593,8 +681,8 @@ mod tests { ); } - fn run_only_mandatory_headers_mode_test( - only_mandatory_headers: bool, + fn run_headers_to_relay_mode_test( + headers_to_relay: HeadersToRelay, has_mandatory_headers: bool, ) -> Option> { let (exit_sender, _) = futures::channel::mpsc::unbounded(); @@ -619,7 +707,7 @@ mod tests { tick: Duration::from_secs(0), recent_finality_proofs_limit: 0, stall_timeout: Duration::from_secs(0), - only_mandatory_headers, + headers_to_relay, }, None, ); @@ -628,16 +716,22 @@ mod tests { best_number_at_target: 5, is_using_same_fork: true, }; - finality_loop.select_header_to_submit(&info).await.unwrap() + finality_loop.select_header_to_submit(&info, Some(3)).await.unwrap() }) } #[test] - fn select_header_to_submit_skips_non_mandatory_headers_when_only_mandatory_headers_are_required( - ) { - assert_eq!(run_only_mandatory_headers_mode_test(true, false), None); + fn select_header_to_submit_may_select_non_mandatory_header() { + assert_eq!(run_headers_to_relay_mode_test(HeadersToRelay::Mandatory, false), None); assert_eq!( - run_only_mandatory_headers_mode_test(false, false), + run_headers_to_relay_mode_test(HeadersToRelay::Free, false), + Some(JustifiedHeader { + header: TestSourceHeader(false, 10, 10), + proof: TestFinalityProof(10) + }), + ); + assert_eq!( + run_headers_to_relay_mode_test(HeadersToRelay::All, false), Some(JustifiedHeader { header: TestSourceHeader(false, 10, 10), proof: TestFinalityProof(10) @@ -646,17 +740,23 @@ mod tests { } #[test] - fn select_header_to_submit_selects_mandatory_headers_when_only_mandatory_headers_are_required() - { + fn select_header_to_submit_may_select_mandatory_header() { + assert_eq!( + run_headers_to_relay_mode_test(HeadersToRelay::Mandatory, true), + Some(JustifiedHeader { + header: TestSourceHeader(true, 8, 8), + proof: TestFinalityProof(8) + }), + ); assert_eq!( - run_only_mandatory_headers_mode_test(true, true), + run_headers_to_relay_mode_test(HeadersToRelay::Free, true), Some(JustifiedHeader { header: TestSourceHeader(true, 8, 8), proof: TestFinalityProof(8) }), ); assert_eq!( - run_only_mandatory_headers_mode_test(false, true), + run_headers_to_relay_mode_test(HeadersToRelay::All, true), Some(JustifiedHeader { header: TestSourceHeader(true, 8, 8), proof: TestFinalityProof(8) @@ -690,7 +790,7 @@ mod tests { test_sync_params(), Some(metrics_sync.clone()), ); - finality_loop.run_iteration().await.unwrap() + finality_loop.run_iteration(None).await.unwrap() }); assert!(!metrics_sync.is_using_same_fork()); diff --git a/bridges/relays/finality/src/headers.rs b/bridges/relays/finality/src/headers.rs index 91f7cd0378ecd9ac8a0ee558266d993cc2253c9e..5bba4a384562d1f97334cd809ba47267698308f9 100644 --- a/bridges/relays/finality/src/headers.rs +++ b/bridges/relays/finality/src/headers.rs @@ -16,10 +16,11 @@ use crate::{ finality_loop::SyncInfo, finality_proofs::FinalityProofsBuf, Error, FinalitySyncPipeline, - SourceClient, SourceHeader, TargetClient, + HeadersToRelay, SourceClient, SourceHeader, TargetClient, }; use bp_header_chain::FinalityProof; +use num_traits::Saturating; use std::cmp::Ordering; /// Unjustified headers container. Ordered by header number. @@ -50,9 +51,13 @@ pub enum JustifiedHeaderSelector { } impl JustifiedHeaderSelector

{ + /// Selects last header with persistent justification, missing from the target and matching + /// the `headers_to_relay` criteria. pub(crate) async fn new, TC: TargetClient

>( source_client: &SC, info: &SyncInfo

, + headers_to_relay: HeadersToRelay, + free_headers_interval: Option, ) -> Result> { let mut unjustified_headers = Vec::new(); let mut maybe_justified_header = None; @@ -70,12 +75,19 @@ impl JustifiedHeaderSelector

{ return Ok(Self::Mandatory(JustifiedHeader { header, proof })) }, (true, None) => return Err(Error::MissingMandatoryFinalityProof(header.number())), - (false, Some(proof)) => { + (false, Some(proof)) + if need_to_relay::

( + info, + headers_to_relay, + free_headers_interval, + &header, + ) => + { log::trace!(target: "bridge", "Header {:?} has persistent finality proof", header_number); unjustified_headers.clear(); maybe_justified_header = Some(JustifiedHeader { header, proof }); }, - (false, None) => { + _ => { unjustified_headers.push(header); }, } @@ -97,6 +109,7 @@ impl JustifiedHeaderSelector

{ }) } + /// Returns selected mandatory header if we have seen one. Otherwise returns `None`. pub fn select_mandatory(self) -> Option> { match self { JustifiedHeaderSelector::Mandatory(header) => Some(header), @@ -104,7 +117,15 @@ impl JustifiedHeaderSelector

{ } } - pub fn select(self, buf: &FinalityProofsBuf

) -> Option> { + /// Tries to improve previously selected header using ephemeral + /// justifications stream. + pub fn select( + self, + info: &SyncInfo

, + headers_to_relay: HeadersToRelay, + free_headers_interval: Option, + buf: &FinalityProofsBuf

, + ) -> Option> { let (unjustified_headers, maybe_justified_header) = match self { JustifiedHeaderSelector::Mandatory(justified_header) => return Some(justified_header), JustifiedHeaderSelector::Regular(unjustified_headers, justified_header) => @@ -122,7 +143,14 @@ impl JustifiedHeaderSelector

{ (maybe_finality_proof, maybe_unjustified_header) { match finality_proof.target_header_number().cmp(&unjustified_header.number()) { - Ordering::Equal => { + Ordering::Equal + if need_to_relay::

( + info, + headers_to_relay, + free_headers_interval, + &unjustified_header, + ) => + { log::trace!( target: "bridge", "Managed to improve selected {} finality proof {:?} to {:?}.", @@ -135,6 +163,10 @@ impl JustifiedHeaderSelector

{ proof: finality_proof.clone(), }) }, + Ordering::Equal => { + maybe_finality_proof = finality_proofs_iter.next(); + maybe_unjustified_header = unjustified_headers_iter.next(); + }, Ordering::Less => maybe_unjustified_header = unjustified_headers_iter.next(), Ordering::Greater => { maybe_finality_proof = finality_proofs_iter.next(); @@ -152,6 +184,27 @@ impl JustifiedHeaderSelector

{ } } +/// Returns true if we want to relay header `header_number`. +fn need_to_relay( + info: &SyncInfo

, + headers_to_relay: HeadersToRelay, + free_headers_interval: Option, + header: &P::Header, +) -> bool { + match headers_to_relay { + HeadersToRelay::All => true, + HeadersToRelay::Mandatory => header.is_mandatory(), + HeadersToRelay::Free => + header.is_mandatory() || + free_headers_interval + .map(|free_headers_interval| { + header.number().saturating_sub(info.best_number_at_target) >= + free_headers_interval + }) + .unwrap_or(false), + } +} + #[cfg(test)] mod tests { use super::*; @@ -159,13 +212,22 @@ mod tests { #[test] fn select_better_recent_finality_proof_works() { + let info = SyncInfo { + best_number_at_source: 10, + best_number_at_target: 5, + is_using_same_fork: true, + }; + // if there are no unjustified headers, nothing is changed let finality_proofs_buf = FinalityProofsBuf::::new(vec![TestFinalityProof(5)]); let justified_header = JustifiedHeader { header: TestSourceHeader(false, 2, 2), proof: TestFinalityProof(2) }; let selector = JustifiedHeaderSelector::Regular(vec![], justified_header.clone()); - assert_eq!(selector.select(&finality_proofs_buf), Some(justified_header)); + assert_eq!( + selector.select(&info, HeadersToRelay::All, None, &finality_proofs_buf), + Some(justified_header) + ); // if there are no buffered finality proofs, nothing is changed let finality_proofs_buf = FinalityProofsBuf::::new(vec![]); @@ -175,7 +237,10 @@ mod tests { vec![TestSourceHeader(false, 5, 5)], justified_header.clone(), ); - assert_eq!(selector.select(&finality_proofs_buf), Some(justified_header)); + assert_eq!( + selector.select(&info, HeadersToRelay::All, None, &finality_proofs_buf), + Some(justified_header) + ); // if there's no intersection between recent finality proofs and unjustified headers, // nothing is changed @@ -189,7 +254,10 @@ mod tests { vec![TestSourceHeader(false, 9, 9), TestSourceHeader(false, 10, 10)], justified_header.clone(), ); - assert_eq!(selector.select(&finality_proofs_buf), Some(justified_header)); + assert_eq!( + selector.select(&info, HeadersToRelay::All, None, &finality_proofs_buf), + Some(justified_header) + ); // if there's intersection between recent finality proofs and unjustified headers, but there // are no proofs in this intersection, nothing is changed @@ -207,7 +275,10 @@ mod tests { ], justified_header.clone(), ); - assert_eq!(selector.select(&finality_proofs_buf), Some(justified_header)); + assert_eq!( + selector.select(&info, HeadersToRelay::All, None, &finality_proofs_buf), + Some(justified_header) + ); // if there's intersection between recent finality proofs and unjustified headers and // there's a proof in this intersection: @@ -228,11 +299,63 @@ mod tests { justified_header, ); assert_eq!( - selector.select(&finality_proofs_buf), + selector.select(&info, HeadersToRelay::All, None, &finality_proofs_buf), Some(JustifiedHeader { header: TestSourceHeader(false, 9, 9), proof: TestFinalityProof(9) }) ); + + // when only free headers needs to be relayed and there are no free headers + let finality_proofs_buf = FinalityProofsBuf::::new(vec![ + TestFinalityProof(7), + TestFinalityProof(9), + ]); + let selector = JustifiedHeaderSelector::None(vec![ + TestSourceHeader(false, 8, 8), + TestSourceHeader(false, 9, 9), + TestSourceHeader(false, 10, 10), + ]); + assert_eq!( + selector.select(&info, HeadersToRelay::Free, Some(7), &finality_proofs_buf), + None, + ); + + // when only free headers needs to be relayed, mandatory header may be selected + let finality_proofs_buf = FinalityProofsBuf::::new(vec![ + TestFinalityProof(6), + TestFinalityProof(9), + ]); + let selector = JustifiedHeaderSelector::None(vec![ + TestSourceHeader(false, 8, 8), + TestSourceHeader(true, 9, 9), + TestSourceHeader(false, 10, 10), + ]); + assert_eq!( + selector.select(&info, HeadersToRelay::Free, Some(7), &finality_proofs_buf), + Some(JustifiedHeader { + header: TestSourceHeader(true, 9, 9), + proof: TestFinalityProof(9) + }) + ); + + // when only free headers needs to be relayed and there is free header + let finality_proofs_buf = FinalityProofsBuf::::new(vec![ + TestFinalityProof(7), + TestFinalityProof(9), + TestFinalityProof(14), + ]); + let selector = JustifiedHeaderSelector::None(vec![ + TestSourceHeader(false, 7, 7), + TestSourceHeader(false, 10, 10), + TestSourceHeader(false, 14, 14), + ]); + assert_eq!( + selector.select(&info, HeadersToRelay::Free, Some(7), &finality_proofs_buf), + Some(JustifiedHeader { + header: TestSourceHeader(false, 14, 14), + proof: TestFinalityProof(14) + }) + ); } } diff --git a/bridges/relays/finality/src/lib.rs b/bridges/relays/finality/src/lib.rs index 3579e68e1ef9c686575e3ddba239bead7bd9312f..4346f96674b4c43c153ad8bf55cb5ee963871849 100644 --- a/bridges/relays/finality/src/lib.rs +++ b/bridges/relays/finality/src/lib.rs @@ -21,7 +21,9 @@ pub use crate::{ base::{FinalityPipeline, SourceClientBase}, - finality_loop::{metrics_prefix, run, FinalitySyncParams, SourceClient, TargetClient}, + finality_loop::{ + metrics_prefix, run, FinalitySyncParams, HeadersToRelay, SourceClient, TargetClient, + }, finality_proofs::{FinalityProofsBuf, FinalityProofsStream}, sync_loop_metrics::SyncLoopMetrics, }; diff --git a/bridges/relays/finality/src/mock.rs b/bridges/relays/finality/src/mock.rs index e3ec4e4d0d47a04ce5a22ee75374ebe08064df5e..69357f71ce27d54a2ca4866e3fd6db0a73fb44e2 100644 --- a/bridges/relays/finality/src/mock.rs +++ b/bridges/relays/finality/src/mock.rs @@ -198,10 +198,15 @@ impl TargetClient for TestTargetClient { Ok(data.target_best_block_id) } + async fn free_source_headers_interval(&self) -> Result, TestError> { + Ok(Some(3)) + } + async fn submit_finality_proof( &self, header: TestSourceHeader, proof: TestFinalityProof, + _is_free_execution_expected: bool, ) -> Result { let mut data = self.data.lock(); (self.on_method_call)(&mut data); diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs index 90558ed46138366221c1f9834d21060e7e54e66b..093f98ef21ed24b40a0c2d8f217d84b841137a69 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs @@ -19,11 +19,15 @@ use async_trait::async_trait; use structopt::StructOpt; -use relay_utils::metrics::{GlobalMetrics, StandaloneMetric}; +use relay_utils::{ + metrics::{GlobalMetrics, StandaloneMetric}, + UniqueSaturatedInto, +}; use crate::{ cli::{bridge::*, chain_schema::*, PrometheusParams}, finality::SubstrateFinalitySyncPipeline, + HeadersToRelay, }; /// Chain headers relaying params. @@ -33,6 +37,10 @@ pub struct RelayHeadersParams { /// are relayed. #[structopt(long)] only_mandatory_headers: bool, + /// If passed, only free headers (mandatory and every Nth header, if configured in runtime) + /// are relayed. Overrides `only_mandatory_headers`. + #[structopt(long)] + only_free_headers: bool, #[structopt(flatten)] source: SourceConnectionParams, #[structopt(flatten)] @@ -43,11 +51,37 @@ pub struct RelayHeadersParams { prometheus_params: PrometheusParams, } +/// Single header relaying params. +#[derive(StructOpt)] +pub struct RelayHeaderParams { + #[structopt(flatten)] + source: SourceConnectionParams, + #[structopt(flatten)] + target: TargetConnectionParams, + #[structopt(flatten)] + target_sign: TargetSigningParams, + /// Number of the source chain header that we want to relay. It must have a persistent + /// storage proof at the [`Self::source`] node, otherwise the command will fail. + #[structopt(long)] + number: u128, +} + +impl RelayHeadersParams { + fn headers_to_relay(&self) -> HeadersToRelay { + match (self.only_mandatory_headers, self.only_free_headers) { + (_, true) => HeadersToRelay::Free, + (true, false) => HeadersToRelay::Mandatory, + _ => HeadersToRelay::All, + } + } +} + /// Trait used for relaying headers between 2 chains. #[async_trait] pub trait HeadersRelayer: RelayToRelayHeadersCliBridge { /// Relay headers. async fn relay_headers(data: RelayHeadersParams) -> anyhow::Result<()> { + let headers_to_relay = data.headers_to_relay(); let source_client = data.source.into_client::().await?; let target_client = data.target.into_client::().await?; let target_transactions_mortality = data.target_sign.target_transactions_mortality; @@ -67,10 +101,29 @@ pub trait HeadersRelayer: RelayToRelayHeadersCliBridge { crate::finality::run::( source_client, target_client, - data.only_mandatory_headers, + headers_to_relay, target_transactions_params, metrics_params, ) .await } + + /// Relay single header. No checks are made to ensure that transaction will succeed. + async fn relay_header(data: RelayHeaderParams) -> anyhow::Result<()> { + let source_client = data.source.into_client::().await?; + let target_client = data.target.into_client::().await?; + let target_transactions_mortality = data.target_sign.target_transactions_mortality; + let target_sign = data.target_sign.to_keypair::()?; + + crate::finality::relay_single_header::( + source_client, + target_client, + crate::TransactionParams { + signer: target_sign, + mortality: target_transactions_mortality, + }, + data.number.unique_saturated_into(), + ) + .await + } } diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs index 27e9f1c21ba0dae1480ef8128afdfd635a1d22c2..a796df6721b8c8afd7f401f92e2fca6afcb41b02 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs @@ -40,7 +40,7 @@ use crate::{ cli::{bridge::MessagesCliBridge, HexLaneId, PrometheusParams}, messages_lane::{MessagesRelayLimits, MessagesRelayParams}, on_demand::OnDemandRelay, - TaggedAccount, TransactionParams, + HeadersToRelay, TaggedAccount, TransactionParams, }; use bp_messages::LaneId; use bp_runtime::BalanceOf; @@ -61,11 +61,25 @@ pub struct HeadersAndMessagesSharedParams { /// are relayed. #[structopt(long)] pub only_mandatory_headers: bool, + /// If passed, only free headers (mandatory and every Nth header, if configured in runtime) + /// are relayed. Overrides `only_mandatory_headers`. + #[structopt(long)] + pub only_free_headers: bool, #[structopt(flatten)] /// Prometheus metrics params. pub prometheus_params: PrometheusParams, } +impl HeadersAndMessagesSharedParams { + fn headers_to_relay(&self) -> HeadersToRelay { + match (self.only_mandatory_headers, self.only_free_headers) { + (_, true) => HeadersToRelay::Free, + (true, false) => HeadersToRelay::Mandatory, + _ => HeadersToRelay::All, + } + } +} + /// Bridge parameters, shared by all bridge types. pub struct Full2WayBridgeCommonParams< Left: ChainWithTransactions + ChainWithRuntimeVersion, @@ -418,6 +432,7 @@ mod tests { shared: HeadersAndMessagesSharedParams { lane: vec![HexLaneId([0x00, 0x00, 0x00, 0x00])], only_mandatory_headers: false, + only_free_headers: false, prometheus_params: PrometheusParams { no_prometheus: false, prometheus_host: "0.0.0.0".into(), diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs index 76accfa29050613070c6579103d4e41f6084eea6..7f6f40777823679c97577f1244eb9a860948d267 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs @@ -180,7 +180,7 @@ where self.left_relay.clone(), self.common.right.client.clone(), self.common.right.tx_params.clone(), - self.common.shared.only_mandatory_headers, + self.common.shared.headers_to_relay(), Some(self.common.metrics_params.clone()), ); let right_relay_to_left_on_demand_headers = @@ -188,7 +188,7 @@ where self.right_relay.clone(), self.common.left.client.clone(), self.common.left.tx_params.clone(), - self.common.shared.only_mandatory_headers, + self.common.shared.headers_to_relay(), Some(self.common.metrics_params.clone()), ); diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs index b75ac3e60c26934c7dda603a2c7a91649d17eb52..5911fe49df4adfc955cbab4d142998fbc7ed4d22 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs @@ -171,7 +171,7 @@ where self.common.left.client.clone(), self.common.right.client.clone(), self.common.right.tx_params.clone(), - self.common.shared.only_mandatory_headers, + self.common.shared.headers_to_relay(), None, ); let right_relay_to_left_on_demand_headers = @@ -179,7 +179,7 @@ where self.right_relay.clone(), self.common.left.client.clone(), self.common.left.tx_params.clone(), - self.common.shared.only_mandatory_headers, + self.common.shared.headers_to_relay(), Some(self.common.metrics_params.clone()), ); let right_to_left_on_demand_parachains = OnDemandParachainsRelay::< diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_relay.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_relay.rs index b397ff50a20a62833d96a3687c8d3c3494efc5c2..832df4ae4003ced1715d7b9d495989d9163417d5 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_relay.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_relay.rs @@ -152,7 +152,7 @@ where self.common.left.client.clone(), self.common.right.client.clone(), self.common.right.tx_params.clone(), - self.common.shared.only_mandatory_headers, + self.common.shared.headers_to_relay(), None, ); let right_to_left_on_demand_headers = @@ -160,7 +160,7 @@ where self.common.right.client.clone(), self.common.left.client.clone(), self.common.left.tx_params.clone(), - self.common.shared.only_mandatory_headers, + self.common.shared.headers_to_relay(), None, ); diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs index e5a52349469bbdf36c7de228078d8d10b0e882f0..1425233add1ee3a58b1729a10ed2bb2d2fbc8667 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs @@ -43,6 +43,10 @@ pub struct RelayParachainsParams { target: TargetConnectionParams, #[structopt(flatten)] target_sign: TargetSigningParams, + /// If passed, only free headers (those, available at "free" relay chain headers) + /// are relayed. + #[structopt(long)] + only_free_headers: bool, #[structopt(flatten)] prometheus_params: PrometheusParams, } @@ -59,9 +63,9 @@ where { /// Start relaying parachains finality. async fn relay_parachains(data: RelayParachainsParams) -> anyhow::Result<()> { - let source_client = data.source.into_client::().await?; + let source_chain_client = data.source.into_client::().await?; let source_client = ParachainsSource::::new( - source_client, + source_chain_client.clone(), Arc::new(Mutex::new(AvailableHeader::Missing)), ); @@ -69,9 +73,10 @@ where signer: data.target_sign.to_keypair::()?, mortality: data.target_sign.target_transactions_mortality, }; - let target_client = data.target.into_client::().await?; + let target_chain_client = data.target.into_client::().await?; let target_client = ParachainsTarget::::new( - target_client.clone(), + source_chain_client, + target_chain_client, target_transaction_params, ); @@ -83,6 +88,7 @@ where source_client, target_client, metrics_params, + data.only_free_headers, futures::future::pending(), ) .await diff --git a/bridges/relays/lib-substrate-relay/src/finality/mod.rs b/bridges/relays/lib-substrate-relay/src/finality/mod.rs index 206f628b143b8a085dec9a0ef5de929c178c25f6..0293e1da224a6323fed59f7f727b5d5263391bb8 100644 --- a/bridges/relays/lib-substrate-relay/src/finality/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/finality/mod.rs @@ -25,13 +25,15 @@ use crate::{ use async_trait::async_trait; use bp_header_chain::justification::{GrandpaJustification, JustificationVerificationContext}; -use finality_relay::{FinalityPipeline, FinalitySyncPipeline}; +use finality_relay::{ + FinalityPipeline, FinalitySyncPipeline, HeadersToRelay, SourceClient, TargetClient, +}; use pallet_bridge_grandpa::{Call as BridgeGrandpaCall, Config as BridgeGrandpaConfig}; use relay_substrate_client::{ transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, ChainWithTransactions, Client, HashOf, HeaderOf, SyncHeader, }; -use relay_utils::metrics::MetricsParams; +use relay_utils::{metrics::MetricsParams, TrackedTransactionStatus, TransactionTracker}; use sp_core::Pair; use std::{fmt::Debug, marker::PhantomData}; @@ -115,6 +117,7 @@ pub trait SubmitFinalityProofCallBuilder { fn build_submit_finality_proof_call( header: SyncHeader>, proof: SubstrateFinalityProof

, + is_free_execution_expected: bool, context: <

::FinalityEngine as Engine>::FinalityVerificationContext, ) -> CallOf; } @@ -142,6 +145,7 @@ where fn build_submit_finality_proof_call( header: SyncHeader>, proof: GrandpaJustification>, + _is_free_execution_expected: bool, _context: JustificationVerificationContext, ) -> CallOf { BridgeGrandpaCall::::submit_finality_proof { @@ -176,6 +180,7 @@ macro_rules! generate_submit_finality_proof_call_builder { <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain > >, + _is_free_execution_expected: bool, _context: bp_header_chain::justification::JustificationVerificationContext, ) -> relay_substrate_client::CallOf< <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::TargetChain @@ -215,6 +220,7 @@ macro_rules! generate_submit_finality_proof_ex_call_builder { <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain > >, + is_free_execution_expected: bool, context: bp_header_chain::justification::JustificationVerificationContext, ) -> relay_substrate_client::CallOf< <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::TargetChain @@ -223,7 +229,8 @@ macro_rules! generate_submit_finality_proof_ex_call_builder { $bridge_grandpa($submit_finality_proof { finality_target: Box::new(header.into_inner()), justification: proof, - current_set_id: context.authority_set_id + current_set_id: context.authority_set_id, + is_free_execution_expected, }) } } @@ -235,15 +242,16 @@ macro_rules! generate_submit_finality_proof_ex_call_builder { pub async fn run( source_client: Client, target_client: Client, - only_mandatory_headers: bool, + headers_to_relay: HeadersToRelay, transaction_params: TransactionParams>, metrics_params: MetricsParams, ) -> anyhow::Result<()> { log::info!( target: "bridge", - "Starting {} -> {} finality proof relay", + "Starting {} -> {} finality proof relay: relaying {:?} headers", P::SourceChain::NAME, P::TargetChain::NAME, + headers_to_relay, ); finality_relay::run( @@ -260,7 +268,7 @@ pub async fn run( P::TargetChain::AVERAGE_BLOCK_INTERVAL, relay_utils::STALL_TIMEOUT, ), - only_mandatory_headers, + headers_to_relay, }, metrics_params, futures::future::pending(), @@ -268,3 +276,34 @@ pub async fn run( .await .map_err(|e| anyhow::format_err!("{}", e)) } + +/// Relay single header. No checks are made to ensure that transaction will succeed. +pub async fn relay_single_header( + source_client: Client, + target_client: Client, + transaction_params: TransactionParams>, + header_number: BlockNumberOf, +) -> anyhow::Result<()> { + let finality_source = SubstrateFinalitySource::

::new(source_client, None); + let (header, proof) = finality_source.header_and_finality_proof(header_number).await?; + let Some(proof) = proof else { + return Err(anyhow::format_err!( + "Unable to submit {} header #{} to {}: no finality proof", + P::SourceChain::NAME, + header_number, + P::TargetChain::NAME, + )); + }; + + let finality_target = SubstrateFinalityTarget::

::new(target_client, transaction_params); + let tx_tracker = finality_target.submit_finality_proof(header, proof, false).await?; + match tx_tracker.wait().await { + TrackedTransactionStatus::Finalized(_) => Ok(()), + TrackedTransactionStatus::Lost => Err(anyhow::format_err!( + "Transaction with {} header #{} is considered lost at {}", + P::SourceChain::NAME, + header_number, + P::TargetChain::NAME, + )), + } +} diff --git a/bridges/relays/lib-substrate-relay/src/finality/target.rs b/bridges/relays/lib-substrate-relay/src/finality/target.rs index 18464d523f4f6cf4c9c165af82f6b7c2c2504070..adbcfe0096d5f46ddacdf80e335f74967febca0e 100644 --- a/bridges/relays/lib-substrate-relay/src/finality/target.rs +++ b/bridges/relays/lib-substrate-relay/src/finality/target.rs @@ -25,9 +25,10 @@ use crate::{ }; use async_trait::async_trait; +use bp_runtime::BlockNumberOf; use finality_relay::TargetClient; use relay_substrate_client::{ - AccountKeyPairOf, Client, Error, HeaderIdOf, HeaderOf, SyncHeader, TransactionEra, + AccountKeyPairOf, Chain, Client, Error, HeaderIdOf, HeaderOf, SyncHeader, TransactionEra, TransactionTracker, UnsignedTransaction, }; use relay_utils::relay_loop::Client as RelayClient; @@ -103,10 +104,23 @@ impl TargetClient Result>, Self::Error> { + self.client + .typed_state_call( + P::SourceChain::FREE_HEADERS_INTERVAL_METHOD.into(), + (), + Some(self.client.best_header().await?.hash()), + ) + .await + } + async fn submit_finality_proof( &self, header: SyncHeader>, mut proof: SubstrateFinalityProof

, + is_free_execution_expected: bool, ) -> Result { // verify and runtime module at target chain may require optimized finality proof let context = @@ -115,7 +129,10 @@ impl TargetClient Substrate messages synchronization pipeline. pub trait SubstrateMessageLane: 'static + Clone + Debug + Send + Sync { diff --git a/bridges/relays/lib-substrate-relay/src/messages_metrics.rs b/bridges/relays/lib-substrate-relay/src/messages_metrics.rs index 27bf6186c3ba0d0db6552128574c8759d45d220c..b30e75bd8bacbbd25c056eb7d499cc18d040f991 100644 --- a/bridges/relays/lib-substrate-relay/src/messages_metrics.rs +++ b/bridges/relays/lib-substrate-relay/src/messages_metrics.rs @@ -32,7 +32,7 @@ use relay_substrate_client::{ use relay_utils::metrics::{MetricsParams, StandaloneMetric}; use sp_core::storage::StorageData; use sp_runtime::{FixedPointNumber, FixedU128}; -use std::{convert::TryFrom, fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData}; /// Add relay accounts balance metrics. pub async fn add_relay_balances_metrics( diff --git a/bridges/relays/lib-substrate-relay/src/messages_target.rs b/bridges/relays/lib-substrate-relay/src/messages_target.rs index 9396e785530d2ec5855e332e8db3dd7836938f25..633b11f0b8028636fdb8c9c6b4f1ec5fa42ccf33 100644 --- a/bridges/relays/lib-substrate-relay/src/messages_target.rs +++ b/bridges/relays/lib-substrate-relay/src/messages_target.rs @@ -45,7 +45,7 @@ use relay_substrate_client::{ }; use relay_utils::relay_loop::Client as RelayClient; use sp_core::Pair; -use std::{convert::TryFrom, ops::RangeInclusive}; +use std::ops::RangeInclusive; /// Message receiving proof returned by the target Substrate node. pub type SubstrateMessagesDeliveryProof = diff --git a/bridges/relays/lib-substrate-relay/src/on_demand/headers.rs b/bridges/relays/lib-substrate-relay/src/on_demand/headers.rs index e8a2a3c6c58aaedeb55da67871d0ddb51830338d..74f3a70c5e81bbc1d27162a74fb8dadab46a6d09 100644 --- a/bridges/relays/lib-substrate-relay/src/on_demand/headers.rs +++ b/bridges/relays/lib-substrate-relay/src/on_demand/headers.rs @@ -28,7 +28,7 @@ use futures::{select, FutureExt}; use num_traits::{One, Saturating, Zero}; use sp_runtime::traits::Header; -use finality_relay::{FinalitySyncParams, TargetClient as FinalityTargetClient}; +use finality_relay::{FinalitySyncParams, HeadersToRelay, TargetClient as FinalityTargetClient}; use relay_substrate_client::{ AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, Client, Error as SubstrateError, HeaderIdOf, @@ -75,7 +75,7 @@ impl OnDemandHeadersRelay

{ source_client: Client, target_client: Client, target_transaction_params: TransactionParams>, - only_mandatory_headers: bool, + headers_to_relay: HeadersToRelay, metrics_params: Option, ) -> Self where @@ -94,7 +94,7 @@ impl OnDemandHeadersRelay

{ source_client, target_client, target_transaction_params, - only_mandatory_headers, + headers_to_relay, required_header_number, metrics_params, ) @@ -191,7 +191,7 @@ impl OnDemandRelay( source_client: Client, target_client: Client, target_transaction_params: TransactionParams>, - only_mandatory_headers: bool, + headers_to_relay: HeadersToRelay, required_header_number: RequiredHeaderNumberRef, metrics_params: Option, ) where @@ -346,11 +346,11 @@ async fn background_task( log::info!( target: "bridge", "[{}] Starting on-demand headers relay task\n\t\ - Only mandatory headers: {}\n\t\ + Headers to relay: {:?}\n\t\ Tx mortality: {:?} (~{}m)\n\t\ Stall timeout: {:?}", relay_task_name, - only_mandatory_headers, + headers_to_relay, target_transactions_mortality, stall_timeout.as_secs_f64() / 60.0f64, stall_timeout, @@ -367,7 +367,7 @@ async fn background_task( ), recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT, stall_timeout, - only_mandatory_headers, + headers_to_relay, }, metrics_params.clone().unwrap_or_else(MetricsParams::disabled), futures::future::pending(), diff --git a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs index f67c002bba7f9c61925cb83e96433dbd40db7d4b..966bdc3107203a61cf405adba2cf09124330954e 100644 --- a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs +++ b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs @@ -222,6 +222,7 @@ where proved_relay_block, vec![(para_id, para_hash)], para_proof, + false, )); Ok((proved_parachain_block, calls)) @@ -256,8 +257,11 @@ async fn background_task( let mut parachains_source = ParachainsSource::

::new(source_relay_client.clone(), required_para_header_ref.clone()); - let mut parachains_target = - ParachainsTarget::

::new(target_client.clone(), target_transaction_params.clone()); + let mut parachains_target = ParachainsTarget::

::new( + source_relay_client.clone(), + target_client.clone(), + target_transaction_params.clone(), + ); loop { select! { @@ -392,6 +396,8 @@ async fn background_task( parachains_source.clone(), parachains_target.clone(), MetricsParams::disabled(), + // we do not support free parachain headers relay in on-demand relays + false, futures::future::pending(), ) .fuse(), @@ -481,7 +487,7 @@ where let para_header_at_target = best_finalized_peer_header_at_self::< P::TargetChain, P::SourceParachain, - >(target.client(), best_target_block_hash) + >(target.target_client(), best_target_block_hash) .await; // if there are no parachain heads at the target (`NoParachainHeadAtTarget`), we'll need to // submit at least one. Otherwise the pallet will be treated as uninitialized and messages @@ -504,7 +510,7 @@ where let relay_header_at_target = best_finalized_peer_header_at_self::< P::TargetChain, P::SourceRelayChain, - >(target.client(), best_target_block_hash) + >(target.target_client(), best_target_block_hash) .await .map_err(map_target_err)?; diff --git a/bridges/relays/lib-substrate-relay/src/parachains/mod.rs b/bridges/relays/lib-substrate-relay/src/parachains/mod.rs index 722f9b61f9f08d87dac3bd95a0780d8422097a38..8b128bb770dd7a05d28ad46d4561f4d859b1deb6 100644 --- a/bridges/relays/lib-substrate-relay/src/parachains/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/parachains/mod.rs @@ -71,6 +71,7 @@ pub trait SubmitParachainHeadsCallBuilder: at_relay_block: HeaderIdOf, parachains: Vec<(ParaId, ParaHash)>, parachain_heads_proof: ParaHeadsProof, + is_free_execution_expected: bool, ) -> CallOf; } @@ -97,6 +98,7 @@ where at_relay_block: HeaderIdOf, parachains: Vec<(ParaId, ParaHash)>, parachain_heads_proof: ParaHeadsProof, + _is_free_execution_expected: bool, ) -> CallOf { BridgeParachainsCall::::submit_parachain_heads { at_relay_block: (at_relay_block.0, at_relay_block.1), diff --git a/bridges/relays/lib-substrate-relay/src/parachains/target.rs b/bridges/relays/lib-substrate-relay/src/parachains/target.rs index 6df7bc0a742a9f6693a422b994c7a9203f3c8b74..e10d15b6edf6c75773e2e10bc9caf2e406632284 100644 --- a/bridges/relays/lib-substrate-relay/src/parachains/target.rs +++ b/bridges/relays/lib-substrate-relay/src/parachains/target.rs @@ -24,42 +24,53 @@ use crate::{ }; use async_trait::async_trait; -use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; -use bp_runtime::HeaderIdProvider; -use codec::Decode; +use bp_parachains::{ + ImportedParaHeadsKeyProvider, ParaInfo, ParaStoredHeaderData, ParasInfoKeyProvider, +}; +use bp_polkadot_core::{ + parachains::{ParaHash, ParaHeadsProof, ParaId}, + BlockNumber as RelayBlockNumber, +}; +use bp_runtime::{ + Chain as ChainBase, HeaderId, HeaderIdProvider, StorageDoubleMapKeyProvider, + StorageMapKeyProvider, +}; use parachains_relay::parachains_loop::TargetClient; use relay_substrate_client::{ - AccountIdOf, AccountKeyPairOf, Chain, Client, Error as SubstrateError, HeaderIdOf, - ParachainBase, TransactionEra, TransactionTracker, UnsignedTransaction, + AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, Client, Error as SubstrateError, + HeaderIdOf, ParachainBase, RelayChain, TransactionEra, TransactionTracker, UnsignedTransaction, }; use relay_utils::relay_loop::Client as RelayClient; -use sp_core::{Bytes, Pair}; +use sp_core::Pair; /// Substrate client as parachain heads source. pub struct ParachainsTarget { - client: Client, + source_client: Client, + target_client: Client, transaction_params: TransactionParams>, } impl ParachainsTarget

{ /// Creates new parachains target client. pub fn new( - client: Client, + source_client: Client, + target_client: Client, transaction_params: TransactionParams>, ) -> Self { - ParachainsTarget { client, transaction_params } + ParachainsTarget { source_client, target_client, transaction_params } } /// Returns reference to the underlying RPC client. - pub fn client(&self) -> &Client { - &self.client + pub fn target_client(&self) -> &Client { + &self.target_client } } impl Clone for ParachainsTarget

{ fn clone(&self) -> Self { ParachainsTarget { - client: self.client.clone(), + source_client: self.source_client.clone(), + target_client: self.target_client.clone(), transaction_params: self.transaction_params.clone(), } } @@ -70,7 +81,9 @@ impl RelayClient for ParachainsTarget

{ type Error = SubstrateError; async fn reconnect(&mut self) -> Result<(), SubstrateError> { - self.client.reconnect().await + self.target_client.reconnect().await?; + self.source_client.reconnect().await?; + Ok(()) } } @@ -79,11 +92,13 @@ impl

TargetClient> for ParachainsTarget

where P: SubstrateParachainsPipeline, AccountIdOf: From< as Pair>::Public>, + P::SourceParachain: ChainBase, + P::SourceRelayChain: ChainBase, { type TransactionTracker = TransactionTracker>; async fn best_block(&self) -> Result, Self::Error> { - let best_header = self.client.best_header().await?; + let best_header = self.target_client.best_header().await?; let best_id = best_header.id(); Ok(best_id) @@ -93,7 +108,7 @@ where &self, at_block: &HeaderIdOf, ) -> Result, Self::Error> { - self.client + self.target_client .typed_state_call::<_, Option>>( P::SourceRelayChain::BEST_FINALIZED_HEADER_ID_METHOD.into(), (), @@ -104,23 +119,57 @@ where .unwrap_or(Err(SubstrateError::BridgePalletIsNotInitialized)) } + async fn free_source_relay_headers_interval( + &self, + ) -> Result>, Self::Error> { + self.target_client + .typed_state_call(P::SourceRelayChain::FREE_HEADERS_INTERVAL_METHOD.into(), (), None) + .await + } + async fn parachain_head( &self, at_block: HeaderIdOf, - ) -> Result>, Self::Error> { - let encoded_best_finalized_source_para_block = self - .client - .state_call( - P::SourceParachain::BEST_FINALIZED_HEADER_ID_METHOD.into(), - Bytes(Vec::new()), - Some(at_block.1), - ) - .await?; + ) -> Result< + Option<(HeaderIdOf, HeaderIdOf)>, + Self::Error, + > { + // read best parachain head from the target bridge-parachains pallet + let storage_key = ParasInfoKeyProvider::final_key( + P::SourceRelayChain::WITH_CHAIN_BRIDGE_PARACHAINS_PALLET_NAME, + &P::SourceParachain::PARACHAIN_ID.into(), + ); + let storage_value: Option = + self.target_client.storage_value(storage_key, Some(at_block.hash())).await?; + let para_info = match storage_value { + Some(para_info) => para_info, + None => return Ok(None), + }; + + // now we need to get full header ids. For source relay chain it is simple, because we + // are connected + let relay_header_id = self + .source_client + .header_by_number(para_info.best_head_hash.at_relay_block_number) + .await? + .id(); - Ok(Option::>::decode( - &mut &encoded_best_finalized_source_para_block.0[..], - ) - .map_err(SubstrateError::ResponseParseFailed)?) + // for parachain, we need to read from the target chain runtime storage + let storage_key = ImportedParaHeadsKeyProvider::final_key( + P::SourceRelayChain::WITH_CHAIN_BRIDGE_PARACHAINS_PALLET_NAME, + &P::SourceParachain::PARACHAIN_ID.into(), + ¶_info.best_head_hash.head_hash, + ); + let storage_value: Option = + self.target_client.storage_value(storage_key, Some(at_block.hash())).await?; + let para_head_number = match storage_value { + Some(para_head_data) => + para_head_data.decode_parachain_head_data::()?.number, + None => return Ok(None), + }; + + let para_head_id = HeaderId(para_head_number, para_info.best_head_hash.head_hash); + Ok(Some((relay_header_id, para_head_id))) } async fn submit_parachain_head_proof( @@ -128,14 +177,16 @@ where at_relay_block: HeaderIdOf, updated_head_hash: ParaHash, proof: ParaHeadsProof, + is_free_execution_expected: bool, ) -> Result { let transaction_params = self.transaction_params.clone(); let call = P::SubmitParachainHeadsCallBuilder::build_submit_parachain_heads_call( at_relay_block, vec![(ParaId(P::SourceParachain::PARACHAIN_ID), updated_head_hash)], proof, + is_free_execution_expected, ); - self.client + self.target_client .submit_and_watch_signed_extrinsic( &transaction_params.signer, move |best_block_id, transaction_nonce| { diff --git a/bridges/relays/messages/src/message_race_delivery.rs b/bridges/relays/messages/src/message_race_delivery.rs index 137deb5b74f757aa111d5652cbb251a94979e166..f18c43cc7f0e084100d8096432dccafbd61301be 100644 --- a/bridges/relays/messages/src/message_race_delivery.rs +++ b/bridges/relays/messages/src/message_race_delivery.rs @@ -446,7 +446,7 @@ where )) } - /// Returns lastest confirmed message at source chain, given source block. + /// Returns latest confirmed message at source chain, given source block. fn latest_confirmed_nonce_at_source(&self, at: &SourceHeaderIdOf

) -> Option { self.latest_confirmed_nonces_at_source .iter() diff --git a/bridges/relays/messages/src/message_race_strategy.rs b/bridges/relays/messages/src/message_race_strategy.rs index 93d178e55b04f64a9631f04b4e93b67594d67e54..3a532331d79dc83f680ca7b5e21e471e60335b84 100644 --- a/bridges/relays/messages/src/message_race_strategy.rs +++ b/bridges/relays/messages/src/message_race_strategy.rs @@ -567,7 +567,7 @@ mod tests { let source_header_1 = header_id(1); let target_header_1 = header_id(1); - // we start in perfec sync state - all headers are synced and finalized on both ends + // we start in perfect sync state - all headers are synced and finalized on both ends let mut state = TestRaceStateImpl { best_finalized_source_header_id_at_source: Some(source_header_1), best_finalized_source_header_id_at_best_target: Some(source_header_1), diff --git a/bridges/relays/parachains/README.md b/bridges/relays/parachains/README.md index 9043b0b0a9cdd84c984d62b4e7c3adc6df44d6bc..f24e7a4c5d3040750361c31d7196e684f46edd13 100644 --- a/bridges/relays/parachains/README.md +++ b/bridges/relays/parachains/README.md @@ -23,7 +23,7 @@ to return the best known head of given parachain. When required, it must be able finality delivery transaction to the target node. The main entrypoint for the crate is the [`run` function](./src/parachains_loop.rs), which takes source and target -clients and [`ParachainSyncParams`](./src/parachains_loop.rs) parameters. The most imporant parameter is the +clients and [`ParachainSyncParams`](./src/parachains_loop.rs) parameters. The most important parameter is the `parachains` - it is the set of parachains, which relay tracks and updates. The other important parameter that may affect the relay operational costs is the `strategy`. If it is set to `Any`, then the finality delivery transaction is submitted if at least one of tracked parachain heads is updated. The other option is `All`. Then diff --git a/bridges/relays/parachains/src/parachains_loop.rs b/bridges/relays/parachains/src/parachains_loop.rs index 41ebbf5aadede2b4a1f0c9dcde73ee83bd5b0766..55f236eeac1d842ff9ed0f337cf1ea2c725dc763 100644 --- a/bridges/relays/parachains/src/parachains_loop.rs +++ b/bridges/relays/parachains/src/parachains_loop.rs @@ -25,7 +25,7 @@ use futures::{ future::{FutureExt, Shared}, poll, select_biased, }; -use relay_substrate_client::{Chain, HeaderIdOf, ParachainBase}; +use relay_substrate_client::{BlockNumberOf, Chain, HeaderIdOf, ParachainBase}; use relay_utils::{ metrics::MetricsParams, relay_loop::Client as RelayClient, FailedClient, TrackedTransactionStatus, TransactionTracker, @@ -96,17 +96,27 @@ pub trait TargetClient: RelayClient { /// Get best block id. async fn best_block(&self) -> Result, Self::Error>; - /// Get best finalized source relay chain block id. + /// Get best finalized source relay chain block id. If `free_source_relay_headers_interval` + /// is `Some(_)`, the returned async fn best_finalized_source_relay_chain_block( &self, at_block: &HeaderIdOf, ) -> Result, Self::Error>; + /// Get free source **relay** headers submission interval, if it is configured in the + /// target runtime. We assume that the target chain will accept parachain header, proved + /// at such relay header for free. + async fn free_source_relay_headers_interval( + &self, + ) -> Result>, Self::Error>; /// Get parachain head id at given block. async fn parachain_head( &self, at_block: HeaderIdOf, - ) -> Result>, Self::Error>; + ) -> Result< + Option<(HeaderIdOf, HeaderIdOf)>, + Self::Error, + >; /// Submit parachain heads proof. async fn submit_parachain_head_proof( @@ -114,6 +124,7 @@ pub trait TargetClient: RelayClient { at_source_block: HeaderIdOf, para_head_hash: ParaHash, proof: ParaHeadsProof, + is_free_execution_expected: bool, ) -> Result; } @@ -133,6 +144,7 @@ pub async fn run( source_client: impl SourceClient

, target_client: impl TargetClient

, metrics_params: MetricsParams, + only_free_headers: bool, exit_signal: impl Future + 'static + Send, ) -> Result<(), relay_utils::Error> where @@ -145,7 +157,13 @@ where .expose() .await? .run(metrics_prefix::

(), move |source_client, target_client, metrics| { - run_until_connection_lost(source_client, target_client, metrics, exit_signal.clone()) + run_until_connection_lost( + source_client, + target_client, + metrics, + only_free_headers, + exit_signal.clone(), + ) }) .await } @@ -155,6 +173,7 @@ async fn run_until_connection_lost( source_client: impl SourceClient

, target_client: impl TargetClient

, metrics: Option, + only_free_headers: bool, exit_signal: impl Future + Send, ) -> Result<(), FailedClient> where @@ -166,6 +185,47 @@ where P::TargetChain::AVERAGE_BLOCK_INTERVAL, ); + // free parachain header = header, available (proved) at free relay chain block. Let's + // read interval of free source relay chain blocks from target client + let free_source_relay_headers_interval = if only_free_headers { + let free_source_relay_headers_interval = + target_client.free_source_relay_headers_interval().await.map_err(|e| { + log::warn!( + target: "bridge", + "Failed to read free {} headers interval at {}: {:?}", + P::SourceRelayChain::NAME, + P::TargetChain::NAME, + e, + ); + FailedClient::Target + })?; + match free_source_relay_headers_interval { + Some(free_source_relay_headers_interval) if free_source_relay_headers_interval != 0 => { + log::trace!( + target: "bridge", + "Free {} headers interval at {}: {:?}", + P::SourceRelayChain::NAME, + P::TargetChain::NAME, + free_source_relay_headers_interval, + ); + free_source_relay_headers_interval + }, + _ => { + log::warn!( + target: "bridge", + "Invalid free {} headers interval at {}: {:?}", + P::SourceRelayChain::NAME, + P::TargetChain::NAME, + free_source_relay_headers_interval, + ); + return Err(FailedClient::Target) + }, + } + } else { + // ignore - we don't need it + 0 + }; + let mut submitted_heads_tracker: Option> = None; futures::pin_mut!(exit_signal); @@ -211,7 +271,7 @@ where log::warn!(target: "bridge", "Failed to read best {} block: {:?}", P::SourceRelayChain::NAME, e); FailedClient::Target })?; - let head_at_target = + let (relay_of_head_at_target, head_at_target) = read_head_at_target(&target_client, metrics.as_ref(), &best_target_block).await?; // check if our transaction has been mined @@ -238,9 +298,9 @@ where } } - // we have no active transaction and may need to update heads, but do we have something for - // update? - let best_finalized_relay_block = target_client + // in all-headers strategy we'll be submitting para head, available at + // `best_finalized_relay_block_at_target` + let best_finalized_relay_block_at_target = target_client .best_finalized_source_relay_chain_block(&best_target_block) .await .map_err(|e| { @@ -253,21 +313,56 @@ where ); FailedClient::Target })?; + + // ..but if we only need to submit free headers, we need to submit para + // head, available at best free source relay chain header, known to the + // target chain + let prove_at_relay_block = if only_free_headers { + match relay_of_head_at_target { + Some(relay_of_head_at_target) => { + // find last free relay chain header in the range that we are interested in + let scan_range_begin = relay_of_head_at_target.number(); + let scan_range_end = best_finalized_relay_block_at_target.number(); + if scan_range_end.saturating_sub(scan_range_begin) < + free_source_relay_headers_interval + { + // there are no new **free** relay chain headers in the range + log::trace!( + target: "bridge", + "Waiting for new free {} headers at {}: scanned {:?}..={:?}", + P::SourceRelayChain::NAME, + P::TargetChain::NAME, + scan_range_begin, + scan_range_end, + ); + continue; + } + + // we may submit new parachain head for free + best_finalized_relay_block_at_target + }, + None => { + // no parachain head at target => let's submit first one + best_finalized_relay_block_at_target + }, + } + } else { + best_finalized_relay_block_at_target + }; + + // now let's check if we need to update parachain head at all let head_at_source = - read_head_at_source(&source_client, metrics.as_ref(), &best_finalized_relay_block) - .await?; + read_head_at_source(&source_client, metrics.as_ref(), &prove_at_relay_block).await?; let is_update_required = is_update_required::

( head_at_source, head_at_target, - best_finalized_relay_block, + prove_at_relay_block, best_target_block, ); if is_update_required { - let (head_proof, head_hash) = source_client - .prove_parachain_head(best_finalized_relay_block) - .await - .map_err(|e| { + let (head_proof, head_hash) = + source_client.prove_parachain_head(prove_at_relay_block).await.map_err(|e| { log::warn!( target: "bridge", "Failed to prove {} parachain ParaId({}) heads: {:?}", @@ -283,12 +378,17 @@ where P::SourceRelayChain::NAME, P::SourceParachain::PARACHAIN_ID, P::TargetChain::NAME, - best_finalized_relay_block, + prove_at_relay_block, head_hash, ); let transaction_tracker = target_client - .submit_parachain_head_proof(best_finalized_relay_block, head_hash, head_proof) + .submit_parachain_head_proof( + prove_at_relay_block, + head_hash, + head_proof, + only_free_headers, + ) .await .map_err(|e| { log::warn!( @@ -311,7 +411,7 @@ where fn is_update_required( head_at_source: AvailableHeader>, head_at_target: Option>, - best_finalized_relay_block_at_source: HeaderIdOf, + prove_at_relay_block: HeaderIdOf, best_target_block: HeaderIdOf, ) -> bool where @@ -326,7 +426,7 @@ where P::SourceParachain::PARACHAIN_ID, P::TargetChain::NAME, P::SourceRelayChain::NAME, - best_finalized_relay_block_at_source, + prove_at_relay_block, head_at_source, P::TargetChain::NAME, best_target_block, @@ -413,24 +513,28 @@ async fn read_head_at_source( } } -/// Reads parachain head from the target client. +/// Reads parachain head from the target client. Also returns source relay chain header +/// that has been used to prove that head. async fn read_head_at_target( target_client: &impl TargetClient

, metrics: Option<&ParachainsLoopMetrics>, at_block: &HeaderIdOf, -) -> Result>, FailedClient> { +) -> Result< + (Option>, Option>), + FailedClient, +> { let para_head_id = target_client.parachain_head(*at_block).await; match para_head_id { - Ok(Some(para_head_id)) => { + Ok(Some((relay_header_id, para_head_id))) => { if let Some(metrics) = metrics { metrics.update_best_parachain_block_at_target( ParaId(P::SourceParachain::PARACHAIN_ID), para_head_id.number(), ); } - Ok(Some(para_head_id)) + Ok((Some(relay_header_id), Some(para_head_id))) }, - Ok(None) => Ok(None), + Ok(None) => Ok((None, None)), Err(e) => { log::warn!( target: "bridge", @@ -543,6 +647,7 @@ mod tests { use relay_substrate_client::test_chain::{TestChain, TestParachain}; use relay_utils::{HeaderId, MaybeConnectionError}; use sp_core::H256; + use std::collections::HashMap; const PARA_10_HASH: ParaHash = H256([10u8; 32]); const PARA_20_HASH: ParaHash = H256([20u8; 32]); @@ -590,14 +695,21 @@ mod tests { #[derive(Clone, Debug)] struct TestClientData { source_sync_status: Result, - source_head: Result>, TestError>, + source_head: HashMap< + BlockNumberOf, + Result>, TestError>, + >, source_proof: Result<(), TestError>, + target_free_source_relay_headers_interval: + Result>, TestError>, target_best_block: Result, TestError>, target_best_finalized_source_block: Result, TestError>, - target_head: Result>, TestError>, + #[allow(clippy::type_complexity)] + target_head: Result, HeaderIdOf)>, TestError>, target_submit_result: Result<(), TestError>, + submitted_proof_at_source_relay_block: Option>, exit_signal_sender: Option>>, } @@ -605,14 +717,18 @@ mod tests { pub fn minimal() -> Self { TestClientData { source_sync_status: Ok(true), - source_head: Ok(AvailableHeader::Available(HeaderId(0, PARA_20_HASH))), + source_head: vec![(0, Ok(AvailableHeader::Available(HeaderId(0, PARA_20_HASH))))] + .into_iter() + .collect(), source_proof: Ok(()), + target_free_source_relay_headers_interval: Ok(None), target_best_block: Ok(HeaderId(0, Default::default())), target_best_finalized_source_block: Ok(HeaderId(0, Default::default())), target_head: Ok(None), target_submit_result: Ok(()), + submitted_proof_at_source_relay_block: None, exit_signal_sender: None, } } @@ -649,16 +765,24 @@ mod tests { async fn parachain_head( &self, - _at_block: HeaderIdOf, + at_block: HeaderIdOf, ) -> Result>, TestError> { - self.data.lock().await.source_head.clone() + self.data + .lock() + .await + .source_head + .get(&at_block.0) + .expect(&format!("SourceClient::parachain_head({})", at_block.0)) + .clone() } async fn prove_parachain_head( &self, - _at_block: HeaderIdOf, + at_block: HeaderIdOf, ) -> Result<(ParaHeadsProof, ParaHash), TestError> { - let head = *self.data.lock().await.source_head.clone()?.as_available().unwrap(); + let head_result = + SourceClient::::parachain_head(self, at_block).await?; + let head = head_result.as_available().unwrap(); let storage_proof = vec![head.hash().encode()]; let proof = (ParaHeadsProof { storage_proof }, head.hash()); self.data.lock().await.source_proof.clone().map(|_| proof) @@ -680,21 +804,29 @@ mod tests { self.data.lock().await.target_best_finalized_source_block.clone() } + async fn free_source_relay_headers_interval( + &self, + ) -> Result>, TestError> { + self.data.lock().await.target_free_source_relay_headers_interval.clone() + } + async fn parachain_head( &self, _at_block: HeaderIdOf, - ) -> Result>, TestError> { + ) -> Result, HeaderIdOf)>, TestError> { self.data.lock().await.target_head.clone() } async fn submit_parachain_head_proof( &self, - _at_source_block: HeaderIdOf, + at_source_block: HeaderIdOf, _updated_parachain_head: ParaHash, _proof: ParaHeadsProof, + _is_free_execution_expected: bool, ) -> Result { let mut data = self.data.lock().await; data.target_submit_result.clone()?; + data.submitted_proof_at_source_relay_block = Some(at_source_block); if let Some(mut exit_signal_sender) = data.exit_signal_sender.take() { exit_signal_sender.send(()).await.unwrap(); @@ -715,6 +847,7 @@ mod tests { TestClient::from(test_source_client), TestClient::from(TestClientData::minimal()), None, + false, futures::future::pending(), )), Err(FailedClient::Source), @@ -731,6 +864,7 @@ mod tests { TestClient::from(TestClientData::minimal()), TestClient::from(test_target_client), None, + false, futures::future::pending(), )), Err(FailedClient::Target), @@ -747,6 +881,7 @@ mod tests { TestClient::from(TestClientData::minimal()), TestClient::from(test_target_client), None, + false, futures::future::pending(), )), Err(FailedClient::Target), @@ -763,6 +898,7 @@ mod tests { TestClient::from(TestClientData::minimal()), TestClient::from(test_target_client), None, + false, futures::future::pending(), )), Err(FailedClient::Target), @@ -772,13 +908,14 @@ mod tests { #[test] fn when_source_client_fails_to_read_heads() { let mut test_source_client = TestClientData::minimal(); - test_source_client.source_head = Err(TestError::Error); + test_source_client.source_head.insert(0, Err(TestError::Error)); assert_eq!( async_std::task::block_on(run_until_connection_lost( TestClient::from(test_source_client), TestClient::from(TestClientData::minimal()), None, + false, futures::future::pending(), )), Err(FailedClient::Source), @@ -795,6 +932,7 @@ mod tests { TestClient::from(test_source_client), TestClient::from(TestClientData::minimal()), None, + false, futures::future::pending(), )), Err(FailedClient::Source), @@ -811,6 +949,7 @@ mod tests { TestClient::from(TestClientData::minimal()), TestClient::from(test_target_client), None, + false, futures::future::pending(), )), Err(FailedClient::Target), @@ -825,12 +964,108 @@ mod tests { TestClient::from(TestClientData::minimal()), TestClient::from(TestClientData::with_exit_signal_sender(exit_signal_sender)), None, + false, exit_signal.into_future().map(|(_, _)| ()), )), Ok(()), ); } + #[async_std::test] + async fn free_headers_are_relayed() { + // prepare following case: + // 1) best source relay at target: 95 + // 2) best source parachain at target: 5 at relay 50 + // 3) free headers interval: 10 + // 4) at source relay chain block 90 source parachain block is 9 + // + + // 5) best finalized source relay chain block is 95 + // 6) at source relay chain block 95 source parachain block is 42 + // => + // parachain block 42 would have been relayed, because 95 - 50 > 10 + let (exit_signal_sender, exit_signal) = futures::channel::mpsc::unbounded(); + let clients_data = TestClientData { + source_sync_status: Ok(true), + source_head: vec![ + (90, Ok(AvailableHeader::Available(HeaderId(9, [9u8; 32].into())))), + (95, Ok(AvailableHeader::Available(HeaderId(42, [42u8; 32].into())))), + ] + .into_iter() + .collect(), + source_proof: Ok(()), + + target_free_source_relay_headers_interval: Ok(Some(10)), + target_best_block: Ok(HeaderId(200, [200u8; 32].into())), + target_best_finalized_source_block: Ok(HeaderId(95, [95u8; 32].into())), + target_head: Ok(Some((HeaderId(50, [50u8; 32].into()), HeaderId(5, [5u8; 32].into())))), + target_submit_result: Ok(()), + + submitted_proof_at_source_relay_block: None, + exit_signal_sender: Some(Box::new(exit_signal_sender)), + }; + + let source_client = TestClient::from(clients_data.clone()); + let target_client = TestClient::from(clients_data); + assert_eq!( + run_until_connection_lost( + source_client, + target_client.clone(), + None, + true, + exit_signal.into_future().map(|(_, _)| ()), + ) + .await, + Ok(()), + ); + + assert_eq!( + target_client + .data + .lock() + .await + .submitted_proof_at_source_relay_block + .map(|id| id.0), + Some(95) + ); + + // now source relay block chain 104 is mined with parachain head #84 + // => since 104 - 95 < 10, there are no free headers + // => nothing is submitted + let mut clients_data: TestClientData = target_client.data.lock().await.clone(); + clients_data + .source_head + .insert(104, Ok(AvailableHeader::Available(HeaderId(84, [84u8; 32].into())))); + clients_data.target_best_finalized_source_block = Ok(HeaderId(104, [104u8; 32].into())); + clients_data.target_head = + Ok(Some((HeaderId(95, [95u8; 32].into()), HeaderId(42, [42u8; 32].into())))); + clients_data.target_best_block = Ok(HeaderId(255, [255u8; 32].into())); + clients_data.exit_signal_sender = None; + + let source_client = TestClient::from(clients_data.clone()); + let target_client = TestClient::from(clients_data); + assert_eq!( + run_until_connection_lost( + source_client, + target_client.clone(), + None, + true, + async_std::task::sleep(std::time::Duration::from_millis(100)), + ) + .await, + Ok(()), + ); + + assert_eq!( + target_client + .data + .lock() + .await + .submitted_proof_at_source_relay_block + .map(|id| id.0), + Some(95) + ); + } + fn test_tx_tracker() -> SubmittedHeadsTracker { SubmittedHeadsTracker::new( AvailableHeader::Available(HeaderId(20, PARA_20_HASH)), diff --git a/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs index 826d535c2cb922610ba4811d607a9024de8d33ab..31a8992442d83b789849bdd99bedef31109ecc5a 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs @@ -3,7 +3,7 @@ use snowbridge_core::{inbound::Log, ChannelId}; use sp_core::{RuntimeDebug, H160, H256}; -use sp_std::{convert::TryFrom, prelude::*}; +use sp_std::prelude::*; use alloy_primitives::B256; use alloy_sol_types::{sol, SolEvent}; diff --git a/bridges/snowbridge/pallets/inbound-queue/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs index 8acbb0c2916e704930268835e12bd14972737114..4a1486204eb08a43846bafaacfa05c465d8dc5fd 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs @@ -50,7 +50,7 @@ use frame_system::ensure_signed; use scale_info::TypeInfo; use sp_core::{H160, H256}; use sp_runtime::traits::Zero; -use sp_std::{convert::TryFrom, vec}; +use sp_std::vec; use xcm::prelude::{ send_xcm, Instruction::SetTopic, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm, XcmContext, XcmHash, diff --git a/bridges/snowbridge/primitives/beacon/src/bits.rs b/bridges/snowbridge/primitives/beacon/src/bits.rs index 72b7135ee2939bdabb98c9c06df801c43c3db230..fb03588cf8b71bfa12530d46ddac353dac3c7e28 100644 --- a/bridges/snowbridge/primitives/beacon/src/bits.rs +++ b/bridges/snowbridge/primitives/beacon/src/bits.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -use sp_std::{convert::TryInto, prelude::*}; +use sp_std::prelude::*; use ssz_rs::{Bitvector, Deserialize}; pub fn decompress_sync_committee_bits< diff --git a/bridges/snowbridge/primitives/beacon/src/serde_utils.rs b/bridges/snowbridge/primitives/beacon/src/serde_utils.rs index 07f5cbe724ed92bbda1d0cc7ded1a60c92a38cf0..5e39ff91225774a6b201b05a6c1325964eb256db 100644 --- a/bridges/snowbridge/primitives/beacon/src/serde_utils.rs +++ b/bridges/snowbridge/primitives/beacon/src/serde_utils.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Deserializer}; // helper to deserialize arbitrary arrays like [T; N] pub mod arrays { - use std::{convert::TryInto, marker::PhantomData}; + use std::marker::PhantomData; use serde::{ de::{SeqAccess, Visitor}, diff --git a/bridges/snowbridge/primitives/ethereum/src/header.rs b/bridges/snowbridge/primitives/ethereum/src/header.rs index f0b51f8c79de8fa3f1b37205c38d8a8640771f0c..48fa179fe4fa8dee5db7ffc4d4b0704f8f8a5584 100644 --- a/bridges/snowbridge/primitives/ethereum/src/header.rs +++ b/bridges/snowbridge/primitives/ethereum/src/header.rs @@ -8,7 +8,7 @@ use rlp::RlpStream; use scale_info::TypeInfo; use sp_io::hashing::keccak_256; use sp_runtime::RuntimeDebug; -use sp_std::{convert::TryInto, prelude::*}; +use sp_std::prelude::*; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; diff --git a/bridges/snowbridge/primitives/ethereum/src/mpt.rs b/bridges/snowbridge/primitives/ethereum/src/mpt.rs index 9a2dae486dcc05ee5c078e0794ee2d27193eb207..0365f5e994feada7eadde9c908f962e47854c8dc 100644 --- a/bridges/snowbridge/primitives/ethereum/src/mpt.rs +++ b/bridges/snowbridge/primitives/ethereum/src/mpt.rs @@ -3,7 +3,7 @@ //! Helper types to work with Ethereum's Merkle Patricia Trie nodes use ethereum_types::H256; -use sp_std::{convert::TryFrom, prelude::*}; +use sp_std::prelude::*; pub trait Node { fn contains_hash(&self, hash: H256) -> bool; diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index c20554c6d184412f6bff0ec332a775ca37c16a6a..54e47a7a8b6af0a0dce1d54d52e3e0799e6f6e84 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -273,8 +273,10 @@ where }, None => { instructions.extend(vec![ - // Deposit asset to beneficiary. - DepositAsset { assets: Definite(asset.into()), beneficiary }, + // Deposit both asset and fees to beneficiary so the fees will not get + // trapped. Another benefit is when fees left more than ED on AssetHub could be + // used to create the beneficiary account in case it does not exist. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, ]); }, } diff --git a/bridges/snowbridge/runtime/test-common/Cargo.toml b/bridges/snowbridge/runtime/test-common/Cargo.toml index 92970339fac03dd9209546c9319b899509faca2c..7cbb38574034c0b4c7f8b0bf82866c0392c82a10 100644 --- a/bridges/snowbridge/runtime/test-common/Cargo.toml +++ b/bridges/snowbridge/runtime/test-common/Cargo.toml @@ -3,7 +3,7 @@ name = "snowbridge-runtime-test-common" description = "Snowbridge Runtime Tests" version = "0.2.0" authors = ["Snowfork "] -edition = "2021" +edition.workspace = true license = "Apache-2.0" categories = ["cryptography::cryptocurrencies"] diff --git a/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml index 52271f9442131923f8a758b16df7610e73813d15..f59f689bf6b5c40a09854b93eb7927fc4b5929c9 100644 --- a/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml +++ b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml @@ -40,7 +40,7 @@ cumulus_based = true rpc_port = 8933 ws_port = 8943 args = [ - "-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" + "-lparachain=debug,runtime::bridge=trace,xcm=trace,txpool=trace" ] # run bob as parachain collator @@ -51,7 +51,7 @@ cumulus_based = true rpc_port = 8934 ws_port = 8944 args = [ - "-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" + "-lparachain=debug,runtime::bridge=trace,xcm=trace,txpool=trace" ] [[parachains]] @@ -65,14 +65,14 @@ cumulus_based = true ws_port = 9910 command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" + "-lparachain=debug,xcm=trace,runtime::bridge=trace,txpool=trace" ] [[parachains.collators]] name = "asset-hub-rococo-collator2" command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" + "-lparachain=debug,xcm=trace,runtime::bridge=trace,txpool=trace" ] #[[hrmp_channels]] diff --git a/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml index f2550bcc9959638b21ea78043cca3bc12d3d23ea..6ab03ad5fe2c380ea4201bf8ef2a2cf405fe314b 100644 --- a/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml +++ b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml @@ -40,7 +40,7 @@ cumulus_based = true rpc_port = 8935 ws_port = 8945 args = [ - "-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" + "-lparachain=debug,runtime::bridge=trace,xcm=trace,txpool=trace" ] # run bob as parachain collator @@ -51,7 +51,7 @@ cumulus_based = true rpc_port = 8936 ws_port = 8946 args = [ - "-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" + "-lparachain=debug,runtime::bridge=trace,xcm=trace,txpool=trace" ] [[parachains]] @@ -65,14 +65,14 @@ cumulus_based = true ws_port = 9010 command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" + "-lparachain=debug,xcm=trace,runtime::bridge=trace,txpool=trace" ] [[parachains.collators]] name = "asset-hub-westend-collator2" command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" + "-lparachain=debug,xcm=trace,runtime::bridge=trace,txpool=trace" ] #[[hrmp_channels]] diff --git a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh index 66c9ddc037b8efb005d2239b174eb5710dddaf53..2f11692d97b9d2ff1853b9e85f7f30f884702bc7 100755 --- a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh +++ b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh @@ -169,12 +169,107 @@ function run_relay() { --lane "${LANE_ID}" } +function run_finality_relay() { + local relayer_path=$(ensure_relayer) + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-headers rococo-to-bridge-hub-westend \ + --only-free-headers \ + --source-host localhost \ + --source-port 9942 \ + --target-host localhost \ + --target-port 8945 \ + --target-version-mode Auto \ + --target-signer //Charlie \ + --target-transactions-mortality 4& + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-headers westend-to-bridge-hub-rococo \ + --only-free-headers \ + --source-host localhost \ + --source-port 9945 \ + --target-host localhost \ + --target-port 8943 \ + --target-version-mode Auto \ + --target-signer //Charlie \ + --target-transactions-mortality 4 +} + +function run_parachains_relay() { + local relayer_path=$(ensure_relayer) + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-parachains rococo-to-bridge-hub-westend \ + --only-free-headers \ + --source-host localhost \ + --source-port 9942 \ + --target-host localhost \ + --target-port 8945 \ + --target-version-mode Auto \ + --target-signer //Dave \ + --target-transactions-mortality 4& + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-parachains westend-to-bridge-hub-rococo \ + --only-free-headers \ + --source-host localhost \ + --source-port 9945 \ + --target-host localhost \ + --target-port 8943 \ + --target-version-mode Auto \ + --target-signer //Dave \ + --target-transactions-mortality 4 +} + +function run_messages_relay() { + local relayer_path=$(ensure_relayer) + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-messages bridge-hub-rococo-to-bridge-hub-westend \ + --source-host localhost \ + --source-port 8943 \ + --source-version-mode Auto \ + --source-signer //Eve \ + --source-transactions-mortality 4 \ + --target-host localhost \ + --target-port 8945 \ + --target-version-mode Auto \ + --target-signer //Eve \ + --target-transactions-mortality 4 \ + --lane $LANE_ID& + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-messages bridge-hub-westend-to-bridge-hub-rococo \ + --source-host localhost \ + --source-port 8945 \ + --source-version-mode Auto \ + --source-signer //Ferdie \ + --source-transactions-mortality 4 \ + --target-host localhost \ + --target-port 8943 \ + --target-version-mode Auto \ + --target-signer //Ferdie \ + --target-transactions-mortality 4 \ + --lane $LANE_ID +} + case "$1" in run-relay) init_wnd_ro init_ro_wnd run_relay ;; + run-finality-relay) + init_wnd_ro + init_ro_wnd + run_finality_relay + ;; + run-parachains-relay) + run_parachains_relay + ;; + run-messages-relay) + run_messages_relay + ;; init-asset-hub-rococo-local) ensure_polkadot_js_api # create foreign assets for native Westend token (governance call on Rococo) @@ -212,19 +307,19 @@ case "$1" in "ws://127.0.0.1:8943" \ "//Alice" \ "$ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO" \ - $((1000000000000 + 50000000000 * 20)) + 100000000000000 # drip SA of lane dedicated to asset hub for paying rewards for delivery transfer_balance \ "ws://127.0.0.1:8943" \ "//Alice" \ "$ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain" \ - $((1000000000000 + 2000000000000)) + 100000000000000 # drip SA of lane dedicated to asset hub for paying rewards for delivery confirmation transfer_balance \ "ws://127.0.0.1:8943" \ "//Alice" \ "$ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain" \ - $((1000000000000 + 2000000000000)) + 100000000000000 # set XCM version of remote BridgeHubWestend force_xcm_version \ "ws://127.0.0.1:9942" \ @@ -270,19 +365,19 @@ case "$1" in "ws://127.0.0.1:8945" \ "//Alice" \ "$ASSET_HUB_WESTEND_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WESTEND" \ - $((1000000000000000 + 50000000000 * 20)) + 100000000000000 # drip SA of lane dedicated to asset hub for paying rewards for delivery transfer_balance \ "ws://127.0.0.1:8945" \ "//Alice" \ "$ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain" \ - $((1000000000000000 + 2000000000000)) + 100000000000000 # drip SA of lane dedicated to asset hub for paying rewards for delivery confirmation transfer_balance \ "ws://127.0.0.1:8945" \ "//Alice" \ "$ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain" \ - $((1000000000000000 + 2000000000000)) + 100000000000000 # set XCM version of remote BridgeHubRococo force_xcm_version \ "ws://127.0.0.1:9945" \ @@ -386,6 +481,9 @@ case "$1" in echo "A command is require. Supported commands for: Local (zombienet) run: - run-relay + - run-finality-relay + - run-parachains-relay + - run-messages-relay - init-asset-hub-rococo-local - init-bridge-hub-rococo-local - init-asset-hub-westend-local diff --git a/bridges/testing/environments/rococo-westend/explorers.sh b/bridges/testing/environments/rococo-westend/explorers.sh new file mode 100755 index 0000000000000000000000000000000000000000..fb137726c93cb789c6a03fb22d913b4ee5a822bd --- /dev/null +++ b/bridges/testing/environments/rococo-westend/explorers.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Rococo AH +xdg-open https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9910#/explorer& +# Rococo BH +xdg-open https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8943#/explorer& + +# Westend BH +xdg-open https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8945#/explorer& +# Westend AH +xdg-open https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9010#/explorer& diff --git a/bridges/testing/environments/rococo-westend/helper.sh b/bridges/testing/environments/rococo-westend/helper.sh index 0a13ded213f5d3a0920cb466fc974c129e9ad79a..571c78fea584893b5c24c7f1b2b68335559bf26f 100755 --- a/bridges/testing/environments/rococo-westend/helper.sh +++ b/bridges/testing/environments/rococo-westend/helper.sh @@ -1,3 +1,9 @@ #!/bin/bash -$ENV_PATH/bridges_rococo_westend.sh "$@" +if [ $1 == "auto-log" ]; then + shift # ignore "auto-log" + log_name=$1 + $ENV_PATH/bridges_rococo_westend.sh "$@" >$TEST_DIR/logs/$log_name.log +else + $ENV_PATH/bridges_rococo_westend.sh "$@" +fi diff --git a/bridges/testing/environments/rococo-westend/spawn.sh b/bridges/testing/environments/rococo-westend/spawn.sh index cbd0b1bc623ab77876ed5ce3beefd7ab72db2d37..a0ab00be14448f92bf31f2eea2eba91c2ac5240e 100755 --- a/bridges/testing/environments/rococo-westend/spawn.sh +++ b/bridges/testing/environments/rococo-westend/spawn.sh @@ -59,12 +59,12 @@ if [[ $init -eq 1 ]]; then fi if [[ $start_relayer -eq 1 ]]; then - ${BASH_SOURCE%/*}/start_relayer.sh $rococo_dir $westend_dir relayer_pid + ${BASH_SOURCE%/*}/start_relayer.sh $rococo_dir $westend_dir finality_relayer_pid parachains_relayer_pid messages_relayer_pid fi echo $rococo_dir > $TEST_DIR/rococo.env echo $westend_dir > $TEST_DIR/westend.env echo -wait -n $rococo_pid $westend_pid $relayer_pid +wait -n $rococo_pid $westend_pid $finality_relayer_pid $parachains_relayer_pid $messages_relayer_pid kill -9 -$$ diff --git a/bridges/testing/environments/rococo-westend/start_relayer.sh b/bridges/testing/environments/rococo-westend/start_relayer.sh index 7ddd312d395aa8733d2afea59277b48721c8a36b..9c57e4a6ab6e198e10e8c233c9c9e64a3499a0f4 100755 --- a/bridges/testing/environments/rococo-westend/start_relayer.sh +++ b/bridges/testing/environments/rococo-westend/start_relayer.sh @@ -7,17 +7,31 @@ source "$FRAMEWORK_PATH/utils/zombienet.sh" rococo_dir=$1 westend_dir=$2 -__relayer_pid=$3 +__finality_relayer_pid=$3 +__parachains_relayer_pid=$4 +__messages_relayer_pid=$5 logs_dir=$TEST_DIR/logs helper_script="${BASH_SOURCE%/*}/helper.sh" -relayer_log=$logs_dir/relayer.log -echo -e "Starting rococo-westend relayer. Logs available at: $relayer_log\n" -start_background_process "$helper_script run-relay" $relayer_log relayer_pid +# start finality relayer +finality_relayer_log=$logs_dir/relayer_finality.log +echo -e "Starting rococo-westend finality relayer. Logs available at: $finality_relayer_log\n" +start_background_process "$helper_script run-finality-relay" $finality_relayer_log finality_relayer_pid + +# start parachains relayer +parachains_relayer_log=$logs_dir/relayer_parachains.log +echo -e "Starting rococo-westend parachains relayer. Logs available at: $parachains_relayer_log\n" +start_background_process "$helper_script run-parachains-relay" $parachains_relayer_log parachains_relayer_pid + +# start messages relayer +messages_relayer_log=$logs_dir/relayer_messages.log +echo -e "Starting rococo-westend messages relayer. Logs available at: $messages_relayer_log\n" +start_background_process "$helper_script run-messages-relay" $messages_relayer_log messages_relayer_pid run_zndsl ${BASH_SOURCE%/*}/rococo.zndsl $rococo_dir run_zndsl ${BASH_SOURCE%/*}/westend.zndsl $westend_dir -eval $__relayer_pid="'$relayer_pid'" - +eval $__finality_relayer_pid="'$finality_relayer_pid'" +eval $__parachains_relayer_pid="'$parachains_relayer_pid'" +eval $__messages_relayer_pid="'$messages_relayer_pid'" diff --git a/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js b/bridges/testing/framework/js-helpers/multiple-headers-synced.js similarity index 61% rename from bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js rename to bridges/testing/framework/js-helpers/multiple-headers-synced.js index 979179245ebe9f5b250efca6f2e6199ef0ac86d7..a30efc821657c3b70b072ad2399db8a5cf3d6d76 100644 --- a/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js +++ b/bridges/testing/framework/js-helpers/multiple-headers-synced.js @@ -10,33 +10,23 @@ async function run(nodeName, networkInfo, args) { // start listening to new blocks let totalGrandpaHeaders = 0; - let initialParachainHeaderImported = false; + let totalParachainHeaders = 0; api.rpc.chain.subscribeNewHeads(async function (header) { const apiAtParent = await api.at(header.parentHash); const apiAtCurrent = await api.at(header.hash); const currentEvents = await apiAtCurrent.query.system.events(); - totalGrandpaHeaders += await utils.ensureOnlyMandatoryGrandpaHeadersImported( - bridgedChain, - apiAtParent, - apiAtCurrent, - currentEvents, - ); - initialParachainHeaderImported = await utils.ensureOnlyInitialParachainHeaderImported( - bridgedChain, - apiAtParent, - apiAtCurrent, - currentEvents, - ); + totalGrandpaHeaders += await utils.countGrandpaHeaderImports(bridgedChain, currentEvents); + totalParachainHeaders += await utils.countParachainHeaderImports(bridgedChain, currentEvents); }); // wait given time await new Promise(resolve => setTimeout(resolve, exitAfterSeconds * 1000)); - // if we haven't seen any new GRANDPA or parachain headers => fail - if (totalGrandpaHeaders == 0) { + // if we haven't seen many (>1) new GRANDPA or parachain headers => fail + if (totalGrandpaHeaders <= 1) { throw new Error("No bridged relay chain headers imported"); } - if (!initialParachainHeaderImported) { + if (totalParachainHeaders <= 1) { throw new Error("No bridged parachain headers imported"); } } diff --git a/bridges/testing/framework/js-helpers/native-asset-balance.js b/bridges/testing/framework/js-helpers/native-asset-balance.js new file mode 100644 index 0000000000000000000000000000000000000000..4869eba35d8dd53278793f89b1fd38d2d703aa3b --- /dev/null +++ b/bridges/testing/framework/js-helpers/native-asset-balance.js @@ -0,0 +1,12 @@ +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + const accountAddress = args[0]; + const accountData = await api.query.system.account(accountAddress); + const accountBalance = accountData.data['free']; + console.log("Balance of " + accountAddress + ": " + accountBalance); + return accountBalance; +} + +module.exports = {run} diff --git a/bridges/testing/framework/utils/bridges.sh b/bridges/testing/framework/utils/bridges.sh index 7c8399461584a85e4e8eedf5f347d9d74725f1c9..07d9e4cd50b1651961724ac2d4c2badca2030e71 100755 --- a/bridges/testing/framework/utils/bridges.sh +++ b/bridges/testing/framework/utils/bridges.sh @@ -53,7 +53,7 @@ function call_polkadot_js_api() { # With it, it just submits it to the tx pool and exits. # --nonce -1: means to compute transaction nonce using `system_accountNextIndex` RPC, which includes all # transaction that are in the tx pool. - polkadot-js-api --noWait --nonce -1 "$@" + polkadot-js-api --nonce -1 "$@" || true } function generate_hex_encoded_call_data() { diff --git a/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl index cdb7d28e940cf1ac90562e761cdbad00e95e1748..6e26632fd9f9cc30b108476ea414ef432254e32e 100644 --- a/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl +++ b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl @@ -3,10 +3,10 @@ Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml Creds: config # send 5 ROC to //Alice from Rococo AH to Westend AH -asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-rococo-local 5000000000000" within 120 seconds +asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "auto-log reserve-transfer-assets-from-asset-hub-rococo-local 5000000000000" within 120 seconds # check that //Alice received at least 4.8 ROC on Westend AH asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,4800000000000,Rococo" within 600 seconds -# check that the relayer //Charlie is rewarded by Westend AH -bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 30 seconds +# relayer //Ferdie is rewarded for delivering messages from Rococo BH +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw,0x00000002,0x6268726F,ThisChain,0" within 300 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/roc-relayer-balance-does-not-change.zndsl b/bridges/testing/tests/0001-asset-transfer/roc-relayer-balance-does-not-change.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..4839c19c0ff2b6343718711d117c86834f6fa6b8 --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/roc-relayer-balance-does-not-change.zndsl @@ -0,0 +1,11 @@ +Description: Finality and parachain relays should have the constant balance, because their transactions are free +Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml +Creds: config + +# local chain spec gives `1u64 << 60` tokens to every endowed account: if it'll ever +# change, it'd need to be fixed here as well + +# //Charlie only submits free and mandatory relay chain headers, so the balance should stay the same +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" return is 1152921504606846976 within 30 seconds +# //Dave only submits free parachain headers, so the balance should stay the same +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy" return is 1152921504606846976 within 30 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/run.sh b/bridges/testing/tests/0001-asset-transfer/run.sh index a7bb122919b40187c49e89c489d2271d646bff40..227069932f2d985da05c82b88247da0542e46c58 100755 --- a/bridges/testing/tests/0001-asset-transfer/run.sh +++ b/bridges/testing/tests/0001-asset-transfer/run.sh @@ -18,8 +18,14 @@ ensure_process_file $env_pid $TEST_DIR/westend.env 300 westend_dir=`cat $TEST_DIR/westend.env` echo +run_zndsl ${BASH_SOURCE%/*}/roc-relayer-balance-does-not-change.zndsl $rococo_dir +run_zndsl ${BASH_SOURCE%/*}/wnd-relayer-balance-does-not-change.zndsl $westend_dir + run_zndsl ${BASH_SOURCE%/*}/roc-reaches-westend.zndsl $westend_dir run_zndsl ${BASH_SOURCE%/*}/wnd-reaches-rococo.zndsl $rococo_dir run_zndsl ${BASH_SOURCE%/*}/wroc-reaches-rococo.zndsl $rococo_dir run_zndsl ${BASH_SOURCE%/*}/wwnd-reaches-westend.zndsl $westend_dir + +run_zndsl ${BASH_SOURCE%/*}/roc-relayer-balance-does-not-change.zndsl $rococo_dir +run_zndsl ${BASH_SOURCE%/*}/wnd-relayer-balance-does-not-change.zndsl $westend_dir diff --git a/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl index dbc03864e2b6e5e10636532ad965860b381fa8f2..5a8d6dabc20e3060e92ef6feef8211b7353d23d1 100644 --- a/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl +++ b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl @@ -3,10 +3,10 @@ Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml Creds: config # send 5 WND to //Alice from Westend AH to Rococo AH -asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-westend-local 5000000000000" within 120 seconds +asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "auto-log reserve-transfer-assets-from-asset-hub-westend-local 5000000000000" within 120 seconds # check that //Alice received at least 4.8 WND on Rococo AH asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,4800000000000,Westend" within 600 seconds -# check that the relayer //Charlie is rewarded by Rococo AH -bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 30 seconds +# relayer //Eve is rewarded for delivering messages from Westend BH +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL,0x00000002,0x62687764,ThisChain,0" within 300 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/wnd-relayer-balance-does-not-change.zndsl b/bridges/testing/tests/0001-asset-transfer/wnd-relayer-balance-does-not-change.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..d2563e1807869754cbe3153d973467826b1c71fd --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/wnd-relayer-balance-does-not-change.zndsl @@ -0,0 +1,11 @@ +Description: Finality and parachain relays should have the constant balance, because their transactions are free +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# local chain spec gives `1u64 << 60` tokens to every endowed account: if it'll ever +# change, it'd need to be fixed here as well + +# //Charlie only submits free and mandatory relay chain headers, so the balance should stay the same +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" return is 1152921504606846976 within 30 seconds +# //Dave only submits free parachain headers, so the balance should stay the same +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy" return is 1152921504606846976 within 30 seconds diff --git a/bridges/testing/tests/0002-free-headers-synced-while-idle/rococo-to-westend.zndsl b/bridges/testing/tests/0002-free-headers-synced-while-idle/rococo-to-westend.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..0f779caa87cd183901fe434f693950c0b3b48338 --- /dev/null +++ b/bridges/testing/tests/0002-free-headers-synced-while-idle/rococo-to-westend.zndsl @@ -0,0 +1,20 @@ +Description: While relayer is idle, we only sync free Rococo (and a single Rococo BH) headers to Westend BH. +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# local chain spec gives `1u64 << 60` tokens to every endowed account: if it'll ever +# change, it'd need to be fixed here as well + +# //Charlie only submits free and mandatory relay chain headers, so the balance should stay the same +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" return is 1152921504606846976 within 30 seconds +# //Dave only submits free parachain headers, so the balance should stay the same +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy" return is 1152921504606846976 within 30 seconds + +# ensure that we have synced multiple relay and parachain headers while idle. This includes both +# headers that were generated while relay was offline and those in the next 100 seconds while script is active. +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/multiple-headers-synced.js with "300,rococo-at-westend" within 600 seconds + +# //Charlie only submits free and mandatory relay chain headers, so the balance should stay the same +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" return is 1152921504606846976 within 30 seconds +# //Dave only submits free parachain headers, so the balance should stay the same +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy" return is 1152921504606846976 within 30 seconds diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh b/bridges/testing/tests/0002-free-headers-synced-while-idle/run.sh similarity index 72% rename from bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh rename to bridges/testing/tests/0002-free-headers-synced-while-idle/run.sh index 3a604b3876d96241903c1c5a110cc6392f26cb7e..9d19a9688f948e84004054400397566b7c8b8d94 100755 --- a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh +++ b/bridges/testing/tests/0002-free-headers-synced-while-idle/run.sh @@ -22,13 +22,7 @@ echo # which is expected to be 60 seconds for the test environment. echo -e "Sleeping 90s before starting relayer ...\n" sleep 90 -${BASH_SOURCE%/*}/../../environments/rococo-westend/start_relayer.sh $rococo_dir $westend_dir relayer_pid - -# Sometimes the relayer syncs multiple parachain heads in the beginning leading to test failures. -# See issue: https://github.com/paritytech/parity-bridges-common/issues/2838. -# TODO: Remove this sleep after the issue is fixed. -echo -e "Sleeping 180s before runing the tests ...\n" -sleep 180 +${BASH_SOURCE%/*}/../../environments/rococo-westend/start_relayer.sh $rococo_dir $westend_dir finality_relayer_pid parachains_relayer_pid messages_relayer_pid run_zndsl ${BASH_SOURCE%/*}/rococo-to-westend.zndsl $westend_dir run_zndsl ${BASH_SOURCE%/*}/westend-to-rococo.zndsl $rococo_dir diff --git a/bridges/testing/tests/0002-free-headers-synced-while-idle/westend-to-rococo.zndsl b/bridges/testing/tests/0002-free-headers-synced-while-idle/westend-to-rococo.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..7a6f1ec379d2fc3f105725d0c4b2da69586a37ac --- /dev/null +++ b/bridges/testing/tests/0002-free-headers-synced-while-idle/westend-to-rococo.zndsl @@ -0,0 +1,20 @@ +Description: While relayer is idle, we only sync free Westend (and a single Westend BH) headers to Rococo BH. +Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml +Creds: config + +# local chain spec gives `1u64 << 60` tokens to every endowed account: if it'll ever +# change, it'd need to be fixed here as well + +# //Charlie has inital balance +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" return is 1152921504606846976 within 30 seconds +# //Dave has inital balance +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy" return is 1152921504606846976 within 30 seconds + +# ensure that we have synced multiple relay and parachain headers while idle. This includes both +# headers that were generated while relay was offline and those in the next 100 seconds while script is active. +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/multiple-headers-synced.js with "300,westend-at-rococo" within 600 seconds + +# //Charlie only submits free and mandatory relay chain headers, so the balance should stay the same +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" return is 1152921504606846976 within 30 seconds +# //Dave only submits free parachain headers, so the balance should stay the same +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy" return is 1152921504606846976 within 30 seconds diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl deleted file mode 100644 index 6e381f5377329430c0d7a8723f9ea9081556bfeb..0000000000000000000000000000000000000000 --- a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl +++ /dev/null @@ -1,8 +0,0 @@ -Description: While relayer is idle, we only sync mandatory Rococo (and a single Rococo BH) headers to Westend BH. -Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml -Creds: config - -# ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were -# generated while relay was offline and those in the next 100 seconds while script is active. -bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds - diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl deleted file mode 100644 index b4b3e43679162feb8c3c5253f3f963d950f31d55..0000000000000000000000000000000000000000 --- a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl +++ /dev/null @@ -1,7 +0,0 @@ -Description: While relayer is idle, we only sync mandatory Westend (and a single Westend BH) headers to Rococo BH. -Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml -Creds: config - -# ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were -# generated while relay was offline and those in the next 100 seconds while script is active. -bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds diff --git a/cumulus/client/consensus/aura/src/collator.rs b/cumulus/client/consensus/aura/src/collator.rs index 5b7669c88f473b8765b6b343d1797aa707ed5916..776052215d9397c529699ed07040819f666e16b5 100644 --- a/cumulus/client/consensus/aura/src/collator.rs +++ b/cumulus/client/consensus/aura/src/collator.rs @@ -55,7 +55,7 @@ use sp_runtime::{ }; use sp_state_machine::StorageChanges; use sp_timestamp::Timestamp; -use std::{convert::TryFrom, error::Error, time::Duration}; +use std::{error::Error, time::Duration}; /// Parameters for instantiating a [`Collator`]. pub struct Params { diff --git a/cumulus/client/consensus/aura/src/collators/basic.rs b/cumulus/client/consensus/aura/src/collators/basic.rs index a4c22a45266cf53d2015b840a54a159492e87ba4..1047c6219ad132403014cacaf3d071d8009b9dbc 100644 --- a/cumulus/client/consensus/aura/src/collators/basic.rs +++ b/cumulus/client/consensus/aura/src/collators/basic.rs @@ -48,7 +48,7 @@ use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member}; use sp_state_machine::Backend as _; -use std::{convert::TryFrom, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use crate::collator as collator_util; diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 3fe87e94b7b97b6d839f3f723ebe22fb26e8760d..09416233ea9b39dfd4bd4126149d51f922d7b6e4 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -67,7 +67,7 @@ use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member}; use sp_timestamp::Timestamp; -use std::{convert::TryFrom, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use crate::collator::{self as collator_util, SlotClaim}; diff --git a/cumulus/client/network/src/lib.rs b/cumulus/client/network/src/lib.rs index ebd557b805c5d7ea4362632dac8bf0a59ddc6469..f442ed5840bddcd4a859d149635ff740e4d232f4 100644 --- a/cumulus/client/network/src/lib.rs +++ b/cumulus/client/network/src/lib.rs @@ -36,7 +36,7 @@ use polkadot_primitives::{ use codec::{Decode, DecodeAll, Encode}; use futures::{channel::oneshot, future::FutureExt, Future}; -use std::{convert::TryFrom, fmt, marker::PhantomData, pin::Pin, sync::Arc}; +use std::{fmt, marker::PhantomData, pin::Pin, sync::Arc}; #[cfg(test)] mod tests; diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index 6ea02b2e7c1f6d9b5313459890dd2147015359e5..578b942776dcde99553e0a5af513b03acb3a80a1 100644 --- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs +++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs @@ -312,6 +312,9 @@ fn build_polkadot_full_node( overseer_message_channel_capacity_override: None, malus_finality_delay: None, hwbench, + execute_workers_max_num: None, + prepare_workers_hard_max_num: None, + prepare_workers_soft_max_num: None, }, )?; diff --git a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs index aa5e67e453f69bd02920888a19d73b94e92addce..06f19941165a26374555064efcd797e1b9ebde34 100644 --- a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs +++ b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs @@ -451,6 +451,17 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { ) -> Result>, ApiError> { Ok(self.rpc_client.parachain_host_claim_queue(at).await?) } + + async fn candidates_pending_availability( + &self, + at: Hash, + para_id: cumulus_primitives_core::ParaId, + ) -> Result>, sp_api::ApiError> { + Ok(self + .rpc_client + .parachain_host_candidates_pending_availability(at, para_id) + .await?) + } } #[async_trait::async_trait] diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index 547803865c28a28a7534b3a5d7e0075a2332c71b..864ce6c57125ae5c8dcb5419561dd61a43c38579 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -655,6 +655,20 @@ impl RelayChainRpcClient { .await } + /// Get the receipt of all candidates pending availability. + pub async fn parachain_host_candidates_pending_availability( + &self, + at: RelayHash, + para_id: ParaId, + ) -> Result, RelayChainError> { + self.call_remote_runtime_function( + "ParachainHost_candidates_pending_availability", + at, + Some(para_id), + ) + .await + } + pub async fn validation_code_hash( &self, at: RelayHash, diff --git a/cumulus/pallets/collator-selection/Cargo.toml b/cumulus/pallets/collator-selection/Cargo.toml index c04d9e1403ec0e3d527e06c7b0c7c5e6f64150ad..25ca2fe057baf2a3e9c313556961615a3b14c2f7 100644 --- a/cumulus/pallets/collator-selection/Cargo.toml +++ b/cumulus/pallets/collator-selection/Cargo.toml @@ -27,6 +27,7 @@ sp-staking = { path = "../../../substrate/primitives/staking", default-features frame-support = { path = "../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../substrate/frame/system", default-features = false } pallet-authorship = { path = "../../../substrate/frame/authorship", default-features = false } +pallet-balances = { path = "../../../substrate/frame/balances", default-features = false } pallet-session = { path = "../../../substrate/frame/session", default-features = false } frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } @@ -38,7 +39,6 @@ sp-tracing = { path = "../../../substrate/primitives/tracing" } sp-runtime = { path = "../../../substrate/primitives/runtime" } pallet-timestamp = { path = "../../../substrate/frame/timestamp" } sp-consensus-aura = { path = "../../../substrate/primitives/consensus/aura" } -pallet-balances = { path = "../../../substrate/frame/balances" } pallet-aura = { path = "../../../substrate/frame/aura" } [features] @@ -59,6 +59,7 @@ std = [ "frame-system/std", "log/std", "pallet-authorship/std", + "pallet-balances/std", "pallet-session/std", "rand/std", "scale-info/std", diff --git a/cumulus/pallets/collator-selection/src/lib.rs b/cumulus/pallets/collator-selection/src/lib.rs index 17bbe2591d48f767305adfe4c87237c926b83187..2fa384367528a1f1306a6c34c0c45d3ef94843a2 100644 --- a/cumulus/pallets/collator-selection/src/lib.rs +++ b/cumulus/pallets/collator-selection/src/lib.rs @@ -121,7 +121,7 @@ pub mod pallet { use sp_std::vec::Vec; /// The in-code storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; diff --git a/cumulus/pallets/collator-selection/src/migration.rs b/cumulus/pallets/collator-selection/src/migration.rs index 5dc2fba4279a9b04dc9f1866fb2c36ea1abde80c..425acdd8bfb59768241399e3be5efb44a13c8a74 100644 --- a/cumulus/pallets/collator-selection/src/migration.rs +++ b/cumulus/pallets/collator-selection/src/migration.rs @@ -17,9 +17,107 @@ //! A module that is responsible for migration of storage for Collator Selection. use super::*; -use frame_support::traits::OnRuntimeUpgrade; +use frame_support::traits::{OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade}; use log; +/// Migrate to v2. Should have been part of . +pub mod v2 { + use super::*; + use frame_support::{ + pallet_prelude::*, + storage_alias, + traits::{Currency, ReservableCurrency}, + }; + use sp_runtime::traits::{Saturating, Zero}; + #[cfg(feature = "try-runtime")] + use sp_std::vec::Vec; + + /// [`UncheckedMigrationToV2`] wrapped in a + /// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the + /// migration is only performed when on-chain version is 1. + pub type MigrationToV2 = frame_support::migrations::VersionedMigration< + 1, + 2, + UncheckedMigrationToV2, + Pallet, + ::DbWeight, + >; + + #[storage_alias] + pub type Candidates = StorageValue< + Pallet, + BoundedVec::AccountId, <::Currency as Currency<::AccountId>>::Balance>, ::MaxCandidates>, + ValueQuery, + >; + + /// Migrate to V2. + pub struct UncheckedMigrationToV2(sp_std::marker::PhantomData); + impl UncheckedOnRuntimeUpgrade for UncheckedMigrationToV2 { + fn on_runtime_upgrade() -> Weight { + let mut weight = Weight::zero(); + let mut count: u64 = 0; + // candidates who exist under the old `Candidates` key + let candidates = Candidates::::take(); + + // New candidates who have registered since the upgrade. Under normal circumstances, + // this should not exist because the migration should be applied when the upgrade + // happens. But in Polkadot/Kusama we messed this up, and people registered under + // `CandidateList` while their funds were locked in `Candidates`. + let new_candidate_list = CandidateList::::get(); + if new_candidate_list.len().is_zero() { + // The new list is empty, so this is essentially being applied correctly. We just + // put the candidates into the new storage item. + CandidateList::::put(&candidates); + // 1 write for the new list + weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); + } else { + // Oops, the runtime upgraded without the migration. There are new candidates in + // `CandidateList`. So, let's just refund the old ones and assume they have already + // started participating in the new system. + for candidate in candidates { + let err = T::Currency::unreserve(&candidate.who, candidate.deposit); + if err > Zero::zero() { + log::error!( + target: LOG_TARGET, + "{:?} balance was unable to be unreserved from {:?}", + err, &candidate.who, + ); + } + count.saturating_inc(); + } + weight.saturating_accrue( + <::WeightInfo as pallet_balances::WeightInfo>::force_unreserve().saturating_mul(count.into()), + ); + } + + log::info!( + target: LOG_TARGET, + "Unreserved locked bond of {} candidates, upgraded storage to version 2", + count, + ); + + weight.saturating_accrue(T::DbWeight::get().reads_writes(3, 2)); + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::DispatchError> { + let number_of_candidates = Candidates::::get().to_vec().len(); + Ok((number_of_candidates as u32).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_number_of_candidates: Vec) -> Result<(), sp_runtime::DispatchError> { + let new_number_of_candidates = Candidates::::get().to_vec().len(); + assert_eq!( + new_number_of_candidates, 0 as usize, + "after migration, the candidates map should be empty" + ); + Ok(()) + } + } +} + /// Version 1 Migration /// This migration ensures that any existing `Invulnerables` storage lists are sorted. pub mod v1 { @@ -90,3 +188,136 @@ pub mod v1 { } } } + +#[cfg(all(feature = "try-runtime", test))] +mod tests { + use super::*; + use crate::{ + migration::v2::Candidates, + mock::{new_test_ext, Balances, Test}, + }; + use frame_support::{ + traits::{Currency, ReservableCurrency, StorageVersion}, + BoundedVec, + }; + use sp_runtime::traits::ConstU32; + + #[test] + fn migrate_to_v2_with_new_candidates() { + new_test_ext().execute_with(|| { + let storage_version = StorageVersion::new(1); + storage_version.put::>(); + + let one = 1u64; + let two = 2u64; + let three = 3u64; + let deposit = 10u64; + + // Set balance to 100 + Balances::make_free_balance_be(&one, 100u64); + Balances::make_free_balance_be(&two, 100u64); + Balances::make_free_balance_be(&three, 100u64); + + // Reservations: 10 for the "old" candidacy and 10 for the "new" + Balances::reserve(&one, 10u64).unwrap(); // old + Balances::reserve(&two, 20u64).unwrap(); // old + new + Balances::reserve(&three, 10u64).unwrap(); // new + + // Candidate info + let candidate_one = CandidateInfo { who: one, deposit }; + let candidate_two = CandidateInfo { who: two, deposit }; + let candidate_three = CandidateInfo { who: three, deposit }; + + // Storage lists + let bounded_candidates = + BoundedVec::, ConstU32<20>>::try_from(vec![ + candidate_one.clone(), + candidate_two.clone(), + ]) + .expect("it works"); + let bounded_candidate_list = + BoundedVec::, ConstU32<20>>::try_from(vec![ + candidate_two.clone(), + candidate_three.clone(), + ]) + .expect("it works"); + + // Set storage + Candidates::::put(bounded_candidates); + CandidateList::::put(bounded_candidate_list.clone()); + + // Sanity check + assert_eq!(Balances::free_balance(one), 90); + assert_eq!(Balances::free_balance(two), 80); + assert_eq!(Balances::free_balance(three), 90); + + // Run migration + v2::MigrationToV2::::on_runtime_upgrade(); + + let new_storage_version = StorageVersion::get::>(); + assert_eq!(new_storage_version, 2); + + // 10 should have been unreserved from the old candidacy + assert_eq!(Balances::free_balance(one), 100); + assert_eq!(Balances::free_balance(two), 90); + assert_eq!(Balances::free_balance(three), 90); + // The storage item should be gone + assert!(Candidates::::get().is_empty()); + // The new storage item should be preserved + assert_eq!(CandidateList::::get(), bounded_candidate_list); + }); + } + + #[test] + fn migrate_to_v2_without_new_candidates() { + new_test_ext().execute_with(|| { + let storage_version = StorageVersion::new(1); + storage_version.put::>(); + + let one = 1u64; + let two = 2u64; + let deposit = 10u64; + + // Set balance to 100 + Balances::make_free_balance_be(&one, 100u64); + Balances::make_free_balance_be(&two, 100u64); + + // Reservations + Balances::reserve(&one, 10u64).unwrap(); // old + Balances::reserve(&two, 10u64).unwrap(); // old + + // Candidate info + let candidate_one = CandidateInfo { who: one, deposit }; + let candidate_two = CandidateInfo { who: two, deposit }; + + // Storage lists + let bounded_candidates = + BoundedVec::, ConstU32<20>>::try_from(vec![ + candidate_one.clone(), + candidate_two.clone(), + ]) + .expect("it works"); + + // Set storage + Candidates::::put(bounded_candidates.clone()); + + // Sanity check + assert_eq!(Balances::free_balance(one), 90); + assert_eq!(Balances::free_balance(two), 90); + + // Run migration + v2::MigrationToV2::::on_runtime_upgrade(); + + let new_storage_version = StorageVersion::get::>(); + assert_eq!(new_storage_version, 2); + + // Nothing changes deposit-wise + assert_eq!(Balances::free_balance(one), 90); + assert_eq!(Balances::free_balance(two), 90); + // The storage item should be gone + assert!(Candidates::::get().is_empty()); + // The new storage item should have the info now + assert_eq!(CandidateList::::get(), bounded_candidates); + }); + } +} diff --git a/cumulus/pallets/session-benchmarking/src/inner.rs b/cumulus/pallets/session-benchmarking/src/inner.rs new file mode 100644 index 0000000000000000000000000000000000000000..cffd0776f3d99c99525d12e0392bdf177b246bd5 --- /dev/null +++ b/cumulus/pallets/session-benchmarking/src/inner.rs @@ -0,0 +1,42 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarking setup for pallet-session. + +use sp_std::{prelude::*, vec}; + +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_system::RawOrigin; +use pallet_session::*; +use parity_scale_codec::Decode; +pub struct Pallet(pallet_session::Pallet); +pub trait Config: pallet_session::Config {} + +benchmarks! { + set_keys { + let caller: T::AccountId = whitelisted_caller(); + frame_system::Pallet::::inc_providers(&caller); + let keys = T::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap(); + let proof: Vec = vec![0,1,2,3]; + }: _(RawOrigin::Signed(caller), keys, proof) + + purge_keys { + let caller: T::AccountId = whitelisted_caller(); + frame_system::Pallet::::inc_providers(&caller); + let keys = T::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap(); + let proof: Vec = vec![0,1,2,3]; + let _t = pallet_session::Pallet::::set_keys(RawOrigin::Signed(caller.clone()).into(), keys, proof); + }: _(RawOrigin::Signed(caller)) +} diff --git a/cumulus/pallets/session-benchmarking/src/lib.rs b/cumulus/pallets/session-benchmarking/src/lib.rs index f474def6b13762136ca90b2f812e9c5a3e26a0e2..a95d6fb7d591460f2055f076f60199b213b055b8 100644 --- a/cumulus/pallets/session-benchmarking/src/lib.rs +++ b/cumulus/pallets/session-benchmarking/src/lib.rs @@ -1,3 +1,5 @@ +// This file is part of Substrate. + // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 @@ -13,31 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Benchmarking setup for pallet-session -#![cfg_attr(not(feature = "std"), no_std)] -#![cfg(feature = "runtime-benchmarks")] -use sp_std::{prelude::*, vec}; +//! Benchmarks for the Session Pallet. +// This is separated into its own crate due to cyclic dependency issues. -use frame_benchmarking::{benchmarks, whitelisted_caller}; -use frame_system::RawOrigin; -use pallet_session::*; -use parity_scale_codec::Decode; -pub struct Pallet(pallet_session::Pallet); -pub trait Config: pallet_session::Config {} +#![cfg_attr(not(feature = "std"), no_std)] -benchmarks! { - set_keys { - let caller: T::AccountId = whitelisted_caller(); - frame_system::Pallet::::inc_providers(&caller); - let keys = T::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap(); - let proof: Vec = vec![0,1,2,3]; - }: _(RawOrigin::Signed(caller), keys, proof) +#[cfg(feature = "runtime-benchmarks")] +pub mod inner; - purge_keys { - let caller: T::AccountId = whitelisted_caller(); - frame_system::Pallet::::inc_providers(&caller); - let keys = T::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap(); - let proof: Vec = vec![0,1,2,3]; - let _t = pallet_session::Pallet::::set_keys(RawOrigin::Signed(caller.clone()).into(), keys, proof); - }: _(RawOrigin::Signed(caller)) -} +#[cfg(feature = "runtime-benchmarks")] +pub use inner::*; diff --git a/cumulus/pallets/xcmp-queue/src/lib.rs b/cumulus/pallets/xcmp-queue/src/lib.rs index b4cd925d540ead4ef17af337f59192c4cfec0042..7de2fd8094213aaa0f8b8ef0dbb4539556b48465 100644 --- a/cumulus/pallets/xcmp-queue/src/lib.rs +++ b/cumulus/pallets/xcmp-queue/src/lib.rs @@ -916,7 +916,8 @@ impl SendXcm for Pallet { let price = T::PriceForSiblingDelivery::price_for_delivery(id, &xcm); let versioned_xcm = T::VersionWrapper::wrap_version(&d, xcm) .map_err(|()| SendError::DestinationUnsupported)?; - validate_xcm_nesting(&versioned_xcm) + versioned_xcm + .validate_xcm_nesting() .map_err(|()| SendError::ExceedsMaxMessageSize)?; Ok(((id, versioned_xcm), price)) @@ -932,31 +933,20 @@ impl SendXcm for Pallet { fn deliver((id, xcm): (ParaId, VersionedXcm<()>)) -> Result { let hash = xcm.using_encoded(sp_io::hashing::blake2_256); - defensive_assert!( - validate_xcm_nesting(&xcm).is_ok(), - "Tickets are valid prior to delivery by trait XCM; qed" - ); match Self::send_fragment(id, XcmpMessageFormat::ConcatenatedVersionedXcm, xcm) { Ok(_) => { Self::deposit_event(Event::XcmpMessageSent { message_hash: hash }); Ok(hash) }, - Err(e) => Err(SendError::Transport(e.into())), + Err(e) => { + log::error!(target: LOG_TARGET, "Deliver error: {e:?}"); + Err(SendError::Transport(e.into())) + }, } } } -/// Checks that the XCM is decodable with `MAX_XCM_DECODE_DEPTH`. -/// -/// Note that this uses the limit of the sender - not the receiver. It it best effort. -pub(crate) fn validate_xcm_nesting(xcm: &VersionedXcm<()>) -> Result<(), ()> { - xcm.using_encoded(|mut enc| { - VersionedXcm::<()>::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut enc).map(|_| ()) - }) - .map_err(|_| ()) -} - impl FeeTracker for Pallet { type Id = ParaId; diff --git a/cumulus/parachains/chain-specs/asset-hub-rococo.json b/cumulus/parachains/chain-specs/asset-hub-rococo.json index 900d9f0ffb2c35645d59e16649ee49b7713a01f1..87ff2fb220a19b1ec01d280be6e58ab2cafffd90 100644 --- a/cumulus/parachains/chain-specs/asset-hub-rococo.json +++ b/cumulus/parachains/chain-specs/asset-hub-rococo.json @@ -4,7 +4,11 @@ "chainType": "Live", "bootNodes": [ "/dns/rococo-asset-hub-bootnode-0.polkadot.io/tcp/30333/p2p/12D3KooWRrZMndHAopzao34uGsN7srjS3gh9nAjTGKLSyJeU31Lg", - "/dns/rococo-asset-hub-bootnode-1.polkadot.io/tcp/30333/p2p/12D3KooWAewimoNJqMaiiV5pYiowA5hLuh5JS5QiRJCCyWVrrSTS" + "/dns/rococo-asset-hub-bootnode-1.polkadot.io/tcp/30333/p2p/12D3KooWAewimoNJqMaiiV5pYiowA5hLuh5JS5QiRJCCyWVrrSTS", + "/dns/rococo-asset-hub-bootnode-0.polkadot.io/tcp/30335/ws/p2p/12D3KooWRrZMndHAopzao34uGsN7srjS3gh9nAjTGKLSyJeU31Lg", + "/dns/rococo-asset-hub-bootnode-1.polkadot.io/tcp/30335/ws/p2p/12D3KooWAewimoNJqMaiiV5pYiowA5hLuh5JS5QiRJCCyWVrrSTS", + "/dns/rococo-asset-hub-bootnode-0.polkadot.io/tcp/443/wss/p2p/12D3KooWRrZMndHAopzao34uGsN7srjS3gh9nAjTGKLSyJeU31Lg", + "/dns/rococo-asset-hub-bootnode-1.polkadot.io/tcp/443/wss/p2p/12D3KooWAewimoNJqMaiiV5pYiowA5hLuh5JS5QiRJCCyWVrrSTS" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/asset-hub-westend.json b/cumulus/parachains/chain-specs/asset-hub-westend.json index 670935c9d2474242307d691d8707030c70f49982..3752213e702eb8d1ef86e042531cc1f6fdec2767 100644 --- a/cumulus/parachains/chain-specs/asset-hub-westend.json +++ b/cumulus/parachains/chain-specs/asset-hub-westend.json @@ -5,6 +5,10 @@ "bootNodes": [ "/dns/westend-asset-hub-bootnode-0.polkadot.io/tcp/30333/p2p/12D3KooWJaAfPyiye7ZQBuHengTJJoMrcaz7Jj1UzHiKdNxA1Nkd", "/dns/westend-asset-hub-bootnode-1.polkadot.io/tcp/30333/p2p/12D3KooWGL3hpWycWyeqyL9gHNnmmsL474WkPZdqraBHu4L6fQrW", + "/dns/westend-asset-hub-bootnode-0.polkadot.io/tcp/30335/ws/p2p/12D3KooWJaAfPyiye7ZQBuHengTJJoMrcaz7Jj1UzHiKdNxA1Nkd", + "/dns/westend-asset-hub-bootnode-1.polkadot.io/tcp/30335/ws/p2p/12D3KooWGL3hpWycWyeqyL9gHNnmmsL474WkPZdqraBHu4L6fQrW", + "/dns/westend-asset-hub-connect-0.polkadot.io/tcp/443/wss/p2p/12D3KooWJaAfPyiye7ZQBuHengTJJoMrcaz7Jj1UzHiKdNxA1Nkd", + "/dns/westend-asset-hub-connect-1.polkadot.io/tcp/443/wss/p2p/12D3KooWGL3hpWycWyeqyL9gHNnmmsL474WkPZdqraBHu4L6fQrW", "/dns/boot.stake.plus/tcp/33333/p2p/12D3KooWNiB27rpXX7EYongoWWUeRKzLQxWGms6MQU2B9LX7Ztzo", "/dns/boot.stake.plus/tcp/33334/wss/p2p/12D3KooWNiB27rpXX7EYongoWWUeRKzLQxWGms6MQU2B9LX7Ztzo", "/dns/boot.metaspan.io/tcp/36052/p2p/12D3KooWBCqfNb6Y39DXTr4UBWXyjuS3hcZM1qTbHhDXxF6HkAJJ", diff --git a/cumulus/parachains/chain-specs/bridge-hub-rococo.json b/cumulus/parachains/chain-specs/bridge-hub-rococo.json index 6b430678a86c88d35894819d6433fcf605270d79..53aef58422db38f8417d7add5abee0b6ecbbd755 100644 --- a/cumulus/parachains/chain-specs/bridge-hub-rococo.json +++ b/cumulus/parachains/chain-specs/bridge-hub-rococo.json @@ -4,7 +4,11 @@ "chainType": "Live", "bootNodes": [ "/dns/rococo-bridge-hub-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWJCFBJmFF65xz5xHeZQRSCf35BxfSEB3RHQFoLza28LWU", - "/dns/rococo-bridge-hub-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWJzLd8skcAgA24EcJey7aJAhYctfUxWGjSP5Usk9wbpPZ" + "/dns/rococo-bridge-hub-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWJzLd8skcAgA24EcJey7aJAhYctfUxWGjSP5Usk9wbpPZ", + "/dns/rococo-bridge-hub-collator-node-0.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWJCFBJmFF65xz5xHeZQRSCf35BxfSEB3RHQFoLza28LWU", + "/dns/rococo-bridge-hub-collator-node-1.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWJzLd8skcAgA24EcJey7aJAhYctfUxWGjSP5Usk9wbpPZ", + "/dns/rococo-bridge-hub-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWJCFBJmFF65xz5xHeZQRSCf35BxfSEB3RHQFoLza28LWU", + "/dns/rococo-bridge-hub-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWJzLd8skcAgA24EcJey7aJAhYctfUxWGjSP5Usk9wbpPZ" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/bridge-hub-westend.json b/cumulus/parachains/chain-specs/bridge-hub-westend.json index 447207a58107a95bcd5fca173d9d5476a0ce9cab..5140071ec44ca1956b01f608ce85ca2d6177a617 100644 --- a/cumulus/parachains/chain-specs/bridge-hub-westend.json +++ b/cumulus/parachains/chain-specs/bridge-hub-westend.json @@ -5,6 +5,10 @@ "bootNodes": [ "/dns/westend-bridge-hub-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWKyEuqkkWvFSrwZWKWBAsHgLV3HGfHj7yH3LNJLAVhmxY", "/dns/westend-bridge-hub-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWBpvudthz61XC4oP2YYFFJdhWohBeQ1ffn1BMSGWhapjd", + "/dns/westend-bridge-hub-collator-node-0.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWKyEuqkkWvFSrwZWKWBAsHgLV3HGfHj7yH3LNJLAVhmxY", + "/dns/westend-bridge-hub-collator-node-1.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWBpvudthz61XC4oP2YYFFJdhWohBeQ1ffn1BMSGWhapjd", + "/dns/westend-bridge-hub-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWKyEuqkkWvFSrwZWKWBAsHgLV3HGfHj7yH3LNJLAVhmxY", + "/dns/westend-bridge-hub-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWBpvudthz61XC4oP2YYFFJdhWohBeQ1ffn1BMSGWhapjd", "/dns/westend-bridge-hub-boot-ng.dwellir.com/tcp/30338/p2p/12D3KooWJWWRYTAwBLqYkh7iMBGDr5ouJ3MHj7M3fZ7zWS4zEk6F", "/dns/westend-bridge-hub-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWJWWRYTAwBLqYkh7iMBGDr5ouJ3MHj7M3fZ7zWS4zEk6F", "/dns/boot-cr.gatotech.network/tcp/33330/p2p/12D3KooWJHG6qznPzTSEbuujHNcvyzBZcR9zNRPFcXWUaoVWZBEw", diff --git a/cumulus/parachains/chain-specs/collectives-westend.json b/cumulus/parachains/chain-specs/collectives-westend.json index e459c631f8be9df7c5c52993c116f11ef619fefe..fdd6348f02a9ce7b07c0483847a348c7b334f5c2 100644 --- a/cumulus/parachains/chain-specs/collectives-westend.json +++ b/cumulus/parachains/chain-specs/collectives-westend.json @@ -5,6 +5,8 @@ "bootNodes": [ "/dns/westend-collectives-collator-node-0.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWBMAuyzQu3yAf8YXyoyxsSzSsgoaqAepgnNyQcPaPjPXe", "/dns/westend-collectives-collator-node-1.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWAujYtHbCs4MiDD57JNTntTJnYnikfnaPa7JdnMyAUrHB", + "/dns/westend-collectives-collator-node-0.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWBMAuyzQu3yAf8YXyoyxsSzSsgoaqAepgnNyQcPaPjPXe", + "/dns/westend-collectives-collator-node-1.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWAujYtHbCs4MiDD57JNTntTJnYnikfnaPa7JdnMyAUrHB", "/dns/westend-collectives-collator-0.polkadot.io/tcp/443/wss/p2p/12D3KooWBMAuyzQu3yAf8YXyoyxsSzSsgoaqAepgnNyQcPaPjPXe", "/dns/westend-collectives-collator-1.polkadot.io/tcp/443/wss/p2p/12D3KooWAujYtHbCs4MiDD57JNTntTJnYnikfnaPa7JdnMyAUrHB", "/dns/boot.stake.plus/tcp/38333/p2p/12D3KooWQoVsFCfgu21iu6kdtQsU9T6dPn1wsyLn1U34yPerR6zQ", diff --git a/cumulus/parachains/chain-specs/contracts-rococo.json b/cumulus/parachains/chain-specs/contracts-rococo.json index 422268a5efdb2f8efc96da27dde92a92b11ac9ab..71783481e5cc7ba962c04701f71fb21dc0e5eee9 100644 --- a/cumulus/parachains/chain-specs/contracts-rococo.json +++ b/cumulus/parachains/chain-specs/contracts-rococo.json @@ -5,6 +5,8 @@ "bootNodes": [ "/dns/rococo-contracts-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWKg3Rpxcr9oJ8n6khoxpGKWztCZydtUZk2cojHqnfLrpj", "/dns/rococo-contracts-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWPEXYrz8tHU3nDtPoPw4V7ou5dzMEWSTuUj7vaWiYVAVh", + "/dns/rococo-contracts-collator-node-0.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWKg3Rpxcr9oJ8n6khoxpGKWztCZydtUZk2cojHqnfLrpj", + "/dns/rococo-contracts-collator-node-1.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWPEXYrz8tHU3nDtPoPw4V7ou5dzMEWSTuUj7vaWiYVAVh", "/dns/rococo-contracts-collator-node-0.polkadot.io/tcp/443/wss/p2p/12D3KooWKg3Rpxcr9oJ8n6khoxpGKWztCZydtUZk2cojHqnfLrpj", "/dns/rococo-contracts-collator-node-1.polkadot.io/tcp/443/wss/p2p/12D3KooWPEXYrz8tHU3nDtPoPw4V7ou5dzMEWSTuUj7vaWiYVAVh" ], diff --git a/cumulus/parachains/chain-specs/coretime-rococo.json b/cumulus/parachains/chain-specs/coretime-rococo.json index 39506095bfe0983c182850084f2602a882ea0caa..082e7dd26a952ccf7d156ea25ad208d9468be71d 100644 --- a/cumulus/parachains/chain-specs/coretime-rococo.json +++ b/cumulus/parachains/chain-specs/coretime-rococo.json @@ -4,7 +4,11 @@ "chainType": "Live", "bootNodes": [ "/dns/rococo-coretime-collator-node-0.polkadot.io/tcp/30333/p2p/12D3KooWHBUH9wGBx1Yq1ZePov9VL3AzxRPv5DTR4KadiCU6VKxy", - "/dns/rococo-coretime-collator-node-1.polkadot.io/tcp/30333/p2p/12D3KooWB3SKxdj6kpwTkdMnHJi6YmadojCzmEqFkeFJjxN812XX" + "/dns/rococo-coretime-collator-node-1.polkadot.io/tcp/30333/p2p/12D3KooWB3SKxdj6kpwTkdMnHJi6YmadojCzmEqFkeFJjxN812XX", + "/dns/rococo-coretime-collator-node-0.polkadot.io/tcp/30335/ws/p2p/12D3KooWHBUH9wGBx1Yq1ZePov9VL3AzxRPv5DTR4KadiCU6VKxy", + "/dns/rococo-coretime-collator-node-1.polkadot.io/tcp/30335/ws/p2p/12D3KooWB3SKxdj6kpwTkdMnHJi6YmadojCzmEqFkeFJjxN812XX", + "/dns/rococo-coretime-collator-node-0.polkadot.io/tcp/443/wss/p2p/12D3KooWHBUH9wGBx1Yq1ZePov9VL3AzxRPv5DTR4KadiCU6VKxy", + "/dns/rococo-coretime-collator-node-1.polkadot.io/tcp/443/wss/p2p/12D3KooWB3SKxdj6kpwTkdMnHJi6YmadojCzmEqFkeFJjxN812XX" ], "telemetryEndpoints": null, "protocolId": null, @@ -67,4 +71,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} diff --git a/cumulus/parachains/chain-specs/coretime-westend.json b/cumulus/parachains/chain-specs/coretime-westend.json index 377870f9e2b3dd8f9303936cc54668a8ed830bf4..85e129e684896a137be58727d1190ddfb3f81b33 100644 --- a/cumulus/parachains/chain-specs/coretime-westend.json +++ b/cumulus/parachains/chain-specs/coretime-westend.json @@ -5,9 +5,15 @@ "bootNodes": [ "/dns/westend-coretime-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWP93Dzk8T7GWxyWw9jhLcz8Pksokk3R9vL2eEH337bNkT", "/dns/westend-coretime-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH", + "/dns/westend-coretime-collator-node-0.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWP93Dzk8T7GWxyWw9jhLcz8Pksokk3R9vL2eEH337bNkT", + "/dns/westend-coretime-collator-node-1.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH", + "/dns/westend-coretime-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWP93Dzk8T7GWxyWw9jhLcz8Pksokk3R9vL2eEH337bNkT", + "/dns/westend-coretime-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH", "/dns/boot.metaspan.io/tcp/33019/p2p/12D3KooWCa1uNnEZqiqJY9jkKNQxwSLGPeZ5MjWHhjQMGwga9JMM", "/dns/boot-node.helikon.io/tcp/9420/p2p/12D3KooWFBPartM873MNm1AmVK3etUz34cAE9A9rwPztPno2epQ3", - "/dns/boot-node.helikon.io/tcp/9422/wss/p2p/12D3KooWFBPartM873MNm1AmVK3etUz34cAE9A9rwPztPno2epQ3" + "/dns/boot-node.helikon.io/tcp/9422/wss/p2p/12D3KooWFBPartM873MNm1AmVK3etUz34cAE9A9rwPztPno2epQ3", + "/dns/coretime-westend-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWHewSFwJueRprNZNfkncdjud9DrGzvP1qfmgPd7VK66gw", + "/dns/coretime-westend-boot-ng.dwellir.com/tcp/30356/p2p/12D3KooWHewSFwJueRprNZNfkncdjud9DrGzvP1qfmgPd7VK66gw" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/people-rococo.json b/cumulus/parachains/chain-specs/people-rococo.json index b2819157152174cd803e55cc7ede8fd38a626e08..a4361b77df790ad816d3c95afcf93c0575ee2b85 100644 --- a/cumulus/parachains/chain-specs/people-rococo.json +++ b/cumulus/parachains/chain-specs/people-rococo.json @@ -4,13 +4,11 @@ "chainType": "Live", "bootNodes": [ "/dns/rococo-people-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWDZg5jMYhKXTu6RU491V5sxsFnP4oaEmZJEUfcRkYzps5", - "/dns/rococo-people-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWDZg5jMYhKXTu6RU491V5sxsFnP4oaEmZJEUfcRkYzps5", "/dns/rococo-people-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWGGR5i6qQqfo7iDNp7vjDRKPWuDk53idGV6nFLwS12X5H", - "/dns/rococo-people-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWGGR5i6qQqfo7iDNp7vjDRKPWuDk53idGV6nFLwS12X5H", - "/dns/rococo-people-collator-node-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWBvA9BmBfrsVMcAcqVXGYFCpMTvkSk2igNXpmoareYbeT", - "/dns/rococo-people-collator-node-2.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWBvA9BmBfrsVMcAcqVXGYFCpMTvkSk2igNXpmoareYbeT", - "/dns/rococo-people-collator-node-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWQ7Q9jLcJTPXy7KEp5hSZ8YMY9pHx9CnQVz3T8TKQ81UG", - "/dns/rococo-people-collator-node-3.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWQ7Q9jLcJTPXy7KEp5hSZ8YMY9pHx9CnQVz3T8TKQ81UG" + "/dns/rococo-people-collator-node-0.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWDZg5jMYhKXTu6RU491V5sxsFnP4oaEmZJEUfcRkYzps5", + "/dns/rococo-people-collator-node-1.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWGGR5i6qQqfo7iDNp7vjDRKPWuDk53idGV6nFLwS12X5H", + "/dns/rococo-people-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWDZg5jMYhKXTu6RU491V5sxsFnP4oaEmZJEUfcRkYzps5", + "/dns/rococo-people-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWGGR5i6qQqfo7iDNp7vjDRKPWuDk53idGV6nFLwS12X5H" ], "telemetryEndpoints": null, "protocolId": null, @@ -79,4 +77,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} diff --git a/cumulus/parachains/chain-specs/people-westend.json b/cumulus/parachains/chain-specs/people-westend.json index d6f0e15e0248e967151767c64771c062d5a89741..93b8c064113f01c133ef301056ff79ade6dd032a 100644 --- a/cumulus/parachains/chain-specs/people-westend.json +++ b/cumulus/parachains/chain-specs/people-westend.json @@ -4,8 +4,10 @@ "chainType": "Live", "bootNodes": [ "/dns/westend-people-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWDcLjDLTu9fNhmas9DTWtqdv8eUbFMWQzVwvXRK7QcjHD", - "/dns/westend-people-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWDcLjDLTu9fNhmas9DTWtqdv8eUbFMWQzVwvXRK7QcjHD", "/dns/westend-people-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWM56JbKWAXsDyWh313z73aKYVMp1Hj2nSnAKY3q6MnoC9", + "/dns/westend-people-collator-node-0.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWDcLjDLTu9fNhmas9DTWtqdv8eUbFMWQzVwvXRK7QcjHD", + "/dns/westend-people-collator-node-1.parity-testnet.parity.io/tcp/30335/ws/p2p/12D3KooWM56JbKWAXsDyWh313z73aKYVMp1Hj2nSnAKY3q6MnoC9", + "/dns/westend-people-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWDcLjDLTu9fNhmas9DTWtqdv8eUbFMWQzVwvXRK7QcjHD", "/dns/westend-people-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWM56JbKWAXsDyWh313z73aKYVMp1Hj2nSnAKY3q6MnoC9", "/dns/identity-westend.bootnodes.polkadotters.com/tcp/30532/p2p/12D3KooWKr9San6KTM7REJ95cBaDoiciGcWnW8TTftEJgxGF5Ehb", "/dns/identity-westend.bootnodes.polkadotters.com/tcp/30534/wss/p2p/12D3KooWKr9San6KTM7REJ95cBaDoiciGcWnW8TTftEJgxGF5Ehb", @@ -24,7 +26,7 @@ "/dns/people-westend-bootnode.turboflakes.io/tcp/30650/p2p/12D3KooWQEhmZg3uMkuxVUx3jbsD84zEX4dUKtvHfmCoBWMhybKW", "/dns/people-westend-bootnode.turboflakes.io/tcp/30750/wss/p2p/12D3KooWQEhmZg3uMkuxVUx3jbsD84zEX4dUKtvHfmCoBWMhybKW", "/dns/people-westend-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWBdCpCabhgBpLn67LWcXE2JJCCTMhuJHrfDNiTiCCr3KX", - "/dns/people-westend-boot-ng.dwellir.com/tcp/30355/p2p/12D3KooWBdCpCabhgBpLn67LWcXE2JJCCTMhuJHrfDNiTiCCr3KX" + "/dns/people-westend-boot-ng.dwellir.com/tcp/30355/p2p/12D3KooWBdCpCabhgBpLn67LWcXE2JJCCTMhuJHrfDNiTiCCr3KX" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml index 98762beb0cb23132c3880515287328bb09bde032..8100e681348836fb28c9236b9ba20d27f117d71b 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml @@ -23,3 +23,6 @@ emulated-integration-tests-common = { path = "../../../../common", default-featu asset-hub-rococo-runtime = { path = "../../../../../../runtimes/assets/asset-hub-rococo" } rococo-emulated-chain = { path = "../../../relays/rococo" } testnet-parachains-constants = { path = "../../../../../../runtimes/constants", features = ["rococo"] } + +# Polkadot +xcm = { package = "staging-xcm", path = "../../../../../../../../polkadot/xcm", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs index f1e972e869dc94465aa28356a7eaa1c4cd4503ef..202d02b250bb2e90261a01c13c6aab59c674b511 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs @@ -22,7 +22,8 @@ use frame_support::traits::OnInitialize; use emulated_integration_tests_common::{ impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain, impl_assets_helpers_for_parachain, impl_assets_helpers_for_system_parachain, - impl_xcm_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains, + impl_foreign_assets_helpers_for_parachain, impl_xcm_helpers_for_parachain, impls::Parachain, + xcm_emulator::decl_test_parachains, }; use rococo_emulated_chain::Rococo; @@ -56,4 +57,5 @@ impl_accounts_helpers_for_parachain!(AssetHubRococo); impl_assert_events_helpers_for_parachain!(AssetHubRococo); impl_assets_helpers_for_system_parachain!(AssetHubRococo, Rococo); impl_assets_helpers_for_parachain!(AssetHubRococo); +impl_foreign_assets_helpers_for_parachain!(AssetHubRococo, xcm::v3::Location); impl_xcm_helpers_for_parachain!(AssetHubRococo); diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml index a42a9abf618d403852561d5d4b20e7fb6ad576e7..e0abaa66c5cabba445b91c19436f9a4ce3642386 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml @@ -23,3 +23,6 @@ emulated-integration-tests-common = { path = "../../../../common", default-featu asset-hub-westend-runtime = { path = "../../../../../../runtimes/assets/asset-hub-westend" } westend-emulated-chain = { path = "../../../relays/westend" } testnet-parachains-constants = { path = "../../../../../../runtimes/constants", features = ["westend"] } + +# Polkadot +xcm = { package = "staging-xcm", path = "../../../../../../../../polkadot/xcm", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs index 7f05eefb4c208dab2192bc111347ffa4f2760fc0..6043a6aeda48f1e1ec010ac42e98a50feaae3a30 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs @@ -22,7 +22,8 @@ use frame_support::traits::OnInitialize; use emulated_integration_tests_common::{ impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain, impl_assets_helpers_for_parachain, impl_assets_helpers_for_system_parachain, - impl_xcm_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains, + impl_foreign_assets_helpers_for_parachain, impl_xcm_helpers_for_parachain, impls::Parachain, + xcm_emulator::decl_test_parachains, }; use westend_emulated_chain::Westend; @@ -56,4 +57,5 @@ impl_accounts_helpers_for_parachain!(AssetHubWestend); impl_assert_events_helpers_for_parachain!(AssetHubWestend); impl_assets_helpers_for_system_parachain!(AssetHubWestend, Westend); impl_assets_helpers_for_parachain!(AssetHubWestend); +impl_foreign_assets_helpers_for_parachain!(AssetHubWestend, xcm::v3::Location); impl_xcm_helpers_for_parachain!(AssetHubWestend); diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs index d81ab8143ddba678617aaa67db122298ce29606c..450439f5ea3080b66c5c572dfdae972c23c52a4b 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs @@ -17,8 +17,6 @@ use frame_support::parameter_types; use sp_core::{sr25519, storage::Storage}; -// Polkadot -use xcm::v3::Location; // Cumulus use emulated_integration_tests_common::{ accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION, @@ -79,20 +77,9 @@ pub fn genesis(para_id: u32) -> Storage { foreign_assets: penpal_runtime::ForeignAssetsConfig { assets: vec![ // Relay Native asset representation - ( - Location::try_from(RelayLocation::get()).expect("conversion works"), - PenpalAssetOwner::get(), - true, - ED, - ), + (RelayLocation::get(), PenpalAssetOwner::get(), true, ED), // Sufficient AssetHub asset representation - ( - Location::try_from(LocalReservableFromAssetHub::get()) - .expect("conversion works"), - PenpalAssetOwner::get(), - true, - ED, - ), + (LocalReservableFromAssetHub::get(), PenpalAssetOwner::get(), true, ED), ], ..Default::default() }, diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs index 0b49c7a3e091a615632728b6ba74ca4dffefae66..c268b014bfa34e1b8c0a450ae2e446bb6f636c9d 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs @@ -16,16 +16,20 @@ mod genesis; pub use genesis::{genesis, PenpalAssetOwner, PenpalSudoAccount, ED, PARA_ID_A, PARA_ID_B}; pub use penpal_runtime::xcm_config::{ - CustomizableAssetFromSystemAssetHub, LocalTeleportableToAssetHub, XcmConfig, + CustomizableAssetFromSystemAssetHub, RelayNetworkId as PenpalRelayNetworkId, }; // Substrate use frame_support::traits::OnInitialize; +use sp_core::Encode; // Cumulus use emulated_integration_tests_common::{ impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain, - impl_assets_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains, + impl_assets_helpers_for_parachain, impl_foreign_assets_helpers_for_parachain, + impl_xcm_helpers_for_parachain, + impls::{NetworkId, Parachain}, + xcm_emulator::decl_test_parachains, }; // Penpal Parachain declaration @@ -34,6 +38,10 @@ decl_test_parachains! { genesis = genesis(PARA_ID_A), on_init = { penpal_runtime::AuraExt::on_initialize(1); + frame_support::assert_ok!(penpal_runtime::System::set_storage( + penpal_runtime::RuntimeOrigin::root(), + vec![(PenpalRelayNetworkId::key().to_vec(), NetworkId::Rococo.encode())], + )); }, runtime = penpal_runtime, core = { @@ -53,6 +61,10 @@ decl_test_parachains! { genesis = genesis(PARA_ID_B), on_init = { penpal_runtime::AuraExt::on_initialize(1); + frame_support::assert_ok!(penpal_runtime::System::set_storage( + penpal_runtime::RuntimeOrigin::root(), + vec![(PenpalRelayNetworkId::key().to_vec(), NetworkId::Westend.encode())], + )); }, runtime = penpal_runtime, core = { @@ -76,4 +88,8 @@ impl_accounts_helpers_for_parachain!(PenpalB); impl_assert_events_helpers_for_parachain!(PenpalA); impl_assert_events_helpers_for_parachain!(PenpalB); impl_assets_helpers_for_parachain!(PenpalA); +impl_foreign_assets_helpers_for_parachain!(PenpalA, xcm::latest::Location); impl_assets_helpers_for_parachain!(PenpalB); +impl_foreign_assets_helpers_for_parachain!(PenpalB, xcm::latest::Location); +impl_xcm_helpers_for_parachain!(PenpalA); +impl_xcm_helpers_for_parachain!(PenpalB); diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs index 379a29d697bcb564b670eadb638f7088f042137a..7a3a936ec972f0a8c99e3b472c7cce9e9914e29c 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs @@ -39,6 +39,8 @@ decl_test_relay_chains! { Hrmp: rococo_runtime::Hrmp, Identity: rococo_runtime::Identity, IdentityMigrator: rococo_runtime::IdentityMigrator, + Treasury: rococo_runtime::Treasury, + AssetRate: rococo_runtime::AssetRate, } }, } diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml index 12a3ad60e0e046f1881d322118d522da3f9b52b1..20aedb50e6a18ef1112ab23b2a6d05780b6b443f 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml @@ -11,6 +11,7 @@ publish = false workspace = true [dependencies] + # Substrate sp-core = { path = "../../../../../../../substrate/primitives/core", default-features = false } sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs index 618c3addc5d0c67c3954610425345d3ec8b2f36b..8f2789eb2f3a0e45d5c98263a16a118bd63cb425 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs @@ -38,9 +38,7 @@ pub use polkadot_runtime_parachains::{ inclusion::{AggregateMessageOrigin, UmpQueueId}, }; pub use xcm::{ - prelude::{Location, OriginKind, Outcome, VersionedXcm, XcmVersion}, - v3, - v4::Error as XcmError, + prelude::{Location, OriginKind, Outcome, VersionedXcm, XcmError, XcmVersion}, DoubleEncoded, }; @@ -362,7 +360,7 @@ macro_rules! impl_send_transact_helpers_for_relay_chain { recipient: $crate::impls::ParaId, call: $crate::impls::DoubleEncoded<()> ) { - use $crate::impls::{bx, Chain, RelayChain, Encode}; + use $crate::impls::{bx, Chain, RelayChain}; ::execute_with(|| { let root_origin = ::RuntimeOrigin::root(); @@ -370,10 +368,10 @@ macro_rules! impl_send_transact_helpers_for_relay_chain { let xcm = $crate::impls::xcm_transact_unpaid_execution(call, $crate::impls::OriginKind::Superuser); // Send XCM `Transact` - $crate::impls::assert_ok!(]>::XcmPallet::send_blob( + $crate::impls::assert_ok!(]>::XcmPallet::send( root_origin, bx!(destination.into()), - xcm.encode().try_into().unwrap(), + bx!(xcm), )); Self::assert_xcm_pallet_sent(); }); @@ -696,12 +694,12 @@ macro_rules! impl_assets_helpers_for_system_parachain { #[macro_export] macro_rules! impl_assets_helpers_for_parachain { - ( $chain:ident) => { + ($chain:ident) => { $crate::impls::paste::paste! { impl $chain { - /// Create foreign assets using sudo `ForeignAssets::force_create()` - pub fn force_create_foreign_asset( - id: $crate::impls::v3::Location, + /// Create assets using sudo `Assets::force_create()` + pub fn force_create_asset( + id: u32, owner: $crate::impls::AccountId, is_sufficient: bool, min_balance: u128, @@ -711,20 +709,20 @@ macro_rules! impl_assets_helpers_for_parachain { let sudo_origin = <$chain as $crate::impls::Chain>::RuntimeOrigin::root(); ::execute_with(|| { $crate::impls::assert_ok!( - ]>::ForeignAssets::force_create( + ]>::Assets::force_create( sudo_origin, - id.clone(), + id.clone().into(), owner.clone().into(), is_sufficient, min_balance, ) ); - assert!(]>::ForeignAssets::asset_exists(id.clone())); + assert!(]>::Assets::asset_exists(id.clone())); type RuntimeEvent = <$chain as $crate::impls::Chain>::RuntimeEvent; $crate::impls::assert_expected_events!( Self, vec![ - RuntimeEvent::::ForeignAssets( + RuntimeEvent::::Assets( $crate::impls::pallet_assets::Event::ForceCreated { asset_id, .. @@ -736,19 +734,19 @@ macro_rules! impl_assets_helpers_for_parachain { for (beneficiary, amount) in prefund_accounts.into_iter() { let signed_origin = <$chain as $crate::impls::Chain>::RuntimeOrigin::signed(owner.clone()); - Self::mint_foreign_asset(signed_origin, id.clone(), beneficiary, amount); + Self::mint_asset(signed_origin, id.clone(), beneficiary, amount); } } - /// Mint assets making use of the ForeignAssets pallet-assets instance - pub fn mint_foreign_asset( + /// Mint assets making use of the assets pallet + pub fn mint_asset( signed_origin: ::RuntimeOrigin, - id: $crate::impls::v3::Location, + id: u32, beneficiary: $crate::impls::AccountId, amount_to_mint: u128, ) { ::execute_with(|| { - $crate::impls::assert_ok!(]>::ForeignAssets::mint( + $crate::impls::assert_ok!(]>::Assets::mint( signed_origin, id.clone().into(), beneficiary.clone().into(), @@ -760,7 +758,7 @@ macro_rules! impl_assets_helpers_for_parachain { $crate::impls::assert_expected_events!( Self, vec![ - RuntimeEvent::::ForeignAssets( + RuntimeEvent::::Assets( $crate::impls::pallet_assets::Event::Issued { asset_id, owner, amount } ) => { asset_id: *asset_id == id, @@ -771,9 +769,39 @@ macro_rules! impl_assets_helpers_for_parachain { ); }); } - /// Create assets using sudo `Assets::force_create()` - pub fn force_create_asset( - id: u32, + + /// Returns the encoded call for `create` from the assets pallet + pub fn create_asset_call( + asset_id: u32, + min_balance: $crate::impls::Balance, + admin: $crate::impls::AccountId, + ) -> $crate::impls::DoubleEncoded<()> { + use $crate::impls::{Chain, Encode}; + + ::RuntimeCall::Assets($crate::impls::pallet_assets::Call::< + ::Runtime, + $crate::impls::pallet_assets::Instance1, + >::create { + id: asset_id.into(), + min_balance, + admin: admin.into(), + }) + .encode() + .into() + } + } + } + }; +} + +#[macro_export] +macro_rules! impl_foreign_assets_helpers_for_parachain { + ($chain:ident, $asset_id_type:ty) => { + $crate::impls::paste::paste! { + impl $chain { + /// Create foreign assets using sudo `ForeignAssets::force_create()` + pub fn force_create_foreign_asset( + id: $asset_id_type, owner: $crate::impls::AccountId, is_sufficient: bool, min_balance: u128, @@ -783,20 +811,20 @@ macro_rules! impl_assets_helpers_for_parachain { let sudo_origin = <$chain as $crate::impls::Chain>::RuntimeOrigin::root(); ::execute_with(|| { $crate::impls::assert_ok!( - ]>::Assets::force_create( + ]>::ForeignAssets::force_create( sudo_origin, - id.clone().into(), + id.clone(), owner.clone().into(), is_sufficient, min_balance, ) ); - assert!(]>::Assets::asset_exists(id.clone())); + assert!(]>::ForeignAssets::asset_exists(id.clone())); type RuntimeEvent = <$chain as $crate::impls::Chain>::RuntimeEvent; $crate::impls::assert_expected_events!( Self, vec![ - RuntimeEvent::::Assets( + RuntimeEvent::::ForeignAssets( $crate::impls::pallet_assets::Event::ForceCreated { asset_id, .. @@ -808,19 +836,19 @@ macro_rules! impl_assets_helpers_for_parachain { for (beneficiary, amount) in prefund_accounts.into_iter() { let signed_origin = <$chain as $crate::impls::Chain>::RuntimeOrigin::signed(owner.clone()); - Self::mint_asset(signed_origin, id.clone(), beneficiary, amount); + Self::mint_foreign_asset(signed_origin, id.clone(), beneficiary, amount); } } - /// Mint assets making use of the assets pallet - pub fn mint_asset( + /// Mint assets making use of the ForeignAssets pallet-assets instance + pub fn mint_foreign_asset( signed_origin: ::RuntimeOrigin, - id: u32, + id: $asset_id_type, beneficiary: $crate::impls::AccountId, amount_to_mint: u128, ) { ::execute_with(|| { - $crate::impls::assert_ok!(]>::Assets::mint( + $crate::impls::assert_ok!(]>::ForeignAssets::mint( signed_origin, id.clone().into(), beneficiary.clone().into(), @@ -832,7 +860,7 @@ macro_rules! impl_assets_helpers_for_parachain { $crate::impls::assert_expected_events!( Self, vec![ - RuntimeEvent::::Assets( + RuntimeEvent::::ForeignAssets( $crate::impls::pallet_assets::Event::Issued { asset_id, owner, amount } ) => { asset_id: *asset_id == id, @@ -844,29 +872,9 @@ macro_rules! impl_assets_helpers_for_parachain { }); } - /// Returns the encoded call for `create` from the assets pallet - pub fn create_asset_call( - asset_id: u32, - min_balance: $crate::impls::Balance, - admin: $crate::impls::AccountId, - ) -> $crate::impls::DoubleEncoded<()> { - use $crate::impls::{Chain, Encode}; - - ::RuntimeCall::Assets($crate::impls::pallet_assets::Call::< - ::Runtime, - $crate::impls::pallet_assets::Instance1, - >::create { - id: asset_id.into(), - min_balance, - admin: admin.into(), - }) - .encode() - .into() - } - /// Returns the encoded call for `create` from the foreign assets pallet pub fn create_foreign_asset_call( - asset_id: $crate::impls::v3::Location, + asset_id: $asset_id_type, min_balance: $crate::impls::Balance, admin: $crate::impls::AccountId, ) -> $crate::impls::DoubleEncoded<()> { diff --git a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs index ee8b038a364d73301732f278786b30b18d534643..d87bc5aa9633468a0f379da1c417a1caaba2505f 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs @@ -25,7 +25,7 @@ use asset_hub_rococo_emulated_chain::AssetHubRococo; use asset_hub_westend_emulated_chain::AssetHubWestend; use bridge_hub_rococo_emulated_chain::BridgeHubRococo; use bridge_hub_westend_emulated_chain::BridgeHubWestend; -use penpal_emulated_chain::PenpalA; +use penpal_emulated_chain::{PenpalA, PenpalB}; use rococo_emulated_chain::Rococo; use westend_emulated_chain::Westend; @@ -48,13 +48,13 @@ decl_test_networks! { PenpalA, ], bridge = RococoWestendMockBridge - }, pub struct WestendMockNet { relay_chain = Westend, parachains = vec![ AssetHubWestend, BridgeHubWestend, + PenpalB, ], bridge = WestendRococoMockBridge }, @@ -96,5 +96,6 @@ decl_test_sender_receiver_accounts_parameter_types! { WestendRelay { sender: ALICE, receiver: BOB }, AssetHubWestendPara { sender: ALICE, receiver: BOB }, BridgeHubWestendPara { sender: ALICE, receiver: BOB }, - PenpalAPara { sender: ALICE, receiver: BOB } + PenpalAPara { sender: ALICE, receiver: BOB }, + PenpalBPara { sender: ALICE, receiver: BOB } } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml index c5a672234a0d21603c86b64af75adc57beffb81b..ddd6d2d049823f36ed193597ff438f39468a8a55 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml @@ -21,15 +21,20 @@ pallet-balances = { path = "../../../../../../../substrate/frame/balances", defa pallet-assets = { path = "../../../../../../../substrate/frame/assets", default-features = false } pallet-asset-conversion = { path = "../../../../../../../substrate/frame/asset-conversion", default-features = false } pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue", default-features = false } +pallet-treasury = { path = "../../../../../../../substrate/frame/treasury", default-features = false } +pallet-utility = { path = "../../../../../../../substrate/frame/utility", default-features = false } # Polkadot xcm = { package = "staging-xcm", path = "../../../../../../../polkadot/xcm", default-features = false } pallet-xcm = { path = "../../../../../../../polkadot/xcm/pallet-xcm", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = "../../../../../../../polkadot/xcm/xcm-executor", default-features = false } rococo-runtime = { path = "../../../../../../../polkadot/runtime/rococo" } +polkadot-runtime-common = { path = "../../../../../../../polkadot/runtime/common" } +rococo-runtime-constants = { path = "../../../../../../../polkadot/runtime/rococo/constants" } # Cumulus asset-test-utils = { path = "../../../../../runtimes/assets/test-utils" } +cumulus-pallet-parachain-system = { path = "../../../../../../pallets/parachain-system", default-features = false } parachains-common = { path = "../../../../../common" } asset-hub-rococo-runtime = { path = "../../../../../runtimes/assets/asset-hub-rococo" } penpal-runtime = { path = "../../../../../runtimes/testing/penpal" } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index a5a4914e21d826ea6c70af4ae31a0d4dee43ef64..2bd388bee400ed2e61869e126a1828b93422f2c0 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -30,6 +30,7 @@ mod imports { prelude::{AccountId32 as AccountId32Junction, *}, v3, }; + pub use xcm_executor::traits::TransferType; // Cumulus pub use asset_test_utils::xcm_helpers; @@ -69,7 +70,9 @@ mod imports { LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub, LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, }; - pub use rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig; + pub use rococo_runtime::xcm_config::{ + UniversalLocation as RococoUniversalLocation, XcmConfig as RococoXcmConfig, + }; pub const ASSET_ID: u32 = 3; pub const ASSET_MIN_BALANCE: u128 = 1000; @@ -81,6 +84,8 @@ mod imports { pub type SystemParaToParaTest = Test; pub type ParaToSystemParaTest = Test; pub type ParaToParaThroughRelayTest = Test; + pub type ParaToParaThroughAHTest = Test; + pub type RelayToParaThroughAHTest = Test; } #[cfg(test)] diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs new file mode 100644 index 0000000000000000000000000000000000000000..edaaa998a9ca11f97b9d2c85e8b2b88d1c570fbc --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs @@ -0,0 +1,811 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::reserve_transfer::*; +use crate::{ + imports::*, + tests::teleport::do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt, +}; + +fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_a_on_ah = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + ); + let sov_penpal_b_on_ah = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalB::para_id()), + ); + + assert_expected_events!( + AssetHubRococo, + vec![ + // Withdrawn from sender parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_a_on_ah, + amount: *amount == t.args.amount, + }, + // Deposited to receiver parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Minted { who, .. } + ) => { + who: *who == sov_penpal_b_on_ah, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); + ::PolkadotXcm::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.assets.into()), + bx!(TransferType::LocalReserve), + bx!(fee.id.into()), + bx!(TransferType::LocalReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + t.args.weight_limit, + ) +} + +fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); + ::PolkadotXcm::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.assets.into()), + bx!(TransferType::DestinationReserve), + bx!(fee.id.into()), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + t.args.weight_limit, + ) +} + +fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); + ::PolkadotXcm::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.assets.into()), + bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())), + bx!(fee.id.into()), + bx!(TransferType::RemoteReserve(asset_hub_location.into())), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + t.args.weight_limit, + ) +} + +fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); + ::PolkadotXcm::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.assets.into()), + bx!(TransferType::Teleport), + bx!(fee.id.into()), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + t.args.weight_limit, + ) +} + +fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); + ::PolkadotXcm::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.assets.into()), + bx!(TransferType::Teleport), + bx!(fee.id.into()), + bx!(TransferType::LocalReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + t.args.weight_limit, + ) +} + +// =========================================================================== +// ======= Transfer - Native + Bridged Assets - AssetHub->Parachain ========== +// =========================================================================== +/// Transfers of native asset plus bridged asset from AssetHub to some Parachain +/// while paying fees using native asset. +#[test] +fn transfer_foreign_assets_from_asset_hub_to_para() { + let destination = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sender = AssetHubRococoSender::get(); + let native_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000; + let native_asset_location = RelayLocation::get(); + let receiver = PenpalAReceiver::get(); + let assets_owner = PenpalAssetOwner::get(); + // Foreign asset used: bridged WND + let foreign_amount_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000; + let wnd_at_rococo_parachains = + Location::new(2, [Junction::GlobalConsensus(NetworkId::Westend)]); + + // Configure destination chain to trust AH as reserve of WND + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Westend)]).encode(), + )], + )); + }); + PenpalA::force_create_foreign_asset( + wnd_at_rococo_parachains.clone(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + AssetHubRococo::force_create_foreign_asset( + wnd_at_rococo_parachains.clone().try_into().unwrap(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + AssetHubRococo::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner), + wnd_at_rococo_parachains.clone().try_into().unwrap(), + sender.clone(), + foreign_amount_to_send * 2, + ); + + // Assets to send + let assets: Vec = vec![ + (Parent, native_amount_to_send).into(), + (wnd_at_rococo_parachains.clone(), foreign_amount_to_send).into(), + ]; + let fee_asset_id = AssetId(Parent.into()); + let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32; + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + native_amount_to_send, + assets.into(), + None, + fee_asset_item, + ), + }; + let mut test = SystemParaToParaTest::new(test_args); + + // Query initial balances + let sender_balance_before = test.sender.balance; + let sender_wnds_before = AssetHubRococo::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + wnd_at_rococo_parachains.clone().try_into().unwrap(), + &sender, + ) + }); + let receiver_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location.clone(), &receiver) + }); + let receiver_wnds_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains.clone(), &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(system_para_to_para_sender_assertions); + test.set_assertion::(system_para_to_para_receiver_assertions); + test.set_dispatchable::(ah_to_para_transfer_assets); + test.assert(); + + // Query final balances + let sender_balance_after = test.sender.balance; + let sender_wnds_after = AssetHubRococo::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + wnd_at_rococo_parachains.clone().try_into().unwrap(), + &sender, + ) + }); + let receiver_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location, &receiver) + }); + let receiver_wnds_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_balance_after < sender_balance_before - native_amount_to_send); + // Sender's balance is reduced by foreign amount sent + assert_eq!(sender_wnds_after, sender_wnds_before - foreign_amount_to_send); + // Receiver's assets is increased + assert!(receiver_assets_after > receiver_assets_before); + // Receiver's assets increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_assets_after < receiver_assets_before + native_amount_to_send); + // Receiver's balance is increased by foreign amount sent + assert_eq!(receiver_wnds_after, receiver_wnds_before + foreign_amount_to_send); +} + +/// Reserve Transfers of native asset from Parachain to System Parachain should work +// =========================================================================== +// ======= Transfer - Native + Bridged Assets - Parachain->AssetHub ========== +// =========================================================================== +/// Transfers of native asset plus bridged asset from some Parachain to AssetHub +/// while paying fees using native asset. +#[test] +fn transfer_foreign_assets_from_para_to_asset_hub() { + // Init values for Parachain + let destination = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let sender = PenpalASender::get(); + let native_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000; + let native_asset_location = RelayLocation::get(); + let assets_owner = PenpalAssetOwner::get(); + + // Foreign asset used: bridged WND + let foreign_amount_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000; + let wnd_at_rococo_parachains = + Location::new(2, [Junction::GlobalConsensus(NetworkId::Westend)]); + + // Configure destination chain to trust AH as reserve of WND + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Westend)]).encode(), + )], + )); + }); + PenpalA::force_create_foreign_asset( + wnd_at_rococo_parachains.clone(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + AssetHubRococo::force_create_foreign_asset( + wnd_at_rococo_parachains.clone().try_into().unwrap(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + native_asset_location.clone(), + sender.clone(), + native_amount_to_send * 2, + ); + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + wnd_at_rococo_parachains.clone(), + sender.clone(), + foreign_amount_to_send * 2, + ); + + // Init values for System Parachain + let receiver = AssetHubRococoReceiver::get(); + let penpal_location_as_seen_by_ahr = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location_as_seen_by_ahr); + + // fund Parachain's SA on AssetHub with the assets held in reserve + AssetHubRococo::fund_accounts(vec![( + sov_penpal_on_ahr.clone().into(), + native_amount_to_send * 2, + )]); + AssetHubRococo::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner), + wnd_at_rococo_parachains.clone().try_into().unwrap(), + sov_penpal_on_ahr, + foreign_amount_to_send * 2, + ); + + // Assets to send + let assets: Vec = vec![ + (Parent, native_amount_to_send).into(), + (wnd_at_rococo_parachains.clone(), foreign_amount_to_send).into(), + ]; + let fee_asset_id = AssetId(Parent.into()); + let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32; + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + native_amount_to_send, + assets.into(), + None, + fee_asset_item, + ), + }; + let mut test = ParaToSystemParaTest::new(test_args); + + // Query initial balances + let sender_native_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location.clone(), &sender) + }); + let sender_wnds_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains.clone(), &sender) + }); + let receiver_native_before = test.receiver.balance; + let receiver_wnds_before = AssetHubRococo::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + wnd_at_rococo_parachains.clone().try_into().unwrap(), + &receiver, + ) + }); + + // Set assertions and dispatchables + test.set_assertion::(para_to_system_para_sender_assertions); + test.set_assertion::(para_to_system_para_receiver_assertions); + test.set_dispatchable::(para_to_ah_transfer_assets); + test.assert(); + + // Query final balances + let sender_native_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location, &sender) + }); + let sender_wnds_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains.clone(), &sender) + }); + let receiver_native_after = test.receiver.balance; + let receiver_wnds_after = AssetHubRococo::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + wnd_at_rococo_parachains.try_into().unwrap(), + &receiver, + ) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_native_after < sender_native_before - native_amount_to_send); + // Sender's balance is reduced by foreign amount sent + assert_eq!(sender_wnds_after, sender_wnds_before - foreign_amount_to_send); + // Receiver's balance is increased + assert!(receiver_native_after > receiver_native_before); + // Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_native_after < receiver_native_before + native_amount_to_send); + // Receiver's balance is increased by foreign amount sent + assert_eq!(receiver_wnds_after, receiver_wnds_before + foreign_amount_to_send); +} + +// ============================================================================== +// ===== Transfer - Native + Bridged Assets - Parachain->AssetHub->Parachain ==== +// ============================================================================== +/// Transfers of native asset plus bridged asset from Parachain to Parachain +/// (through AssetHub reserve) with fees paid using native asset. +#[test] +fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { + // Init values for Parachain Origin + let destination = PenpalA::sibling_location_of(PenpalB::para_id()); + let sender = PenpalASender::get(); + let roc_to_send: Balance = ROCOCO_ED * 10000; + let assets_owner = PenpalAssetOwner::get(); + let roc_location = RelayLocation::get(); + let sender_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_of_sender_on_ah = AssetHubRococo::sovereign_account_id_of(sender_as_seen_by_ah); + let receiver_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalB::para_id()); + let sov_of_receiver_on_ah = AssetHubRococo::sovereign_account_id_of(receiver_as_seen_by_ah); + let wnd_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000; + + // Configure destination chain to trust AH as reserve of WND + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Westend)]).encode(), + )], + )); + }); + + // Register WND as foreign asset and transfer it around the Rococo ecosystem + let wnd_at_rococo_parachains = + Location::new(2, [Junction::GlobalConsensus(NetworkId::Westend)]); + AssetHubRococo::force_create_foreign_asset( + wnd_at_rococo_parachains.clone().try_into().unwrap(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + PenpalA::force_create_foreign_asset( + wnd_at_rococo_parachains.clone(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + PenpalB::force_create_foreign_asset( + wnd_at_rococo_parachains.clone(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + roc_location.clone(), + sender.clone(), + roc_to_send * 2, + ); + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + wnd_at_rococo_parachains.clone(), + sender.clone(), + wnd_to_send * 2, + ); + // fund the Parachain Origin's SA on Asset Hub with the assets held in reserve + AssetHubRococo::fund_accounts(vec![(sov_of_sender_on_ah.clone().into(), roc_to_send * 2)]); + AssetHubRococo::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner), + wnd_at_rococo_parachains.clone().try_into().unwrap(), + sov_of_sender_on_ah.clone(), + wnd_to_send * 2, + ); + + // Init values for Parachain Destination + let receiver = PenpalBReceiver::get(); + + // Assets to send + let assets: Vec = vec![ + (roc_location.clone(), roc_to_send).into(), + (wnd_at_rococo_parachains.clone(), wnd_to_send).into(), + ]; + let fee_asset_id: AssetId = roc_location.clone().into(); + let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32; + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination, + receiver.clone(), + roc_to_send, + assets.into(), + None, + fee_asset_item, + ), + }; + let mut test = ParaToParaThroughAHTest::new(test_args); + + // Query initial balances + let sender_rocs_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_location.clone(), &sender) + }); + let sender_wnds_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains.clone(), &sender) + }); + let rocs_in_sender_reserve_on_ahr_before = + ::account_data_of(sov_of_sender_on_ah.clone()).free; + let wnds_in_sender_reserve_on_ahr_before = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + wnd_at_rococo_parachains.clone().try_into().unwrap(), + &sov_of_sender_on_ah, + ) + }); + let rocs_in_receiver_reserve_on_ahr_before = + ::account_data_of(sov_of_receiver_on_ah.clone()).free; + let wnds_in_receiver_reserve_on_ahr_before = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + wnd_at_rococo_parachains.clone().try_into().unwrap(), + &sov_of_receiver_on_ah, + ) + }); + let receiver_rocs_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_location.clone(), &receiver) + }); + let receiver_wnds_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains.clone(), &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(para_to_para_through_hop_sender_assertions); + test.set_assertion::(para_to_para_assethub_hop_assertions); + test.set_assertion::(para_to_para_through_hop_receiver_assertions); + test.set_dispatchable::(para_to_para_transfer_assets_through_ah); + test.assert(); + + // Query final balances + let sender_rocs_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_location.clone(), &sender) + }); + let sender_wnds_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains.clone(), &sender) + }); + let wnds_in_sender_reserve_on_ahr_after = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + wnd_at_rococo_parachains.clone().try_into().unwrap(), + &sov_of_sender_on_ah, + ) + }); + let rocs_in_sender_reserve_on_ahr_after = + ::account_data_of(sov_of_sender_on_ah).free; + let wnds_in_receiver_reserve_on_ahr_after = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + wnd_at_rococo_parachains.clone().try_into().unwrap(), + &sov_of_receiver_on_ah, + ) + }); + let rocs_in_receiver_reserve_on_ahr_after = + ::account_data_of(sov_of_receiver_on_ah).free; + let receiver_rocs_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_location, &receiver) + }); + let receiver_wnds_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_rococo_parachains, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_rocs_after < sender_rocs_before - roc_to_send); + assert_eq!(sender_wnds_after, sender_wnds_before - wnd_to_send); + // Sovereign accounts on reserve are changed accordingly + assert_eq!( + rocs_in_sender_reserve_on_ahr_after, + rocs_in_sender_reserve_on_ahr_before - roc_to_send + ); + assert_eq!( + wnds_in_sender_reserve_on_ahr_after, + wnds_in_sender_reserve_on_ahr_before - wnd_to_send + ); + assert!(rocs_in_receiver_reserve_on_ahr_after > rocs_in_receiver_reserve_on_ahr_before); + assert_eq!( + wnds_in_receiver_reserve_on_ahr_after, + wnds_in_receiver_reserve_on_ahr_before + wnd_to_send + ); + // Receiver's balance is increased + assert!(receiver_rocs_after > receiver_rocs_before); + assert_eq!(receiver_wnds_after, receiver_wnds_before + wnd_to_send); +} + +// ============================================================================================== +// ==== Bidirectional Transfer - Native + Teleportable Foreign Assets - Parachain<->AssetHub ==== +// ============================================================================================== +/// Transfers of native asset plus teleportable foreign asset from Parachain to AssetHub and back +/// with fees paid using native asset. +#[test] +fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explicit_transfer_types() { + do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( + para_to_asset_hub_teleport_foreign_assets, + asset_hub_to_para_teleport_foreign_assets, + ); +} + +// =============================================================== +// ===== Transfer - Native Asset - Relay->AssetHub->Parachain ==== +// =============================================================== +/// Transfers of native asset Relay to Parachain (using AssetHub reserve). Parachains want to avoid +/// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their +/// Sovereign Account on Asset Hub. +#[test] +fn transfer_native_asset_from_relay_to_para_through_asset_hub() { + // Init values for Relay + let destination = Rococo::child_location_of(PenpalA::para_id()); + let sender = RococoSender::get(); + let amount_to_send: Balance = ROCOCO_ED * 1000; + + // Init values for Parachain + let relay_native_asset_location = RelayLocation::get(); + let receiver = PenpalAReceiver::get(); + + // Init Test + let test_args = TestContext { + sender, + receiver: receiver.clone(), + args: TestArgs::new_relay(destination.clone(), receiver.clone(), amount_to_send), + }; + let mut test = RelayToParaThroughAHTest::new(test_args); + + let sov_penpal_on_ah = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + ); + // Query initial balances + let sender_balance_before = test.sender.balance; + let sov_penpal_on_ah_before = AssetHubRococo::execute_with(|| { + ::Balances::free_balance(sov_penpal_on_ah.clone()) + }); + let receiver_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.clone(), &receiver) + }); + + fn relay_assertions(t: RelayToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + Rococo::assert_xcm_pallet_attempted_complete(None); + assert_expected_events!( + Rococo, + vec![ + // Amount to teleport is withdrawn from Sender + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + // Amount to teleport is deposited in Relay's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + ] + ); + } + fn asset_hub_assertions(_: RelayToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_on_ah = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + ); + assert_expected_events!( + AssetHubRococo, + vec![ + // Deposited to receiver parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Minted { who, .. } + ) => { + who: *who == sov_penpal_on_ah, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + } + fn penpal_assertions(t: RelayToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let expected_id = + t.args.assets.into_inner().first().unwrap().id.0.clone().try_into().unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + }, + ] + ); + } + fn transfer_assets_dispatchable(t: RelayToParaThroughAHTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let asset_hub_location = Rococo::child_location_of(AssetHubRococo::para_id()); + let context = RococoUniversalLocation::get(); + + // reanchor fees to the view of destination (Penpal) + let mut remote_fees = fee.clone().reanchored(&t.args.dest, &context).unwrap(); + if let Fungible(ref mut amount) = remote_fees.fun { + // we already spent some fees along the way, just use half of what we started with + *amount = *amount / 2; + } + let xcm_on_final_dest = Xcm::<()>(vec![ + BuyExecution { fees: remote_fees, weight_limit: t.args.weight_limit.clone() }, + DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }, + ]); + + // reanchor final dest (Penpal) to the view of hop (Asset Hub) + let mut dest = t.args.dest.clone(); + dest.reanchor(&asset_hub_location, &context).unwrap(); + // on Asset Hub, forward assets to Penpal + let xcm_on_hop = Xcm::<()>(vec![DepositReserveAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + dest, + xcm: xcm_on_final_dest, + }]); + + // First leg is a teleport, from there a local-reserve-transfer to final dest + ::XcmPallet::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(asset_hub_location.into()), + bx!(t.args.assets.into()), + bx!(TransferType::Teleport), + bx!(fee.id.into()), + bx!(TransferType::Teleport), + bx!(VersionedXcm::from(xcm_on_hop)), + t.args.weight_limit, + ) + } + + // Set assertions and dispatchables + test.set_assertion::(relay_assertions); + test.set_assertion::(asset_hub_assertions); + test.set_assertion::(penpal_assertions); + test.set_dispatchable::(transfer_assets_dispatchable); + test.assert(); + + // Query final balances + let sender_balance_after = test.sender.balance; + let sov_penpal_on_ah_after = AssetHubRococo::execute_with(|| { + ::Balances::free_balance(sov_penpal_on_ah) + }); + let receiver_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_balance_after < sender_balance_before - amount_to_send); + // SA on AH balance is increased + assert!(sov_penpal_on_ah_after > sov_penpal_on_ah_before); + // Receiver's asset balance is increased + assert!(receiver_assets_after > receiver_assets_before); + // Receiver's asset balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_assets_after < receiver_assets_before + amount_to_send); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs index b3841af0e6c38372b8fb621fac468b25bdec63a1..138ce419757b98b03d4e9a6b26259d81ca779d69 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs @@ -13,8 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod hybrid_transfers; mod reserve_transfer; mod send; mod set_xcm_versions; mod swap; mod teleport; +mod treasury; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs index a0738839087a51e87df3187ac6f06d2889cce64e..8b9fedcd4947cf5aaef5db0233166c6bc7cbcf21 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -47,7 +47,7 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) { RuntimeEvent::ForeignAssets( pallet_assets::Event::Burned { asset_id, owner, balance, .. } ) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + asset_id: *asset_id == RelayLocation::get(), owner: *owner == t.sender.account_id, balance: *balance == t.args.amount, }, @@ -55,70 +55,92 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) { ); } -fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { +pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; - - AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( - 864_610_000, - 8_799, - ))); - + AssetHubRococo::assert_xcm_pallet_attempted_complete(None); + + let sov_acc_of_dest = AssetHubRococo::sovereign_account_id_of(t.args.dest.clone()); + for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + if idx == t.args.fee_asset_item as usize { + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount of native asset is transferred to Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Transfer { from, to, amount } + ) => { + from: *from == t.sender.account_id, + to: *to == sov_acc_of_dest, + amount: *amount == asset_amount, + }, + ] + ); + } else { + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount of foreign asset is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Transferred { asset_id, from, to, amount }, + ) => { + asset_id: *asset_id == expected_id, + from: *from == t.sender.account_id, + to: *to == sov_acc_of_dest, + amount: *amount == asset_amount, + }, + ] + ); + } + } assert_expected_events!( AssetHubRococo, vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account - RuntimeEvent::Balances( - pallet_balances::Event::Transfer { from, to, amount } - ) => { - from: *from == t.sender.account_id, - to: *to == AssetHubRococo::sovereign_account_id_of( - t.args.dest.clone() - ), - amount: *amount == t.args.amount, - }, // Transport fees are paid - RuntimeEvent::PolkadotXcm( - pallet_xcm::Event::FeesPaid { .. } - ) => {}, + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {}, ] ); AssetHubRococo::assert_xcm_pallet_sent(); } -fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) { +pub fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); PenpalA::assert_xcmp_queue_success(None); - - assert_expected_events!( - PenpalA, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == system_para_native_asset_location, - owner: *owner == t.receiver.account_id, - }, - ] - ); + for asset in t.args.assets.into_inner().into_iter() { + let expected_id = asset.id.0.try_into().unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + }, + ] + ); + } } -fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { +pub fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799))); - assert_expected_events!( - PenpalA, - vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account - RuntimeEvent::ForeignAssets( - pallet_assets::Event::Burned { asset_id, owner, balance, .. } - ) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), - owner: *owner == t.sender.account_id, - balance: *balance == t.args.amount, - }, - ] - ); + PenpalA::assert_xcm_pallet_attempted_complete(None); + for asset in t.args.assets.into_inner().into_iter() { + let expected_id = asset.id.0; + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance } + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.sender.account_id, + balance: *balance == asset_amount, + }, + ] + ); + } } fn para_to_relay_receiver_assertions(t: ParaToRelayTest) { @@ -150,25 +172,57 @@ fn para_to_relay_receiver_assertions(t: ParaToRelayTest) { ); } -fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { +pub fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of( - AssetHubRococo::sibling_location_of(PenpalA::para_id()), - ); - AssetHubRococo::assert_xcmp_queue_success(None); + let sov_acc_of_penpal = AssetHubRococo::sovereign_account_id_of(t.args.dest.clone()); + for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + if idx == t.args.fee_asset_item as usize { + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount of native is withdrawn from Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_acc_of_penpal.clone().into(), + amount: *amount == asset_amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); + } else { + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount of foreign asset is transferred from Parachain's Sovereign account + // to Receiver's account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance }, + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == sov_acc_of_penpal, + balance: *balance == asset_amount, + }, + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Issued { asset_id, owner, amount }, + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + amount: *amount == asset_amount, + }, + ] + ); + } + } assert_expected_events!( AssetHubRococo, vec![ - // Amount to reserve transfer is withdrawn from Parachain's Sovereign account - RuntimeEvent::Balances( - pallet_balances::Event::Burned { who, amount } - ) => { - who: *who == sov_penpal_on_ahr.clone().into(), - amount: *amount == t.args.amount, - }, - RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, @@ -212,10 +266,8 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) { fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - let reservable_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); + let reservable_asset_location = PenpalLocalReservableFromAssetHub::get(); PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8799))); assert_expected_events!( PenpalA, @@ -245,14 +297,13 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { fn system_para_to_para_assets_receiver_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + let system_para_asset_location = PenpalLocalReservableFromAssetHub::get(); PenpalA::assert_xcmp_queue_success(None); assert_expected_events!( PenpalA, vec![ RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + asset_id: *asset_id == RelayLocation::get(), owner: *owner == t.receiver.account_id, }, RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { @@ -304,7 +355,7 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) { PenpalA, vec![ RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + asset_id: *asset_id == RelayLocation::get(), owner: *owner == t.receiver.account_id, }, RuntimeEvent::MessageQueue( @@ -314,29 +365,27 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) { ); } -fn para_to_para_through_relay_sender_assertions(t: ParaToParaThroughRelayTest) { +pub fn para_to_para_through_hop_sender_assertions(t: Test) { type RuntimeEvent = ::RuntimeEvent; - let relay_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - PenpalA::assert_xcm_pallet_attempted_complete(None); - // XCM sent to relay reserve - PenpalA::assert_parachain_system_ump_sent(); - - assert_expected_events!( - PenpalA, - vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account - RuntimeEvent::ForeignAssets( - pallet_assets::Event::Burned { asset_id, owner, balance }, - ) => { - asset_id: *asset_id == relay_asset_location, - owner: *owner == t.sender.account_id, - balance: *balance == t.args.amount, - }, - ] - ); + for asset in t.args.assets.into_inner() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + assert_expected_events!( + PenpalA, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance }, + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.sender.account_id, + balance: *balance == amount, + }, + ] + ); + } } fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) { @@ -369,22 +418,22 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) { ); } -fn para_to_para_through_relay_receiver_assertions(t: ParaToParaThroughRelayTest) { +pub fn para_to_para_through_hop_receiver_assertions(t: Test) { type RuntimeEvent = ::RuntimeEvent; - let relay_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); PenpalB::assert_xcmp_queue_success(None); - - assert_expected_events!( - PenpalB, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == relay_asset_location, - owner: *owner == t.receiver.account_id, - }, - ] - ); + for asset in t.args.assets.into_inner().into_iter() { + let expected_id = asset.id.0.try_into().unwrap(); + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + }, + ] + ); + } } fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { @@ -525,9 +574,8 @@ fn reserve_transfer_native_asset_from_relay_to_para() { let sender = RococoSender::get(); let amount_to_send: Balance = ROCOCO_ED * 1000; - // Init values fot Parachain - let relay_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + // Init values for Parachain + let relay_native_asset_location = RelayLocation::get(); let receiver = PenpalAReceiver::get(); // Init Test @@ -542,7 +590,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { let sender_balance_before = test.sender.balance; let receiver_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location.into(), &receiver) + >::balance(relay_native_asset_location.clone(), &receiver) }); // Set assertions and dispatchables @@ -555,7 +603,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { let sender_balance_after = test.sender.balance; let receiver_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location.into(), &receiver) + >::balance(relay_native_asset_location, &receiver) }); // Sender's balance is reduced by amount sent plus delivery fees @@ -577,13 +625,12 @@ fn reserve_transfer_native_asset_from_para_to_relay() { let amount_to_send: Balance = ROCOCO_ED * 1000; let assets: Assets = (Parent, amount_to_send).into(); let asset_owner = PenpalAssetOwner::get(); - let relay_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let relay_native_asset_location = RelayLocation::get(); // fund Parachain's sender account PenpalA::mint_foreign_asset( ::RuntimeOrigin::signed(asset_owner), - relay_native_asset_location, + relay_native_asset_location.clone(), sender.clone(), amount_to_send * 2, ); @@ -614,7 +661,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() { // Query initial balances let sender_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location.into(), &sender) + >::balance(relay_native_asset_location.clone(), &sender) }); let receiver_balance_before = test.receiver.balance; @@ -627,7 +674,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() { // Query final balances let sender_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location.into(), &sender) + >::balance(relay_native_asset_location, &sender) }); let receiver_balance_after = test.receiver.balance; @@ -654,8 +701,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { let assets: Assets = (Parent, amount_to_send).into(); // Init values for Parachain - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); let receiver = PenpalAReceiver::get(); // Init Test @@ -677,7 +723,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { let sender_balance_before = test.sender.balance; let receiver_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location.into(), &receiver) + >::balance(system_para_native_asset_location.clone(), &receiver) }); // Set assertions and dispatchables @@ -711,14 +757,13 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { let sender = PenpalASender::get(); let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000; let assets: Assets = (Parent, amount_to_send).into(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); let asset_owner = PenpalAssetOwner::get(); // fund Parachain's sender account PenpalA::mint_foreign_asset( ::RuntimeOrigin::signed(asset_owner), - system_para_native_asset_location, + system_para_native_asset_location.clone(), sender.clone(), amount_to_send * 2, ); @@ -749,7 +794,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { // Query initial balances let sender_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location, &sender) + >::balance(system_para_native_asset_location.clone(), &sender) }); let receiver_balance_before = test.receiver.balance; @@ -776,9 +821,9 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { assert!(receiver_balance_after < receiver_balance_before + amount_to_send); } -// ========================================================================= -// ======= Reserve Transfers - Non-system Asset - AssetHub<>Parachain ====== -// ========================================================================= +// ================================================================================== +// ======= Reserve Transfers - Native + Non-system Asset - AssetHub<>Parachain ====== +// ================================================================================== /// Reserve Transfers of a local asset and native asset from System Parachain to Parachain should /// work #[test] @@ -817,10 +862,8 @@ fn reserve_transfer_assets_from_system_para_to_para() { // Init values for Parachain let receiver = PenpalAReceiver::get(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - let system_para_foreign_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); + let system_para_foreign_asset_location = PenpalLocalReservableFromAssetHub::get(); // Init Test let para_test_args = TestContext { @@ -845,11 +888,14 @@ fn reserve_transfer_assets_from_system_para_to_para() { }); let receiver_system_native_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location, &receiver) + >::balance(system_para_native_asset_location.clone(), &receiver) }); let receiver_foreign_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location, &receiver) + >::balance( + system_para_foreign_asset_location.clone(), + &receiver, + ) }); // Set assertions and dispatchables @@ -866,7 +912,7 @@ fn reserve_transfer_assets_from_system_para_to_para() { }); let receiver_system_native_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location, &receiver) + >::balance(system_para_native_asset_location.clone(), &receiver) }); let receiver_foreign_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; @@ -904,14 +950,11 @@ fn reserve_transfer_assets_from_para_to_system_para() { let asset_amount_to_send = ASSET_HUB_ROCOCO_ED * 10000; let penpal_asset_owner = PenpalAssetOwner::get(); let penpal_asset_owner_signer = ::RuntimeOrigin::signed(penpal_asset_owner); - let asset_location_on_penpal = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); - let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap(); - let system_asset_location_on_penpal = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let asset_location_on_penpal = PenpalLocalReservableFromAssetHub::get(); + let system_asset_location_on_penpal = RelayLocation::get(); let assets: Assets = vec![ (Parent, fee_amount_to_send).into(), - (asset_location_on_penpal_latest, asset_amount_to_send).into(), + (asset_location_on_penpal.clone(), asset_amount_to_send).into(), ] .into(); let fee_asset_index = assets @@ -938,10 +981,8 @@ fn reserve_transfer_assets_from_para_to_system_para() { let receiver = AssetHubRococoReceiver::get(); let penpal_location_as_seen_by_ahr = AssetHubRococo::sibling_location_of(PenpalA::para_id()); let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location_as_seen_by_ahr); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - let system_para_foreign_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); + let system_para_foreign_asset_location = PenpalLocalReservableFromAssetHub::get(); let ah_asset_owner = AssetHubRococoAssetOwner::get(); let ah_asset_owner_signer = ::RuntimeOrigin::signed(ah_asset_owner); @@ -976,11 +1017,11 @@ fn reserve_transfer_assets_from_para_to_system_para() { // Query initial balances let sender_system_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location, &sender) + >::balance(system_para_native_asset_location.clone(), &sender) }); let sender_foreign_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location, &sender) + >::balance(system_para_foreign_asset_location.clone(), &sender) }); let receiver_balance_before = test.receiver.balance; let receiver_assets_before = AssetHubRococo::execute_with(|| { @@ -997,7 +1038,7 @@ fn reserve_transfer_assets_from_para_to_system_para() { // Query final balances let sender_system_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location, &sender) + >::balance(system_para_native_asset_location.clone(), &sender) }); let sender_foreign_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; @@ -1029,22 +1070,21 @@ fn reserve_transfer_assets_from_para_to_system_para() { /// Reserve Transfers of native asset from Parachain to Parachain (through Relay reserve) should /// work #[test] -fn reserve_transfer_native_asset_from_para_to_para_trough_relay() { +fn reserve_transfer_native_asset_from_para_to_para_through_relay() { // Init values for Parachain Origin let destination = PenpalA::sibling_location_of(PenpalB::para_id()); let sender = PenpalASender::get(); let amount_to_send: Balance = ROCOCO_ED * 10000; let asset_owner = PenpalAssetOwner::get(); let assets = (Parent, amount_to_send).into(); - let relay_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let relay_native_asset_location = RelayLocation::get(); let sender_as_seen_by_relay = Rococo::child_location_of(PenpalA::para_id()); let sov_of_sender_on_relay = Rococo::sovereign_account_id_of(sender_as_seen_by_relay); // fund Parachain's sender account PenpalA::mint_foreign_asset( ::RuntimeOrigin::signed(asset_owner), - relay_native_asset_location, + relay_native_asset_location.clone(), sender.clone(), amount_to_send * 2, ); @@ -1066,24 +1106,24 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() { // Query initial balances let sender_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location, &sender) + >::balance(relay_native_asset_location.clone(), &sender) }); let receiver_assets_before = PenpalB::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location, &receiver) + >::balance(relay_native_asset_location.clone(), &receiver) }); // Set assertions and dispatchables - test.set_assertion::(para_to_para_through_relay_sender_assertions); + test.set_assertion::(para_to_para_through_hop_sender_assertions); test.set_assertion::(para_to_para_relay_hop_assertions); - test.set_assertion::(para_to_para_through_relay_receiver_assertions); + test.set_assertion::(para_to_para_through_hop_receiver_assertions); test.set_dispatchable::(para_to_para_through_relay_limited_reserve_transfer_assets); test.assert(); // Query final balances let sender_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location, &sender) + >::balance(relay_native_asset_location.clone(), &sender) }); let receiver_assets_after = PenpalB::execute_with(|| { type ForeignAssets = ::ForeignAssets; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/send.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/send.rs index 1d120f1dc4c7ed4f923514e0692f3a4beb48103a..364fbd0d439f62ed1fce356d1935331ec8e0d90b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/send.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/send.rs @@ -75,10 +75,10 @@ fn send_xcm_from_para_to_system_para_paying_fee_with_system_assets_works() { )]); PenpalA::execute_with(|| { - assert_ok!(::PolkadotXcm::send_blob( + assert_ok!(::PolkadotXcm::send( root_origin, bx!(system_para_destination), - xcm.encode().try_into().unwrap(), + bx!(xcm), )); PenpalA::assert_xcm_pallet_sent(); @@ -159,10 +159,10 @@ fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() { )]); PenpalA::execute_with(|| { - assert_ok!(::PolkadotXcm::send_blob( + assert_ok!(::PolkadotXcm::send( root_origin, bx!(system_para_destination), - xcm.encode().try_into().unwrap(), + bx!(xcm), )); PenpalA::assert_xcm_pallet_sent(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs index e13300b7c11426416f543c2ee026702277b695b1..ec48e400ff545686fe728025eacc7ea5cd783d6f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs @@ -17,7 +17,10 @@ use crate::imports::*; #[test] fn swap_locally_on_chain_using_local_assets() { - let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocationV3::get()); + let asset_native = Box::new( + v3::Location::try_from(asset_hub_rococo_runtime::xcm_config::TokenLocation::get()) + .expect("conversion works"), + ); let asset_one = Box::new(v3::Location::new( 0, [ @@ -112,10 +115,9 @@ fn swap_locally_on_chain_using_local_assets() { #[test] fn swap_locally_on_chain_using_foreign_assets() { - let asset_native = - Box::new(v3::Location::try_from(RelayLocation::get()).expect("conversion works")); + let asset_native = Box::new(v3::Location::try_from(RelayLocation::get()).unwrap()); let asset_location_on_penpal = - v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works"); + v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).unwrap(); let foreign_asset_at_asset_hub_rococo = v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) .appended_with(asset_location_on_penpal) @@ -228,11 +230,9 @@ fn swap_locally_on_chain_using_foreign_assets() { #[test] fn cannot_create_pool_from_pool_assets() { - let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocationV3::get()); - let mut asset_one = asset_hub_rococo_runtime::xcm_config::PoolAssetsPalletLocationV3::get(); - asset_one - .append_with(v3::Junction::GeneralIndex(ASSET_ID.into())) - .expect("pool assets"); + let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocation::get(); + let mut asset_one = asset_hub_rococo_runtime::xcm_config::PoolAssetsPalletLocation::get(); + asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets"); AssetHubRococo::execute_with(|| { let pool_owner_account_id = asset_hub_rococo_runtime::AssetConversionOrigin::get(); @@ -255,8 +255,8 @@ fn cannot_create_pool_from_pool_assets() { assert_matches::assert_matches!( ::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - asset_native, - Box::new(asset_one), + Box::new(v3::Location::try_from(asset_native).expect("conversion works")), + Box::new(v3::Location::try_from(asset_one).expect("conversion works")), ), Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("Unknown")) ); @@ -265,7 +265,9 @@ fn cannot_create_pool_from_pool_assets() { #[test] fn pay_xcm_fee_with_some_asset_swapped_for_native() { - let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocationV3::get(); + let asset_native = + v3::Location::try_from(asset_hub_rococo_runtime::xcm_config::TokenLocation::get()) + .expect("conversion works"); let asset_one = xcm::v3::Location { parents: 0, interior: [ @@ -370,10 +372,10 @@ fn pay_xcm_fee_with_some_asset_swapped_for_native() { penpal.clone(), ); - assert_ok!(::PolkadotXcm::send_blob( + assert_ok!(::PolkadotXcm::send( penpal_root, bx!(asset_hub_location), - xcm.encode().try_into().unwrap(), + bx!(xcm), )); PenpalA::assert_xcm_pallet_sent(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs index 1cbb7fb8c193accc65ef160a3a09514bd51debf5..f74378d7631a610a57c61153e62aedfcb588a611 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs @@ -110,8 +110,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); let expected_asset_id = t.args.asset_id.unwrap(); let (_, expected_asset_amount) = non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); @@ -204,8 +203,7 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { let (_, expected_asset_amount) = non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); let checking_account = ::PolkadotXcm::check_account(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); PenpalA::assert_xcmp_queue_success(None); @@ -414,29 +412,28 @@ fn teleport_to_other_system_parachains_works() { ); } -/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work -/// (using native reserve-based transfer for fees) -#[test] -fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { +/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets while paying +/// fees using (reserve transferred) native asset. +pub fn do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( + para_to_ah_dispatchable: fn(ParaToSystemParaTest) -> DispatchResult, + ah_to_para_dispatchable: fn(SystemParaToParaTest) -> DispatchResult, +) { // Init values for Parachain let fee_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000; - let asset_location_on_penpal = - v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works"); + let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); let asset_id_on_penpal = match asset_location_on_penpal.last() { - Some(v3::Junction::GeneralIndex(id)) => *id as u32, + Some(Junction::GeneralIndex(id)) => *id as u32, _ => unreachable!(), }; let asset_amount_to_send = ASSET_HUB_ROCOCO_ED * 1000; let asset_owner = PenpalAssetOwner::get(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); let sender = PenpalASender::get(); let penpal_check_account = ::PolkadotXcm::check_account(); let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubRococo::para_id()); - let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap(); let penpal_assets: Assets = vec![ (Parent, fee_amount_to_send).into(), - (asset_location_on_penpal_latest, asset_amount_to_send).into(), + (asset_location_on_penpal.clone(), asset_amount_to_send).into(), ] .into(); let fee_asset_index = penpal_assets @@ -448,7 +445,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { // fund Parachain's sender account PenpalA::mint_foreign_asset( ::RuntimeOrigin::signed(asset_owner.clone()), - system_para_native_asset_location, + system_para_native_asset_location.clone(), sender.clone(), fee_amount_to_send * 2, ); @@ -472,7 +469,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { // Init values for System Parachain let foreign_asset_at_asset_hub_rococo = - v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) + Location::new(1, [Junction::Parachain(PenpalA::para_id().into())]) .appended_with(asset_location_on_penpal) .unwrap(); let penpal_to_ah_beneficiary_id = AssetHubRococoReceiver::get(); @@ -494,7 +491,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { let penpal_sender_balance_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; >::balance( - system_para_native_asset_location, + system_para_native_asset_location.clone(), &PenpalASender::get(), ) }); @@ -508,20 +505,20 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { let ah_receiver_assets_before = AssetHubRococo::execute_with(|| { type Assets = ::ForeignAssets; >::balance( - foreign_asset_at_asset_hub_rococo, + foreign_asset_at_asset_hub_rococo.clone().try_into().unwrap(), &AssetHubRococoReceiver::get(), ) }); penpal_to_ah.set_assertion::(penpal_to_ah_foreign_assets_sender_assertions); penpal_to_ah.set_assertion::(penpal_to_ah_foreign_assets_receiver_assertions); - penpal_to_ah.set_dispatchable::(para_to_system_para_transfer_assets); + penpal_to_ah.set_dispatchable::(para_to_ah_dispatchable); penpal_to_ah.assert(); let penpal_sender_balance_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; >::balance( - system_para_native_asset_location, + system_para_native_asset_location.clone(), &PenpalASender::get(), ) }); @@ -535,7 +532,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { let ah_receiver_assets_after = AssetHubRococo::execute_with(|| { type Assets = ::ForeignAssets; >::balance( - foreign_asset_at_asset_hub_rococo, + foreign_asset_at_asset_hub_rococo.clone().try_into().unwrap(), &AssetHubRococoReceiver::get(), ) }); @@ -563,19 +560,17 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { type ForeignAssets = ::ForeignAssets; assert_ok!(ForeignAssets::transfer( ::RuntimeOrigin::signed(AssetHubRococoReceiver::get()), - foreign_asset_at_asset_hub_rococo, + foreign_asset_at_asset_hub_rococo.clone().try_into().unwrap(), AssetHubRococoSender::get().into(), asset_amount_to_send, )); }); - let foreign_asset_at_asset_hub_rococo_latest: Location = - foreign_asset_at_asset_hub_rococo.try_into().unwrap(); let ah_to_penpal_beneficiary_id = PenpalAReceiver::get(); let penpal_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id()); let ah_assets: Assets = vec![ (Parent, fee_amount_to_send).into(), - (foreign_asset_at_asset_hub_rococo_latest, asset_amount_to_send).into(), + (foreign_asset_at_asset_hub_rococo.clone(), asset_amount_to_send).into(), ] .into(); let fee_asset_index = ah_assets @@ -603,7 +598,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { let penpal_receiver_balance_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; >::balance( - system_para_native_asset_location, + system_para_native_asset_location.clone(), &PenpalAReceiver::get(), ) }); @@ -611,7 +606,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { let ah_sender_assets_before = AssetHubRococo::execute_with(|| { type ForeignAssets = ::ForeignAssets; >::balance( - foreign_asset_at_asset_hub_rococo, + foreign_asset_at_asset_hub_rococo.clone().try_into().unwrap(), &AssetHubRococoSender::get(), ) }); @@ -622,7 +617,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { ah_to_penpal.set_assertion::(ah_to_penpal_foreign_assets_sender_assertions); ah_to_penpal.set_assertion::(ah_to_penpal_foreign_assets_receiver_assertions); - ah_to_penpal.set_dispatchable::(system_para_to_para_transfer_assets); + ah_to_penpal.set_dispatchable::(ah_to_para_dispatchable); ah_to_penpal.assert(); let ah_sender_balance_after = ah_to_penpal.sender.balance; @@ -637,7 +632,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { let ah_sender_assets_after = AssetHubRococo::execute_with(|| { type ForeignAssets = ::ForeignAssets; >::balance( - foreign_asset_at_asset_hub_rococo, + foreign_asset_at_asset_hub_rococo.try_into().unwrap(), &AssetHubRococoSender::get(), ) }); @@ -660,3 +655,13 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { // Receiver's balance is increased by exact amount assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send); } + +/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work +/// (using native reserve-based transfer for fees) +#[test] +fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { + do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( + para_to_system_para_transfer_assets, + system_para_to_para_transfer_assets, + ); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs new file mode 100644 index 0000000000000000000000000000000000000000..01bf40ae8fdf2cf87092c83ef604ef25427e2939 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs @@ -0,0 +1,270 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::imports::*; +use emulated_integration_tests_common::accounts::{ALICE, BOB}; +use frame_support::{ + dispatch::RawOrigin, + sp_runtime::traits::Dispatchable, + traits::{ + fungible::Inspect, + fungibles::{Create, Inspect as FungiblesInspect, Mutate}, + }, +}; +use parachains_common::AccountId; +use polkadot_runtime_common::impls::VersionedLocatableAsset; +use rococo_runtime::OriginCaller; +use rococo_runtime_constants::currency::GRAND; +use xcm_executor::traits::ConvertLocation; + +// Fund Treasury account on Asset Hub from Treasury account on Relay Chain with ROCs. +#[test] +fn spend_roc_on_asset_hub() { + // initial treasury balance on Asset Hub in ROCs. + let treasury_balance = 9_000 * GRAND; + // the balance spend on Asset Hub. + let treasury_spend_balance = 1_000 * GRAND; + + let init_alice_balance = AssetHubRococo::execute_with(|| { + <::Balances as Inspect<_>>::balance( + &AssetHubRococo::account_id_of(ALICE), + ) + }); + + Rococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeCall = ::RuntimeCall; + type Runtime = ::Runtime; + type Balances = ::Balances; + type Treasury = ::Treasury; + + // Fund Treasury account on Asset Hub with ROCs. + + let root = ::RuntimeOrigin::root(); + let treasury_account = Treasury::account_id(); + + // Mint assets to Treasury account on Relay Chain. + assert_ok!(Balances::force_set_balance( + root.clone(), + treasury_account.clone().into(), + treasury_balance * 2, + )); + + let native_asset = Location::here(); + let asset_hub_location: Location = [Parachain(1000)].into(); + let treasury_location: Location = (Parent, PalletInstance(18)).into(); + + let teleport_call = RuntimeCall::Utility(pallet_utility::Call::::dispatch_as { + as_origin: bx!(OriginCaller::system(RawOrigin::Signed(treasury_account))), + call: bx!(RuntimeCall::XcmPallet(pallet_xcm::Call::::teleport_assets { + dest: bx!(VersionedLocation::V4(asset_hub_location.clone())), + beneficiary: bx!(VersionedLocation::V4(treasury_location)), + assets: bx!(VersionedAssets::V4( + Asset { id: native_asset.clone().into(), fun: treasury_balance.into() }.into() + )), + fee_asset_item: 0, + })), + }); + + // Dispatched from Root to `despatch_as` `Signed(treasury_account)`. + assert_ok!(teleport_call.dispatch(root)); + + assert_expected_events!( + Rococo, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + Rococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeCall = ::RuntimeCall; + type RuntimeOrigin = ::RuntimeOrigin; + type Runtime = ::Runtime; + type Treasury = ::Treasury; + + // Fund Alice account from Rococo Treasury account on Asset Hub. + + let treasury_origin: RuntimeOrigin = + rococo_runtime::governance::pallet_custom_origins::Origin::Treasurer.into(); + + let alice_location: Location = + [Junction::AccountId32 { network: None, id: Rococo::account_id_of(ALICE).into() }] + .into(); + let asset_hub_location: Location = [Parachain(1000)].into(); + let native_asset = Location::parent(); + + let treasury_spend_call = RuntimeCall::Treasury(pallet_treasury::Call::::spend { + asset_kind: bx!(VersionedLocatableAsset::V4 { + location: asset_hub_location.clone(), + asset_id: native_asset.into(), + }), + amount: treasury_spend_balance, + beneficiary: bx!(VersionedLocation::V4(alice_location)), + valid_from: None, + }); + + assert_ok!(treasury_spend_call.dispatch(treasury_origin)); + + // Claim the spend. + + let bob_signed = RuntimeOrigin::signed(Rococo::account_id_of(BOB)); + assert_ok!(Treasury::payout(bob_signed.clone(), 0)); + + assert_expected_events!( + Rococo, + vec![ + RuntimeEvent::Treasury(pallet_treasury::Event::AssetSpendApproved { .. }) => {}, + RuntimeEvent::Treasury(pallet_treasury::Event::Paid { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Balances = ::Balances; + + // Ensure that the funds deposited to Alice account. + + let alice_account = AssetHubRococo::account_id_of(ALICE); + assert_eq!( + >::balance(&alice_account), + treasury_spend_balance + init_alice_balance + ); + + // Assert events triggered by xcm pay program: + // 1. treasury asset transferred to spend beneficiary; + // 2. response to Relay Chain Treasury pallet instance sent back; + // 3. XCM program completed; + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::Balances(pallet_balances::Event::Transfer { .. }) => {}, + RuntimeEvent::ParachainSystem(cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true ,.. }) => {}, + ] + ); + }); +} + +#[test] +fn create_and_claim_treasury_spend_in_usdt() { + const ASSET_ID: u32 = 1984; + const SPEND_AMOUNT: u128 = 1_000_000; + // treasury location from a sibling parachain. + let treasury_location: Location = Location::new(1, PalletInstance(18)); + // treasury account on a sibling parachain. + let treasury_account = + asset_hub_rococo_runtime::xcm_config::LocationToAccountId::convert_location( + &treasury_location, + ) + .unwrap(); + let asset_hub_location = + v3::Location::new(0, v3::Junction::Parachain(AssetHubRococo::para_id().into())); + let root = ::RuntimeOrigin::root(); + // asset kind to be spend from the treasury. + let asset_kind = VersionedLocatableAsset::V3 { + location: asset_hub_location, + asset_id: v3::AssetId::Concrete( + (v3::Junction::PalletInstance(50), v3::Junction::GeneralIndex(ASSET_ID.into())).into(), + ), + }; + // treasury spend beneficiary. + let alice: AccountId = Rococo::account_id_of(ALICE); + let bob: AccountId = Rococo::account_id_of(BOB); + let bob_signed = ::RuntimeOrigin::signed(bob.clone()); + + AssetHubRococo::execute_with(|| { + type Assets = ::Assets; + + // create an asset class and mint some assets to the treasury account. + assert_ok!(>::create( + ASSET_ID, + treasury_account.clone(), + true, + SPEND_AMOUNT / 2 + )); + assert_ok!(>::mint_into(ASSET_ID, &treasury_account, SPEND_AMOUNT * 4)); + // beneficiary has zero balance. + assert_eq!(>::balance(ASSET_ID, &alice,), 0u128,); + }); + + Rococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Treasury = ::Treasury; + type AssetRate = ::AssetRate; + + // create a conversion rate from `asset_kind` to the native currency. + assert_ok!(AssetRate::create(root.clone(), Box::new(asset_kind.clone()), 2.into())); + + // create and approve a treasury spend. + assert_ok!(Treasury::spend( + root, + Box::new(asset_kind), + SPEND_AMOUNT, + Box::new(Location::new(0, Into::<[u8; 32]>::into(alice.clone())).into()), + None, + )); + // claim the spend. + assert_ok!(Treasury::payout(bob_signed.clone(), 0)); + + assert_expected_events!( + Rococo, + vec![ + RuntimeEvent::Treasury(pallet_treasury::Event::Paid { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Assets = ::Assets; + + // assert events triggered by xcm pay program + // 1. treasury asset transferred to spend beneficiary + // 2. response to Relay Chain treasury pallet instance sent back + // 3. XCM program completed + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::Assets(pallet_assets::Event::Transferred { asset_id: id, from, to, amount }) => { + id: id == &ASSET_ID, + from: from == &treasury_account, + to: to == &alice, + amount: amount == &SPEND_AMOUNT, + }, + RuntimeEvent::ParachainSystem(cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true ,.. }) => {}, + ] + ); + // beneficiary received the assets from the treasury. + assert_eq!(>::balance(ASSET_ID, &alice,), SPEND_AMOUNT,); + }); + + Rococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Treasury = ::Treasury; + + // check the payment status to ensure the response from the AssetHub was received. + assert_ok!(Treasury::check_status(bob_signed, 0)); + assert_expected_events!( + Rococo, + vec![ + RuntimeEvent::Treasury(pallet_treasury::Event::SpendProcessed { .. }) => {}, + ] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index c9f5fe0647e12ba0121261505e27ff56c3f82f96..1c4a0ef4c8d2af7f773fbb6916391012ec9fdfc2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -30,6 +30,7 @@ mod imports { prelude::{AccountId32 as AccountId32Junction, *}, v3, }; + pub use xcm_executor::traits::TransferType; // Cumulus pub use asset_test_utils::xcm_helpers; @@ -73,7 +74,9 @@ mod imports { LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub, LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, }; - pub use westend_runtime::xcm_config::XcmConfig as WestendXcmConfig; + pub use westend_runtime::xcm_config::{ + UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig, + }; pub const ASSET_ID: u32 = 3; pub const ASSET_MIN_BALANCE: u128 = 1000; @@ -85,6 +88,8 @@ mod imports { pub type SystemParaToParaTest = Test; pub type ParaToSystemParaTest = Test; pub type ParaToParaThroughRelayTest = Test; + pub type ParaToParaThroughAHTest = Test; + pub type RelayToParaThroughAHTest = Test; } #[cfg(test)] diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs new file mode 100644 index 0000000000000000000000000000000000000000..d39c72c7c5f0d21815ca0091e9fd888b5ab54924 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs @@ -0,0 +1,812 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::reserve_transfer::*; +use crate::{ + imports::*, + tests::teleport::do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt, +}; + +fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_a_on_ah = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + let sov_penpal_b_on_ah = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalB::para_id()), + ); + + assert_expected_events!( + AssetHubWestend, + vec![ + // Withdrawn from sender parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_a_on_ah, + amount: *amount == t.args.amount, + }, + // Deposited to receiver parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Minted { who, .. } + ) => { + who: *who == sov_penpal_b_on_ah, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); + ::PolkadotXcm::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.assets.into()), + bx!(TransferType::LocalReserve), + bx!(fee.id.into()), + bx!(TransferType::LocalReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + t.args.weight_limit, + ) +} + +fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); + ::PolkadotXcm::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.assets.into()), + bx!(TransferType::DestinationReserve), + bx!(fee.id.into()), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + t.args.weight_limit, + ) +} + +fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); + ::PolkadotXcm::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.assets.into()), + bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())), + bx!(fee.id.into()), + bx!(TransferType::RemoteReserve(asset_hub_location.into())), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + t.args.weight_limit, + ) +} + +fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); + ::PolkadotXcm::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.assets.into()), + bx!(TransferType::Teleport), + bx!(fee.id.into()), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + t.args.weight_limit, + ) +} + +fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); + ::PolkadotXcm::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.assets.into()), + bx!(TransferType::Teleport), + bx!(fee.id.into()), + bx!(TransferType::LocalReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + t.args.weight_limit, + ) +} + +// =========================================================================== +// ======= Transfer - Native + Bridged Assets - AssetHub->Parachain ========== +// =========================================================================== +/// Transfers of native asset plus bridged asset from AssetHub to some Parachain +/// while paying fees using native asset. +#[test] +fn transfer_foreign_assets_from_asset_hub_to_para() { + let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sender = AssetHubWestendSender::get(); + let native_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; + let native_asset_location = RelayLocation::get(); + let receiver = PenpalAReceiver::get(); + let assets_owner = PenpalAssetOwner::get(); + // Foreign asset used: bridged ROC + let foreign_amount_to_send = ASSET_HUB_WESTEND_ED * 10_000_000; + let roc_at_westend_parachains = + Location::new(2, [Junction::GlobalConsensus(NetworkId::Rococo)]); + + // Configure destination chain to trust AH as reserve of ROC + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Rococo)]).encode(), + )], + )); + }); + PenpalA::force_create_foreign_asset( + roc_at_westend_parachains.clone(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + AssetHubWestend::force_create_foreign_asset( + roc_at_westend_parachains.clone().try_into().unwrap(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + AssetHubWestend::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner), + roc_at_westend_parachains.clone().try_into().unwrap(), + sender.clone(), + foreign_amount_to_send * 2, + ); + + // Assets to send + let assets: Vec = vec![ + (Parent, native_amount_to_send).into(), + (roc_at_westend_parachains.clone(), foreign_amount_to_send).into(), + ]; + let fee_asset_id = AssetId(Parent.into()); + let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32; + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + native_amount_to_send, + assets.into(), + None, + fee_asset_item, + ), + }; + let mut test = SystemParaToParaTest::new(test_args); + + // Query initial balances + let sender_balance_before = test.sender.balance; + let sender_rocs_before = AssetHubWestend::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + roc_at_westend_parachains.clone().try_into().unwrap(), + &sender, + ) + }); + let receiver_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location.clone(), &receiver) + }); + let receiver_rocs_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains.clone(), &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(system_para_to_para_sender_assertions); + test.set_assertion::(system_para_to_para_receiver_assertions); + test.set_dispatchable::(ah_to_para_transfer_assets); + test.assert(); + + // Query final balances + let sender_balance_after = test.sender.balance; + let sender_rocs_after = AssetHubWestend::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + roc_at_westend_parachains.clone().try_into().unwrap(), + &sender, + ) + }); + let receiver_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location, &receiver) + }); + let receiver_rocs_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_balance_after < sender_balance_before - native_amount_to_send); + // Sender's balance is reduced by foreign amount sent + assert_eq!(sender_rocs_after, sender_rocs_before - foreign_amount_to_send); + // Receiver's assets is increased + assert!(receiver_assets_after > receiver_assets_before); + // Receiver's assets increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_assets_after < receiver_assets_before + native_amount_to_send); + // Receiver's balance is increased by foreign amount sent + assert_eq!(receiver_rocs_after, receiver_rocs_before + foreign_amount_to_send); +} + +/// Reserve Transfers of native asset from Parachain to System Parachain should work +// =========================================================================== +// ======= Transfer - Native + Bridged Assets - Parachain->AssetHub ========== +// =========================================================================== +/// Transfers of native asset plus bridged asset from some Parachain to AssetHub +/// while paying fees using native asset. +#[test] +fn transfer_foreign_assets_from_para_to_asset_hub() { + // Init values for Parachain + let destination = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let sender = PenpalASender::get(); + let native_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 10000; + let native_asset_location = RelayLocation::get(); + let assets_owner = PenpalAssetOwner::get(); + + // Foreign asset used: bridged ROC + let foreign_amount_to_send = ASSET_HUB_WESTEND_ED * 10_000_000; + let roc_at_westend_parachains = + Location::new(2, [Junction::GlobalConsensus(NetworkId::Rococo)]); + + // Configure destination chain to trust AH as reserve of ROC + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Rococo)]).encode(), + )], + )); + }); + PenpalA::force_create_foreign_asset( + roc_at_westend_parachains.clone(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + AssetHubWestend::force_create_foreign_asset( + roc_at_westend_parachains.clone().try_into().unwrap(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + native_asset_location.clone(), + sender.clone(), + native_amount_to_send * 2, + ); + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + roc_at_westend_parachains.clone(), + sender.clone(), + foreign_amount_to_send * 2, + ); + + // Init values for System Parachain + let receiver = AssetHubWestendReceiver::get(); + let penpal_location_as_seen_by_ahr = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = + AssetHubWestend::sovereign_account_id_of(penpal_location_as_seen_by_ahr); + + // fund Parachain's SA on AssetHub with the assets held in reserve + AssetHubWestend::fund_accounts(vec![( + sov_penpal_on_ahr.clone().into(), + native_amount_to_send * 2, + )]); + AssetHubWestend::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner), + roc_at_westend_parachains.clone().try_into().unwrap(), + sov_penpal_on_ahr, + foreign_amount_to_send * 2, + ); + + // Assets to send + let assets: Vec = vec![ + (Parent, native_amount_to_send).into(), + (roc_at_westend_parachains.clone(), foreign_amount_to_send).into(), + ]; + let fee_asset_id = AssetId(Parent.into()); + let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32; + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + native_amount_to_send, + assets.into(), + None, + fee_asset_item, + ), + }; + let mut test = ParaToSystemParaTest::new(test_args); + + // Query initial balances + let sender_native_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location.clone(), &sender) + }); + let sender_rocs_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains.clone(), &sender) + }); + let receiver_native_before = test.receiver.balance; + let receiver_rocs_before = AssetHubWestend::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + roc_at_westend_parachains.clone().try_into().unwrap(), + &receiver, + ) + }); + + // Set assertions and dispatchables + test.set_assertion::(para_to_system_para_sender_assertions); + test.set_assertion::(para_to_system_para_receiver_assertions); + test.set_dispatchable::(para_to_ah_transfer_assets); + test.assert(); + + // Query final balances + let sender_native_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(native_asset_location, &sender) + }); + let sender_rocs_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains.clone(), &sender) + }); + let receiver_native_after = test.receiver.balance; + let receiver_rocs_after = AssetHubWestend::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + roc_at_westend_parachains.try_into().unwrap(), + &receiver, + ) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_native_after < sender_native_before - native_amount_to_send); + // Sender's balance is reduced by foreign amount sent + assert_eq!(sender_rocs_after, sender_rocs_before - foreign_amount_to_send); + // Receiver's balance is increased + assert!(receiver_native_after > receiver_native_before); + // Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_native_after < receiver_native_before + native_amount_to_send); + // Receiver's balance is increased by foreign amount sent + assert_eq!(receiver_rocs_after, receiver_rocs_before + foreign_amount_to_send); +} + +// ============================================================================== +// ===== Transfer - Native + Bridged Assets - Parachain->AssetHub->Parachain ==== +// ============================================================================== +/// Transfers of native asset plus bridged asset from Parachain to Parachain +/// (through AssetHub reserve) with fees paid using native asset. +#[test] +fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { + // Init values for Parachain Origin + let destination = PenpalA::sibling_location_of(PenpalB::para_id()); + let sender = PenpalASender::get(); + let wnd_to_send: Balance = WESTEND_ED * 10000; + let assets_owner = PenpalAssetOwner::get(); + let wnd_location = RelayLocation::get(); + let sender_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_of_sender_on_ah = AssetHubWestend::sovereign_account_id_of(sender_as_seen_by_ah); + let receiver_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + let sov_of_receiver_on_ah = AssetHubWestend::sovereign_account_id_of(receiver_as_seen_by_ah); + let roc_to_send = ASSET_HUB_WESTEND_ED * 10_000_000; + + // Configure destination chain to trust AH as reserve of ROC + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Rococo)]).encode(), + )], + )); + }); + + // Register ROC as foreign asset and transfer it around the Westend ecosystem + let roc_at_westend_parachains = + Location::new(2, [Junction::GlobalConsensus(NetworkId::Rococo)]); + AssetHubWestend::force_create_foreign_asset( + roc_at_westend_parachains.clone().try_into().unwrap(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + PenpalA::force_create_foreign_asset( + roc_at_westend_parachains.clone(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + PenpalB::force_create_foreign_asset( + roc_at_westend_parachains.clone(), + assets_owner.clone(), + false, + ASSET_MIN_BALANCE, + vec![], + ); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + wnd_location.clone(), + sender.clone(), + wnd_to_send * 2, + ); + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner.clone()), + roc_at_westend_parachains.clone(), + sender.clone(), + roc_to_send * 2, + ); + // fund the Parachain Origin's SA on Asset Hub with the assets held in reserve + AssetHubWestend::fund_accounts(vec![(sov_of_sender_on_ah.clone().into(), wnd_to_send * 2)]); + AssetHubWestend::mint_foreign_asset( + ::RuntimeOrigin::signed(assets_owner), + roc_at_westend_parachains.clone().try_into().unwrap(), + sov_of_sender_on_ah.clone(), + roc_to_send * 2, + ); + + // Init values for Parachain Destination + let receiver = PenpalBReceiver::get(); + + // Assets to send + let assets: Vec = vec![ + (wnd_location.clone(), wnd_to_send).into(), + (roc_at_westend_parachains.clone(), roc_to_send).into(), + ]; + let fee_asset_id: AssetId = wnd_location.clone().into(); + let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32; + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination, + receiver.clone(), + wnd_to_send, + assets.into(), + None, + fee_asset_item, + ), + }; + let mut test = ParaToParaThroughAHTest::new(test_args); + + // Query initial balances + let sender_wnds_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_location.clone(), &sender) + }); + let sender_rocs_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains.clone(), &sender) + }); + let wnds_in_sender_reserve_on_ah_before = + ::account_data_of(sov_of_sender_on_ah.clone()).free; + let rocs_in_sender_reserve_on_ah_before = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + roc_at_westend_parachains.clone().try_into().unwrap(), + &sov_of_sender_on_ah, + ) + }); + let wnds_in_receiver_reserve_on_ah_before = + ::account_data_of(sov_of_receiver_on_ah.clone()).free; + let rocs_in_receiver_reserve_on_ah_before = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + roc_at_westend_parachains.clone().try_into().unwrap(), + &sov_of_receiver_on_ah, + ) + }); + let receiver_wnds_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_location.clone(), &receiver) + }); + let receiver_rocs_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains.clone(), &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(para_to_para_through_hop_sender_assertions); + test.set_assertion::(para_to_para_assethub_hop_assertions); + test.set_assertion::(para_to_para_through_hop_receiver_assertions); + test.set_dispatchable::(para_to_para_transfer_assets_through_ah); + test.assert(); + + // Query final balances + let sender_wnds_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_location.clone(), &sender) + }); + let sender_rocs_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains.clone(), &sender) + }); + let rocs_in_sender_reserve_on_ah_after = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + roc_at_westend_parachains.clone().try_into().unwrap(), + &sov_of_sender_on_ah, + ) + }); + let wnds_in_sender_reserve_on_ah_after = + ::account_data_of(sov_of_sender_on_ah).free; + let rocs_in_receiver_reserve_on_ah_after = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + roc_at_westend_parachains.clone().try_into().unwrap(), + &sov_of_receiver_on_ah, + ) + }); + let wnds_in_receiver_reserve_on_ah_after = + ::account_data_of(sov_of_receiver_on_ah).free; + let receiver_wnds_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_location, &receiver) + }); + let receiver_rocs_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_westend_parachains, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_wnds_after < sender_wnds_before - wnd_to_send); + assert_eq!(sender_rocs_after, sender_rocs_before - roc_to_send); + // Sovereign accounts on reserve are changed accordingly + assert_eq!( + wnds_in_sender_reserve_on_ah_after, + wnds_in_sender_reserve_on_ah_before - wnd_to_send + ); + assert_eq!( + rocs_in_sender_reserve_on_ah_after, + rocs_in_sender_reserve_on_ah_before - roc_to_send + ); + assert!(wnds_in_receiver_reserve_on_ah_after > wnds_in_receiver_reserve_on_ah_before); + assert_eq!( + rocs_in_receiver_reserve_on_ah_after, + rocs_in_receiver_reserve_on_ah_before + roc_to_send + ); + // Receiver's balance is increased + assert!(receiver_wnds_after > receiver_wnds_before); + assert_eq!(receiver_rocs_after, receiver_rocs_before + roc_to_send); +} + +// ============================================================================================== +// ==== Bidirectional Transfer - Native + Teleportable Foreign Assets - Parachain<->AssetHub ==== +// ============================================================================================== +/// Transfers of native asset plus teleportable foreign asset from Parachain to AssetHub and back +/// with fees paid using native asset. +#[test] +fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explicit_transfer_types() { + do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( + para_to_asset_hub_teleport_foreign_assets, + asset_hub_to_para_teleport_foreign_assets, + ); +} + +// =============================================================== +// ===== Transfer - Native Asset - Relay->AssetHub->Parachain ==== +// =============================================================== +/// Transfers of native asset Relay to Parachain (using AssetHub reserve). Parachains want to avoid +/// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their +/// Sovereign Account on Asset Hub. +#[test] +fn transfer_native_asset_from_relay_to_para_through_asset_hub() { + // Init values for Relay + let destination = Westend::child_location_of(PenpalA::para_id()); + let sender = WestendSender::get(); + let amount_to_send: Balance = WESTEND_ED * 1000; + + // Init values for Parachain + let relay_native_asset_location = RelayLocation::get(); + let receiver = PenpalAReceiver::get(); + + // Init Test + let test_args = TestContext { + sender, + receiver: receiver.clone(), + args: TestArgs::new_relay(destination.clone(), receiver.clone(), amount_to_send), + }; + let mut test = RelayToParaThroughAHTest::new(test_args); + + let sov_penpal_on_ah = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + // Query initial balances + let sender_balance_before = test.sender.balance; + let sov_penpal_on_ah_before = AssetHubWestend::execute_with(|| { + ::Balances::free_balance(sov_penpal_on_ah.clone()) + }); + let receiver_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.clone(), &receiver) + }); + + fn relay_assertions(t: RelayToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + Westend::assert_xcm_pallet_attempted_complete(None); + assert_expected_events!( + Westend, + vec![ + // Amount to teleport is withdrawn from Sender + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + // Amount to teleport is deposited in Relay's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + ] + ); + } + fn asset_hub_assertions(_: RelayToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_on_ah = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + assert_expected_events!( + AssetHubWestend, + vec![ + // Deposited to receiver parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Minted { who, .. } + ) => { + who: *who == sov_penpal_on_ah, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + } + fn penpal_assertions(t: RelayToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let expected_id = + t.args.assets.into_inner().first().unwrap().id.0.clone().try_into().unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + }, + ] + ); + } + fn transfer_assets_dispatchable(t: RelayToParaThroughAHTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let asset_hub_location = Westend::child_location_of(AssetHubWestend::para_id()); + let context = WestendUniversalLocation::get(); + + // reanchor fees to the view of destination (Penpal) + let mut remote_fees = fee.clone().reanchored(&t.args.dest, &context).unwrap(); + if let Fungible(ref mut amount) = remote_fees.fun { + // we already spent some fees along the way, just use half of what we started with + *amount = *amount / 2; + } + let xcm_on_final_dest = Xcm::<()>(vec![ + BuyExecution { fees: remote_fees, weight_limit: t.args.weight_limit.clone() }, + DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }, + ]); + + // reanchor final dest (Penpal) to the view of hop (Asset Hub) + let mut dest = t.args.dest.clone(); + dest.reanchor(&asset_hub_location, &context).unwrap(); + // on Asset Hub, forward assets to Penpal + let xcm_on_hop = Xcm::<()>(vec![DepositReserveAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + dest, + xcm: xcm_on_final_dest, + }]); + + // First leg is a teleport, from there a local-reserve-transfer to final dest + ::XcmPallet::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(asset_hub_location.into()), + bx!(t.args.assets.into()), + bx!(TransferType::Teleport), + bx!(fee.id.into()), + bx!(TransferType::Teleport), + bx!(VersionedXcm::from(xcm_on_hop)), + t.args.weight_limit, + ) + } + + // Set assertions and dispatchables + test.set_assertion::(relay_assertions); + test.set_assertion::(asset_hub_assertions); + test.set_assertion::(penpal_assertions); + test.set_dispatchable::(transfer_assets_dispatchable); + test.assert(); + + // Query final balances + let sender_balance_after = test.sender.balance; + let sov_penpal_on_ah_after = AssetHubWestend::execute_with(|| { + ::Balances::free_balance(sov_penpal_on_ah) + }); + let receiver_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_balance_after < sender_balance_before - amount_to_send); + // SA on AH balance is increased + assert!(sov_penpal_on_ah_after > sov_penpal_on_ah_before); + // Receiver's asset balance is increased + assert!(receiver_assets_after > receiver_assets_before); + // Receiver's asset balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_assets_after < receiver_assets_before + amount_to_send); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs index 3cd7c9c46d69edd738f067724485824ef51d3259..bf013697b4c750c8a9f3da3732e97386ba1cae74 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs @@ -14,6 +14,7 @@ // limitations under the License. mod fellowship_treasury; +mod hybrid_transfers; mod reserve_transfer; mod send; mod set_xcm_versions; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index a26dfef8e8e702ee3f22870116adc03ee8ed1ca2..65d013a0eec40aa90b42a475f40dca9197f318e9 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -47,7 +47,7 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) { RuntimeEvent::ForeignAssets( pallet_assets::Event::Burned { asset_id, owner, balance, .. } ) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + asset_id: *asset_id == RelayLocation::get(), owner: *owner == t.sender.account_id, balance: *balance == t.args.amount, }, @@ -55,70 +55,92 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) { ); } -fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { +pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; - - AssetHubWestend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( - 864_610_000, - 8_799, - ))); - + AssetHubWestend::assert_xcm_pallet_attempted_complete(None); + + let sov_acc_of_dest = AssetHubWestend::sovereign_account_id_of(t.args.dest.clone()); + for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + if idx == t.args.fee_asset_item as usize { + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount of native asset is transferred to Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Transfer { from, to, amount } + ) => { + from: *from == t.sender.account_id, + to: *to == sov_acc_of_dest, + amount: *amount == asset_amount, + }, + ] + ); + } else { + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount of foreign asset is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Transferred { asset_id, from, to, amount }, + ) => { + asset_id: *asset_id == expected_id, + from: *from == t.sender.account_id, + to: *to == sov_acc_of_dest, + amount: *amount == asset_amount, + }, + ] + ); + } + } assert_expected_events!( AssetHubWestend, vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account - RuntimeEvent::Balances( - pallet_balances::Event::Transfer { from, to, amount } - ) => { - from: *from == t.sender.account_id, - to: *to == AssetHubWestend::sovereign_account_id_of( - t.args.dest.clone() - ), - amount: *amount == t.args.amount, - }, // Transport fees are paid - RuntimeEvent::PolkadotXcm( - pallet_xcm::Event::FeesPaid { .. } - ) => {}, + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {}, ] ); AssetHubWestend::assert_xcm_pallet_sent(); } -fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) { +pub fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); PenpalA::assert_xcmp_queue_success(None); - - assert_expected_events!( - PenpalA, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == system_para_native_asset_location, - owner: *owner == t.receiver.account_id, - }, - ] - ); + for asset in t.args.assets.into_inner().into_iter() { + let expected_id = asset.id.0.try_into().unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + }, + ] + ); + } } -fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { +pub fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799))); - assert_expected_events!( - PenpalA, - vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account - RuntimeEvent::ForeignAssets( - pallet_assets::Event::Burned { asset_id, owner, balance, .. } - ) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), - owner: *owner == t.sender.account_id, - balance: *balance == t.args.amount, - }, - ] - ); + PenpalA::assert_xcm_pallet_attempted_complete(None); + for asset in t.args.assets.into_inner().into_iter() { + let expected_id = asset.id.0; + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance } + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.sender.account_id, + balance: *balance == asset_amount, + }, + ] + ); + } } fn para_to_relay_receiver_assertions(t: ParaToRelayTest) { @@ -150,25 +172,57 @@ fn para_to_relay_receiver_assertions(t: ParaToRelayTest) { ); } -fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { +pub fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of( - AssetHubWestend::sibling_location_of(PenpalA::para_id()), - ); - AssetHubWestend::assert_xcmp_queue_success(None); + let sov_acc_of_penpal = AssetHubWestend::sovereign_account_id_of(t.args.dest.clone()); + for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + if idx == t.args.fee_asset_item as usize { + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount of native is withdrawn from Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_acc_of_penpal.clone().into(), + amount: *amount == asset_amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); + } else { + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount of foreign asset is transferred from Parachain's Sovereign account + // to Receiver's account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance }, + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == sov_acc_of_penpal, + balance: *balance == asset_amount, + }, + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Issued { asset_id, owner, amount }, + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + amount: *amount == asset_amount, + }, + ] + ); + } + } assert_expected_events!( AssetHubWestend, vec![ - // Amount to reserve transfer is withdrawn from Parachain's Sovereign account - RuntimeEvent::Balances( - pallet_balances::Event::Burned { who, amount } - ) => { - who: *who == sov_penpal_on_ahr.clone().into(), - amount: *amount == t.args.amount, - }, - RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, @@ -212,10 +266,8 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) { fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - let reservable_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); + let reservable_asset_location = PenpalLocalReservableFromAssetHub::get(); PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8799))); assert_expected_events!( PenpalA, @@ -245,14 +297,13 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { fn system_para_to_para_assets_receiver_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + let system_para_asset_location = PenpalLocalReservableFromAssetHub::get(); PenpalA::assert_xcmp_queue_success(None); assert_expected_events!( PenpalA, vec![ RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + asset_id: *asset_id == RelayLocation::get(), owner: *owner == t.receiver.account_id, }, RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { @@ -304,7 +355,7 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) { PenpalA, vec![ RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + asset_id: *asset_id == RelayLocation::get(), owner: *owner == t.receiver.account_id, }, RuntimeEvent::MessageQueue( @@ -314,29 +365,27 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) { ); } -fn para_to_para_through_relay_sender_assertions(t: ParaToParaThroughRelayTest) { +pub fn para_to_para_through_hop_sender_assertions(t: Test) { type RuntimeEvent = ::RuntimeEvent; - - let relay_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - PenpalA::assert_xcm_pallet_attempted_complete(None); - // XCM sent to relay reserve - PenpalA::assert_parachain_system_ump_sent(); - assert_expected_events!( - PenpalA, - vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account - RuntimeEvent::ForeignAssets( - pallet_assets::Event::Burned { asset_id, owner, balance }, - ) => { - asset_id: *asset_id == relay_asset_location, - owner: *owner == t.sender.account_id, - balance: *balance == t.args.amount, - }, - ] - ); + for asset in t.args.assets.into_inner() { + let expected_id = asset.id.0.clone().try_into().unwrap(); + let amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap(); + assert_expected_events!( + PenpalA, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance }, + ) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.sender.account_id, + balance: *balance == amount, + }, + ] + ); + } } fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) { @@ -369,22 +418,22 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) { ); } -fn para_to_para_through_relay_receiver_assertions(t: ParaToParaThroughRelayTest) { +pub fn para_to_para_through_hop_receiver_assertions(t: Test) { type RuntimeEvent = ::RuntimeEvent; - let relay_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); PenpalB::assert_xcmp_queue_success(None); - - assert_expected_events!( - PenpalB, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == relay_asset_location, - owner: *owner == t.receiver.account_id, - }, - ] - ); + for asset in t.args.assets.into_inner().into_iter() { + let expected_id = asset.id.0.try_into().unwrap(); + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + }, + ] + ); + } } fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { @@ -525,9 +574,8 @@ fn reserve_transfer_native_asset_from_relay_to_para() { let sender = WestendSender::get(); let amount_to_send: Balance = WESTEND_ED * 1000; - // Init values fot Parachain - let relay_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + // Init values for Parachain + let relay_native_asset_location = RelayLocation::get(); let receiver = PenpalAReceiver::get(); // Init Test @@ -542,7 +590,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { let sender_balance_before = test.sender.balance; let receiver_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location.into(), &receiver) + >::balance(relay_native_asset_location.clone(), &receiver) }); // Set assertions and dispatchables @@ -555,7 +603,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { let sender_balance_after = test.sender.balance; let receiver_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location.into(), &receiver) + >::balance(relay_native_asset_location, &receiver) }); // Sender's balance is reduced by amount sent plus delivery fees @@ -577,13 +625,12 @@ fn reserve_transfer_native_asset_from_para_to_relay() { let amount_to_send: Balance = WESTEND_ED * 1000; let assets: Assets = (Parent, amount_to_send).into(); let asset_owner = PenpalAssetOwner::get(); - let relay_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let relay_native_asset_location = RelayLocation::get(); // fund Parachain's sender account PenpalA::mint_foreign_asset( ::RuntimeOrigin::signed(asset_owner), - relay_native_asset_location, + relay_native_asset_location.clone(), sender.clone(), amount_to_send * 2, ); @@ -614,7 +661,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() { // Query initial balances let sender_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location.into(), &sender) + >::balance(relay_native_asset_location.clone(), &sender) }); let receiver_balance_before = test.receiver.balance; @@ -627,7 +674,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() { // Query final balances let sender_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location.into(), &sender) + >::balance(relay_native_asset_location, &sender) }); let receiver_balance_after = test.receiver.balance; @@ -654,8 +701,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { let assets: Assets = (Parent, amount_to_send).into(); // Init values for Parachain - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); let receiver = PenpalAReceiver::get(); // Init Test @@ -677,7 +723,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { let sender_balance_before = test.sender.balance; let receiver_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location.into(), &receiver) + >::balance(system_para_native_asset_location.clone(), &receiver) }); // Set assertions and dispatchables @@ -711,14 +757,13 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { let sender = PenpalASender::get(); let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; let assets: Assets = (Parent, amount_to_send).into(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); let asset_owner = PenpalAssetOwner::get(); // fund Parachain's sender account PenpalA::mint_foreign_asset( ::RuntimeOrigin::signed(asset_owner), - system_para_native_asset_location, + system_para_native_asset_location.clone(), sender.clone(), amount_to_send * 2, ); @@ -750,7 +795,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { // Query initial balances let sender_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location, &sender) + >::balance(system_para_native_asset_location.clone(), &sender) }); let receiver_balance_before = test.receiver.balance; @@ -818,10 +863,8 @@ fn reserve_transfer_assets_from_system_para_to_para() { // Init values for Parachain let receiver = PenpalAReceiver::get(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - let system_para_foreign_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); + let system_para_foreign_asset_location = PenpalLocalReservableFromAssetHub::get(); // Init Test let para_test_args = TestContext { @@ -846,11 +889,14 @@ fn reserve_transfer_assets_from_system_para_to_para() { }); let receiver_system_native_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location, &receiver) + >::balance(system_para_native_asset_location.clone(), &receiver) }); let receiver_foreign_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location, &receiver) + >::balance( + system_para_foreign_asset_location.clone(), + &receiver, + ) }); // Set assertions and dispatchables @@ -905,14 +951,11 @@ fn reserve_transfer_assets_from_para_to_system_para() { let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100; let penpal_asset_owner = PenpalAssetOwner::get(); let penpal_asset_owner_signer = ::RuntimeOrigin::signed(penpal_asset_owner); - let asset_location_on_penpal = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); - let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap(); - let system_asset_location_on_penpal = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let asset_location_on_penpal = PenpalLocalReservableFromAssetHub::get(); + let system_asset_location_on_penpal = RelayLocation::get(); let assets: Assets = vec![ (Parent, fee_amount_to_send).into(), - (asset_location_on_penpal_latest, asset_amount_to_send).into(), + (asset_location_on_penpal.clone(), asset_amount_to_send).into(), ] .into(); let fee_asset_index = assets @@ -940,10 +983,8 @@ fn reserve_transfer_assets_from_para_to_system_para() { let penpal_location_as_seen_by_ahr = AssetHubWestend::sibling_location_of(PenpalA::para_id()); let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(penpal_location_as_seen_by_ahr); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - let system_para_foreign_asset_location = - v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); + let system_para_foreign_asset_location = PenpalLocalReservableFromAssetHub::get(); let ah_asset_owner = AssetHubWestendAssetOwner::get(); let ah_asset_owner_signer = ::RuntimeOrigin::signed(ah_asset_owner); @@ -978,11 +1019,11 @@ fn reserve_transfer_assets_from_para_to_system_para() { // Query initial balances let sender_system_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location, &sender) + >::balance(system_para_native_asset_location.clone(), &sender) }); let sender_foreign_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location, &sender) + >::balance(system_para_foreign_asset_location.clone(), &sender) }); let receiver_balance_before = test.receiver.balance; let receiver_assets_before = AssetHubWestend::execute_with(|| { @@ -1031,22 +1072,21 @@ fn reserve_transfer_assets_from_para_to_system_para() { /// Reserve Transfers of native asset from Parachain to Parachain (through Relay reserve) should /// work #[test] -fn reserve_transfer_native_asset_from_para_to_para_trough_relay() { +fn reserve_transfer_native_asset_from_para_to_para_through_relay() { // Init values for Parachain Origin let destination = PenpalA::sibling_location_of(PenpalB::para_id()); let sender = PenpalASender::get(); let amount_to_send: Balance = WESTEND_ED * 10000; let asset_owner = PenpalAssetOwner::get(); let assets = (Parent, amount_to_send).into(); - let relay_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let relay_native_asset_location = RelayLocation::get(); let sender_as_seen_by_relay = Westend::child_location_of(PenpalA::para_id()); let sov_of_sender_on_relay = Westend::sovereign_account_id_of(sender_as_seen_by_relay); // fund Parachain's sender account PenpalA::mint_foreign_asset( ::RuntimeOrigin::signed(asset_owner), - relay_native_asset_location, + relay_native_asset_location.clone(), sender.clone(), amount_to_send * 2, ); @@ -1068,24 +1108,24 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() { // Query initial balances let sender_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location, &sender) + >::balance(relay_native_asset_location.clone(), &sender) }); let receiver_assets_before = PenpalB::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location, &receiver) + >::balance(relay_native_asset_location.clone(), &receiver) }); // Set assertions and dispatchables - test.set_assertion::(para_to_para_through_relay_sender_assertions); + test.set_assertion::(para_to_para_through_hop_sender_assertions); test.set_assertion::(para_to_para_relay_hop_assertions); - test.set_assertion::(para_to_para_through_relay_receiver_assertions); + test.set_assertion::(para_to_para_through_hop_receiver_assertions); test.set_dispatchable::(para_to_para_through_relay_limited_reserve_transfer_assets); test.assert(); // Query final balances let sender_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(relay_native_asset_location, &sender) + >::balance(relay_native_asset_location.clone(), &sender) }); let receiver_assets_after = PenpalB::execute_with(|| { type ForeignAssets = ::ForeignAssets; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/send.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/send.rs index f218b539c387988f70314eff41c2e1ce4e97092b..eb0e985cc0ce6f84318f763cb37e707beaeca718 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/send.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/send.rs @@ -75,10 +75,10 @@ fn send_xcm_from_para_to_system_para_paying_fee_with_system_assets_works() { )]); PenpalA::execute_with(|| { - assert_ok!(::PolkadotXcm::send_blob( + assert_ok!(::PolkadotXcm::send( root_origin, bx!(system_para_destination), - xcm.encode().try_into().unwrap(), + bx!(xcm), )); PenpalA::assert_xcm_pallet_sent(); @@ -159,10 +159,10 @@ fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() { )]); PenpalA::execute_with(|| { - assert_ok!(::PolkadotXcm::send_blob( + assert_ok!(::PolkadotXcm::send( root_origin, bx!(system_para_destination), - xcm.encode().try_into().unwrap(), + bx!(xcm), )); PenpalA::assert_xcm_pallet_sent(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs index aa673c03483af13cae2ac146049399644b265b6b..f6b6580988658f5fadead0250a6bcc886c9f125d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs @@ -17,7 +17,10 @@ use crate::imports::*; #[test] fn swap_locally_on_chain_using_local_assets() { - let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocationV3::get()); + let asset_native = Box::new( + v3::Location::try_from(asset_hub_westend_runtime::xcm_config::WestendLocation::get()) + .expect("conversion works"), + ); let asset_one = Box::new(v3::Location { parents: 0, interior: [ @@ -111,8 +114,7 @@ fn swap_locally_on_chain_using_local_assets() { #[test] fn swap_locally_on_chain_using_foreign_assets() { - let asset_native = - Box::new(v3::Location::try_from(RelayLocation::get()).expect("conversion works")); + let asset_native = Box::new(v3::Location::try_from(RelayLocation::get()).unwrap()); let asset_location_on_penpal = v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion_works"); let foreign_asset_at_asset_hub_westend = @@ -227,11 +229,9 @@ fn swap_locally_on_chain_using_foreign_assets() { #[test] fn cannot_create_pool_from_pool_assets() { - let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocationV3::get()); - let mut asset_one = asset_hub_westend_runtime::xcm_config::PoolAssetsPalletLocationV3::get(); - asset_one - .append_with(v3::Junction::GeneralIndex(ASSET_ID.into())) - .expect("pool assets"); + let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocation::get(); + let mut asset_one = asset_hub_westend_runtime::xcm_config::PoolAssetsPalletLocation::get(); + asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets"); AssetHubWestend::execute_with(|| { let pool_owner_account_id = asset_hub_westend_runtime::AssetConversionOrigin::get(); @@ -254,8 +254,8 @@ fn cannot_create_pool_from_pool_assets() { assert_matches::assert_matches!( ::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native, - Box::new(asset_one), + Box::new(v3::Location::try_from(asset_native).expect("conversion works")), + Box::new(v3::Location::try_from(asset_one).expect("conversion works")), ), Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("Unknown")) ); @@ -264,7 +264,9 @@ fn cannot_create_pool_from_pool_assets() { #[test] fn pay_xcm_fee_with_some_asset_swapped_for_native() { - let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocationV3::get(); + let asset_native = + v3::Location::try_from(asset_hub_westend_runtime::xcm_config::WestendLocation::get()) + .expect("conversion works"); let asset_one = xcm::v3::Location { parents: 0, interior: [ @@ -369,10 +371,10 @@ fn pay_xcm_fee_with_some_asset_swapped_for_native() { penpal.clone(), ); - assert_ok!(::PolkadotXcm::send_blob( + assert_ok!(::PolkadotXcm::send( penpal_root, bx!(asset_hub_location), - xcm.encode().try_into().unwrap(), + bx!(xcm), )); PenpalA::assert_xcm_pallet_sent(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs index ac518d2ed4a445836364a23df313b319b8193e78..a524b87b2daf3a1352af1ea33b64282c2f4a8137 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs @@ -110,8 +110,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); let expected_asset_id = t.args.asset_id.unwrap(); let (_, expected_asset_amount) = non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); @@ -204,8 +203,7 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { let (_, expected_asset_amount) = non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); let checking_account = ::PolkadotXcm::check_account(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); PenpalA::assert_xcmp_queue_success(None); @@ -414,29 +412,28 @@ fn teleport_to_other_system_parachains_works() { ); } -/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work -/// (using native reserve-based transfer for fees) -#[test] -fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { +/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets while paying +/// fees using (reserve transferred) native asset. +pub fn do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( + para_to_ah_dispatchable: fn(ParaToSystemParaTest) -> DispatchResult, + ah_to_para_dispatchable: fn(SystemParaToParaTest) -> DispatchResult, +) { // Init values for Parachain let fee_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 100; - let asset_location_on_penpal = - v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works"); + let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); let asset_id_on_penpal = match asset_location_on_penpal.last() { - Some(v3::Junction::GeneralIndex(id)) => *id as u32, + Some(Junction::GeneralIndex(id)) => *id as u32, _ => unreachable!(), }; let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100; let asset_owner = PenpalAssetOwner::get(); - let system_para_native_asset_location = - v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_native_asset_location = RelayLocation::get(); let sender = PenpalASender::get(); let penpal_check_account = ::PolkadotXcm::check_account(); let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubWestend::para_id()); - let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap(); let penpal_assets: Assets = vec![ (Parent, fee_amount_to_send).into(), - (asset_location_on_penpal_latest, asset_amount_to_send).into(), + (asset_location_on_penpal.clone(), asset_amount_to_send).into(), ] .into(); let fee_asset_index = penpal_assets @@ -448,7 +445,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { // fund Parachain's sender account PenpalA::mint_foreign_asset( ::RuntimeOrigin::signed(asset_owner.clone()), - system_para_native_asset_location, + system_para_native_asset_location.clone(), sender.clone(), fee_amount_to_send * 2, ); @@ -475,7 +472,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { // Init values for System Parachain let foreign_asset_at_asset_hub_westend = - v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) + Location::new(1, [Junction::Parachain(PenpalA::para_id().into())]) .appended_with(asset_location_on_penpal) .unwrap(); let penpal_to_ah_beneficiary_id = AssetHubWestendReceiver::get(); @@ -497,7 +494,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { let penpal_sender_balance_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; >::balance( - system_para_native_asset_location, + system_para_native_asset_location.clone(), &PenpalASender::get(), ) }); @@ -511,20 +508,20 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { let ah_receiver_assets_before = AssetHubWestend::execute_with(|| { type Assets = ::ForeignAssets; >::balance( - foreign_asset_at_asset_hub_westend, + foreign_asset_at_asset_hub_westend.clone().try_into().unwrap(), &AssetHubWestendReceiver::get(), ) }); penpal_to_ah.set_assertion::(penpal_to_ah_foreign_assets_sender_assertions); penpal_to_ah.set_assertion::(penpal_to_ah_foreign_assets_receiver_assertions); - penpal_to_ah.set_dispatchable::(para_to_system_para_transfer_assets); + penpal_to_ah.set_dispatchable::(para_to_ah_dispatchable); penpal_to_ah.assert(); let penpal_sender_balance_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; >::balance( - system_para_native_asset_location, + system_para_native_asset_location.clone(), &PenpalASender::get(), ) }); @@ -538,7 +535,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { let ah_receiver_assets_after = AssetHubWestend::execute_with(|| { type Assets = ::ForeignAssets; >::balance( - foreign_asset_at_asset_hub_westend, + foreign_asset_at_asset_hub_westend.clone().try_into().unwrap(), &AssetHubWestendReceiver::get(), ) }); @@ -566,19 +563,17 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { type ForeignAssets = ::ForeignAssets; assert_ok!(ForeignAssets::transfer( ::RuntimeOrigin::signed(AssetHubWestendReceiver::get()), - foreign_asset_at_asset_hub_westend, + foreign_asset_at_asset_hub_westend.clone().try_into().unwrap(), AssetHubWestendSender::get().into(), asset_amount_to_send, )); }); - let foreign_asset_at_asset_hub_westend_latest: Location = - foreign_asset_at_asset_hub_westend.try_into().unwrap(); let ah_to_penpal_beneficiary_id = PenpalAReceiver::get(); let penpal_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalA::para_id()); let ah_assets: Assets = vec![ (Parent, fee_amount_to_send).into(), - (foreign_asset_at_asset_hub_westend_latest, asset_amount_to_send).into(), + (foreign_asset_at_asset_hub_westend.clone(), asset_amount_to_send).into(), ] .into(); let fee_asset_index = ah_assets @@ -606,7 +601,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { let penpal_receiver_balance_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; >::balance( - system_para_native_asset_location, + system_para_native_asset_location.clone(), &PenpalAReceiver::get(), ) }); @@ -614,7 +609,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { let ah_sender_assets_before = AssetHubWestend::execute_with(|| { type ForeignAssets = ::ForeignAssets; >::balance( - foreign_asset_at_asset_hub_westend, + foreign_asset_at_asset_hub_westend.clone().try_into().unwrap(), &AssetHubWestendSender::get(), ) }); @@ -625,7 +620,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { ah_to_penpal.set_assertion::(ah_to_penpal_foreign_assets_sender_assertions); ah_to_penpal.set_assertion::(ah_to_penpal_foreign_assets_receiver_assertions); - ah_to_penpal.set_dispatchable::(system_para_to_para_transfer_assets); + ah_to_penpal.set_dispatchable::(ah_to_para_dispatchable); ah_to_penpal.assert(); let ah_sender_balance_after = ah_to_penpal.sender.balance; @@ -640,7 +635,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { let ah_sender_assets_after = AssetHubWestend::execute_with(|| { type ForeignAssets = ::ForeignAssets; >::balance( - foreign_asset_at_asset_hub_westend, + foreign_asset_at_asset_hub_westend.clone().try_into().unwrap(), &AssetHubWestendSender::get(), ) }); @@ -663,3 +658,13 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { // Receiver's balance is increased by exact amount assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send); } + +/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work +/// (using native reserve-based transfer for fees) +#[test] +fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { + do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( + para_to_system_para_transfer_assets, + system_para_to_para_transfer_assets, + ); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs index b5e19cf3fa3a252abe28b31229c876d104b6a8e9..0415af580ef8add90c92620e93052e356abe2de9 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -25,6 +25,7 @@ mod imports { prelude::{AccountId32 as AccountId32Junction, *}, v3::{self, NetworkId::Westend as WestendId}, }; + pub use xcm_executor::traits::TransferType; // Cumulus pub use emulated_integration_tests_common::{ @@ -46,7 +47,7 @@ mod imports { bridge_hub_rococo_emulated_chain::{ genesis::ED as BRIDGE_HUB_ROCOCO_ED, BridgeHubRococoParaPallet as BridgeHubRococoPallet, }, - penpal_emulated_chain::PenpalAParaPallet as PenpalAPallet, + penpal_emulated_chain::{PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner}, rococo_emulated_chain::{genesis::ED as ROCOCO_ED, RococoRelayPallet as RococoPallet}, AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver, AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs index 787a82ed32f7376f1c94c584711098c15d4da198..87fb70e4de23857bf929ed1a663fd2dcc3120e93 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -31,6 +31,77 @@ fn send_asset_from_asset_hub_rococo_to_asset_hub_westend(id: Location, amount: u assert_bridge_hub_westend_message_received(); } +fn send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub( + id: Location, + transfer_amount: u128, +) { + let destination = asset_hub_westend_location(); + let local_asset_hub: Location = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + ); + let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( + Westend, + AssetHubWestend::para_id(), + ); + + // fund the AHR's SA on BHR for paying bridge transport fees + BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128); + + // set XCM versions + PenpalA::force_xcm_version(local_asset_hub.clone(), XCM_VERSION); + AssetHubRococo::force_xcm_version(destination.clone(), XCM_VERSION); + BridgeHubRococo::force_xcm_version(bridge_hub_westend_location(), XCM_VERSION); + + // send message over bridge + assert_ok!(PenpalA::execute_with(|| { + let signed_origin = ::RuntimeOrigin::signed(PenpalASender::get()); + let beneficiary: Location = + AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into(); + let assets: Assets = (id.clone(), transfer_amount).into(); + let fees_id: AssetId = id.into(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(assets.len() as u32)), + beneficiary, + }]); + + ::PolkadotXcm::transfer_assets_using_type_and_then( + signed_origin, + bx!(destination.into()), + bx!(assets.clone().into()), + bx!(TransferType::RemoteReserve(local_asset_hub.clone().into())), + bx!(fees_id.into()), + bx!(TransferType::RemoteReserve(local_asset_hub.into())), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + WeightLimit::Unlimited, + ) + })); + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount to reserve transfer is withdrawn from Penpal's sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_on_ahr.clone().into(), + amount: *amount == transfer_amount, + }, + // Amount deposited in AHW's sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == sov_ahw_on_ahr.clone().into(), + }, + RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + ) => {}, + ] + ); + }); + assert_bridge_hub_rococo_message_accepted(true); + assert_bridge_hub_westend_message_received(); +} + #[test] fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() { let roc_at_asset_hub_rococo: v3::Location = v3::Parent.into(); @@ -45,7 +116,7 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() { vec![], ); let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Westend, + Westend, AssetHubWestend::para_id(), ); @@ -101,9 +172,11 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() { >::balance(roc_at_asset_hub_westend, &AssetHubWestendReceiver::get()) }); - let roc_at_asset_hub_rococo_latest: Location = roc_at_asset_hub_rococo.try_into().unwrap(); let amount = ASSET_HUB_ROCOCO_ED * 1_000_000; - send_asset_from_asset_hub_rococo_to_asset_hub_westend(roc_at_asset_hub_rococo_latest, amount); + send_asset_from_asset_hub_rococo_to_asset_hub_westend( + roc_at_asset_hub_rococo.try_into().unwrap(), + amount, + ); AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; assert_expected_events!( @@ -135,7 +208,7 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() { assert!(sender_rocs_before > sender_rocs_after); // Receiver's balance is increased assert!(receiver_rocs_after > receiver_rocs_before); - // Reserve balance is reduced by sent amount + // Reserve balance is increased by sent amount assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before + amount); } @@ -144,7 +217,7 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { let prefund_amount = 10_000_000_000_000u128; let wnd_at_asset_hub_rococo = v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]); - let owner: AccountId = AssetHubWestend::account_id_of(ALICE); + let owner: AccountId = AssetHubRococo::account_id_of(ALICE); AssetHubRococo::force_create_foreign_asset( wnd_at_asset_hub_rococo, owner, @@ -155,7 +228,7 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { // fund the AHR's SA on AHW with the WND tokens held in reserve let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Rococo, + Rococo, AssetHubRococo::para_id(), ); AssetHubWestend::fund_accounts(vec![(sov_ahr_on_ahw.clone(), prefund_amount)]); @@ -171,10 +244,9 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { let receiver_wnds_before = ::account_data_of(AssetHubWestendReceiver::get()).free; - let wnd_at_asset_hub_rococo_latest: Location = wnd_at_asset_hub_rococo.try_into().unwrap(); let amount_to_send = ASSET_HUB_WESTEND_ED * 1_000; send_asset_from_asset_hub_rococo_to_asset_hub_westend( - wnd_at_asset_hub_rococo_latest.clone(), + Location::try_from(wnd_at_asset_hub_rococo).unwrap(), amount_to_send, ); AssetHubWestend::execute_with(|| { @@ -217,3 +289,95 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { // Reserve balance is reduced by sent amount assert_eq!(wnds_in_reserve_on_ahw_after, wnds_in_reserve_on_ahw_before - amount_to_send); } + +#[test] +fn send_rocs_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_westend() { + let roc_at_rococo_parachains: Location = Parent.into(); + let roc_at_asset_hub_westend = Location::new(2, [Junction::GlobalConsensus(NetworkId::Rococo)]); + let owner: AccountId = AssetHubWestend::account_id_of(ALICE); + AssetHubWestend::force_create_foreign_asset( + roc_at_asset_hub_westend.clone().try_into().unwrap(), + owner, + true, + ASSET_MIN_BALANCE, + vec![], + ); + let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( + Westend, + AssetHubWestend::para_id(), + ); + + let amount = ASSET_HUB_ROCOCO_ED * 10_000_000; + let penpal_location = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location); + // fund Penpal's sovereign account on AssetHub + AssetHubRococo::fund_accounts(vec![(sov_penpal_on_ahr.into(), amount * 2)]); + // fund Penpal's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + roc_at_rococo_parachains.clone(), + PenpalASender::get(), + amount * 2, + ); + + let rocs_in_reserve_on_ahr_before = + ::account_data_of(sov_ahw_on_ahr.clone()).free; + let sender_rocs_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + roc_at_rococo_parachains.clone(), + &PenpalASender::get(), + ) + }); + let receiver_rocs_before = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + roc_at_asset_hub_westend.clone().try_into().unwrap(), + &AssetHubWestendReceiver::get(), + ) + }); + send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub( + roc_at_rococo_parachains.clone(), + amount, + ); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + // issue ROCs on AHW + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == roc_at_rococo_parachains.clone().try_into().unwrap(), + owner: *owner == AssetHubWestendReceiver::get(), + }, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_rocs_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(roc_at_rococo_parachains, &PenpalASender::get()) + }); + let receiver_rocs_after = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + roc_at_asset_hub_westend.try_into().unwrap(), + &AssetHubWestendReceiver::get(), + ) + }); + let rocs_in_reserve_on_ahr_after = + ::account_data_of(sov_ahw_on_ahr.clone()).free; + + // Sender's balance is reduced + assert!(sender_rocs_after < sender_rocs_before); + // Receiver's balance is increased + assert!(receiver_rocs_after > receiver_rocs_before); + // Reserve balance is increased by sent amount (less fess) + assert!(rocs_in_reserve_on_ahr_after > rocs_in_reserve_on_ahr_before); + assert!(rocs_in_reserve_on_ahr_after <= rocs_in_reserve_on_ahr_before + amount); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs index 4bd041dc03f4216c9eddf811d325e8262873e473..a1d871cdb618fdddfbbbc3e7812d0ec7f7ae7866 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs @@ -14,7 +14,6 @@ // limitations under the License. use crate::tests::*; -use codec::Encode; #[test] fn send_xcm_from_rococo_relay_to_westend_asset_hub_should_fail_on_not_applicable() { @@ -27,7 +26,7 @@ fn send_xcm_from_rococo_relay_to_westend_asset_hub_should_fail_on_not_applicable let remote_xcm = Xcm(vec![ClearOrigin]); - let xcm = VersionedXcm::from(Xcm::<()>(vec![ + let xcm = VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit, check_origin }, ExportMessage { network: WestendId.into(), @@ -39,10 +38,10 @@ fn send_xcm_from_rococo_relay_to_westend_asset_hub_should_fail_on_not_applicable // Rococo Global Consensus // Send XCM message from Relay Chain to Bridge Hub source Parachain Rococo::execute_with(|| { - assert_ok!(::XcmPallet::send_blob( + assert_ok!(::XcmPallet::send( sudo_origin, bx!(destination), - xcm.encode().try_into().unwrap(), + bx!(xcm), )); type RuntimeEvent = ::RuntimeEvent; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index 780ba57f78a18c87c327db2acc5cd27442a0221b..1c1c51404aa48a78d110dbcc2e28fefa01ee94bb 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -27,7 +27,7 @@ use snowbridge_pallet_inbound_queue_fixtures::{ }; use snowbridge_pallet_system; use snowbridge_router_primitives::inbound::{ - Command, GlobalConsensusEthereumConvertsFor, MessageV1, VersionedMessage, + Command, Destination, GlobalConsensusEthereumConvertsFor, MessageV1, VersionedMessage, }; use sp_core::H256; use sp_runtime::{DispatchError::Token, TokenError::FundsUnavailable}; @@ -40,6 +40,7 @@ const TREASURY_ACCOUNT: [u8; 32] = const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const INSUFFICIENT_XCM_FEE: u128 = 1000; +const XCM_FEE: u128 = 4_000_000_000; #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum ControlCall { @@ -82,7 +83,7 @@ fn create_agent() { let create_agent_call = SnowbridgeControl::Control(ControlCall::CreateAgent {}); // Construct XCM to create an agent for para 1001 - let remote_xcm = VersionedXcm::from(Xcm::<()>(vec![ + let remote_xcm = VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, DescendOrigin(Parachain(origin_para).into()), Transact { @@ -95,10 +96,10 @@ fn create_agent() { // Rococo Global Consensus // Send XCM message from Relay Chain to Bridge Hub source Parachain Rococo::execute_with(|| { - assert_ok!(::XcmPallet::send_blob( + assert_ok!(::XcmPallet::send( sudo_origin, bx!(destination), - remote_xcm.encode().try_into().unwrap(), + bx!(remote_xcm), )); type RuntimeEvent = ::RuntimeEvent; @@ -140,7 +141,7 @@ fn create_channel() { let create_agent_call = SnowbridgeControl::Control(ControlCall::CreateAgent {}); // Construct XCM to create an agent for para 1001 - let create_agent_xcm = VersionedXcm::from(Xcm::<()>(vec![ + let create_agent_xcm = VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, DescendOrigin(Parachain(origin_para).into()), Transact { @@ -153,7 +154,7 @@ fn create_channel() { let create_channel_call = SnowbridgeControl::Control(ControlCall::CreateChannel { mode: OperatingMode::Normal }); // Construct XCM to create a channel for para 1001 - let create_channel_xcm = VersionedXcm::from(Xcm::<()>(vec![ + let create_channel_xcm = VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, DescendOrigin(Parachain(origin_para).into()), Transact { @@ -166,16 +167,16 @@ fn create_channel() { // Rococo Global Consensus // Send XCM message from Relay Chain to Bridge Hub source Parachain Rococo::execute_with(|| { - assert_ok!(::XcmPallet::send_blob( + assert_ok!(::XcmPallet::send( sudo_origin.clone(), bx!(destination.clone()), - create_agent_xcm.encode().try_into().unwrap(), + bx!(create_agent_xcm), )); - assert_ok!(::XcmPallet::send_blob( + assert_ok!(::XcmPallet::send( sudo_origin, bx!(destination), - create_channel_xcm.encode().try_into().unwrap(), + bx!(create_channel_xcm), )); type RuntimeEvent = ::RuntimeEvent; @@ -306,8 +307,6 @@ fn send_token_from_ethereum_to_penpal() { // The Weth asset location, identified by the contract address on Ethereum let weth_asset_location: Location = (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); - // Converts the Weth asset location into an asset ID - let weth_asset_id: v3::Location = weth_asset_location.try_into().unwrap(); let origin_location = (Parent, Parent, EthereumNetwork::get()).into(); @@ -321,12 +320,12 @@ fn send_token_from_ethereum_to_penpal() { PenpalA::execute_with(|| { assert_ok!(::ForeignAssets::create( ::RuntimeOrigin::signed(PenpalASender::get()), - weth_asset_id, + weth_asset_location.clone(), asset_hub_sovereign.into(), 1000, )); - assert!(::ForeignAssets::asset_exists(weth_asset_id)); + assert!(::ForeignAssets::asset_exists(weth_asset_location)); }); BridgeHubRococo::execute_with(|| { @@ -381,10 +380,8 @@ fn send_token_from_ethereum_to_penpal() { #[test] fn send_weth_asset_from_asset_hub_to_ethereum() { use asset_hub_rococo_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; - let assethub_sovereign = BridgeHubRococo::sovereign_account_id_of(Location::new( - 1, - [Parachain(AssetHubRococo::para_id().into())], - )); + let assethub_location = BridgeHubRococo::sibling_location_of(AssetHubRococo::para_id()); + let assethub_sovereign = BridgeHubRococo::sovereign_account_id_of(assethub_location); AssetHubRococo::force_default_xcm_version(Some(XCM_VERSION)); BridgeHubRococo::force_default_xcm_version(Some(XCM_VERSION)); @@ -559,3 +556,133 @@ fn register_weth_token_in_asset_hub_fail_for_insufficient_fee() { ); }); } + +fn send_token_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u128) { + let weth_asset_location: Location = Location::new( + 2, + [EthereumNetwork::get().into(), AccountKey20 { network: None, key: WETH }], + ); + // (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }) + // Fund asset hub sovereign on bridge hub + let asset_hub_sovereign = BridgeHubRococo::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubRococo::para_id().into())], + )); + BridgeHubRococo::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + + // Register WETH + AssetHubRococo::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + asset_hub_sovereign.into(), + false, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + weth_asset_location.clone().try_into().unwrap(), + )); + }); + + // Send WETH to an existent account on asset hub + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: account_id }, + amount: 1_000_000, + fee, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubRococo::para_id().into())); + + // Check that the message was sent + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_token_from_ethereum_to_existent_account_on_asset_hub() { + send_token_from_ethereum_to_asset_hub_with_fee(AssetHubRococoSender::get().into(), XCM_FEE); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_token_from_ethereum_to_non_existent_account_on_asset_hub() { + send_token_from_ethereum_to_asset_hub_with_fee([1; 32], XCM_FEE); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_token_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficient_fee() { + send_token_from_ethereum_to_asset_hub_with_fee([1; 32], INSUFFICIENT_XCM_FEE); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the message was not processed successfully due to insufficient fee + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_token_from_ethereum_to_non_existent_account_on_asset_hub_with_sufficient_fee_but_do_not_satisfy_ed( +) { + // On AH the xcm fee is 33_873_024 and the ED is 3_300_000 + send_token_from_ethereum_to_asset_hub_with_fee([1; 32], 36_000_000); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the message was not processed successfully due to insufficient ED + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index 3aa2e2bcbe063c21603e0763eb8e490911177478..6aebf8862d62e794a24e6926ff487d1956ed792b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -11,7 +11,6 @@ publish = false workspace = true [dependencies] -codec = { package = "parity-scale-codec", version = "3.6.0" } # Substrate frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs index 60c31ce5a4aefeb5852f87ddd6804ca267857801..36b846e103131882e36b899bdb323d9b969cddde 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs @@ -26,6 +26,7 @@ mod imports { v3, v4::NetworkId::Rococo as RococoId, }; + pub use xcm_executor::traits::TransferType; // Cumulus pub use emulated_integration_tests_common::{ @@ -48,13 +49,15 @@ mod imports { genesis::ED as BRIDGE_HUB_WESTEND_ED, BridgeHubWestendParaPallet as BridgeHubWestendPallet, }, + penpal_emulated_chain::{PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet}, westend_emulated_chain::WestendRelayPallet as WestendPallet, AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver, AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend, AssetHubWestendParaReceiver as AssetHubWestendReceiver, AssetHubWestendParaSender as AssetHubWestendSender, BridgeHubRococoPara as BridgeHubRococo, BridgeHubWestendPara as BridgeHubWestend, - BridgeHubWestendParaSender as BridgeHubWestendSender, WestendRelay as Westend, + BridgeHubWestendParaSender as BridgeHubWestendSender, PenpalBPara as PenpalB, + PenpalBParaSender as PenpalBSender, WestendRelay as Westend, }; pub const ASSET_MIN_BALANCE: u128 = 1000; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs index 5b0990973d2103f7fa606c4abcccd41a893067d2..597e77d9049cf030fc2b0f0d8e986da53f1f08e2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -30,6 +30,77 @@ fn send_asset_from_asset_hub_westend_to_asset_hub_rococo(id: Location, amount: u assert_bridge_hub_rococo_message_received(); } +fn send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub( + id: Location, + transfer_amount: u128, +) { + let destination = asset_hub_rococo_location(); + let local_asset_hub: Location = PenpalB::sibling_location_of(AssetHubWestend::para_id()); + let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalB::para_id()), + ); + let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( + Rococo, + AssetHubRococo::para_id(), + ); + + // fund the AHW's SA on BHW for paying bridge transport fees + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128); + + // set XCM versions + PenpalB::force_xcm_version(local_asset_hub.clone(), XCM_VERSION); + AssetHubWestend::force_xcm_version(destination.clone(), XCM_VERSION); + BridgeHubWestend::force_xcm_version(bridge_hub_rococo_location(), XCM_VERSION); + + // send message over bridge + assert_ok!(PenpalB::execute_with(|| { + let signed_origin = ::RuntimeOrigin::signed(PenpalBSender::get()); + let beneficiary: Location = + AccountId32Junction { network: None, id: AssetHubRococoReceiver::get().into() }.into(); + let assets: Assets = (id.clone(), transfer_amount).into(); + let fees_id: AssetId = id.into(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(assets.len() as u32)), + beneficiary, + }]); + + ::PolkadotXcm::transfer_assets_using_type_and_then( + signed_origin, + bx!(destination.into()), + bx!(assets.into()), + bx!(TransferType::RemoteReserve(local_asset_hub.clone().into())), + bx!(fees_id.into()), + bx!(TransferType::RemoteReserve(local_asset_hub.into())), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + WeightLimit::Unlimited, + ) + })); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount to reserve transfer is withdrawn from Penpal's sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_on_ahw.clone().into(), + amount: *amount == transfer_amount, + }, + // Amount deposited in AHR's sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == sov_ahr_on_ahw.clone().into(), + }, + RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + ) => {}, + ] + ); + }); + assert_bridge_hub_westend_message_accepted(true); + assert_bridge_hub_rococo_message_received(); +} + #[test] fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { let wnd_at_asset_hub_westend: Location = Parent.into(); @@ -44,7 +115,7 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { vec![], ); let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Rococo, + Rococo, AssetHubRococo::para_id(), ); @@ -153,7 +224,7 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { // fund the AHW's SA on AHR with the ROC tokens held in reserve let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( - NetworkId::Westend, + Westend, AssetHubWestend::para_id(), ); AssetHubRococo::fund_accounts(vec![(sov_ahw_on_ahr.clone(), prefund_amount)]); @@ -169,10 +240,9 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { let receiver_rocs_before = ::account_data_of(AssetHubRococoReceiver::get()).free; - let roc_at_asset_hub_westend_latest: Location = roc_at_asset_hub_westend.try_into().unwrap(); let amount_to_send = ASSET_HUB_ROCOCO_ED * 1_000; send_asset_from_asset_hub_westend_to_asset_hub_rococo( - roc_at_asset_hub_westend_latest.clone(), + roc_at_asset_hub_westend.try_into().unwrap(), amount_to_send, ); AssetHubRococo::execute_with(|| { @@ -215,3 +285,95 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { // Reserve balance is reduced by sent amount assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before - amount_to_send); } + +#[test] +fn send_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() { + let wnd_at_westend_parachains: Location = Parent.into(); + let wnd_at_asset_hub_rococo = Location::new(2, [Junction::GlobalConsensus(NetworkId::Westend)]); + let owner: AccountId = AssetHubRococo::account_id_of(ALICE); + AssetHubRococo::force_create_foreign_asset( + wnd_at_asset_hub_rococo.clone().try_into().unwrap(), + owner, + true, + ASSET_MIN_BALANCE, + vec![], + ); + let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( + Rococo, + AssetHubRococo::para_id(), + ); + + let amount = ASSET_HUB_WESTEND_ED * 10_000_000; + let penpal_location = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_location); + // fund Penpal's sovereign account on AssetHub + AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahw.into(), amount * 2)]); + // fund Penpal's sender account + PenpalB::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + wnd_at_westend_parachains.clone(), + PenpalBSender::get(), + amount * 2, + ); + + let wnds_in_reserve_on_ahw_before = + ::account_data_of(sov_ahr_on_ahw.clone()).free; + let sender_wnds_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + wnd_at_westend_parachains.clone(), + &PenpalBSender::get(), + ) + }); + let receiver_wnds_before = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + wnd_at_asset_hub_rococo.clone().try_into().unwrap(), + &AssetHubRococoReceiver::get(), + ) + }); + send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub( + wnd_at_westend_parachains.clone(), + amount, + ); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubRococo, + vec![ + // issue WNDs on AHR + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == wnd_at_westend_parachains.clone().try_into().unwrap(), + owner: *owner == AssetHubRococoReceiver::get(), + }, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_wnds_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(wnd_at_westend_parachains, &PenpalBSender::get()) + }); + let receiver_wnds_after = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + wnd_at_asset_hub_rococo.try_into().unwrap(), + &AssetHubRococoReceiver::get(), + ) + }); + let wnds_in_reserve_on_ahw_after = + ::account_data_of(sov_ahr_on_ahw.clone()).free; + + // Sender's balance is reduced + assert!(sender_wnds_after < sender_wnds_before); + // Receiver's balance is increased + assert!(receiver_wnds_after > receiver_wnds_before); + // Reserve balance is increased by sent amount (less fess) + assert!(wnds_in_reserve_on_ahw_after > wnds_in_reserve_on_ahw_before); + assert!(wnds_in_reserve_on_ahw_after <= wnds_in_reserve_on_ahw_before + amount); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs index f69747c17704cb47e11ec00e2d8a08a413fab0a4..b01be5e8dc84b4edf35651d0388baa1462b54c9b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs @@ -14,7 +14,6 @@ // limitations under the License. use crate::tests::*; -use codec::Encode; #[test] fn send_xcm_from_westend_relay_to_rococo_asset_hub_should_fail_on_not_applicable() { @@ -27,7 +26,7 @@ fn send_xcm_from_westend_relay_to_rococo_asset_hub_should_fail_on_not_applicable let remote_xcm = Xcm(vec![ClearOrigin]); - let xcm = VersionedXcm::from(Xcm::<()>(vec![ + let xcm = VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit, check_origin }, ExportMessage { network: RococoId, @@ -39,10 +38,10 @@ fn send_xcm_from_westend_relay_to_rococo_asset_hub_should_fail_on_not_applicable // Westend Global Consensus // Send XCM message from Relay Chain to Bridge Hub source Parachain Westend::execute_with(|| { - assert_ok!(::XcmPallet::send_blob( + assert_ok!(::XcmPallet::send( sudo_origin, bx!(destination), - xcm.encode().try_into().unwrap(), + bx!(xcm), )); type RuntimeEvent = ::RuntimeEvent; diff --git a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..d1dbef9fc4156c08b2c52f54a3759de763c959e0 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "collectives-westend-integration-tests" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Collectives Westend runtime integration tests with xcm-emulator" +publish = false + +[lints] +workspace = true + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false } +assert_matches = "1.5.0" + +# Substrate +sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false } +frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false } +pallet-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false } +pallet-asset-rate = { path = "../../../../../../../substrate/frame/asset-rate", default-features = false } +pallet-assets = { path = "../../../../../../../substrate/frame/assets", default-features = false } +pallet-treasury = { path = "../../../../../../../substrate/frame/treasury", default-features = false } +pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue", default-features = false } +pallet-utility = { path = "../../../../../../../substrate/frame/utility", default-features = false } + +# Polkadot +polkadot-runtime-common = { path = "../../../../../../../polkadot/runtime/common" } +xcm = { package = "staging-xcm", path = "../../../../../../../polkadot/xcm", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../../../../polkadot/xcm/xcm-executor", default-features = false } +pallet-xcm = { path = "../../../../../../../polkadot/xcm/pallet-xcm", default-features = false } +westend-runtime = { path = "../../../../../../../polkadot/runtime/westend" } +westend-runtime-constants = { path = "../../../../../../../polkadot/runtime/westend/constants" } + +# Cumulus +parachains-common = { path = "../../../../../../parachains/common" } +testnet-parachains-constants = { path = "../../../../../runtimes/constants", features = ["westend"] } +asset-hub-westend-runtime = { path = "../../../../../runtimes/assets/asset-hub-westend" } +collectives-westend-runtime = { path = "../../../../../runtimes/collectives/collectives-westend" } +cumulus-pallet-xcmp-queue = { default-features = false, path = "../../../../../../pallets/xcmp-queue" } +cumulus-pallet-parachain-system = { default-features = false, path = "../../../../../../pallets/parachain-system" } +emulated-integration-tests-common = { path = "../../../common", default-features = false } +westend-system-emulated-network = { path = "../../../networks/westend-system" } diff --git a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..97239330216ac8f66a7684811d1de30b13f56f7e --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/lib.rs @@ -0,0 +1,30 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub use xcm::{prelude::*, v3}; + +pub use emulated_integration_tests_common::xcm_emulator::{ + assert_expected_events, bx, Chain, RelayChain as Relay, TestExt, +}; +pub use westend_system_emulated_network::{ + asset_hub_westend_emulated_chain::AssetHubWestendParaPallet as AssetHubWestendPallet, + collectives_westend_emulated_chain::CollectivesWestendParaPallet as CollectivesWestendPallet, + westend_emulated_chain::WestendRelayPallet as WestendPallet, + AssetHubWestendPara as AssetHubWestend, CollectivesWestendPara as CollectivesWestend, + WestendRelay as Westend, +}; + +#[cfg(test)] +mod tests; diff --git a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_treasury.rs new file mode 100644 index 0000000000000000000000000000000000000000..bde1220e2495bc544e507be1a8b40d77fcbde894 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_treasury.rs @@ -0,0 +1,236 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use asset_hub_westend_runtime::xcm_config::LocationToAccountId as AssetHubLocationToAccountId; +use emulated_integration_tests_common::accounts::ALICE; +use frame_support::{ + assert_ok, dispatch::RawOrigin, instances::Instance1, sp_runtime::traits::Dispatchable, + traits::fungible::Inspect, +}; +use polkadot_runtime_common::impls::VersionedLocatableAsset; +use westend_runtime::OriginCaller; +use westend_runtime_constants::currency::UNITS; +use xcm_executor::traits::ConvertLocation; + +// Fund Fellowship Treasury from Westend Treasury and spend from Fellowship Treasury. +#[test] +fn fellowship_treasury_spend() { + // initial treasury balance on Asset Hub in WNDs. + let treasury_balance = 20_000_000 * UNITS; + // target fellowship balance on Asset Hub in WNDs. + let fellowship_treasury_balance = 1_000_000 * UNITS; + // fellowship first spend balance in WNDs. + let fellowship_spend_balance = 10_000 * UNITS; + + let init_alice_balance = AssetHubWestend::execute_with(|| { + <::Balances as Inspect<_>>::balance( + &AssetHubWestend::account_id_of(ALICE), + ) + }); + + Westend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeCall = ::RuntimeCall; + type Runtime = ::Runtime; + type Balances = ::Balances; + type Treasury = ::Treasury; + + // Fund Treasury account on Asset Hub with WNDs. + + let root = ::RuntimeOrigin::root(); + let treasury_account = Treasury::account_id(); + + // Mist assets to Treasury account on Relay Chain. + assert_ok!(Balances::force_set_balance( + root.clone(), + treasury_account.clone().into(), + treasury_balance * 2, + )); + + let native_asset = Location::here(); + let asset_hub_location: Location = [Parachain(1000)].into(); + let treasury_location: Location = (Parent, PalletInstance(37)).into(); + + let teleport_call = RuntimeCall::Utility(pallet_utility::Call::::dispatch_as { + as_origin: bx!(OriginCaller::system(RawOrigin::Signed(treasury_account))), + call: bx!(RuntimeCall::XcmPallet(pallet_xcm::Call::::teleport_assets { + dest: bx!(VersionedLocation::V4(asset_hub_location.clone())), + beneficiary: bx!(VersionedLocation::V4(treasury_location)), + assets: bx!(VersionedAssets::V4( + Asset { id: native_asset.clone().into(), fun: treasury_balance.into() }.into() + )), + fee_asset_item: 0, + })), + }); + + // Dispatched from Root to `dispatch_as` `Signed(treasury_account)`. + assert_ok!(teleport_call.dispatch(root)); + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + Westend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeCall = ::RuntimeCall; + type RuntimeOrigin = ::RuntimeOrigin; + type Runtime = ::Runtime; + type Treasury = ::Treasury; + + // Fund Fellowship Treasury from Westend Treasury. + + let treasury_origin: RuntimeOrigin = + westend_runtime::governance::pallet_custom_origins::Origin::Treasurer.into(); + let fellowship_treasury_location: Location = + Location::new(1, [Parachain(1001), PalletInstance(65)]); + let asset_hub_location: Location = [Parachain(1000)].into(); + let native_asset = Location::parent(); + + let treasury_spend_call = RuntimeCall::Treasury(pallet_treasury::Call::::spend { + asset_kind: bx!(VersionedLocatableAsset::V4 { + location: asset_hub_location.clone(), + asset_id: native_asset.into(), + }), + amount: fellowship_treasury_balance, + beneficiary: bx!(VersionedLocation::V4(fellowship_treasury_location)), + valid_from: None, + }); + + assert_ok!(treasury_spend_call.dispatch(treasury_origin)); + + // Claim the spend. + + let alice_signed = RuntimeOrigin::signed(Westend::account_id_of(ALICE)); + assert_ok!(Treasury::payout(alice_signed.clone(), 0)); + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::Treasury(pallet_treasury::Event::AssetSpendApproved { .. }) => {}, + RuntimeEvent::Treasury(pallet_treasury::Event::Paid { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Balances = ::Balances; + + // Ensure that the funds deposited to the Fellowship Treasury account. + + let fellowship_treasury_location: Location = + Location::new(1, [Parachain(1001), PalletInstance(65)]); + let fellowship_treasury_account = + AssetHubLocationToAccountId::convert_location(&fellowship_treasury_location).unwrap(); + + assert_eq!( + >::balance(&fellowship_treasury_account), + fellowship_treasury_balance + ); + + // Assert events triggered by xcm pay program: + // 1. treasury asset transferred to spend beneficiary; + // 2. response to Relay Chain Treasury pallet instance sent back; + // 3. XCM program completed; + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::Balances(pallet_balances::Event::Transfer { .. }) => {}, + RuntimeEvent::ParachainSystem(cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true ,.. }) => {}, + ] + ); + }); + + CollectivesWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeCall = ::RuntimeCall; + type RuntimeOrigin = ::RuntimeOrigin; + type Runtime = ::Runtime; + type FellowshipTreasury = + ::FellowshipTreasury; + + // Fund Alice account from Fellowship Treasury. + + let fellows_origin: RuntimeOrigin = + collectives_westend_runtime::fellowship::pallet_fellowship_origins::Origin::Fellows + .into(); + let asset_hub_location: Location = (Parent, Parachain(1000)).into(); + let native_asset = Location::parent(); + + let alice_location: Location = [Junction::AccountId32 { + network: None, + id: CollectivesWestend::account_id_of(ALICE).into(), + }] + .into(); + + let fellowship_treasury_spend_call = + RuntimeCall::FellowshipTreasury(pallet_treasury::Call::::spend { + asset_kind: bx!(VersionedLocatableAsset::V4 { + location: asset_hub_location, + asset_id: native_asset.into(), + }), + amount: fellowship_spend_balance, + beneficiary: bx!(VersionedLocation::V4(alice_location)), + valid_from: None, + }); + + assert_ok!(fellowship_treasury_spend_call.dispatch(fellows_origin)); + + // Claim the spend. + + let alice_signed = RuntimeOrigin::signed(CollectivesWestend::account_id_of(ALICE)); + assert_ok!(FellowshipTreasury::payout(alice_signed.clone(), 0)); + + assert_expected_events!( + CollectivesWestend, + vec![ + RuntimeEvent::FellowshipTreasury(pallet_treasury::Event::AssetSpendApproved { .. }) => {}, + RuntimeEvent::FellowshipTreasury(pallet_treasury::Event::Paid { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Balances = ::Balances; + + // Ensure that the funds deposited to Alice account. + + let alice_account = AssetHubWestend::account_id_of(ALICE); + assert_eq!( + >::balance(&alice_account), + fellowship_spend_balance + init_alice_balance + ); + + // Assert events triggered by xcm pay program: + // 1. treasury asset transferred to spend beneficiary; + // 2. response to Relay Chain Treasury pallet instance sent back; + // 3. XCM program completed; + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::Balances(pallet_balances::Event::Transfer { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true ,.. }) => {}, + ] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..a9f65df34b647835b4ce5585be6b53b0489de578 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/mod.rs @@ -0,0 +1,16 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod fellowship_treasury; diff --git a/cumulus/parachains/pallets/collective-content/Cargo.toml b/cumulus/parachains/pallets/collective-content/Cargo.toml index b3fac47cb4ae2d78bdf7431d877a4a6edeb34ebb..207259bee52ceb68ba45b1237100cfe5367201f7 100644 --- a/cumulus/parachains/pallets/collective-content/Cargo.toml +++ b/cumulus/parachains/pallets/collective-content/Cargo.toml @@ -2,7 +2,7 @@ name = "pallet-collective-content" version = "0.6.0" authors = ["Parity Technologies "] -edition = "2021" +edition.workspace = true description = "Managed content" license = "Apache-2.0" diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index 0733156716c11a6e1e50c9d921f059a2e2c1b193..47574783810a024caf971adc087313d8336dde73 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -25,6 +25,7 @@ frame-system-rpc-runtime-api = { path = "../../../../../substrate/frame/system/r frame-try-runtime = { path = "../../../../../substrate/frame/try-runtime", default-features = false, optional = true } pallet-asset-conversion-tx-payment = { path = "../../../../../substrate/frame/transaction-payment/asset-conversion-tx-payment", default-features = false } pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false } +pallet-asset-conversion-ops = { path = "../../../../../substrate/frame/asset-conversion/ops", default-features = false } pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false } pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false } pallet-authorship = { path = "../../../../../substrate/frame/authorship", default-features = false } @@ -36,7 +37,7 @@ pallet-nfts = { path = "../../../../../substrate/frame/nfts", default-features = pallet-nfts-runtime-api = { path = "../../../../../substrate/frame/nfts/runtime-api", default-features = false } pallet-proxy = { path = "../../../../../substrate/frame/proxy", default-features = false } pallet-session = { path = "../../../../../substrate/frame/session", default-features = false } -pallet-state-trie-migration = { path = "../../../../../substrate/frame/state-trie-migration", default-features = false, optional = true } +pallet-state-trie-migration = { path = "../../../../../substrate/frame/state-trie-migration", default-features = false } pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } pallet-transaction-payment = { path = "../../../../../substrate/frame/transaction-payment", default-features = false } pallet-transaction-payment-rpc-runtime-api = { path = "../../../../../substrate/frame/transaction-payment/rpc/runtime-api", default-features = false } @@ -101,14 +102,6 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [features] default = ["std"] -# When enabled the `state_version` is set to `1`. -# This means that the chain will start using the new state format. The migration is lazy, so -# it requires to write a storage value to use the new state format. To migrate all the other -# storage values that aren't touched the state migration pallet is added as well. -# This pallet will migrate the entire state, controlled through some account. -# -# This feature should be removed when the main-net will be migrated. -state-trie-version-1 = ["pallet-state-trie-migration"] runtime-benchmarks = [ "assets-common/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", @@ -120,6 +113,7 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", + "pallet-asset-conversion-ops/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", @@ -153,6 +147,7 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "frame-try-runtime/try-runtime", + "pallet-asset-conversion-ops/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-conversion/try-runtime", "pallet-assets/try-runtime", @@ -201,6 +196,7 @@ std = [ "frame-system/std", "frame-try-runtime?/std", "log/std", + "pallet-asset-conversion-ops/std", "pallet-asset-conversion-tx-payment/std", "pallet-asset-conversion/std", "pallet-assets/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/build.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/build.rs index 60f8a125129ff1344a1799246e931acdb1d139d5..239ccac19ec7778039fb1ee56f4e772b3ddd3711 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/build.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/build.rs @@ -15,11 +15,7 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 0ee98e6c22bff9c2db4a6f7d12da6085762b09cc..151734804632e462eb0db30faa9d0606960566ee 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -107,7 +107,6 @@ impl_opaque_keys! { } } -#[cfg(feature = "state-trie-version-1")] #[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("statemine"), @@ -120,19 +119,6 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { state_version: 1, }; -#[cfg(not(feature = "state-trie-version-1"))] -#[sp_version::runtime_version] -pub const VERSION: RuntimeVersion = RuntimeVersion { - spec_name: create_runtime_str!("statemine"), - impl_name: create_runtime_str!("statemine"), - authoring_version: 1, - spec_version: 1_010_000, - impl_version: 0, - apis: RUNTIME_API_VERSIONS, - transaction_version: 14, - state_version: 0, -}; - /// The version information used to identify this runtime when compiled natively. #[cfg(feature = "std")] pub fn native_version() -> NativeVersion { @@ -316,7 +302,7 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< Assets, ForeignAssets, LocalFromLeft< - AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssetsConvert, AssetIdForTrustBackedAssets, xcm::v3::Location, >, @@ -333,6 +319,11 @@ pub type NativeAndAssets = fungible::UnionOf< AccountId, >; +pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< + AssetConversionPalletId, + (xcm::v3::Location, xcm::v3::Location), +>; + impl pallet_asset_conversion::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; @@ -340,8 +331,12 @@ impl pallet_asset_conversion::Config for Runtime { type AssetKind = xcm::v3::Location; type Assets = NativeAndAssets; type PoolId = (Self::AssetKind, Self::AssetKind); - type PoolLocator = - pallet_asset_conversion::WithFirstAsset; + type PoolLocator = pallet_asset_conversion::WithFirstAsset< + TokenLocationV3, + AccountId, + Self::AssetKind, + PoolIdToAccountId, + >; type PoolAssetId = u32; type PoolAssets = PoolAssets; type PoolSetupFee = ConstU128<0>; // Asset class deposit fees are sufficient to prevent spam @@ -362,6 +357,18 @@ impl pallet_asset_conversion::Config for Runtime { >; } +impl pallet_asset_conversion_ops::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed< + ::PoolId, + >; + type AssetsRefund = ::Assets; + type PoolAssetsRefund = ::PoolAssets; + type PoolAssetsTeam = ::PoolAssets; + type DepositAsset = Balances; + type WeightInfo = weights::pallet_asset_conversion_ops::WeightInfo; +} + parameter_types! { // we just reuse the same deposits pub const ForeignAssetsAssetDeposit: Balance = AssetDeposit::get(); @@ -932,8 +939,11 @@ construct_runtime!( PoolAssets: pallet_assets:: = 55, AssetConversion: pallet_asset_conversion = 56, - #[cfg(feature = "state-trie-version-1")] StateTrieMigration: pallet_state_trie_migration = 70, + + // TODO: the pallet instance should be removed once all pools have migrated + // to the new account IDs. + AssetConversionMigration: pallet_asset_conversion_ops = 200, } ); @@ -961,11 +971,12 @@ pub type SignedExtra = ( pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. +#[allow(deprecated)] pub type Migrations = ( - pallet_collator_selection::migration::v1::MigrateToV1, InitStorageVersions, // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + pallet_collator_selection::migration::v2::MigrationToV2, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); @@ -1054,6 +1065,7 @@ mod benches { [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_xcm_bridge_hub_router, ToWestend] + [pallet_asset_conversion_ops, AssetConversionMigration] // XCM [pallet_xcm, PalletXcmExtrinsicsBenchmark::] // NOTE: Make sure you point to the individual modules below. @@ -1507,27 +1519,23 @@ impl_runtime_apis! { fn worst_case_holding(depositable_count: u32) -> xcm::v4::Assets { // A mix of fungible, non-fungible, and concrete assets. let holding_non_fungibles = MaxAssetsIntoHolding::get() / 2 - depositable_count; - let holding_fungibles = holding_non_fungibles.saturating_sub(1); + let holding_fungibles = holding_non_fungibles.saturating_sub(2); // -2 for two `iter::once` bellow let fungibles_amount: u128 = 100; - let mut assets = (0..holding_fungibles) + (0..holding_fungibles) .map(|i| { Asset { id: GeneralIndex(i as u128).into(), - fun: Fungible(fungibles_amount * i as u128), + fun: Fungible(fungibles_amount * (i + 1) as u128), // non-zero amount } }) .chain(core::iter::once(Asset { id: Here.into(), fun: Fungible(u128::MAX) })) + .chain(core::iter::once(Asset { id: AssetId(TokenLocation::get()), fun: Fungible(1_000_000 * UNITS) })) .chain((0..holding_non_fungibles).map(|i| Asset { id: GeneralIndex(i as u128).into(), fun: NonFungible(asset_instance_from(i)), })) - .collect::>(); - - assets.push(Asset { - id: AssetId(TokenLocation::get()), - fun: Fungible(1_000_000 * UNITS), - }); - assets.into() + .collect::>() + .into() } } @@ -1668,7 +1676,6 @@ cumulus_pallet_parachain_system::register_validate_block! { BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, } -#[cfg(feature = "state-trie-version-1")] parameter_types! { // The deposit configuration for the singed migration. Specially if you want to allow any signed account to do the migration (see `SignedFilter`, these deposits should be high) pub const MigrationSignedDepositPerItem: Balance = CENTS; @@ -1676,7 +1683,6 @@ parameter_types! { pub const MigrationMaxKeyLen: u32 = 512; } -#[cfg(feature = "state-trie-version-1")] impl pallet_state_trie_migration::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; @@ -1694,13 +1700,11 @@ impl pallet_state_trie_migration::Config for Runtime { type MaxKeyLen = MigrationMaxKeyLen; } -#[cfg(feature = "state-trie-version-1")] frame_support::ord_parameter_types! { pub const MigController: AccountId = AccountId::from(hex_literal::hex!("8458ed39dc4b6f6c7255f7bc42be50c2967db126357c999d44e12ca7ac80dc52")); pub const RootMigController: AccountId = AccountId::from(hex_literal::hex!("8458ed39dc4b6f6c7255f7bc42be50c2967db126357c999d44e12ca7ac80dc52")); } -#[cfg(feature = "state-trie-version-1")] #[test] fn ensure_key_ss58() { use frame_support::traits::SortedMembers; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs index fa9e86102c619c9ff68316cae2a27a7f79fea2e6..f20790cde39ced67a7d51c8678441ad423ee888f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs @@ -20,6 +20,7 @@ pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; pub mod pallet_asset_conversion; +pub mod pallet_asset_conversion_ops; pub mod pallet_assets_foreign; pub mod pallet_assets_local; pub mod pallet_assets_pool; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_conversion.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_conversion.rs index 0486932d1d6e44a7fe4a1c01640d6e3329577a2c..ec5a4084361f31b195b1acec747dd4e2fd34567a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_conversion.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_conversion.rs @@ -154,4 +154,26 @@ impl pallet_asset_conversion::WeightInfo for WeightInfo .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(Weight::from_parts(0, 393).saturating_mul(n.into())) } + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:2 w:2) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 3]`. + fn touch(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1571` + // Estimated: `6360` + // Minimum execution time: 381_000_000 picoseconds. + Weight::from_parts(398_540_909, 6360) + // Standard Error: 1_330_283 + .saturating_add(Weight::from_parts(209_463_636, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_conversion_ops.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_conversion_ops.rs new file mode 100644 index 0000000000000000000000000000000000000000..e85420d32d9c28b3cc781cab6e5ac92bedc8af13 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_conversion_ops.rs @@ -0,0 +1,71 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_asset_conversion_ops` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-15, STEPS: `10`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `cob`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/debug/polkadot-parachain +// benchmark +// pallet +// --chain=asset-hub-rococo-dev +// --steps=10 +// --repeat=2 +// --pallet=pallet-asset-conversion-ops +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_asset_conversion_ops`. +pub struct WeightInfo(PhantomData); +impl pallet_asset_conversion_ops::WeightInfo for WeightInfo { + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(1224), added: 3699, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Account` (r:2 w:2) + /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:2 w:2) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Asset` (r:1 w:1) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) + fn migrate_to_new_account() -> Weight { + // Proof Size summary in bytes: + // Measured: `1105` + // Estimated: `7404` + // Minimum execution time: 2_323_000_000 picoseconds. + Weight::from_parts(2_404_000_000, 0) + .saturating_add(Weight::from_parts(0, 7404)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(8)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs index e0e231d7da279022293d42aa5e3b3fc1b8ad12d5..51b6543bae82ba496998706ab6c2aaf6e0ff604b 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -64,30 +64,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 21_224_000 picoseconds. - Weight::from_parts(21_821_000, 0) - .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) - /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) - /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn send_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 21_474_000 picoseconds. - Weight::from_parts(22_072_000, 0) + // Minimum execution time: 22_136_000 picoseconds. + Weight::from_parts(22_518_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) @@ -112,8 +90,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 90_677_000 picoseconds. - Weight::from_parts(93_658_000, 0) + // Minimum execution time: 92_277_000 picoseconds. + Weight::from_parts(94_843_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -140,8 +118,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `400` // Estimated: `6196` - // Minimum execution time: 116_767_000 picoseconds. - Weight::from_parts(118_843_000, 0) + // Minimum execution time: 120_110_000 picoseconds. + Weight::from_parts(122_968_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(5)) @@ -170,8 +148,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `496` // Estimated: `6208` - // Minimum execution time: 137_983_000 picoseconds. - Weight::from_parts(141_396_000, 0) + // Minimum execution time: 143_116_000 picoseconds. + Weight::from_parts(147_355_000, 0) .saturating_add(Weight::from_parts(0, 6208)) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(7)) @@ -186,24 +164,14 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn execute_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_232_000 picoseconds. - Weight::from_parts(6_507_000, 0) + // Minimum execution time: 6_517_000 picoseconds. + Weight::from_parts(6_756_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -213,8 +181,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_884_000 picoseconds. - Weight::from_parts(2_016_000, 0) + // Minimum execution time: 1_894_000 picoseconds. + Weight::from_parts(2_024_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -240,8 +208,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 26_637_000 picoseconds. - Weight::from_parts(27_616_000, 0) + // Minimum execution time: 27_314_000 picoseconds. + Weight::from_parts(28_787_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -266,8 +234,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 28_668_000 picoseconds. - Weight::from_parts(29_413_000, 0) + // Minimum execution time: 29_840_000 picoseconds. + Weight::from_parts(30_589_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -278,8 +246,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_990_000 picoseconds. - Weight::from_parts(2_114_000, 0) + // Minimum execution time: 1_893_000 picoseconds. + Weight::from_parts(2_017_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -289,8 +257,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `159` // Estimated: `13524` - // Minimum execution time: 18_856_000 picoseconds. - Weight::from_parts(19_430_000, 0) + // Minimum execution time: 19_211_000 picoseconds. + Weight::from_parts(19_552_000, 0) .saturating_add(Weight::from_parts(0, 13524)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -301,8 +269,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `163` // Estimated: `13528` - // Minimum execution time: 19_068_000 picoseconds. - Weight::from_parts(19_434_000, 0) + // Minimum execution time: 19_177_000 picoseconds. + Weight::from_parts(19_704_000, 0) .saturating_add(Weight::from_parts(0, 13528)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -313,8 +281,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `173` // Estimated: `16013` - // Minimum execution time: 21_055_000 picoseconds. - Weight::from_parts(21_379_000, 0) + // Minimum execution time: 20_449_000 picoseconds. + Weight::from_parts(21_075_000, 0) .saturating_add(Weight::from_parts(0, 16013)) .saturating_add(T::DbWeight::get().reads(6)) } @@ -336,8 +304,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 25_736_000 picoseconds. - Weight::from_parts(26_423_000, 0) + // Minimum execution time: 26_578_000 picoseconds. + Weight::from_parts(27_545_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -348,8 +316,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `206` // Estimated: `11096` - // Minimum execution time: 11_853_000 picoseconds. - Weight::from_parts(12_215_000, 0) + // Minimum execution time: 11_646_000 picoseconds. + Weight::from_parts(11_944_000, 0) .saturating_add(Weight::from_parts(0, 11096)) .saturating_add(T::DbWeight::get().reads(4)) } @@ -359,8 +327,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `170` // Estimated: `13535` - // Minimum execution time: 19_418_000 picoseconds. - Weight::from_parts(19_794_000, 0) + // Minimum execution time: 19_301_000 picoseconds. + Weight::from_parts(19_664_000, 0) .saturating_add(Weight::from_parts(0, 13535)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -383,8 +351,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `212` // Estimated: `13577` - // Minimum execution time: 34_719_000 picoseconds. - Weight::from_parts(35_260_000, 0) + // Minimum execution time: 35_715_000 picoseconds. + Weight::from_parts(36_915_000, 0) .saturating_add(Weight::from_parts(0, 13577)) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(4)) @@ -397,8 +365,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `1588` - // Minimum execution time: 4_937_000 picoseconds. - Weight::from_parts(5_203_000, 0) + // Minimum execution time: 4_871_000 picoseconds. + Weight::from_parts(5_066_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -409,8 +377,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 26_064_000 picoseconds. - Weight::from_parts(26_497_000, 0) + // Minimum execution time: 25_150_000 picoseconds. + Weight::from_parts(26_119_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -421,8 +389,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `160` // Estimated: `3625` - // Minimum execution time: 37_132_000 picoseconds. - Weight::from_parts(37_868_000, 0) + // Minimum execution time: 38_248_000 picoseconds. + Weight::from_parts(39_122_000, 0) .saturating_add(Weight::from_parts(0, 3625)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index 1c58abcb379e6569ecf838a047259c2e3304064e..a73c1cc33ea0b958bf165fa54cd554548562f099 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -49,19 +49,19 @@ use testnet_parachains_constants::rococo::snowbridge::{ }; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FrameTransactionalProcessor, - FungibleAdapter, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, - IsConcrete, LocalMint, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignPaidRemoteExporter, SovereignSignedViaLocation, StartsWith, - StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, + EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, + GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, + NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignPaidRemoteExporter, + SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; -use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; +use xcm_executor::XcmExecutor; parameter_types! { pub const TokenLocation: Location = Location::parent(); @@ -82,8 +82,6 @@ parameter_types! { PalletInstance(::index() as u8).into(); pub UniquesPalletLocation: Location = PalletInstance(::index() as u8).into(); - pub PoolAssetsPalletLocationV3: xcm::v3::Location = - xcm::v3::Junction::PalletInstance(::index() as u8).into(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); pub const GovernanceLocation: Location = Location::parent(); pub StakingPot: AccountId = CollatorSelection::account_id(); @@ -179,6 +177,7 @@ pub type ForeignAssetsConvertedConcreteId = assets_common::ForeignAssetsConverte StartsWithExplicitGlobalConsensus, ), Balance, + xcm::v3::Location, >; /// Means for transacting foreign assets from different global consensus. @@ -264,223 +263,6 @@ impl Contains for ParentOrParentsPlurality { } } -/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly -/// account for proof size weights. -/// -/// Calls that are allowed through this filter must: -/// 1. Have a fixed weight; -/// 2. Cannot lead to another call being made; -/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. -pub struct SafeCallFilter; -impl Contains for SafeCallFilter { - fn contains(call: &RuntimeCall) -> bool { - #[cfg(feature = "runtime-benchmarks")] - { - if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { - return true - } - } - - // Allow to change dedicated storage items (called by governance-like) - match call { - RuntimeCall::System(frame_system::Call::set_storage { items }) - if items.iter().all(|(k, _)| { - k.eq(&bridging::XcmBridgeHubRouterByteFee::key()) || - k.eq(&bridging::XcmBridgeHubRouterBaseFee::key()) || - k.eq(&bridging::to_ethereum::BridgeHubEthereumBaseFee::key()) - }) => - return true, - _ => (), - }; - - matches!( - call, - RuntimeCall::PolkadotXcm( - pallet_xcm::Call::force_xcm_version { .. } | - pallet_xcm::Call::force_default_xcm_version { .. } - ) | RuntimeCall::System( - frame_system::Call::set_heap_pages { .. } | - frame_system::Call::set_code { .. } | - frame_system::Call::set_code_without_checks { .. } | - frame_system::Call::authorize_upgrade { .. } | - frame_system::Call::authorize_upgrade_without_checks { .. } | - frame_system::Call::kill_prefix { .. }, - ) | RuntimeCall::ParachainSystem(..) | - RuntimeCall::Timestamp(..) | - RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection(..) | - RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | - RuntimeCall::XcmpQueue(..) | - RuntimeCall::MessageQueue(..) | - RuntimeCall::Assets( - pallet_assets::Call::create { .. } | - pallet_assets::Call::force_create { .. } | - pallet_assets::Call::start_destroy { .. } | - pallet_assets::Call::destroy_accounts { .. } | - pallet_assets::Call::destroy_approvals { .. } | - pallet_assets::Call::finish_destroy { .. } | - pallet_assets::Call::block { .. } | - pallet_assets::Call::mint { .. } | - pallet_assets::Call::burn { .. } | - pallet_assets::Call::transfer { .. } | - pallet_assets::Call::transfer_keep_alive { .. } | - pallet_assets::Call::force_transfer { .. } | - pallet_assets::Call::freeze { .. } | - pallet_assets::Call::thaw { .. } | - pallet_assets::Call::freeze_asset { .. } | - pallet_assets::Call::thaw_asset { .. } | - pallet_assets::Call::transfer_ownership { .. } | - pallet_assets::Call::set_team { .. } | - pallet_assets::Call::set_metadata { .. } | - pallet_assets::Call::clear_metadata { .. } | - pallet_assets::Call::force_set_metadata { .. } | - pallet_assets::Call::force_clear_metadata { .. } | - pallet_assets::Call::force_asset_status { .. } | - pallet_assets::Call::approve_transfer { .. } | - pallet_assets::Call::cancel_approval { .. } | - pallet_assets::Call::force_cancel_approval { .. } | - pallet_assets::Call::transfer_approved { .. } | - pallet_assets::Call::touch { .. } | - pallet_assets::Call::touch_other { .. } | - pallet_assets::Call::refund { .. } | - pallet_assets::Call::refund_other { .. }, - ) | RuntimeCall::ForeignAssets( - pallet_assets::Call::create { .. } | - pallet_assets::Call::force_create { .. } | - pallet_assets::Call::start_destroy { .. } | - pallet_assets::Call::destroy_accounts { .. } | - pallet_assets::Call::destroy_approvals { .. } | - pallet_assets::Call::finish_destroy { .. } | - pallet_assets::Call::block { .. } | - pallet_assets::Call::mint { .. } | - pallet_assets::Call::burn { .. } | - pallet_assets::Call::transfer { .. } | - pallet_assets::Call::transfer_keep_alive { .. } | - pallet_assets::Call::force_transfer { .. } | - pallet_assets::Call::freeze { .. } | - pallet_assets::Call::thaw { .. } | - pallet_assets::Call::freeze_asset { .. } | - pallet_assets::Call::thaw_asset { .. } | - pallet_assets::Call::transfer_ownership { .. } | - pallet_assets::Call::set_team { .. } | - pallet_assets::Call::set_metadata { .. } | - pallet_assets::Call::clear_metadata { .. } | - pallet_assets::Call::force_set_metadata { .. } | - pallet_assets::Call::force_clear_metadata { .. } | - pallet_assets::Call::force_asset_status { .. } | - pallet_assets::Call::approve_transfer { .. } | - pallet_assets::Call::cancel_approval { .. } | - pallet_assets::Call::force_cancel_approval { .. } | - pallet_assets::Call::transfer_approved { .. } | - pallet_assets::Call::touch { .. } | - pallet_assets::Call::touch_other { .. } | - pallet_assets::Call::refund { .. } | - pallet_assets::Call::refund_other { .. }, - ) | RuntimeCall::PoolAssets( - pallet_assets::Call::force_create { .. } | - pallet_assets::Call::block { .. } | - pallet_assets::Call::burn { .. } | - pallet_assets::Call::transfer { .. } | - pallet_assets::Call::transfer_keep_alive { .. } | - pallet_assets::Call::force_transfer { .. } | - pallet_assets::Call::freeze { .. } | - pallet_assets::Call::thaw { .. } | - pallet_assets::Call::freeze_asset { .. } | - pallet_assets::Call::thaw_asset { .. } | - pallet_assets::Call::transfer_ownership { .. } | - pallet_assets::Call::set_team { .. } | - pallet_assets::Call::set_metadata { .. } | - pallet_assets::Call::clear_metadata { .. } | - pallet_assets::Call::force_set_metadata { .. } | - pallet_assets::Call::force_clear_metadata { .. } | - pallet_assets::Call::force_asset_status { .. } | - pallet_assets::Call::approve_transfer { .. } | - pallet_assets::Call::cancel_approval { .. } | - pallet_assets::Call::force_cancel_approval { .. } | - pallet_assets::Call::transfer_approved { .. } | - pallet_assets::Call::touch { .. } | - pallet_assets::Call::touch_other { .. } | - pallet_assets::Call::refund { .. } | - pallet_assets::Call::refund_other { .. }, - ) | RuntimeCall::AssetConversion( - pallet_asset_conversion::Call::create_pool { .. } | - pallet_asset_conversion::Call::add_liquidity { .. } | - pallet_asset_conversion::Call::remove_liquidity { .. } | - pallet_asset_conversion::Call::swap_tokens_for_exact_tokens { .. } | - pallet_asset_conversion::Call::swap_exact_tokens_for_tokens { .. }, - ) | RuntimeCall::NftFractionalization( - pallet_nft_fractionalization::Call::fractionalize { .. } | - pallet_nft_fractionalization::Call::unify { .. }, - ) | RuntimeCall::Nfts( - pallet_nfts::Call::create { .. } | - pallet_nfts::Call::force_create { .. } | - pallet_nfts::Call::destroy { .. } | - pallet_nfts::Call::mint { .. } | - pallet_nfts::Call::force_mint { .. } | - pallet_nfts::Call::burn { .. } | - pallet_nfts::Call::transfer { .. } | - pallet_nfts::Call::lock_item_transfer { .. } | - pallet_nfts::Call::unlock_item_transfer { .. } | - pallet_nfts::Call::lock_collection { .. } | - pallet_nfts::Call::transfer_ownership { .. } | - pallet_nfts::Call::set_team { .. } | - pallet_nfts::Call::force_collection_owner { .. } | - pallet_nfts::Call::force_collection_config { .. } | - pallet_nfts::Call::approve_transfer { .. } | - pallet_nfts::Call::cancel_approval { .. } | - pallet_nfts::Call::clear_all_transfer_approvals { .. } | - pallet_nfts::Call::lock_item_properties { .. } | - pallet_nfts::Call::set_attribute { .. } | - pallet_nfts::Call::force_set_attribute { .. } | - pallet_nfts::Call::clear_attribute { .. } | - pallet_nfts::Call::approve_item_attributes { .. } | - pallet_nfts::Call::cancel_item_attributes_approval { .. } | - pallet_nfts::Call::set_metadata { .. } | - pallet_nfts::Call::clear_metadata { .. } | - pallet_nfts::Call::set_collection_metadata { .. } | - pallet_nfts::Call::clear_collection_metadata { .. } | - pallet_nfts::Call::set_accept_ownership { .. } | - pallet_nfts::Call::set_collection_max_supply { .. } | - pallet_nfts::Call::update_mint_settings { .. } | - pallet_nfts::Call::set_price { .. } | - pallet_nfts::Call::buy_item { .. } | - pallet_nfts::Call::pay_tips { .. } | - pallet_nfts::Call::create_swap { .. } | - pallet_nfts::Call::cancel_swap { .. } | - pallet_nfts::Call::claim_swap { .. }, - ) | RuntimeCall::Uniques( - pallet_uniques::Call::create { .. } | - pallet_uniques::Call::force_create { .. } | - pallet_uniques::Call::destroy { .. } | - pallet_uniques::Call::mint { .. } | - pallet_uniques::Call::burn { .. } | - pallet_uniques::Call::transfer { .. } | - pallet_uniques::Call::freeze { .. } | - pallet_uniques::Call::thaw { .. } | - pallet_uniques::Call::freeze_collection { .. } | - pallet_uniques::Call::thaw_collection { .. } | - pallet_uniques::Call::transfer_ownership { .. } | - pallet_uniques::Call::set_team { .. } | - pallet_uniques::Call::approve_transfer { .. } | - pallet_uniques::Call::cancel_approval { .. } | - pallet_uniques::Call::force_item_status { .. } | - pallet_uniques::Call::set_attribute { .. } | - pallet_uniques::Call::clear_attribute { .. } | - pallet_uniques::Call::set_metadata { .. } | - pallet_uniques::Call::clear_metadata { .. } | - pallet_uniques::Call::set_collection_metadata { .. } | - pallet_uniques::Call::clear_collection_metadata { .. } | - pallet_uniques::Call::set_accept_ownership { .. } | - pallet_uniques::Call::set_collection_max_supply { .. } | - pallet_uniques::Call::set_price { .. } | - pallet_uniques::Call::buy_item { .. } - ) | RuntimeCall::ToWestendXcmRouter( - pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } - ) - ) - } -} - pub type Barrier = TrailingSetTopicAsId< DenyThenTry< DenyReserveTransferToRelayChain, @@ -503,6 +285,8 @@ pub type Barrier = TrailingSetTopicAsId< )>, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, ), UniversalLocation, ConstU32<8>, @@ -581,7 +365,11 @@ impl xcm_executor::Config for XcmConfig { WeightToFee, crate::NativeAndAssets, ( - TrustBackedAssetsAsLocation, + TrustBackedAssetsAsLocation< + TrustBackedAssetsPalletLocation, + Balance, + xcm::v3::Location, + >, ForeignAssetsConvertedConcreteId, ), ResolveAssetTo, @@ -629,8 +417,8 @@ impl xcm_executor::Config for XcmConfig { type MessageExporter = (); type UniversalAliases = (bridging::to_westend::UniversalAliases, bridging::to_ethereum::UniversalAliases); - type CallDispatcher = WithOriginFilter; - type SafeCallFilter = SafeCallFilter; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; type Aliasers = Nothing; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs index 5fa7455ad2a0b5620a6f6934b87acfe3b85d4f57..f670c5f424efeac0e00ddf472f1948e06d93bd68 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs @@ -22,8 +22,7 @@ use asset_hub_rococo_runtime::{ xcm_config::{ bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf, - LocationToAccountId, StakingPot, TokenLocation, TokenLocationV3, - TrustBackedAssetsPalletLocation, TrustBackedAssetsPalletLocationV3, XcmConfig, + LocationToAccountId, StakingPot, TokenLocation, TrustBackedAssetsPalletLocation, XcmConfig, }, AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, @@ -53,17 +52,14 @@ use sp_std::ops::Mul; use std::convert::Into; use testnet_parachains_constants::rococo::{consensus::*, currency::UNITS, fee::WeightToFee}; use xcm::latest::prelude::{Assets as XcmAssets, *}; -use xcm_builder::V4V3LocationConverter; +use xcm_builder::WithLatestLocationConverter; use xcm_executor::traits::{JustTry, WeightTrader}; const ALICE: [u8; 32] = [1u8; 32]; const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; type AssetIdForTrustBackedAssetsConvert = - assets_common::AssetIdForTrustBackedAssetsConvert; - -type AssetIdForTrustBackedAssetsConvertLatest = - assets_common::AssetIdForTrustBackedAssetsConvertLatest; + assets_common::AssetIdForTrustBackedAssetsConvert; type RuntimeHelper = asset_test_utils::RuntimeHelper; @@ -204,7 +200,7 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { let bob: AccountId = SOME_ASSET_ADMIN.into(); let staking_pot = CollatorSelection::account_id(); let asset_1: u32 = 1; - let native_location = TokenLocationV3::get(); + let native_location = TokenLocation::get(); let asset_1_location = AssetIdForTrustBackedAssetsConvert::convert_back(&asset_1).unwrap(); // bob's initial balance for native and `asset1` assets. @@ -221,14 +217,24 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { assert_ok!(AssetConversion::create_pool( RuntimeHelper::origin_of(bob.clone()), - Box::new(native_location), - Box::new(asset_1_location) + Box::new( + xcm::v3::Location::try_from(native_location.clone()).expect("conversion works") + ), + Box::new( + xcm::v3::Location::try_from(asset_1_location.clone()) + .expect("conversion works") + ) )); assert_ok!(AssetConversion::add_liquidity( RuntimeHelper::origin_of(bob.clone()), - Box::new(native_location), - Box::new(asset_1_location), + Box::new( + xcm::v3::Location::try_from(native_location.clone()).expect("conversion works") + ), + Box::new( + xcm::v3::Location::try_from(asset_1_location.clone()) + .expect("conversion works") + ), pool_liquidity, pool_liquidity, 1, @@ -240,8 +246,6 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { let asset_total_issuance = Assets::total_issuance(asset_1); let native_total_issuance = Balances::total_issuance(); - let asset_1_location_latest: Location = asset_1_location.try_into().unwrap(); - // prepare input to buy weight. let weight = Weight::from_parts(4_000_000_000, 0); let fee = WeightToFee::weight_to_fee(&weight); @@ -249,7 +253,7 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap(); let extra_amount = 100; let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - let payment: Asset = (asset_1_location_latest.clone(), asset_fee + extra_amount).into(); + let payment: Asset = (asset_1_location.clone(), asset_fee + extra_amount).into(); // init trader and buy weight. let mut trader = ::Trader::new(); @@ -257,24 +261,25 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); // assert. - let unused_amount = unused_asset - .fungible - .get(&asset_1_location_latest.clone().into()) - .map_or(0, |a| *a); + let unused_amount = + unused_asset.fungible.get(&asset_1_location.clone().into()).map_or(0, |a| *a); assert_eq!(unused_amount, extra_amount); assert_eq!(Assets::total_issuance(asset_1), asset_total_issuance + asset_fee); // prepare input to refund weight. let refund_weight = Weight::from_parts(1_000_000_000, 0); let refund = WeightToFee::weight_to_fee(&refund_weight); - let (reserve1, reserve2) = - AssetConversion::get_reserves(native_location, asset_1_location).unwrap(); + let (reserve1, reserve2) = AssetConversion::get_reserves( + xcm::v3::Location::try_from(native_location).expect("conversion works"), + xcm::v3::Location::try_from(asset_1_location.clone()).expect("conversion works"), + ) + .unwrap(); let asset_refund = AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap(); // refund. let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); - assert_eq!(actual_refund, (asset_1_location_latest, asset_refund).into()); + assert_eq!(actual_refund, (asset_1_location, asset_refund).into()); // assert. assert_eq!(Balances::balance(&staking_pot), initial_balance); @@ -303,7 +308,8 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { .execute_with(|| { let bob: AccountId = SOME_ASSET_ADMIN.into(); let staking_pot = CollatorSelection::account_id(); - let native_location = TokenLocationV3::get(); + let native_location = + xcm::v3::Location::try_from(TokenLocation::get()).expect("conversion works"); let foreign_location = xcm::v3::Location { parents: 1, interior: ( @@ -435,7 +441,7 @@ fn test_asset_xcm_take_first_trader() { // get asset id as location let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(); + AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(); // Set Alice as block author, who will receive fees RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); @@ -603,9 +609,7 @@ fn test_asset_xcm_take_first_trader_with_refund() { // We are going to buy 4e9 weight let bought = Weight::from_parts(4_000_000_000u64, 0); - - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); // lets calculate amount needed let amount_bought = WeightToFee::weight_to_fee(&bought); @@ -623,7 +627,7 @@ fn test_asset_xcm_take_first_trader_with_refund() { // We actually use half of the weight let weight_used = bought / 2; - // Make sure refurnd works. + // Make sure refund works. let amount_refunded = WeightToFee::weight_to_fee(&(bought - weight_used)); assert_eq!( @@ -677,8 +681,7 @@ fn test_asset_xcm_take_first_trader_refund_not_possible_since_amount_less_than_e // We are going to buy small amount let bought = Weight::from_parts(500_000_000u64, 0); - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); let amount_bought = WeightToFee::weight_to_fee(&bought); @@ -730,8 +733,7 @@ fn test_that_buying_ed_refund_does_not_refund_for_take_first_trader() { // We are gonna buy ED let bought = Weight::from_parts(ExistentialDeposit::get().try_into().unwrap(), 0); - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); let amount_bought = WeightToFee::weight_to_fee(&bought); @@ -807,8 +809,7 @@ fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() { // lets calculate amount needed let asset_amount_needed = WeightToFee::weight_to_fee(&bought); - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); let asset: Asset = (asset_location, asset_amount_needed).into(); @@ -925,13 +926,16 @@ fn test_assets_balances_api_works() { ))); // check trusted asset assert!(result.inner().iter().any(|asset| asset.eq(&( - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(), + AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(), minimum_asset_balance ) .into()))); // check foreign asset assert!(result.inner().iter().any(|asset| asset.eq(&( - V4V3LocationConverter::convert_back(&foreign_asset_id_location).unwrap(), + WithLatestLocationConverter::::convert_back( + &foreign_asset_id_location + ) + .unwrap(), 6 * foreign_asset_minimum_asset_balance ) .into()))); @@ -1004,7 +1008,7 @@ asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_ XcmConfig, TrustBackedAssetsInstance, AssetIdForTrustBackedAssets, - AssetIdForTrustBackedAssetsConvertLatest, + AssetIdForTrustBackedAssetsConvert, collator_session_keys(), ExistentialDeposit::get(), 12345, @@ -1044,7 +1048,7 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p ForeignCreatorsSovereignAccountOf, ForeignAssetsInstance, xcm::v3::Location, - V4V3LocationConverter, + WithLatestLocationConverter, collator_session_keys(), ExistentialDeposit::get(), AssetDeposit::get(), diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index e25554ec0a5f1af99b86bce2c3c1f60f256aa53c..554659415a0dc4d87dddb63ebcaf08ef356496eb 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -23,6 +23,7 @@ frame-system = { path = "../../../../../substrate/frame/system", default-feature frame-system-benchmarking = { path = "../../../../../substrate/frame/system/benchmarking", default-features = false, optional = true } frame-system-rpc-runtime-api = { path = "../../../../../substrate/frame/system/rpc/runtime-api", default-features = false } frame-try-runtime = { path = "../../../../../substrate/frame/try-runtime", default-features = false, optional = true } +pallet-asset-conversion-ops = { path = "../../../../../substrate/frame/asset-conversion/ops", default-features = false } pallet-asset-conversion-tx-payment = { path = "../../../../../substrate/frame/transaction-payment/asset-conversion-tx-payment", default-features = false } pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false } pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false } @@ -111,6 +112,7 @@ runtime-benchmarks = [ "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "hex-literal", + "pallet-asset-conversion-ops/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", @@ -142,6 +144,7 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "frame-try-runtime/try-runtime", + "pallet-asset-conversion-ops/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-conversion/try-runtime", "pallet-assets/try-runtime", @@ -189,6 +192,7 @@ std = [ "frame-system/std", "frame-try-runtime?/std", "log/std", + "pallet-asset-conversion-ops/std", "pallet-asset-conversion-tx-payment/std", "pallet-asset-conversion/std", "pallet-assets/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/build.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/build.rs index 60f8a125129ff1344a1799246e931acdb1d139d5..239ccac19ec7778039fb1ee56f4e772b3ddd3711 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/build.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/build.rs @@ -15,11 +15,7 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index c1fb6367312a1fd685e0e20a77f371c504d7293d..64127c80b6d5796c7ee2d9f1708b5c3baa63d036 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -298,7 +298,7 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< Assets, ForeignAssets, LocalFromLeft< - AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssetsConvert, AssetIdForTrustBackedAssets, xcm::v3::Location, >, @@ -315,6 +315,11 @@ pub type NativeAndAssets = fungible::UnionOf< AccountId, >; +pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< + AssetConversionPalletId, + (xcm::v3::Location, xcm::v3::Location), +>; + impl pallet_asset_conversion::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; @@ -322,8 +327,12 @@ impl pallet_asset_conversion::Config for Runtime { type AssetKind = xcm::v3::Location; type Assets = NativeAndAssets; type PoolId = (Self::AssetKind, Self::AssetKind); - type PoolLocator = - pallet_asset_conversion::WithFirstAsset; + type PoolLocator = pallet_asset_conversion::WithFirstAsset< + WestendLocationV3, + AccountId, + Self::AssetKind, + PoolIdToAccountId, + >; type PoolAssetId = u32; type PoolAssets = PoolAssets; type PoolSetupFee = ConstU128<0>; // Asset class deposit fees are sufficient to prevent spam @@ -344,6 +353,18 @@ impl pallet_asset_conversion::Config for Runtime { >; } +impl pallet_asset_conversion_ops::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed< + ::PoolId, + >; + type AssetsRefund = ::Assets; + type PoolAssetsRefund = ::PoolAssets; + type PoolAssetsTeam = ::PoolAssets; + type DepositAsset = Balances; + type WeightInfo = weights::pallet_asset_conversion_ops::WeightInfo; +} + parameter_types! { // we just reuse the same deposits pub const ForeignAssetsAssetDeposit: Balance = AssetDeposit::get(); @@ -906,6 +927,10 @@ construct_runtime!( NftFractionalization: pallet_nft_fractionalization = 54, PoolAssets: pallet_assets:: = 55, AssetConversion: pallet_asset_conversion = 56, + + // TODO: the pallet instance should be removed once all pools have migrated + // to the new account IDs. + AssetConversionMigration: pallet_asset_conversion_ops = 200, } ); @@ -938,7 +963,7 @@ pub type Migrations = ( // v9420 pallet_nfts::migration::v1::MigrateToV1, // unreleased - pallet_collator_selection::migration::v1::MigrateToV1, + pallet_collator_selection::migration::v2::MigrationToV2, // unreleased pallet_multisig::migrations::v1::MigrateToV1, // unreleased @@ -1085,6 +1110,7 @@ mod benches { [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_xcm_bridge_hub_router, ToRococo] + [pallet_asset_conversion_ops, AssetConversionMigration] // XCM [pallet_xcm, PalletXcmExtrinsicsBenchmark::] // NOTE: Make sure you point to the individual modules below. @@ -1584,27 +1610,23 @@ impl_runtime_apis! { fn worst_case_holding(depositable_count: u32) -> xcm::v4::Assets { // A mix of fungible, non-fungible, and concrete assets. let holding_non_fungibles = MaxAssetsIntoHolding::get() / 2 - depositable_count; - let holding_fungibles = holding_non_fungibles - 1; + let holding_fungibles = holding_non_fungibles - 2; // -2 for two `iter::once` bellow let fungibles_amount: u128 = 100; - let mut assets = (0..holding_fungibles) + (0..holding_fungibles) .map(|i| { Asset { id: AssetId(GeneralIndex(i as u128).into()), - fun: Fungible(fungibles_amount * i as u128), + fun: Fungible(fungibles_amount * (i + 1) as u128), // non-zero amount } }) .chain(core::iter::once(Asset { id: AssetId(Here.into()), fun: Fungible(u128::MAX) })) + .chain(core::iter::once(Asset { id: AssetId(WestendLocation::get()), fun: Fungible(1_000_000 * UNITS) })) .chain((0..holding_non_fungibles).map(|i| Asset { id: AssetId(GeneralIndex(i as u128).into()), fun: NonFungible(asset_instance_from(i)), })) - .collect::>(); - - assets.push(Asset { - id: AssetId(WestendLocation::get()), - fun: Fungible(1_000_000 * UNITS), - }); - assets.into() + .collect::>() + .into() } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs index 2f1fcfb05f39151e018d74e8587faa0e79afd8b6..4eebb1f8d78678b9f6a037e6fd3e4c5decfd8513 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs @@ -19,6 +19,7 @@ pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; pub mod pallet_asset_conversion; +pub mod pallet_asset_conversion_ops; pub mod pallet_assets_foreign; pub mod pallet_assets_local; pub mod pallet_assets_pool; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_conversion.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_conversion.rs index 7a5aed3d7c69ce54b229d859f56a6a2dd4881460..1c5b9be8f8e6f0067cd5373df23bf22c62215774 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_conversion.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_conversion.rs @@ -153,4 +153,26 @@ impl pallet_asset_conversion::WeightInfo for WeightInfo .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(Weight::from_parts(0, 393).saturating_mul(n.into())) } + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:2 w:2) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 3]`. + fn touch(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1571` + // Estimated: `6360` + // Minimum execution time: 381_000_000 picoseconds. + Weight::from_parts(398_540_909, 6360) + // Standard Error: 1_330_283 + .saturating_add(Weight::from_parts(209_463_636, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_conversion_ops.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_conversion_ops.rs new file mode 100644 index 0000000000000000000000000000000000000000..dfe4092c3f023512e54464e3a1ba8443326e5bfc --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_conversion_ops.rs @@ -0,0 +1,71 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_asset_conversion_ops` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-15, STEPS: `10`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `cob`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/debug/polkadot-parachain +// benchmark +// pallet +// --chain=asset-hub-westend-dev +// --steps=10 +// --repeat=2 +// --pallet=pallet-asset-conversion-ops +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_asset_conversion_ops`. +pub struct WeightInfo(PhantomData); +impl pallet_asset_conversion_ops::WeightInfo for WeightInfo { + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(1224), added: 3699, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Account` (r:2 w:2) + /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:2 w:2) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Asset` (r:1 w:1) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) + fn migrate_to_new_account() -> Weight { + // Proof Size summary in bytes: + // Measured: `1105` + // Estimated: `7404` + // Minimum execution time: 2_216_000_000 picoseconds. + Weight::from_parts(2_379_000_000, 0) + .saturating_add(Weight::from_parts(0, 7404)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(8)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs index a36c25f96043dd9d2b183ff45c3030315bd5bd19..be3d7661ab3cde8d94cf7e22eeed0a48ffa1cd5c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs @@ -70,28 +70,6 @@ impl pallet_xcm::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) - /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) - /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn send_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 21_164_000 picoseconds. - Weight::from_parts(21_656_000, 0) - .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) - } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) @@ -184,14 +162,6 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(7_791_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - fn execute_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 7_585_000 picoseconds. - Weight::from_parts(7_897_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_xcm_version() -> Weight { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index 360b1a7055b70ab7448c6ca180207fc499fff9f8..d610bfd768cdf43c7efce3ee368b6490fcf054b1 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -45,10 +45,10 @@ use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::{AccountIdConversion, ConvertInto}; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, DescribeFamily, DescribePalletTerminal, EnsureXcmOrigin, - FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, + EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, @@ -57,7 +57,7 @@ use xcm_builder::{ WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; -use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; +use xcm_executor::XcmExecutor; parameter_types! { pub const WestendLocation: Location = Location::parent(); @@ -78,8 +78,6 @@ parameter_types! { PalletInstance(::index() as u8).into(); pub UniquesPalletLocation: Location = PalletInstance(::index() as u8).into(); - pub PoolAssetsPalletLocationV3: xcm::v3::Location = - xcm::v3::Junction::PalletInstance(::index() as u8).into(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); pub StakingPot: AccountId = CollatorSelection::account_id(); pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); @@ -172,6 +170,7 @@ pub type ForeignAssetsConvertedConcreteId = assets_common::ForeignAssetsConverte StartsWithExplicitGlobalConsensus, ), Balance, + xcm::v3::Location, >; /// Means for transacting foreign assets from different global consensus. @@ -276,228 +275,6 @@ impl Contains for AmbassadorEntities { } } -/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly -/// account for proof size weights. -/// -/// Calls that are allowed through this filter must: -/// 1. Have a fixed weight; -/// 2. Cannot lead to another call being made; -/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. -pub struct SafeCallFilter; -impl Contains for SafeCallFilter { - fn contains(call: &RuntimeCall) -> bool { - #[cfg(feature = "runtime-benchmarks")] - { - if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { - return true - } - } - - // Allow to change dedicated storage items (called by governance-like) - match call { - RuntimeCall::System(frame_system::Call::set_storage { items }) - if items.iter().all(|(k, _)| k.eq(&bridging::XcmBridgeHubRouterByteFee::key())) || - items - .iter() - .all(|(k, _)| k.eq(&bridging::XcmBridgeHubRouterBaseFee::key())) => - return true, - _ => (), - }; - - matches!( - call, - RuntimeCall::PolkadotXcm( - pallet_xcm::Call::force_xcm_version { .. } | - pallet_xcm::Call::force_default_xcm_version { .. } - ) | RuntimeCall::System( - frame_system::Call::set_heap_pages { .. } | - frame_system::Call::set_code { .. } | - frame_system::Call::set_code_without_checks { .. } | - frame_system::Call::authorize_upgrade { .. } | - frame_system::Call::authorize_upgrade_without_checks { .. } | - frame_system::Call::kill_prefix { .. }, - ) | RuntimeCall::ParachainSystem(..) | - RuntimeCall::Timestamp(..) | - RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection(..) | - RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | - RuntimeCall::XcmpQueue(..) | - RuntimeCall::MessageQueue(..) | - RuntimeCall::Assets( - pallet_assets::Call::create { .. } | - pallet_assets::Call::force_create { .. } | - pallet_assets::Call::start_destroy { .. } | - pallet_assets::Call::destroy_accounts { .. } | - pallet_assets::Call::destroy_approvals { .. } | - pallet_assets::Call::finish_destroy { .. } | - pallet_assets::Call::block { .. } | - pallet_assets::Call::mint { .. } | - pallet_assets::Call::burn { .. } | - pallet_assets::Call::transfer { .. } | - pallet_assets::Call::transfer_keep_alive { .. } | - pallet_assets::Call::force_transfer { .. } | - pallet_assets::Call::freeze { .. } | - pallet_assets::Call::thaw { .. } | - pallet_assets::Call::freeze_asset { .. } | - pallet_assets::Call::thaw_asset { .. } | - pallet_assets::Call::transfer_ownership { .. } | - pallet_assets::Call::set_team { .. } | - pallet_assets::Call::set_metadata { .. } | - pallet_assets::Call::clear_metadata { .. } | - pallet_assets::Call::force_set_metadata { .. } | - pallet_assets::Call::force_clear_metadata { .. } | - pallet_assets::Call::force_asset_status { .. } | - pallet_assets::Call::approve_transfer { .. } | - pallet_assets::Call::cancel_approval { .. } | - pallet_assets::Call::force_cancel_approval { .. } | - pallet_assets::Call::transfer_approved { .. } | - pallet_assets::Call::touch { .. } | - pallet_assets::Call::touch_other { .. } | - pallet_assets::Call::refund { .. } | - pallet_assets::Call::refund_other { .. }, - ) | RuntimeCall::ForeignAssets( - pallet_assets::Call::create { .. } | - pallet_assets::Call::force_create { .. } | - pallet_assets::Call::start_destroy { .. } | - pallet_assets::Call::destroy_accounts { .. } | - pallet_assets::Call::destroy_approvals { .. } | - pallet_assets::Call::finish_destroy { .. } | - pallet_assets::Call::block { .. } | - pallet_assets::Call::mint { .. } | - pallet_assets::Call::burn { .. } | - pallet_assets::Call::transfer { .. } | - pallet_assets::Call::transfer_keep_alive { .. } | - pallet_assets::Call::force_transfer { .. } | - pallet_assets::Call::freeze { .. } | - pallet_assets::Call::thaw { .. } | - pallet_assets::Call::freeze_asset { .. } | - pallet_assets::Call::thaw_asset { .. } | - pallet_assets::Call::transfer_ownership { .. } | - pallet_assets::Call::set_team { .. } | - pallet_assets::Call::set_metadata { .. } | - pallet_assets::Call::clear_metadata { .. } | - pallet_assets::Call::force_set_metadata { .. } | - pallet_assets::Call::force_clear_metadata { .. } | - pallet_assets::Call::force_asset_status { .. } | - pallet_assets::Call::approve_transfer { .. } | - pallet_assets::Call::cancel_approval { .. } | - pallet_assets::Call::force_cancel_approval { .. } | - pallet_assets::Call::transfer_approved { .. } | - pallet_assets::Call::touch { .. } | - pallet_assets::Call::touch_other { .. } | - pallet_assets::Call::refund { .. } | - pallet_assets::Call::refund_other { .. }, - ) | RuntimeCall::PoolAssets( - pallet_assets::Call::create { .. } | - pallet_assets::Call::force_create { .. } | - pallet_assets::Call::start_destroy { .. } | - pallet_assets::Call::destroy_accounts { .. } | - pallet_assets::Call::destroy_approvals { .. } | - pallet_assets::Call::finish_destroy { .. } | - pallet_assets::Call::block { .. } | - pallet_assets::Call::mint { .. } | - pallet_assets::Call::burn { .. } | - pallet_assets::Call::transfer { .. } | - pallet_assets::Call::transfer_keep_alive { .. } | - pallet_assets::Call::force_transfer { .. } | - pallet_assets::Call::freeze { .. } | - pallet_assets::Call::thaw { .. } | - pallet_assets::Call::freeze_asset { .. } | - pallet_assets::Call::thaw_asset { .. } | - pallet_assets::Call::transfer_ownership { .. } | - pallet_assets::Call::set_team { .. } | - pallet_assets::Call::set_metadata { .. } | - pallet_assets::Call::clear_metadata { .. } | - pallet_assets::Call::force_set_metadata { .. } | - pallet_assets::Call::force_clear_metadata { .. } | - pallet_assets::Call::force_asset_status { .. } | - pallet_assets::Call::approve_transfer { .. } | - pallet_assets::Call::cancel_approval { .. } | - pallet_assets::Call::force_cancel_approval { .. } | - pallet_assets::Call::transfer_approved { .. } | - pallet_assets::Call::touch { .. } | - pallet_assets::Call::touch_other { .. } | - pallet_assets::Call::refund { .. } | - pallet_assets::Call::refund_other { .. }, - ) | RuntimeCall::AssetConversion( - pallet_asset_conversion::Call::create_pool { .. } | - pallet_asset_conversion::Call::add_liquidity { .. } | - pallet_asset_conversion::Call::remove_liquidity { .. } | - pallet_asset_conversion::Call::swap_tokens_for_exact_tokens { .. } | - pallet_asset_conversion::Call::swap_exact_tokens_for_tokens { .. }, - ) | RuntimeCall::NftFractionalization( - pallet_nft_fractionalization::Call::fractionalize { .. } | - pallet_nft_fractionalization::Call::unify { .. }, - ) | RuntimeCall::Nfts( - pallet_nfts::Call::create { .. } | - pallet_nfts::Call::force_create { .. } | - pallet_nfts::Call::destroy { .. } | - pallet_nfts::Call::mint { .. } | - pallet_nfts::Call::force_mint { .. } | - pallet_nfts::Call::burn { .. } | - pallet_nfts::Call::transfer { .. } | - pallet_nfts::Call::lock_item_transfer { .. } | - pallet_nfts::Call::unlock_item_transfer { .. } | - pallet_nfts::Call::lock_collection { .. } | - pallet_nfts::Call::transfer_ownership { .. } | - pallet_nfts::Call::set_team { .. } | - pallet_nfts::Call::force_collection_owner { .. } | - pallet_nfts::Call::force_collection_config { .. } | - pallet_nfts::Call::approve_transfer { .. } | - pallet_nfts::Call::cancel_approval { .. } | - pallet_nfts::Call::clear_all_transfer_approvals { .. } | - pallet_nfts::Call::lock_item_properties { .. } | - pallet_nfts::Call::set_attribute { .. } | - pallet_nfts::Call::force_set_attribute { .. } | - pallet_nfts::Call::clear_attribute { .. } | - pallet_nfts::Call::approve_item_attributes { .. } | - pallet_nfts::Call::cancel_item_attributes_approval { .. } | - pallet_nfts::Call::set_metadata { .. } | - pallet_nfts::Call::clear_metadata { .. } | - pallet_nfts::Call::set_collection_metadata { .. } | - pallet_nfts::Call::clear_collection_metadata { .. } | - pallet_nfts::Call::set_accept_ownership { .. } | - pallet_nfts::Call::set_collection_max_supply { .. } | - pallet_nfts::Call::update_mint_settings { .. } | - pallet_nfts::Call::set_price { .. } | - pallet_nfts::Call::buy_item { .. } | - pallet_nfts::Call::pay_tips { .. } | - pallet_nfts::Call::create_swap { .. } | - pallet_nfts::Call::cancel_swap { .. } | - pallet_nfts::Call::claim_swap { .. }, - ) | RuntimeCall::Uniques( - pallet_uniques::Call::create { .. } | - pallet_uniques::Call::force_create { .. } | - pallet_uniques::Call::destroy { .. } | - pallet_uniques::Call::mint { .. } | - pallet_uniques::Call::burn { .. } | - pallet_uniques::Call::transfer { .. } | - pallet_uniques::Call::freeze { .. } | - pallet_uniques::Call::thaw { .. } | - pallet_uniques::Call::freeze_collection { .. } | - pallet_uniques::Call::thaw_collection { .. } | - pallet_uniques::Call::transfer_ownership { .. } | - pallet_uniques::Call::set_team { .. } | - pallet_uniques::Call::approve_transfer { .. } | - pallet_uniques::Call::cancel_approval { .. } | - pallet_uniques::Call::force_item_status { .. } | - pallet_uniques::Call::set_attribute { .. } | - pallet_uniques::Call::clear_attribute { .. } | - pallet_uniques::Call::set_metadata { .. } | - pallet_uniques::Call::clear_metadata { .. } | - pallet_uniques::Call::set_collection_metadata { .. } | - pallet_uniques::Call::clear_collection_metadata { .. } | - pallet_uniques::Call::set_accept_ownership { .. } | - pallet_uniques::Call::set_collection_max_supply { .. } | - pallet_uniques::Call::set_price { .. } | - pallet_uniques::Call::buy_item { .. } - ) | RuntimeCall::ToRococoXcmRouter( - pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } - ) - ) - } -} - pub type Barrier = TrailingSetTopicAsId< DenyThenTry< DenyReserveTransferToRelayChain, @@ -522,6 +299,8 @@ pub type Barrier = TrailingSetTopicAsId< )>, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, ), UniversalLocation, ConstU32<8>, @@ -603,7 +382,11 @@ impl xcm_executor::Config for XcmConfig { WeightToFee, crate::NativeAndAssets, ( - TrustBackedAssetsAsLocation, + TrustBackedAssetsAsLocation< + TrustBackedAssetsPalletLocation, + Balance, + xcm::v3::Location, + >, ForeignAssetsConvertedConcreteId, ), ResolveAssetTo, @@ -650,8 +433,8 @@ impl xcm_executor::Config for XcmConfig { >; type MessageExporter = (); type UniversalAliases = (bridging::to_rococo::UniversalAliases,); - type CallDispatcher = WithOriginFilter; - type SafeCallFilter = SafeCallFilter; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; type Aliasers = Nothing; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs index 6696cb2322391c2dbad5f6c9a0afc4d5537de68c..b5957dd5df92ff1180909535cb6e604deadd8829 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -22,8 +22,8 @@ use asset_hub_westend_runtime::{ xcm_config::{ bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf, - LocationToAccountId, StakingPot, TrustBackedAssetsPalletLocation, - TrustBackedAssetsPalletLocationV3, WestendLocation, WestendLocationV3, XcmConfig, + LocationToAccountId, StakingPot, TrustBackedAssetsPalletLocation, WestendLocation, + XcmConfig, }, AllPalletsWithoutSystem, Assets, Balances, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, @@ -53,17 +53,14 @@ use sp_runtime::traits::MaybeEquivalence; use std::{convert::Into, ops::Mul}; use testnet_parachains_constants::westend::{consensus::*, currency::UNITS, fee::WeightToFee}; use xcm::latest::prelude::{Assets as XcmAssets, *}; -use xcm_builder::V4V3LocationConverter; +use xcm_builder::WithLatestLocationConverter; use xcm_executor::traits::{ConvertLocation, JustTry, WeightTrader}; const ALICE: [u8; 32] = [1u8; 32]; const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; type AssetIdForTrustBackedAssetsConvert = - assets_common::AssetIdForTrustBackedAssetsConvert; - -type AssetIdForTrustBackedAssetsConvertLatest = - assets_common::AssetIdForTrustBackedAssetsConvertLatest; + assets_common::AssetIdForTrustBackedAssetsConvert; type RuntimeHelper = asset_test_utils::RuntimeHelper; @@ -204,7 +201,7 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { let bob: AccountId = SOME_ASSET_ADMIN.into(); let staking_pot = CollatorSelection::account_id(); let asset_1: u32 = 1; - let native_location = WestendLocationV3::get(); + let native_location = WestendLocation::get(); let asset_1_location = AssetIdForTrustBackedAssetsConvert::convert_back(&asset_1).unwrap(); // bob's initial balance for native and `asset1` assets. @@ -221,14 +218,24 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { assert_ok!(AssetConversion::create_pool( RuntimeHelper::origin_of(bob.clone()), - Box::new(native_location), - Box::new(asset_1_location) + Box::new( + xcm::v3::Location::try_from(native_location.clone()).expect("conversion works") + ), + Box::new( + xcm::v3::Location::try_from(asset_1_location.clone()) + .expect("conversion works") + ) )); assert_ok!(AssetConversion::add_liquidity( RuntimeHelper::origin_of(bob.clone()), - Box::new(native_location), - Box::new(asset_1_location), + Box::new( + xcm::v3::Location::try_from(native_location.clone()).expect("conversion works") + ), + Box::new( + xcm::v3::Location::try_from(asset_1_location.clone()) + .expect("conversion works") + ), pool_liquidity, pool_liquidity, 1, @@ -240,8 +247,6 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { let asset_total_issuance = Assets::total_issuance(asset_1); let native_total_issuance = Balances::total_issuance(); - let asset_1_location_latest: Location = asset_1_location.try_into().unwrap(); - // prepare input to buy weight. let weight = Weight::from_parts(4_000_000_000, 0); let fee = WeightToFee::weight_to_fee(&weight); @@ -249,7 +254,7 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap(); let extra_amount = 100; let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - let payment: Asset = (asset_1_location_latest.clone(), asset_fee + extra_amount).into(); + let payment: Asset = (asset_1_location.clone(), asset_fee + extra_amount).into(); // init trader and buy weight. let mut trader = ::Trader::new(); @@ -257,24 +262,25 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); // assert. - let unused_amount = unused_asset - .fungible - .get(&asset_1_location_latest.clone().into()) - .map_or(0, |a| *a); + let unused_amount = + unused_asset.fungible.get(&asset_1_location.clone().into()).map_or(0, |a| *a); assert_eq!(unused_amount, extra_amount); assert_eq!(Assets::total_issuance(asset_1), asset_total_issuance + asset_fee); // prepare input to refund weight. let refund_weight = Weight::from_parts(1_000_000_000, 0); let refund = WeightToFee::weight_to_fee(&refund_weight); - let (reserve1, reserve2) = - AssetConversion::get_reserves(native_location, asset_1_location).unwrap(); + let (reserve1, reserve2) = AssetConversion::get_reserves( + xcm::v3::Location::try_from(native_location).expect("conversion works"), + xcm::v3::Location::try_from(asset_1_location.clone()).expect("conversion works"), + ) + .unwrap(); let asset_refund = AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap(); // refund. let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); - assert_eq!(actual_refund, (asset_1_location_latest, asset_refund).into()); + assert_eq!(actual_refund, (asset_1_location, asset_refund).into()); // assert. assert_eq!(Balances::balance(&staking_pot), initial_balance); @@ -303,7 +309,8 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { .execute_with(|| { let bob: AccountId = SOME_ASSET_ADMIN.into(); let staking_pot = CollatorSelection::account_id(); - let native_location = WestendLocationV3::get(); + let native_location = + xcm::v3::Location::try_from(WestendLocation::get()).expect("conversion works"); let foreign_location = xcm::v3::Location { parents: 1, interior: ( @@ -435,7 +442,7 @@ fn test_asset_xcm_take_first_trader() { // get asset id as location let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(); + AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(); // Set Alice as block author, who will receive fees RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); @@ -599,8 +606,7 @@ fn test_asset_xcm_take_first_trader_with_refund() { // We are going to buy 4e9 weight let bought = Weight::from_parts(4_000_000_000u64, 0); - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); // lets calculate amount needed let amount_bought = WeightToFee::weight_to_fee(&bought); @@ -672,8 +678,7 @@ fn test_asset_xcm_take_first_trader_refund_not_possible_since_amount_less_than_e // We are going to buy small amount let bought = Weight::from_parts(500_000_000u64, 0); - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); let amount_bought = WeightToFee::weight_to_fee(&bought); @@ -724,8 +729,7 @@ fn test_that_buying_ed_refund_does_not_refund_for_take_first_trader() { let bought = Weight::from_parts(500_000_000u64, 0); - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); let amount_bought = WeightToFee::weight_to_fee(&bought); @@ -801,8 +805,7 @@ fn test_asset_xcm_take_first_trader_not_possible_for_non_sufficient_assets() { // lets calculate amount needed let asset_amount_needed = WeightToFee::weight_to_fee(&bought); - let asset_location = - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); + let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); let asset: Asset = (asset_location, asset_amount_needed).into(); @@ -923,13 +926,16 @@ fn test_assets_balances_api_works() { ))); // check trusted asset assert!(result.inner().iter().any(|asset| asset.eq(&( - AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(), + AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(), minimum_asset_balance ) .into()))); // check foreign asset assert!(result.inner().iter().any(|asset| asset.eq(&( - V4V3LocationConverter::convert_back(&foreign_asset_id_location).unwrap(), + WithLatestLocationConverter::::convert_back( + &foreign_asset_id_location + ) + .unwrap(), 6 * foreign_asset_minimum_asset_balance ) .into()))); @@ -1002,7 +1008,7 @@ asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_ XcmConfig, TrustBackedAssetsInstance, AssetIdForTrustBackedAssets, - AssetIdForTrustBackedAssetsConvertLatest, + AssetIdForTrustBackedAssetsConvert, collator_session_keys(), ExistentialDeposit::get(), 12345, @@ -1043,7 +1049,7 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p ForeignCreatorsSovereignAccountOf, ForeignAssetsInstance, xcm::v3::Location, - V4V3LocationConverter, + WithLatestLocationConverter, collator_session_keys(), ExistentialDeposit::get(), AssetDeposit::get(), diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index fa2752179eb6fd238eb8596d8e3ebddf947680d3..431b5766147aeb8279ed2b1298691ceede856e5e 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -26,36 +26,37 @@ pub mod runtime_api; use crate::matching::{LocalLocationPattern, ParentLocation}; use frame_support::traits::{Equals, EverythingBut}; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; +use sp_runtime::traits::TryConvertInto; +use xcm::latest::Location; use xcm_builder::{ - AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, V4V3LocationConverter, + AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, WithLatestLocationConverter, }; -use xcm_executor::traits::JustTry; /// `Location` vs `AssetIdForTrustBackedAssets` converter for `TrustBackedAssets` -pub type AssetIdForTrustBackedAssetsConvert = +pub type AssetIdForTrustBackedAssetsConvert = AsPrefixedGeneralIndex< TrustBackedAssetsPalletLocation, AssetIdForTrustBackedAssets, - JustTry, - xcm::v3::Location, + TryConvertInto, + L, >; -pub type AssetIdForTrustBackedAssetsConvertLatest = - AsPrefixedGeneralIndex; - /// `Location` vs `CollectionId` converter for `Uniques` pub type CollectionIdForUniquesConvert = - AsPrefixedGeneralIndex; + AsPrefixedGeneralIndex; /// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets` -pub type TrustBackedAssetsConvertedConcreteId = - MatchedConvertedConcreteId< - AssetIdForTrustBackedAssets, - Balance, - StartsWith, - AssetIdForTrustBackedAssetsConvertLatest, - JustTry, - >; +pub type TrustBackedAssetsConvertedConcreteId< + TrustBackedAssetsPalletLocation, + Balance, + L = Location, +> = MatchedConvertedConcreteId< + AssetIdForTrustBackedAssets, + Balance, + StartsWith, + AssetIdForTrustBackedAssetsConvert, + TryConvertInto, +>; /// [`MatchedConvertedConcreteId`] converter dedicated for `Uniques` pub type UniquesConvertedConcreteId = MatchedConvertedConcreteId< @@ -65,28 +66,26 @@ pub type UniquesConvertedConcreteId = MatchedConvertedCon // junction within the pallet itself. StartsWith, CollectionIdForUniquesConvert, - JustTry, + TryConvertInto, >; -/// [`MatchedConvertedConcreteId`] converter dedicated for storing `AssetId` as `Location`. -pub type LocationConvertedConcreteId = MatchedConvertedConcreteId< - xcm::v3::Location, +/// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets`, +/// it is a similar implementation to `TrustBackedAssetsConvertedConcreteId`, +/// but it converts `AssetId` to `xcm::v*::Location` type instead of `AssetIdForTrustBackedAssets = +/// u32` +pub type TrustBackedAssetsAsLocation< + TrustBackedAssetsPalletLocation, Balance, - LocationFilter, - V4V3LocationConverter, - JustTry, + L, + LocationConverter = WithLatestLocationConverter, +> = MatchedConvertedConcreteId< + L, + Balance, + StartsWith, + LocationConverter, + TryConvertInto, >; -/// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets` -pub type TrustBackedAssetsAsLocation = - MatchedConvertedConcreteId< - xcm::v3::Location, - Balance, - StartsWith, - V4V3LocationConverter, - JustTry, - >; - /// [`MatchedConvertedConcreteId`] converter dedicated for storing `ForeignAssets` with `AssetId` as /// `Location`. /// @@ -95,26 +94,34 @@ pub type TrustBackedAssetsAsLocation = /// - all local Locations /// /// `AdditionalLocationExclusionFilter` can customize additional excluded Locations -pub type ForeignAssetsConvertedConcreteId = - LocationConvertedConcreteId< - EverythingBut<( - // Excludes relay/parent chain currency - Equals, - // Here we rely on fact that something like this works: - // assert!(Location::new(1, - // [Parachain(100)]).starts_with(&Location::parent())); - // assert!([Parachain(100)].into().starts_with(&Here)); - StartsWith, - // Here we can exclude more stuff or leave it as `()` - AdditionalLocationExclusionFilter, - )>, - Balance, - >; +pub type ForeignAssetsConvertedConcreteId< + AdditionalLocationExclusionFilter, + Balance, + AssetId, + LocationToAssetIdConverter = WithLatestLocationConverter, + BalanceConverter = TryConvertInto, +> = MatchedConvertedConcreteId< + AssetId, + Balance, + EverythingBut<( + // Excludes relay/parent chain currency + Equals, + // Here we rely on fact that something like this works: + // assert!(Location::new(1, + // [Parachain(100)]).starts_with(&Location::parent())); + // assert!([Parachain(100)].into().starts_with(&Here)); + StartsWith, + // Here we can exclude more stuff or leave it as `()` + AdditionalLocationExclusionFilter, + )>, + LocationToAssetIdConverter, + BalanceConverter, +>; type AssetIdForPoolAssets = u32; /// `Location` vs `AssetIdForPoolAssets` converter for `PoolAssets`. pub type AssetIdForPoolAssetsConvert = - AsPrefixedGeneralIndex; + AsPrefixedGeneralIndex; /// [`MatchedConvertedConcreteId`] converter dedicated for `PoolAssets` pub type PoolAssetsConvertedConcreteId = MatchedConvertedConcreteId< @@ -122,7 +129,7 @@ pub type PoolAssetsConvertedConcreteId = Balance, StartsWith, AssetIdForPoolAssetsConvert, - JustTry, + TryConvertInto, >; #[cfg(test)] @@ -130,7 +137,7 @@ mod tests { use super::*; use sp_runtime::traits::MaybeEquivalence; use xcm::prelude::*; - use xcm_builder::StartsWithExplicitGlobalConsensus; + use xcm_builder::{StartsWithExplicitGlobalConsensus, WithLatestLocationConverter}; use xcm_executor::traits::{Error as MatchError, MatchesFungibles}; #[test] @@ -143,14 +150,14 @@ mod tests { Location::new(5, [PalletInstance(13), GeneralIndex(local_asset_id.into())]); assert_eq!( - AssetIdForTrustBackedAssetsConvertLatest::::convert_back( + AssetIdForTrustBackedAssetsConvert::::convert_back( &local_asset_id ) .unwrap(), expected_reverse_ref ); assert_eq!( - AssetIdForTrustBackedAssetsConvertLatest::::convert( + AssetIdForTrustBackedAssetsConvert::::convert( &expected_reverse_ref ) .unwrap(), @@ -163,7 +170,7 @@ mod tests { frame_support::parameter_types! { pub TrustBackedAssetsPalletLocation: Location = Location::new(0, [PalletInstance(13)]); } - // setup convert + // set up a converter type TrustBackedAssetsConvert = TrustBackedAssetsConvertedConcreteId; @@ -246,19 +253,21 @@ mod tests { } #[test] - fn location_converted_concrete_id_converter_works() { + fn foreign_assets_converted_concrete_id_converter_works() { frame_support::parameter_types! { pub Parachain100Pattern: Location = Location::new(1, [Parachain(100)]); pub UniversalLocationNetworkId: NetworkId = NetworkId::ByGenesis([9; 32]); } - // setup convert + // set up a converter which uses `xcm::v3::Location` under the hood type Convert = ForeignAssetsConvertedConcreteId< ( StartsWith, StartsWithExplicitGlobalConsensus, ), u128, + xcm::v3::Location, + WithLatestLocationConverter, >; let test_data = vec![ diff --git a/cumulus/parachains/runtimes/assets/common/src/matching.rs b/cumulus/parachains/runtimes/assets/common/src/matching.rs index 478bba4565dc1a6d8a45d47b1569b406596b6be7..3aad88e177caad1095a3dbe21dd3a3308b103680 100644 --- a/cumulus/parachains/runtimes/assets/common/src/matching.rs +++ b/cumulus/parachains/runtimes/assets/common/src/matching.rs @@ -113,17 +113,14 @@ impl, Reserves: ContainsPair devolved, - Err(_) => { - log::trace!( - target: "xcm::contains", - "IsTrustedBridgedReserveLocationForConcreteAsset origin: {:?} is not remote to the universal_source: {:?}", - origin, universal_source - ); - return false - }, - }; + if ensure_is_remote(universal_source.clone(), origin.clone()).is_err() { + log::trace!( + target: "xcm::contains", + "IsTrustedBridgedReserveLocationForConcreteAsset origin: {:?} is not remote to the universal_source: {:?}", + origin, universal_source + ); + return false + } // check asset according to the configured reserve locations Reserves::contains(asset, origin) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index f5a75aa03acd01381666a30439789387ae699ac9..574406ab305f33d4266dca1a3229b92a80ab9e8c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -22,6 +22,7 @@ scale-info = { version = "2.11.1", default-features = false, features = [ "derive", ] } serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +tuplex = { version = "0.1", default-features = false } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } @@ -218,6 +219,7 @@ std = [ "sp-version/std", "substrate-wasm-builder", "testnet-parachains-constants/std", + "tuplex/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs index 93ef9470363cd3dd41a92fe529226ad3fd7b2e00..5551b05e202547c99501b279e8839611efcc7f66 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs @@ -49,7 +49,8 @@ pub type BridgeGrandpaWestendInstance = pallet_bridge_grandpa::Instance3; impl pallet_bridge_grandpa::Config for Runtime { type RuntimeEvent = RuntimeEvent; type BridgedChain = bp_westend::Westend; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; + type MaxFreeHeadersPerBlock = ConstU32<4>; + type FreeHeadersInterval = ConstU32<5>; type HeadersToKeep = RelayChainHeadersToKeep; type WeightInfo = weights::pallet_bridge_grandpa::WeightInfo; } @@ -89,7 +90,8 @@ pub type BridgeGrandpaRococoBulletinInstance = pallet_bridge_grandpa::Instance4; impl pallet_bridge_grandpa::Config for Runtime { type RuntimeEvent = RuntimeEvent; type BridgedChain = bp_polkadot_bulletin::PolkadotBulletin; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; + type MaxFreeHeadersPerBlock = ConstU32<4>; + type FreeHeadersInterval = ConstU32<5>; type HeadersToKeep = RelayChainHeadersToKeep; // Technically this is incorrect - we have two pallet instances and ideally we shall // benchmark every instance separately. But the benchmarking engine has a flaw - it diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs index 6dbf96edc2ab0360385b8e04bf1dc52732abd9ca..94b936889b77c4460f9921956d6f7abef1ecb52c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -20,15 +20,17 @@ //! are reusing Polkadot Bulletin chain primitives everywhere here. use crate::{ - bridge_common_config::{BridgeGrandpaRococoBulletinInstance, BridgeHubRococo}, - weights, - xcm_config::UniversalLocation, - AccountId, BridgeRococoBulletinGrandpa, BridgeRococoBulletinMessages, PolkadotXcm, Runtime, - RuntimeEvent, XcmOverRococoBulletin, XcmRouter, + bridge_common_config::BridgeHubRococo, weights, xcm_config::UniversalLocation, AccountId, + BridgeRococoBulletinGrandpa, BridgeRococoBulletinMessages, PolkadotXcm, Runtime, RuntimeEvent, + XcmOverRococoBulletin, XcmRouter, }; use bp_messages::LaneId; use bp_runtime::Chain; use bridge_runtime_common::{ + extensions::refund_relayer_extension::{ + ActualFeeRefund, RefundBridgedMessages, RefundSignedExtensionAdapter, + RefundableMessagesLane, + }, messages, messages::{ source::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChainAdapter}, @@ -39,10 +41,6 @@ use bridge_runtime_common::{ SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge, }, - refund_relayer_extension::{ - ActualFeeRefund, RefundBridgedGrandpaMessages, RefundSignedExtensionAdapter, - RefundableMessagesLane, - }, }; use frame_support::{parameter_types, traits::PalletInfoAccess}; @@ -83,6 +81,9 @@ parameter_types! { pub const RococoPeopleToRococoBulletinMessagesLane: bp_messages::LaneId = XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN; + // see the `FEE_BOOST_PER_RELAY_HEADER` constant get the meaning of this value + pub PriorityBoostPerRelayHeader: u64 = 58_014_163_614_163; + /// Priority boost that the registered relayer receives for every additional message in the message /// delivery transaction. /// @@ -169,9 +170,8 @@ impl messages::BridgedChainWithMessages for RococoBulletin {} /// Signed extension that refunds relayers that are delivering messages from the Rococo Bulletin /// chain. pub type OnBridgeHubRococoRefundRococoBulletinMessages = RefundSignedExtensionAdapter< - RefundBridgedGrandpaMessages< + RefundBridgedMessages< Runtime, - BridgeGrandpaRococoBulletinInstance, RefundableMessagesLane< WithRococoBulletinMessagesInstance, RococoPeopleToRococoBulletinMessagesLane, @@ -244,6 +244,9 @@ mod tests { /// operational costs and a faster bridge), so this value should be significant. const FEE_BOOST_PER_MESSAGE: Balance = 2 * rococo::currency::UNITS; + // see `FEE_BOOST_PER_MESSAGE` comment + const FEE_BOOST_PER_RELAY_HEADER: Balance = 2 * rococo::currency::UNITS; + #[test] fn ensure_bridge_hub_rococo_message_lane_weights_are_correct() { check_message_lane_weights::< @@ -273,7 +276,13 @@ mod tests { // Bulletin chain - it has the same (almost) runtime for Polkadot Bulletin and Rococo // Bulletin, so we have to adhere Polkadot names here - bridge_runtime_common::priority_calculator::ensure_priority_boost_is_sane::< + bridge_runtime_common::extensions::priority_calculator::per_relay_header::ensure_priority_boost_is_sane::< + Runtime, + BridgeGrandpaRococoBulletinInstance, + PriorityBoostPerRelayHeader, + >(FEE_BOOST_PER_RELAY_HEADER); + + bridge_runtime_common::extensions::priority_calculator::per_message::ensure_priority_boost_is_sane::< Runtime, WithRococoBulletinMessagesInstance, PriorityBoostPerMessage, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs index 5d55d7afbacfdb22f6939c88e87eaf64321945ff..1681ac7f4687493c82c0a3233439b2a9d47a1ad0 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -28,6 +28,10 @@ use crate::{ use bp_messages::LaneId; use bp_runtime::Chain; use bridge_runtime_common::{ + extensions::refund_relayer_extension::{ + ActualFeeRefund, RefundBridgedMessages, RefundSignedExtensionAdapter, + RefundableMessagesLane, + }, messages, messages::{ source::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChainAdapter}, @@ -38,10 +42,6 @@ use bridge_runtime_common::{ SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge, }, - refund_relayer_extension::{ - ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, - RefundableMessagesLane, RefundableParachain, - }, }; use codec::Encode; @@ -65,6 +65,10 @@ parameter_types! { 2, [GlobalConsensus(WestendGlobalConsensusNetwork::get())] ); + // see the `FEE_BOOST_PER_RELAY_HEADER` constant get the meaning of this value + pub PriorityBoostPerRelayHeader: u64 = 32_007_814_407_814; + // see the `FEE_BOOST_PER_PARACHAIN_HEADER` constant get the meaning of this value + pub PriorityBoostPerParachainHeader: u64 = 1_396_340_903_540_903; // see the `FEE_BOOST_PER_MESSAGE` constant to get the meaning of this value pub PriorityBoostPerMessage: u64 = 182_044_444_444_444; @@ -174,12 +178,8 @@ impl messages::BridgedChainWithMessages for BridgeHubWestend {} /// Signed extension that refunds relayers that are delivering messages from the Westend parachain. pub type OnBridgeHubRococoRefundBridgeHubWestendMessages = RefundSignedExtensionAdapter< - RefundBridgedParachainMessages< + RefundBridgedMessages< Runtime, - RefundableParachain< - BridgeParachainWestendInstance, - bp_bridge_hub_westend::BridgeHubWestend, - >, RefundableMessagesLane< WithBridgeHubWestendMessagesInstance, AssetHubRococoToAssetHubWestendMessagesLane, @@ -246,6 +246,7 @@ mod tests { use crate::bridge_common_config::BridgeGrandpaWestendInstance; use bridge_runtime_common::{ assert_complete_bridge_types, + extensions::refund_relayer_extension::RefundableParachain, integrity::{ assert_complete_bridge_constants, check_message_lane_weights, AssertBridgeMessagesPalletConstants, AssertBridgePalletNames, AssertChainConstants, @@ -266,6 +267,11 @@ mod tests { /// operational costs and a faster bridge), so this value should be significant. const FEE_BOOST_PER_MESSAGE: Balance = 2 * rococo::currency::UNITS; + // see `FEE_BOOST_PER_MESSAGE` comment + const FEE_BOOST_PER_RELAY_HEADER: Balance = 2 * rococo::currency::UNITS; + // see `FEE_BOOST_PER_MESSAGE` comment + const FEE_BOOST_PER_PARACHAIN_HEADER: Balance = 2 * rococo::currency::UNITS; + #[test] fn ensure_bridge_hub_rococo_message_lane_weights_are_correct() { check_message_lane_weights::< @@ -318,7 +324,19 @@ mod tests { }, }); - bridge_runtime_common::priority_calculator::ensure_priority_boost_is_sane::< + bridge_runtime_common::extensions::priority_calculator::per_relay_header::ensure_priority_boost_is_sane::< + Runtime, + BridgeGrandpaWestendInstance, + PriorityBoostPerRelayHeader, + >(FEE_BOOST_PER_RELAY_HEADER); + + bridge_runtime_common::extensions::priority_calculator::per_parachain_header::ensure_priority_boost_is_sane::< + Runtime, + RefundableParachain, + PriorityBoostPerParachainHeader, + >(FEE_BOOST_PER_PARACHAIN_HEADER); + + bridge_runtime_common::extensions::priority_calculator::per_message::ensure_priority_boost_is_sane::< Runtime, WithBridgeHubWestendMessagesInstance, PriorityBoostPerMessage, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 1eac813b10cec7afb60312aea252f1a37cdfa986..7c2aa49088610c7a3c842a2142fc4bcac528efef 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -35,6 +35,12 @@ pub mod bridge_to_westend_config; mod weights; pub mod xcm_config; +use bridge_runtime_common::extensions::{ + check_obsolete_extension::{ + CheckAndBoostBridgeGrandpaTransactions, CheckAndBoostBridgeParachainsTransactions, + }, + refund_relayer_extension::RefundableParachain, +}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{ @@ -63,7 +69,7 @@ use frame_support::{ dispatch::DispatchClass, genesis_builder_helper::{build_state, get_preset}, parameter_types, - traits::{ConstBool, ConstU32, ConstU64, ConstU8, TransformOrigin}, + traits::{ConstBool, ConstU32, ConstU64, ConstU8, Get, TransformOrigin}, weights::{ConstantMultiplier, Weight}, PalletId, }; @@ -139,7 +145,7 @@ pub type UncheckedExtrinsic = /// Migrations to apply on runtime upgrade. pub type Migrations = ( - pallet_collator_selection::migration::v1::MigrateToV1, + pallet_collator_selection::migration::v2::MigrationToV2, pallet_multisig::migrations::v1::MigrateToV1, InitStorageVersions, cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, @@ -740,10 +746,28 @@ pub type XcmOverRococoBulletin = XcmOverPolkadotBulletin; bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages! { RuntimeCall, AccountId, // Grandpa - BridgeWestendGrandpa, - BridgeRococoBulletinGrandpa, + CheckAndBoostBridgeGrandpaTransactions< + Runtime, + bridge_common_config::BridgeGrandpaWestendInstance, + bridge_to_westend_config::PriorityBoostPerRelayHeader, + xcm_config::TreasuryAccount, + >, + CheckAndBoostBridgeGrandpaTransactions< + Runtime, + bridge_common_config::BridgeGrandpaRococoBulletinInstance, + bridge_to_bulletin_config::PriorityBoostPerRelayHeader, + xcm_config::TreasuryAccount, + >, // Parachains - BridgeWestendParachains, + CheckAndBoostBridgeParachainsTransactions< + Runtime, + RefundableParachain< + bridge_common_config::BridgeParachainWestendInstance, + bp_bridge_hub_westend::BridgeHubWestend, + >, + bridge_to_westend_config::PriorityBoostPerParachainHeader, + xcm_config::TreasuryAccount, + >, // Messages BridgeWestendMessages, BridgeRococoBulletinMessages @@ -938,6 +962,11 @@ impl_runtime_apis! { fn best_finalized() -> Option> { BridgeWestendGrandpa::best_finalized() } + fn free_headers_interval() -> Option { + >::FreeHeadersInterval::get() + } fn synced_headers_grandpa_info( ) -> Vec> { BridgeWestendGrandpa::synced_headers_grandpa_info() @@ -950,6 +979,10 @@ impl_runtime_apis! { bp_bridge_hub_westend::BridgeHubWestend >().unwrap_or(None) } + fn free_headers_interval() -> Option { + // "free interval" is not currently used for parachains + None + } } // This is exposed by BridgeHubRococo @@ -984,6 +1017,12 @@ impl_runtime_apis! { BridgePolkadotBulletinGrandpa::best_finalized() } + fn free_headers_interval() -> Option { + >::FreeHeadersInterval::get() + } + fn synced_headers_grandpa_info( ) -> Vec> { BridgePolkadotBulletinGrandpa::synced_headers_grandpa_info() diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs index aac39a4564fb600d9c4f623aa3ba27c78fc8f5fc..942f243141da9c1dcfa47d2e3a1ac7906eb22706 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs @@ -17,8 +17,10 @@ //! Expose the auto generated weight files. +use ::pallet_bridge_grandpa::WeightInfoExt as GrandpaWeightInfoExt; use ::pallet_bridge_messages::WeightInfoExt as MessagesWeightInfoExt; use ::pallet_bridge_parachains::WeightInfoExt as ParachainsWeightInfoExt; +use ::pallet_bridge_relayers::WeightInfo as _; pub mod block_weights; pub mod cumulus_pallet_parachain_system; @@ -56,6 +58,16 @@ use frame_support::weights::Weight; // import trait from dependency module use ::pallet_bridge_relayers::WeightInfoExt as _; +impl GrandpaWeightInfoExt for pallet_bridge_grandpa::WeightInfo { + fn submit_finality_proof_overhead_from_runtime() -> Weight { + // our signed extension: + // 1) checks whether relayer registration is active from validate/pre_dispatch; + // 2) may slash and deregister relayer from post_dispatch + // (2) includes (1), so (2) is the worst case + pallet_bridge_relayers::WeightInfo::::slash_and_deregister() + } +} + impl MessagesWeightInfoExt for pallet_bridge_messages_rococo_to_rococo_bulletin::WeightInfo { @@ -94,4 +106,12 @@ impl ParachainsWeightInfoExt for pallet_bridge_parachains::WeightInfo u32 { bp_bridge_hub_westend::EXTRA_STORAGE_PROOF_SIZE } + + fn submit_parachain_heads_overhead_from_runtime() -> Weight { + // our signed extension: + // 1) checks whether relayer registration is active from validate/pre_dispatch; + // 2) may slash and deregister relayer from post_dispatch + // (2) includes (1), so (2) is the worst case + pallet_bridge_relayers::WeightInfo::::slash_and_deregister() + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs index adfaa9ea2028e4b8880e17a1ccc471beb64c9a3c..a732e1a573439c4b658191697024ac3c396c9de5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -64,30 +64,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 18_732_000 picoseconds. - Weight::from_parts(19_386_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) - /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) - /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn send_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 18_943_000 picoseconds. - Weight::from_parts(19_455_000, 0) + // Minimum execution time: 18_513_000 picoseconds. + Weight::from_parts(19_156_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) @@ -112,8 +90,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `70` // Estimated: `3593` - // Minimum execution time: 88_917_000 picoseconds. - Weight::from_parts(91_611_000, 0) + // Minimum execution time: 88_096_000 picoseconds. + Weight::from_parts(89_732_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -148,8 +126,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `70` // Estimated: `3593` - // Minimum execution time: 88_587_000 picoseconds. - Weight::from_parts(90_303_000, 0) + // Minimum execution time: 88_239_000 picoseconds. + Weight::from_parts(89_729_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -164,24 +142,14 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn execute_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_856_000 picoseconds. - Weight::from_parts(6_202_000, 0) + // Minimum execution time: 5_955_000 picoseconds. + Weight::from_parts(6_266_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -191,8 +159,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_797_000 picoseconds. - Weight::from_parts(1_970_000, 0) + // Minimum execution time: 1_868_000 picoseconds. + Weight::from_parts(1_961_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -218,8 +186,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 24_479_000 picoseconds. - Weight::from_parts(25_058_000, 0) + // Minimum execution time: 24_388_000 picoseconds. + Weight::from_parts(25_072_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -244,8 +212,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 27_282_000 picoseconds. - Weight::from_parts(27_924_000, 0) + // Minimum execution time: 26_762_000 picoseconds. + Weight::from_parts(27_631_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -256,8 +224,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_801_000 picoseconds. - Weight::from_parts(1_988_000, 0) + // Minimum execution time: 1_856_000 picoseconds. + Weight::from_parts(2_033_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -267,8 +235,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `89` // Estimated: `13454` - // Minimum execution time: 16_509_000 picoseconds. - Weight::from_parts(16_939_000, 0) + // Minimum execution time: 17_718_000 picoseconds. + Weight::from_parts(18_208_000, 0) .saturating_add(Weight::from_parts(0, 13454)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -279,8 +247,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `93` // Estimated: `13458` - // Minimum execution time: 16_140_000 picoseconds. - Weight::from_parts(16_843_000, 0) + // Minimum execution time: 17_597_000 picoseconds. + Weight::from_parts(18_090_000, 0) .saturating_add(Weight::from_parts(0, 13458)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -291,8 +259,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `15946` - // Minimum execution time: 18_160_000 picoseconds. - Weight::from_parts(18_948_000, 0) + // Minimum execution time: 19_533_000 picoseconds. + Weight::from_parts(20_164_000, 0) .saturating_add(Weight::from_parts(0, 15946)) .saturating_add(T::DbWeight::get().reads(6)) } @@ -314,8 +282,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 24_409_000 picoseconds. - Weight::from_parts(25_261_000, 0) + // Minimum execution time: 24_958_000 picoseconds. + Weight::from_parts(25_628_000, 0) .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -326,8 +294,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `136` // Estimated: `11026` - // Minimum execution time: 10_848_000 picoseconds. - Weight::from_parts(11_241_000, 0) + // Minimum execution time: 12_209_000 picoseconds. + Weight::from_parts(12_612_000, 0) .saturating_add(Weight::from_parts(0, 11026)) .saturating_add(T::DbWeight::get().reads(4)) } @@ -337,8 +305,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `100` // Estimated: `13465` - // Minimum execution time: 16_609_000 picoseconds. - Weight::from_parts(17_044_000, 0) + // Minimum execution time: 17_844_000 picoseconds. + Weight::from_parts(18_266_000, 0) .saturating_add(Weight::from_parts(0, 13465)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -361,8 +329,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `13471` - // Minimum execution time: 32_500_000 picoseconds. - Weight::from_parts(33_475_000, 0) + // Minimum execution time: 34_131_000 picoseconds. + Weight::from_parts(34_766_000, 0) .saturating_add(Weight::from_parts(0, 13471)) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(4)) @@ -375,8 +343,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_484_000 picoseconds. - Weight::from_parts(3_673_000, 0) + // Minimum execution time: 3_525_000 picoseconds. + Weight::from_parts(3_724_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -387,8 +355,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 25_225_000 picoseconds. - Weight::from_parts(25_731_000, 0) + // Minimum execution time: 24_975_000 picoseconds. + Weight::from_parts(25_517_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -399,8 +367,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 33_961_000 picoseconds. - Weight::from_parts(34_818_000, 0) + // Minimum execution time: 33_761_000 picoseconds. + Weight::from_parts(34_674_000, 0) .saturating_add(Weight::from_parts(0, 3555)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index 063c999aa7ad740fe225cc22ee62f685f1bb35aa..bd1445bee22c3246bf07ba068a8ba0a0490d0770 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -19,22 +19,12 @@ use super::{ ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, WeightToFee, XcmpQueue, }; -use crate::{ - bridge_common_config::{ - BridgeGrandpaRococoBulletinInstance, BridgeGrandpaWestendInstance, - BridgeParachainWestendInstance, DeliveryRewardInBalance, RequiredStakeForStakeAndSlash, - }, - bridge_to_bulletin_config::WithRococoBulletinMessagesInstance, - bridge_to_westend_config::WithBridgeHubWestendMessagesInstance, - EthereumGatewayAddress, -}; use bp_messages::LaneId; use bp_relayers::{PayRewardFromAccount, RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::ChainId; use frame_support::{ parameter_types, traits::{tokens::imbalance::ResolveTo, ConstU32, Contains, Equals, Everything, Nothing}, - StoragePrefixedMap, }; use frame_system::EnsureRoot; use pallet_collator_selection::StakingPotAccountId; @@ -56,15 +46,16 @@ use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; use xcm::latest::prelude::*; use xcm_builder::{ deposit_or_burn_fee, AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, - AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, - FungibleAdapter, HandleFee, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeToAccount, + AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, + AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, + FrameTransactionalProcessor, FungibleAdapter, HandleFee, IsConcrete, ParentAsSuperuser, + ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeToAccount, }; use xcm_executor::{ - traits::{FeeManager, FeeReason, FeeReason::Export, TransactAsset, WithOriginFilter}, + traits::{FeeManager, FeeReason, FeeReason::Export, TransactAsset}, XcmExecutor, }; @@ -138,104 +129,6 @@ impl Contains for ParentOrParentsPlurality { } } -/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly -/// account for proof size weights. -/// -/// Calls that are allowed through this filter must: -/// 1. Have a fixed weight; -/// 2. Cannot lead to another call being made; -/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. -pub struct SafeCallFilter; -impl Contains for SafeCallFilter { - fn contains(call: &RuntimeCall) -> bool { - #[cfg(feature = "runtime-benchmarks")] - { - if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { - return true - } - } - - // Allow to change dedicated storage items (called by governance-like) - match call { - RuntimeCall::System(frame_system::Call::set_storage { items }) - if items.iter().all(|(k, _)| { - k.eq(&DeliveryRewardInBalance::key()) || - k.eq(&RequiredStakeForStakeAndSlash::key()) || - k.eq(&EthereumGatewayAddress::key()) || - // Allow resetting of Ethereum nonces in Rococo only. - k.starts_with(&snowbridge_pallet_inbound_queue::Nonce::::final_prefix()) || - k.starts_with(&snowbridge_pallet_outbound_queue::Nonce::::final_prefix()) - }) => - return true, - _ => (), - }; - - matches!( - call, - RuntimeCall::PolkadotXcm( - pallet_xcm::Call::force_xcm_version { .. } | - pallet_xcm::Call::force_default_xcm_version { .. } - ) | RuntimeCall::System( - frame_system::Call::set_heap_pages { .. } | - frame_system::Call::set_code { .. } | - frame_system::Call::set_code_without_checks { .. } | - frame_system::Call::authorize_upgrade { .. } | - frame_system::Call::authorize_upgrade_without_checks { .. } | - frame_system::Call::kill_prefix { .. }, - ) | RuntimeCall::ParachainSystem(..) | - RuntimeCall::Timestamp(..) | - RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection(..) | - RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | - RuntimeCall::XcmpQueue(..) | - RuntimeCall::MessageQueue(..) | - RuntimeCall::BridgeWestendGrandpa(pallet_bridge_grandpa::Call::< - Runtime, - BridgeGrandpaWestendInstance, - >::initialize { .. }) | - RuntimeCall::BridgeWestendGrandpa(pallet_bridge_grandpa::Call::< - Runtime, - BridgeGrandpaWestendInstance, - >::set_operating_mode { .. }) | - RuntimeCall::BridgeWestendParachains(pallet_bridge_parachains::Call::< - Runtime, - BridgeParachainWestendInstance, - >::set_operating_mode { .. }) | - RuntimeCall::BridgeWestendMessages(pallet_bridge_messages::Call::< - Runtime, - WithBridgeHubWestendMessagesInstance, - >::set_operating_mode { .. }) | - RuntimeCall::BridgePolkadotBulletinGrandpa(pallet_bridge_grandpa::Call::< - Runtime, - BridgeGrandpaRococoBulletinInstance, - >::initialize { .. }) | - RuntimeCall::BridgePolkadotBulletinGrandpa(pallet_bridge_grandpa::Call::< - Runtime, - BridgeGrandpaRococoBulletinInstance, - >::set_operating_mode { .. }) | - RuntimeCall::BridgePolkadotBulletinMessages(pallet_bridge_messages::Call::< - Runtime, - WithRococoBulletinMessagesInstance, - >::set_operating_mode { .. }) | - RuntimeCall::EthereumBeaconClient( - snowbridge_pallet_ethereum_client::Call::force_checkpoint { .. } | - snowbridge_pallet_ethereum_client::Call::set_operating_mode { .. }, - ) | RuntimeCall::EthereumInboundQueue( - snowbridge_pallet_inbound_queue::Call::set_operating_mode { .. }, - ) | RuntimeCall::EthereumOutboundQueue( - snowbridge_pallet_outbound_queue::Call::set_operating_mode { .. }, - ) | RuntimeCall::EthereumSystem( - snowbridge_pallet_system::Call::upgrade { .. } | - snowbridge_pallet_system::Call::set_operating_mode { .. } | - snowbridge_pallet_system::Call::set_pricing_parameters { .. } | - snowbridge_pallet_system::Call::force_update_channel { .. } | - snowbridge_pallet_system::Call::force_transfer_native_from_agent { .. } | - snowbridge_pallet_system::Call::set_token_transfer_fees { .. }, - ) - ) - } -} - pub type Barrier = TrailingSetTopicAsId< DenyThenTry< DenyReserveTransferToRelayChain, @@ -258,6 +151,8 @@ pub type Barrier = TrailingSetTopicAsId< )>, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, ), UniversalLocation, ConstU32<8>, @@ -337,8 +232,8 @@ impl xcm_executor::Config for XcmConfig { crate::bridge_to_ethereum_config::SnowbridgeExporter, ); type UniversalAliases = Nothing; - type CallDispatcher = WithOriginFilter; - type SafeCallFilter = SafeCallFilter; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; type Aliasers = Nothing; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index 776c505fa640fa956ae07e9928ccf2f9293e80dd..b309232825db3aa964b2fa1a1d8d739f06ec3153 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -80,11 +80,10 @@ fn construct_and_apply_extrinsic( r.unwrap() } -fn construct_and_estimate_extrinsic_fee(batch: pallet_utility::Call) -> Balance { - let batch_call = RuntimeCall::Utility(batch); - let batch_info = batch_call.get_dispatch_info(); - let xt = construct_extrinsic(Alice, batch_call); - TransactionPayment::compute_fee(xt.encoded_size() as _, &batch_info, 0) +fn construct_and_estimate_extrinsic_fee(call: RuntimeCall) -> Balance { + let info = call.get_dispatch_info(); + let xt = construct_extrinsic(Alice, call); + TransactionPayment::compute_fee(xt.encoded_size() as _, &info, 0) } fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys { @@ -376,20 +375,20 @@ mod bridge_hub_westend_tests { } #[test] - pub fn complex_relay_extrinsic_works() { - // for Westend - from_parachain::complex_relay_extrinsic_works::( + fn free_relay_extrinsic_works() { + // from Westend + from_parachain::free_relay_extrinsic_works::( collator_session_keys(), slot_durations(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, - SIBLING_PARACHAIN_ID, BridgeHubWestendChainId::get(), + SIBLING_PARACHAIN_ID, Rococo, XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND, || (), construct_and_apply_extrinsic, - ); + ) } #[test] @@ -414,12 +413,12 @@ mod bridge_hub_westend_tests { } #[test] - pub fn can_calculate_fee_for_complex_message_delivery_transaction() { + fn can_calculate_fee_for_standalone_message_delivery_transaction() { bridge_hub_test_utils::check_sane_fees_values( "bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs", bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(), || { - from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< + from_parachain::can_calculate_fee_for_standalone_message_delivery_transaction::< RuntimeTestsAdapter, >(collator_session_keys(), construct_and_estimate_extrinsic_fee) }, @@ -433,12 +432,12 @@ mod bridge_hub_westend_tests { } #[test] - pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { + fn can_calculate_fee_for_standalone_message_confirmation_transaction() { bridge_hub_test_utils::check_sane_fees_values( "bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs", bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(), || { - from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< + from_parachain::can_calculate_fee_for_standalone_message_confirmation_transaction::< RuntimeTestsAdapter, >(collator_session_keys(), construct_and_estimate_extrinsic_fee) }, @@ -581,28 +580,28 @@ mod bridge_hub_bulletin_tests { } #[test] - pub fn complex_relay_extrinsic_works() { - // for Bulletin - from_grandpa_chain::complex_relay_extrinsic_works::( + fn free_relay_extrinsic_works() { + // from Bulletin + from_grandpa_chain::free_relay_extrinsic_works::( collator_session_keys(), slot_durations(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - SIBLING_PARACHAIN_ID, RococoBulletinChainId::get(), + SIBLING_PARACHAIN_ID, Rococo, XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, || (), construct_and_apply_extrinsic, - ); + ) } #[test] - pub fn can_calculate_fee_for_complex_message_delivery_transaction() { + pub fn can_calculate_fee_for_standalone_message_delivery_transaction() { bridge_hub_test_utils::check_sane_fees_values( "bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs", bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(), || { - from_grandpa_chain::can_calculate_fee_for_complex_message_delivery_transaction::< + from_grandpa_chain::can_calculate_fee_for_standalone_message_delivery_transaction::< RuntimeTestsAdapter, >(collator_session_keys(), construct_and_estimate_extrinsic_fee) }, @@ -617,12 +616,12 @@ mod bridge_hub_bulletin_tests { } #[test] - pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { + pub fn can_calculate_fee_for_standalone_message_confirmation_transaction() { bridge_hub_test_utils::check_sane_fees_values( "bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs", bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(), || { - from_grandpa_chain::can_calculate_fee_for_complex_message_confirmation_transaction::< + from_grandpa_chain::can_calculate_fee_for_standalone_message_confirmation_transaction::< RuntimeTestsAdapter, >(collator_session_keys(), construct_and_estimate_extrinsic_fee) }, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 86560caca99ca344065373330638c3232a417f21..a7241cc6d10c45a292c3e0ffc0a8044e9b2fb706 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -18,6 +18,7 @@ hex-literal = { version = "0.4.1" } log = { workspace = true } scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +tuplex = { version = "0.1", default-features = false } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } @@ -180,6 +181,7 @@ std = [ "sp-version/std", "substrate-wasm-builder", "testnet-parachains-constants/std", + "tuplex/std", "westend-runtime-constants/std", "xcm-builder/std", "xcm-executor/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index bce722aa5f87d006af0ec71429d6c84eeab4972d..425b53da30fc8a176fcddfe145fab66a41b60f8a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -25,6 +25,10 @@ use bp_messages::LaneId; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bp_runtime::Chain; use bridge_runtime_common::{ + extensions::refund_relayer_extension::{ + ActualFeeRefund, RefundBridgedMessages, RefundSignedExtensionAdapter, + RefundableMessagesLane, + }, messages, messages::{ source::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChainAdapter}, @@ -35,10 +39,6 @@ use bridge_runtime_common::{ SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge, }, - refund_relayer_extension::{ - ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, - RefundableMessagesLane, RefundableParachain, - }, }; use codec::Encode; use frame_support::{ @@ -70,6 +70,10 @@ parameter_types! { 2, [GlobalConsensus(RococoGlobalConsensusNetwork::get())] ); + // see the `FEE_BOOST_PER_RELAY_HEADER` constant get the meaning of this value + pub PriorityBoostPerRelayHeader: u64 = 32_007_814_407_814; + // see the `FEE_BOOST_PER_PARACHAIN_HEADER` constant get the meaning of this value + pub PriorityBoostPerParachainHeader: u64 = 1_396_340_903_540_903; // see the `FEE_BOOST_PER_MESSAGE` constant to get the meaning of this value pub PriorityBoostPerMessage: u64 = 182_044_444_444_444; @@ -191,9 +195,8 @@ impl ThisChainWithMessages for BridgeHubWestend { /// Signed extension that refunds relayers that are delivering messages from the Rococo parachain. pub type OnBridgeHubWestendRefundBridgeHubRococoMessages = RefundSignedExtensionAdapter< - RefundBridgedParachainMessages< + RefundBridgedMessages< Runtime, - RefundableParachain, RefundableMessagesLane< WithBridgeHubRococoMessagesInstance, AssetHubWestendToAssetHubRococoMessagesLane, @@ -210,7 +213,8 @@ pub type BridgeGrandpaRococoInstance = pallet_bridge_grandpa::Instance1; impl pallet_bridge_grandpa::Config for Runtime { type RuntimeEvent = RuntimeEvent; type BridgedChain = bp_rococo::Rococo; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; + type MaxFreeHeadersPerBlock = ConstU32<4>; + type FreeHeadersInterval = ConstU32<5>; type HeadersToKeep = RelayChainHeadersToKeep; type WeightInfo = weights::pallet_bridge_grandpa::WeightInfo; } @@ -281,6 +285,7 @@ mod tests { use super::*; use bridge_runtime_common::{ assert_complete_bridge_types, + extensions::refund_relayer_extension::RefundableParachain, integrity::{ assert_complete_bridge_constants, check_message_lane_weights, AssertBridgeMessagesPalletConstants, AssertBridgePalletNames, AssertChainConstants, @@ -301,6 +306,11 @@ mod tests { /// operational costs and a faster bridge), so this value should be significant. const FEE_BOOST_PER_MESSAGE: Balance = 2 * westend::currency::UNITS; + // see `FEE_BOOST_PER_MESSAGE` comment + const FEE_BOOST_PER_RELAY_HEADER: Balance = 2 * westend::currency::UNITS; + // see `FEE_BOOST_PER_MESSAGE` comment + const FEE_BOOST_PER_PARACHAIN_HEADER: Balance = 2 * westend::currency::UNITS; + #[test] fn ensure_bridge_hub_westend_message_lane_weights_are_correct() { check_message_lane_weights::< @@ -352,7 +362,19 @@ mod tests { }, }); - bridge_runtime_common::priority_calculator::ensure_priority_boost_is_sane::< + bridge_runtime_common::extensions::priority_calculator::per_relay_header::ensure_priority_boost_is_sane::< + Runtime, + BridgeGrandpaRococoInstance, + PriorityBoostPerRelayHeader, + >(FEE_BOOST_PER_RELAY_HEADER); + + bridge_runtime_common::extensions::priority_calculator::per_parachain_header::ensure_priority_boost_is_sane::< + Runtime, + RefundableParachain, + PriorityBoostPerParachainHeader, + >(FEE_BOOST_PER_PARACHAIN_HEADER); + + bridge_runtime_common::extensions::priority_calculator::per_message::ensure_priority_boost_is_sane::< Runtime, WithBridgeHubRococoMessagesInstance, PriorityBoostPerMessage, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index b4ea2c79f64f1d87de2747191759c9e9990db3ba..640eaf881a571115357572ce39b5bd2daaad9db4 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -32,6 +32,12 @@ pub mod bridge_to_rococo_config; mod weights; pub mod xcm_config; +use bridge_runtime_common::extensions::{ + check_obsolete_extension::{ + CheckAndBoostBridgeGrandpaTransactions, CheckAndBoostBridgeParachainsTransactions, + }, + refund_relayer_extension::RefundableParachain, +}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::ParaId; use sp_api::impl_runtime_apis; @@ -57,7 +63,7 @@ use frame_support::{ dispatch::DispatchClass, genesis_builder_helper::{build_state, get_preset}, parameter_types, - traits::{ConstBool, ConstU32, ConstU64, ConstU8, TransformOrigin}, + traits::{ConstBool, ConstU32, ConstU64, ConstU8, Get, TransformOrigin}, weights::{ConstantMultiplier, Weight}, PalletId, }; @@ -118,7 +124,7 @@ pub type UncheckedExtrinsic = /// Migrations to apply on runtime upgrade. pub type Migrations = ( - pallet_collator_selection::migration::v1::MigrateToV1, + pallet_collator_selection::migration::v2::MigrationToV2, pallet_multisig::migrations::v1::MigrateToV1, InitStorageVersions, // unreleased @@ -502,9 +508,22 @@ construct_runtime!( bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages! { RuntimeCall, AccountId, // Grandpa - BridgeRococoGrandpa, + CheckAndBoostBridgeGrandpaTransactions< + Runtime, + bridge_to_rococo_config::BridgeGrandpaRococoInstance, + bridge_to_rococo_config::PriorityBoostPerRelayHeader, + xcm_config::TreasuryAccount, + >, // Parachains - BridgeRococoParachains, + CheckAndBoostBridgeParachainsTransactions< + Runtime, + RefundableParachain< + bridge_to_rococo_config::BridgeParachainRococoInstance, + bp_bridge_hub_rococo::BridgeHubRococo, + >, + bridge_to_rococo_config::PriorityBoostPerParachainHeader, + xcm_config::TreasuryAccount, + >, // Messages BridgeRococoMessages } @@ -692,6 +711,11 @@ impl_runtime_apis! { fn best_finalized() -> Option> { BridgeRococoGrandpa::best_finalized() } + fn free_headers_interval() -> Option { + >::FreeHeadersInterval::get() + } fn synced_headers_grandpa_info( ) -> Vec> { BridgeRococoGrandpa::synced_headers_grandpa_info() @@ -704,6 +728,10 @@ impl_runtime_apis! { bp_bridge_hub_rococo::BridgeHubRococo >().unwrap_or(None) } + fn free_headers_interval() -> Option { + // "free interval" is not currently used for parachains + None + } } impl bp_bridge_hub_rococo::FromBridgeHubRococoInboundLaneApi for Runtime { diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs index a65ee31d3e55ff8135fdd7dec35120e0a463409b..245daaf8ed91b69db2a604c51e394c2d768b1c26 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs @@ -17,8 +17,10 @@ //! Expose the auto generated weight files. +use ::pallet_bridge_grandpa::WeightInfoExt as GrandpaWeightInfoExt; use ::pallet_bridge_messages::WeightInfoExt as MessagesWeightInfoExt; use ::pallet_bridge_parachains::WeightInfoExt as ParachainsWeightInfoExt; +use ::pallet_bridge_relayers::WeightInfo as _; pub mod block_weights; pub mod cumulus_pallet_parachain_system; @@ -51,6 +53,16 @@ use frame_support::weights::Weight; // import trait from dependency module use ::pallet_bridge_relayers::WeightInfoExt as _; +impl GrandpaWeightInfoExt for pallet_bridge_grandpa::WeightInfo { + fn submit_finality_proof_overhead_from_runtime() -> Weight { + // our signed extension: + // 1) checks whether relayer registration is active from validate/pre_dispatch; + // 2) may slash and deregister relayer from post_dispatch + // (2) includes (1), so (2) is the worst case + pallet_bridge_relayers::WeightInfo::::slash_and_deregister() + } +} + impl MessagesWeightInfoExt for pallet_bridge_messages::WeightInfo { fn expected_extra_storage_proof_size() -> u32 { bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE @@ -70,4 +82,12 @@ impl ParachainsWeightInfoExt for pallet_bridge_parachains::WeightInfo u32 { bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE } + + fn submit_parachain_heads_overhead_from_runtime() -> Weight { + // our signed extension: + // 1) checks whether relayer registration is active from validate/pre_dispatch; + // 2) may slash and deregister relayer from post_dispatch + // (2) includes (1), so (2) is the worst case + pallet_bridge_relayers::WeightInfo::::slash_and_deregister() + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs index 9cf4c61466a1bd37ffea56681648b2c2e2ce4555..a78ff2355efaf06562e44828a8df0730481d4098 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -64,30 +64,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 19_702_000 picoseconds. - Weight::from_parts(20_410_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) - /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) - /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn send_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 19_525_000 picoseconds. - Weight::from_parts(20_071_000, 0) + // Minimum execution time: 19_527_000 picoseconds. + Weight::from_parts(19_839_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) @@ -112,8 +90,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `107` // Estimated: `3593` - // Minimum execution time: 91_793_000 picoseconds. - Weight::from_parts(93_761_000, 0) + // Minimum execution time: 90_938_000 picoseconds. + Weight::from_parts(92_822_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -148,8 +126,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `107` // Estimated: `3593` - // Minimum execution time: 91_819_000 picoseconds. - Weight::from_parts(93_198_000, 0) + // Minimum execution time: 90_133_000 picoseconds. + Weight::from_parts(92_308_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -164,24 +142,14 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn execute_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_183_000 picoseconds. - Weight::from_parts(6_598_000, 0) + // Minimum execution time: 6_205_000 picoseconds. + Weight::from_parts(6_595_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -191,8 +159,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_987_000 picoseconds. - Weight::from_parts(2_076_000, 0) + // Minimum execution time: 1_927_000 picoseconds. + Weight::from_parts(2_062_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -218,8 +186,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 25_375_000 picoseconds. - Weight::from_parts(26_165_000, 0) + // Minimum execution time: 25_078_000 picoseconds. + Weight::from_parts(25_782_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -244,8 +212,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 28_167_000 picoseconds. - Weight::from_parts(28_792_000, 0) + // Minimum execution time: 28_188_000 picoseconds. + Weight::from_parts(28_826_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -256,8 +224,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_039_000 picoseconds. - Weight::from_parts(2_211_000, 0) + // Minimum execution time: 1_886_000 picoseconds. + Weight::from_parts(1_991_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -267,8 +235,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `89` // Estimated: `13454` - // Minimum execution time: 17_127_000 picoseconds. - Weight::from_parts(17_519_000, 0) + // Minimum execution time: 17_443_000 picoseconds. + Weight::from_parts(17_964_000, 0) .saturating_add(Weight::from_parts(0, 13454)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -279,8 +247,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `93` // Estimated: `13458` - // Minimum execution time: 16_701_000 picoseconds. - Weight::from_parts(17_250_000, 0) + // Minimum execution time: 17_357_000 picoseconds. + Weight::from_parts(18_006_000, 0) .saturating_add(Weight::from_parts(0, 13458)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -291,8 +259,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `15946` - // Minimum execution time: 18_795_000 picoseconds. - Weight::from_parts(19_302_000, 0) + // Minimum execution time: 18_838_000 picoseconds. + Weight::from_parts(19_688_000, 0) .saturating_add(Weight::from_parts(0, 15946)) .saturating_add(T::DbWeight::get().reads(6)) } @@ -314,8 +282,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 25_007_000 picoseconds. - Weight::from_parts(25_786_000, 0) + // Minimum execution time: 25_517_000 picoseconds. + Weight::from_parts(26_131_000, 0) .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -326,8 +294,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `136` // Estimated: `11026` - // Minimum execution time: 11_534_000 picoseconds. - Weight::from_parts(11_798_000, 0) + // Minimum execution time: 11_587_000 picoseconds. + Weight::from_parts(11_963_000, 0) .saturating_add(Weight::from_parts(0, 11026)) .saturating_add(T::DbWeight::get().reads(4)) } @@ -337,8 +305,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `100` // Estimated: `13465` - // Minimum execution time: 17_357_000 picoseconds. - Weight::from_parts(17_629_000, 0) + // Minimum execution time: 17_490_000 picoseconds. + Weight::from_parts(18_160_000, 0) .saturating_add(Weight::from_parts(0, 13465)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -361,8 +329,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `13471` - // Minimum execution time: 33_487_000 picoseconds. - Weight::from_parts(34_033_000, 0) + // Minimum execution time: 34_088_000 picoseconds. + Weight::from_parts(34_598_000, 0) .saturating_add(Weight::from_parts(0, 13471)) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(4)) @@ -375,8 +343,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_688_000 picoseconds. - Weight::from_parts(3_854_000, 0) + // Minimum execution time: 3_566_000 picoseconds. + Weight::from_parts(3_754_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -387,8 +355,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 26_336_000 picoseconds. - Weight::from_parts(26_873_000, 0) + // Minimum execution time: 25_078_000 picoseconds. + Weight::from_parts(25_477_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -399,8 +367,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 34_633_000 picoseconds. - Weight::from_parts(35_171_000, 0) + // Minimum execution time: 34_661_000 picoseconds. + Weight::from_parts(35_411_000, 0) .saturating_add(Weight::from_parts(0, 3555)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs index 4870b4a52d7afb1bc303467b370f3cdd50114590..f147cd9653fe7e3eee03bf08a07cf6d3c1d94999 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs @@ -19,7 +19,6 @@ use super::{ ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, WeightToFee, XcmpQueue, }; -use crate::bridge_common_config::{DeliveryRewardInBalance, RequiredStakeForStakeAndSlash}; use frame_support::{ parameter_types, traits::{tokens::imbalance::ResolveTo, ConstU32, Contains, Equals, Everything, Nothing}, @@ -39,16 +38,16 @@ use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, + FungibleAdapter, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; -use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; +use xcm_executor::XcmExecutor; parameter_types! { pub const WestendLocation: Location = Location::parent(); @@ -119,73 +118,6 @@ impl Contains for ParentOrParentsPlurality { } } -/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly -/// account for proof size weights. -/// -/// Calls that are allowed through this filter must: -/// 1. Have a fixed weight; -/// 2. Cannot lead to another call being made; -/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. -pub struct SafeCallFilter; -impl Contains for SafeCallFilter { - fn contains(call: &RuntimeCall) -> bool { - #[cfg(feature = "runtime-benchmarks")] - { - if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { - return true - } - } - - // Allow to change dedicated storage items (called by governance-like) - match call { - RuntimeCall::System(frame_system::Call::set_storage { items }) - if items.iter().all(|(k, _)| { - k.eq(&DeliveryRewardInBalance::key()) | - k.eq(&RequiredStakeForStakeAndSlash::key()) - }) => - return true, - _ => (), - }; - - matches!( - call, - RuntimeCall::PolkadotXcm( - pallet_xcm::Call::force_xcm_version { .. } | - pallet_xcm::Call::force_default_xcm_version { .. } - ) | RuntimeCall::System( - frame_system::Call::set_heap_pages { .. } | - frame_system::Call::set_code { .. } | - frame_system::Call::set_code_without_checks { .. } | - frame_system::Call::authorize_upgrade { .. } | - frame_system::Call::authorize_upgrade_without_checks { .. } | - frame_system::Call::kill_prefix { .. }, - ) | RuntimeCall::ParachainSystem(..) | - RuntimeCall::Timestamp(..) | - RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection(..) | - RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | - RuntimeCall::XcmpQueue(..) | - RuntimeCall::MessageQueue(..) | - RuntimeCall::BridgeRococoGrandpa(pallet_bridge_grandpa::Call::< - Runtime, - crate::bridge_to_rococo_config::BridgeGrandpaRococoInstance, - >::initialize { .. }) | - RuntimeCall::BridgeRococoGrandpa(pallet_bridge_grandpa::Call::< - Runtime, - crate::bridge_to_rococo_config::BridgeGrandpaRococoInstance, - >::set_operating_mode { .. }) | - RuntimeCall::BridgeRococoParachains(pallet_bridge_parachains::Call::< - Runtime, - crate::bridge_to_rococo_config::BridgeParachainRococoInstance, - >::set_operating_mode { .. }) | - RuntimeCall::BridgeRococoMessages(pallet_bridge_messages::Call::< - Runtime, - crate::bridge_to_rococo_config::WithBridgeHubRococoMessagesInstance, - >::set_operating_mode { .. }) - ) - } -} - pub type Barrier = TrailingSetTopicAsId< DenyThenTry< DenyReserveTransferToRelayChain, @@ -207,6 +139,8 @@ pub type Barrier = TrailingSetTopicAsId< )>, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, ), UniversalLocation, ConstU32<8>, @@ -265,8 +199,8 @@ impl xcm_executor::Config for XcmConfig { >; type MessageExporter = (crate::bridge_to_rococo_config::ToBridgeHubRococoHaulBlobExporter,); type UniversalAliases = Nothing; - type CallDispatcher = WithOriginFilter; - type SafeCallFilter = SafeCallFilter; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; type Aliasers = Nothing; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index 988b10e1e2d8fac610c2feaec41d018012ec9fc3..836594140b2328081ff6c0de8cac40ea82dfb6f7 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -94,11 +94,10 @@ fn construct_and_apply_extrinsic( r.unwrap() } -fn construct_and_estimate_extrinsic_fee(batch: pallet_utility::Call) -> Balance { - let batch_call = RuntimeCall::Utility(batch); - let batch_info = batch_call.get_dispatch_info(); - let xt = construct_extrinsic(Alice, batch_call); - TransactionPayment::compute_fee(xt.encoded_size() as _, &batch_info, 0) +fn construct_and_estimate_extrinsic_fee(call: RuntimeCall) -> Balance { + let info = call.get_dispatch_info(); + let xt = construct_extrinsic(Alice, call); + TransactionPayment::compute_fee(xt.encoded_size() as _, &info, 0) } fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys { @@ -271,22 +270,6 @@ fn relayed_incoming_message_works() { ) } -#[test] -pub fn complex_relay_extrinsic_works() { - from_parachain::complex_relay_extrinsic_works::( - collator_session_keys(), - slot_durations(), - bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, - bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - SIBLING_PARACHAIN_ID, - BridgeHubRococoChainId::get(), - Westend, - XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO, - || (), - construct_and_apply_extrinsic, - ); -} - #[test] pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { bridge_hub_test_utils::check_sane_fees_values( @@ -309,12 +292,12 @@ pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { } #[test] -pub fn can_calculate_fee_for_complex_message_delivery_transaction() { +pub fn can_calculate_fee_for_standalone_message_delivery_transaction() { bridge_hub_test_utils::check_sane_fees_values( "bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds", bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds::get(), || { - from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< + from_parachain::can_calculate_fee_for_standalone_message_delivery_transaction::< RuntimeTestsAdapter, >(collator_session_keys(), construct_and_estimate_extrinsic_fee) }, @@ -328,12 +311,12 @@ pub fn can_calculate_fee_for_complex_message_delivery_transaction() { } #[test] -pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { +pub fn can_calculate_fee_for_standalone_message_confirmation_transaction() { bridge_hub_test_utils::check_sane_fees_values( "bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds", bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds::get(), || { - from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< + from_parachain::can_calculate_fee_for_standalone_message_confirmation_transaction::< RuntimeTestsAdapter, >(collator_session_keys(), construct_and_estimate_extrinsic_fee) }, diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs index 8aaaa4f59d7884ff211855a925638317a3b722ea..bfa2f0f50f94ca3ba2f663f9646be3165dd48220 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs @@ -41,6 +41,7 @@ use frame_system::pallet_prelude::BlockNumberFor; use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, }; +use sp_core::Get; use sp_keyring::AccountKeyring::*; use sp_runtime::{traits::Header as HeaderT, AccountId32}; use xcm::latest::prelude::*; @@ -162,7 +163,14 @@ pub fn relayed_incoming_message_works( test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< RuntimeHelper::MB, (), - >(lane_id, xcm.into(), message_nonce, message_destination, relay_header_number); + >( + lane_id, + xcm.into(), + message_nonce, + message_destination, + relay_header_number, + false, + ); let relay_chain_header_hash = relay_chain_header.hash(); vec![ @@ -202,6 +210,142 @@ pub fn relayed_incoming_message_works( ); } +/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, +/// with proofs (finality, message) independently submitted. +/// Finality proof is submitted for free in this test. +/// Also verifies relayer transaction signed extensions work as intended. +pub fn free_relay_extrinsic_works( + collator_session_key: CollatorSessionKeys, + slot_durations: SlotDurations, + runtime_para_id: u32, + bridged_chain_id: bp_runtime::ChainId, + sibling_parachain_id: u32, + local_relay_chain_id: NetworkId, + lane_id: LaneId, + prepare_configuration: impl Fn(), + construct_and_apply_extrinsic: fn( + sp_keyring::AccountKeyring, + RuntimeCallOf, + ) -> sp_runtime::DispatchOutcome, +) where + RuntimeHelper: WithRemoteGrandpaChainHelper, + RuntimeHelper::Runtime: pallet_balances::Config, + AccountIdOf: From, + RuntimeCallOf: From> + + From>, + UnderlyingChainOf>: ChainWithGrandpa, + >::SourceHeaderChain: + SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof< + HashOf>, + >, + >, +{ + // ensure that the runtime allows free header submissions + let free_headers_interval = >::FreeHeadersInterval::get() + .expect("this test requires runtime, configured to accept headers for free; qed"); + + helpers::relayed_incoming_message_works::< + RuntimeHelper::Runtime, + RuntimeHelper::AllPalletsWithoutSystem, + RuntimeHelper::MPI, + >( + collator_session_key, + slot_durations, + runtime_para_id, + sibling_parachain_id, + local_relay_chain_id, + construct_and_apply_extrinsic, + |relayer_id_at_this_chain, + relayer_id_at_bridged_chain, + message_destination, + message_nonce, + xcm| { + prepare_configuration(); + + // start with bridged relay chain block#0 + let initial_block_number = 0; + helpers::initialize_bridge_grandpa_pallet::( + test_data::initialization_data::( + initial_block_number, + ), + ); + + // free relay chain header is `0 + free_headers_interval` + let relay_header_number = initial_block_number + free_headers_interval; + + // relayer balance shall not change after relay and para header submissions + let initial_relayer_balance = + pallet_balances::Pallet::::free_balance( + relayer_id_at_this_chain.clone(), + ); + + // initialize the `FreeHeadersRemaining` storage value + pallet_bridge_grandpa::Pallet::::on_initialize( + 0u32.into(), + ); + + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + let (relay_chain_header, grandpa_justification, message_proof) = + test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< + RuntimeHelper::MB, + (), + >( + lane_id, + xcm.into(), + message_nonce, + message_destination, + relay_header_number.into(), + true, + ); + + let relay_chain_header_hash = relay_chain_header.hash(); + vec![ + ( + BridgeGrandpaCall::::submit_finality_proof { + finality_target: Box::new(relay_chain_header), + justification: grandpa_justification, + }.into(), + Box::new(( + helpers::VerifySubmitGrandpaFinalityProofOutcome::::expect_best_header_hash( + relay_chain_header_hash, + ), + helpers::VerifyRelayerBalance::::expect_relayer_balance( + relayer_id_at_this_chain.clone(), + initial_relayer_balance, + ), + )) + ), + ( + BridgeMessagesCall::::receive_messages_proof { + relayer_id_at_bridged_chain, + proof: message_proof, + messages_count: 1, + dispatch_weight: Weight::from_parts(1000000000, 0), + }.into(), + Box::new(( + helpers::VerifySubmitMessagesProofOutcome::::expect_last_delivered_nonce( + lane_id, + 1, + ), + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ), + )), + ), + ] + }, + ); +} + /// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, /// with proofs (finality, message) batched together in signed extrinsic. /// Also verifies relayer transaction signed extensions work as intended. @@ -265,7 +409,14 @@ pub fn complex_relay_extrinsic_works( test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< RuntimeHelper::MB, (), - >(lane_id, xcm.into(), message_nonce, message_destination, relay_header_number); + >( + lane_id, + xcm.into(), + message_nonce, + message_destination, + relay_header_number, + false, + ); let relay_chain_header_hash = relay_chain_header.hash(); vec![( @@ -344,6 +495,7 @@ where 1, [GlobalConsensus(Polkadot), Parachain(1_000)].into(), 1u32.into(), + false, ); // generate batch call that provides finality for bridged relay and parachains + message @@ -423,3 +575,109 @@ where compute_extrinsic_fee(batch) }) } + +/// Estimates transaction fee for default message delivery transaction from bridged GRANDPA chain. +pub fn can_calculate_fee_for_standalone_message_delivery_transaction( + collator_session_key: CollatorSessionKeys, + compute_extrinsic_fee: fn( + ::RuntimeCall, + ) -> u128, +) -> u128 +where + RuntimeHelper: WithRemoteGrandpaChainHelper, + RuntimeCallOf: + From>, + UnderlyingChainOf>: ChainWithGrandpa, + >::SourceHeaderChain: + SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof< + HashOf>, + >, + >, +{ + run_test::(collator_session_key, 1000, vec![], || { + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + // + // we don't care about parameter values here, apart from the XCM message size. But we + // do not need to have a large message here, because we're charging for every byte of + // the message additionally + let (_, _, message_proof) = + test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< + RuntimeHelper::MB, + (), + >( + LaneId::default(), + vec![Instruction::<()>::ClearOrigin; 1_024].into(), + 1, + [GlobalConsensus(Polkadot), Parachain(1_000)].into(), + 1u32.into(), + false, + ); + + let call = test_data::from_grandpa_chain::make_standalone_relayer_delivery_call::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + RuntimeHelper::MPI, + >( + message_proof, + helpers::relayer_id_at_bridged_chain::(), + ); + + compute_extrinsic_fee(call) + }) +} + +/// Estimates transaction fee for default message confirmation transaction (batched with required +/// proofs) from bridged parachain. +pub fn can_calculate_fee_for_standalone_message_confirmation_transaction( + collator_session_key: CollatorSessionKeys, + compute_extrinsic_fee: fn( + ::RuntimeCall, + ) -> u128, +) -> u128 +where + RuntimeHelper: WithRemoteGrandpaChainHelper, + AccountIdOf: From, + MessageThisChain: + bp_runtime::Chain>, + RuntimeCallOf: + From>, + UnderlyingChainOf>: ChainWithGrandpa, + >::TargetHeaderChain: + TargetHeaderChain< + XcmAsPlainPayload, + AccountIdOf, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof< + HashOf>>, + >, + >, +{ + run_test::(collator_session_key, 1000, vec![], || { + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + let unrewarded_relayers = UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 1, + ..Default::default() + }; + let (_, _, message_delivery_proof) = + test_data::from_grandpa_chain::make_complex_relayer_confirmation_proofs::< + RuntimeHelper::MB, + (), + >( + LaneId::default(), + 1u32.into(), + AccountId32::from(Alice.public()).into(), + unrewarded_relayers.clone(), + ); + + let call = test_data::from_grandpa_chain::make_standalone_relayer_confirmation_call::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + RuntimeHelper::MPI, + >(message_delivery_proof, unrewarded_relayers); + + compute_extrinsic_fee(call) + }) +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs index 72ec0718acf7759aedb02e91356fea73ee73e7e7..12ab382d9e0f6518afb93f118199170acb5f8cc6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs @@ -42,6 +42,7 @@ use frame_system::pallet_prelude::BlockNumberFor; use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, }; +use sp_core::Get; use sp_keyring::AccountKeyring::*; use sp_runtime::{traits::Header as HeaderT, AccountId32}; use xcm::latest::prelude::*; @@ -188,6 +189,7 @@ pub fn relayed_incoming_message_works( para_header_number, relay_header_number, bridged_para_id, + false, ); let parachain_head_hash = parachain_head.hash(); @@ -241,6 +243,177 @@ pub fn relayed_incoming_message_works( ); } +/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, +/// with proofs (finality, para heads, message) independently submitted. +/// Finality and para heads are submitted for free in this test. +/// Also verifies relayer transaction signed extensions work as intended. +pub fn free_relay_extrinsic_works( + collator_session_key: CollatorSessionKeys, + slot_durations: SlotDurations, + runtime_para_id: u32, + bridged_para_id: u32, + bridged_chain_id: bp_runtime::ChainId, + sibling_parachain_id: u32, + local_relay_chain_id: NetworkId, + lane_id: LaneId, + prepare_configuration: impl Fn(), + construct_and_apply_extrinsic: fn( + sp_keyring::AccountKeyring, + ::RuntimeCall, + ) -> sp_runtime::DispatchOutcome, +) where + RuntimeHelper: WithRemoteParachainHelper, + RuntimeHelper::Runtime: pallet_balances::Config, + AccountIdOf: From, + RuntimeCallOf: From> + + From> + + From>, + UnderlyingChainOf>: + bp_runtime::Chain + Parachain, + >::BridgedChain: + bp_runtime::Chain + ChainWithGrandpa, + >::SourceHeaderChain: + SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof< + HashOf>, + >, + >, +{ + // ensure that the runtime allows free header submissions + let free_headers_interval = >::FreeHeadersInterval::get() + .expect("this test requires runtime, configured to accept headers for free; qed"); + + helpers::relayed_incoming_message_works::< + RuntimeHelper::Runtime, + RuntimeHelper::AllPalletsWithoutSystem, + RuntimeHelper::MPI, + >( + collator_session_key, + slot_durations, + runtime_para_id, + sibling_parachain_id, + local_relay_chain_id, + construct_and_apply_extrinsic, + |relayer_id_at_this_chain, + relayer_id_at_bridged_chain, + message_destination, + message_nonce, + xcm| { + prepare_configuration(); + + // start with bridged relay chain block#0 + let initial_block_number = 0; + helpers::initialize_bridge_grandpa_pallet::( + test_data::initialization_data::( + initial_block_number, + ), + ); + + // free relay chain header is `0 + free_headers_interval` + let relay_header_number = initial_block_number + free_headers_interval; + // first parachain header is always submitted for free + let para_header_number = 1; + + // relayer balance shall not change after relay and para header submissions + let initial_relayer_balance = + pallet_balances::Pallet::::free_balance( + relayer_id_at_this_chain.clone(), + ); + + // initialize the `FreeHeadersRemaining` storage value + pallet_bridge_grandpa::Pallet::::on_initialize( + 0u32.into(), + ); + + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + let ( + relay_chain_header, + grandpa_justification, + parachain_head, + parachain_heads, + para_heads_proof, + message_proof, + ) = test_data::from_parachain::make_complex_relayer_delivery_proofs::< + >::BridgedChain, + RuntimeHelper::MB, + (), + >( + lane_id, + xcm.into(), + message_nonce, + message_destination, + para_header_number, + relay_header_number, + bridged_para_id, + true, + ); + + let parachain_head_hash = parachain_head.hash(); + let relay_chain_header_hash = relay_chain_header.hash(); + let relay_chain_header_number = *relay_chain_header.number(); + vec![ + ( + BridgeGrandpaCall::::submit_finality_proof { + finality_target: Box::new(relay_chain_header), + justification: grandpa_justification, + }.into(), + Box::new(( + helpers::VerifySubmitGrandpaFinalityProofOutcome::::expect_best_header_hash( + relay_chain_header_hash, + ), + helpers::VerifyRelayerBalance::::expect_relayer_balance( + relayer_id_at_this_chain.clone(), + initial_relayer_balance, + ), + )), + ), + ( + BridgeParachainsCall::::submit_parachain_heads { + at_relay_block: (relay_chain_header_number, relay_chain_header_hash), + parachains: parachain_heads, + parachain_heads_proof: para_heads_proof, + }.into(), + Box::new(( + helpers::VerifySubmitParachainHeaderProofOutcome::::expect_best_header_hash( + bridged_para_id, + parachain_head_hash, + ), + /*helpers::VerifyRelayerBalance::::expect_relayer_balance( + relayer_id_at_this_chain.clone(), + initial_relayer_balance, + ),*/ + )), + ), + ( + BridgeMessagesCall::::receive_messages_proof { + relayer_id_at_bridged_chain, + proof: message_proof, + messages_count: 1, + dispatch_weight: Weight::from_parts(1000000000, 0), + }.into(), + Box::new(( + helpers::VerifySubmitMessagesProofOutcome::::expect_last_delivered_nonce( + lane_id, + 1, + ), + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ), + )), + ), + ] + }, + ); +} + /// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, /// with proofs (finality, para heads, message) batched together in signed extrinsic. /// Also verifies relayer transaction signed extensions work as intended. @@ -325,6 +498,7 @@ pub fn complex_relay_extrinsic_works( para_header_number, relay_header_number, bridged_para_id, + false, ); let parachain_head_hash = parachain_head.hash(); @@ -428,6 +602,7 @@ where 1, 5, 1_000, + false, ); // generate batch call that provides finality for bridged relay and parachains + message @@ -527,3 +702,126 @@ where compute_extrinsic_fee(batch) }) } + +/// Estimates transaction fee for default message delivery transaction from bridged parachain. +pub fn can_calculate_fee_for_standalone_message_delivery_transaction( + collator_session_key: CollatorSessionKeys, + compute_extrinsic_fee: fn( + ::RuntimeCall, + ) -> u128, +) -> u128 +where + RuntimeHelper: WithRemoteParachainHelper, + RuntimeCallOf: + From>, + UnderlyingChainOf>: + bp_runtime::Chain + Parachain, + >::BridgedChain: + bp_runtime::Chain + ChainWithGrandpa, + >::SourceHeaderChain: + SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof< + HashOf>, + >, + >, +{ + run_test::(collator_session_key, 1000, vec![], || { + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + // + // we don't care about parameter values here, apart from the XCM message size. But we + // do not need to have a large message here, because we're charging for every byte of + // the message additionally + let ( + _, + _, + _, + _, + _, + message_proof, + ) = test_data::from_parachain::make_complex_relayer_delivery_proofs::< + >::BridgedChain, + RuntimeHelper::MB, + (), + >( + LaneId::default(), + vec![Instruction::<()>::ClearOrigin; 1_024].into(), + 1, + [GlobalConsensus(Polkadot), Parachain(1_000)].into(), + 1, + 5, + 1_000, + false, + ); + + let call = test_data::from_parachain::make_standalone_relayer_delivery_call::< + RuntimeHelper::Runtime, + RuntimeHelper::MPI, + _, + >( + message_proof, + helpers::relayer_id_at_bridged_chain::(), + ); + + compute_extrinsic_fee(call) + }) +} + +/// Estimates transaction fee for default message confirmation transaction (batched with required +/// proofs) from bridged parachain. +pub fn can_calculate_fee_for_standalone_message_confirmation_transaction( + collator_session_key: CollatorSessionKeys, + compute_extrinsic_fee: fn( + ::RuntimeCall, + ) -> u128, +) -> u128 +where + RuntimeHelper: WithRemoteParachainHelper, + AccountIdOf: From, + MessageThisChain: + bp_runtime::Chain>, + RuntimeCallOf: + From>, + UnderlyingChainOf>: + bp_runtime::Chain + Parachain, + >::BridgedChain: + bp_runtime::Chain + ChainWithGrandpa, + >::TargetHeaderChain: + TargetHeaderChain< + XcmAsPlainPayload, + AccountIdOf, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof< + HashOf>>, + >, + >, +{ + run_test::(collator_session_key, 1000, vec![], || { + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + let unrewarded_relayers = UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 1, + ..Default::default() + }; + let (_, _, _, _, _, message_delivery_proof) = + test_data::from_parachain::make_complex_relayer_confirmation_proofs::< + >::BridgedChain, + RuntimeHelper::MB, + (), + >( + LaneId::default(), + 1, + 5, + 1_000, + AccountId32::from(Alice.public()).into(), + unrewarded_relayers.clone(), + ); + + let call = test_data::from_parachain::make_standalone_relayer_confirmation_call::< + RuntimeHelper::Runtime, + RuntimeHelper::MPI, + >(message_delivery_proof, unrewarded_relayers); + + compute_extrinsic_fee(call) + }) +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs index 2b48f2e3d515f625532d9c5f50fabadb9a89517a..0ce049cd1c4630c55c244afbc8a72213cb83d6b9 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs @@ -193,6 +193,34 @@ where } } +/// Verifies that relayer balance is equal to given value. +pub struct VerifyRelayerBalance { + relayer: Runtime::AccountId, + balance: Runtime::Balance, +} + +impl VerifyRelayerBalance +where + Runtime: pallet_balances::Config, +{ + /// Expect given relayer balance after transaction. + pub fn expect_relayer_balance( + relayer: Runtime::AccountId, + balance: Runtime::Balance, + ) -> Box { + Box::new(Self { relayer, balance }) + } +} + +impl VerifyTransactionOutcome for VerifyRelayerBalance +where + Runtime: pallet_balances::Config, +{ + fn verify_outcome(&self) { + assert_eq!(pallet_balances::Pallet::::free_balance(&self.relayer), self.balance,); + } +} + /// Initialize bridge GRANDPA pallet. pub(crate) fn initialize_bridge_grandpa_pallet( init_data: bp_header_chain::InitializationData>, diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs index 017ec0fd54052ae0b00c19a2c474a8e265c768b0..e5d5e7cac96ba14f6abfdae792908352f40d3e31 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs @@ -121,6 +121,60 @@ where } } +/// Prepare a call with message proof. +pub fn make_standalone_relayer_delivery_call( + message_proof: FromBridgedChainMessagesProof>>, + relayer_id_at_bridged_chain: AccountIdOf>, +) -> Runtime::RuntimeCall +where + Runtime: pallet_bridge_grandpa::Config + + pallet_bridge_messages::Config< + MPI, + InboundPayload = XcmAsPlainPayload, + InboundRelayer = AccountIdOf>, + >, + MPI: 'static, + >::SourceHeaderChain: SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof>>, + >, + Runtime::RuntimeCall: From>, +{ + pallet_bridge_messages::Call::::receive_messages_proof { + relayer_id_at_bridged_chain, + proof: message_proof, + messages_count: 1, + dispatch_weight: Weight::from_parts(1000000000, 0), + } + .into() +} + +/// Prepare a call with message delivery proof. +pub fn make_standalone_relayer_confirmation_call( + message_delivery_proof: FromBridgedChainMessagesDeliveryProof< + HashOf>, + >, + relayers_state: UnrewardedRelayersState, +) -> Runtime::RuntimeCall +where + Runtime: pallet_bridge_grandpa::Config + + pallet_bridge_messages::Config, + MPI: 'static, + >::TargetHeaderChain: TargetHeaderChain< + XcmAsPlainPayload, + Runtime::AccountId, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof< + HashOf>, + >, + >, + Runtime::RuntimeCall: From>, +{ + pallet_bridge_messages::Call::::receive_messages_delivery_proof { + proof: message_delivery_proof, + relayers_state, + } + .into() +} + /// Prepare storage proofs of messages, stored at the (bridged) source GRANDPA chain. pub fn make_complex_relayer_delivery_proofs( lane_id: LaneId, @@ -128,6 +182,7 @@ pub fn make_complex_relayer_delivery_proofs( message_nonce: MessageNonce, message_destination: Junctions, header_number: BlockNumberOf>, + is_minimal_call: bool, ) -> ( HeaderOf>, GrandpaJustification>>, @@ -153,7 +208,7 @@ where let (header, justification) = make_complex_bridged_grandpa_header_proof::< MessageBridgedChain, - >(state_root, header_number); + >(state_root, header_number, is_minimal_call); let message_proof = FromBridgedChainMessagesProof { bridged_header_hash: header.hash(), @@ -200,8 +255,11 @@ where StorageProofSize::Minimal(0), ); - let (header, justification) = - make_complex_bridged_grandpa_header_proof::(state_root, header_number); + let (header, justification) = make_complex_bridged_grandpa_header_proof::( + state_root, + header_number, + false, + ); let message_delivery_proof = FromBridgedChainMessagesDeliveryProof { bridged_header_hash: header.hash(), @@ -216,6 +274,7 @@ where pub fn make_complex_bridged_grandpa_header_proof( state_root: HashOf, header_number: BlockNumberOf, + is_minimal_call: bool, ) -> (HeaderOf, GrandpaJustification>) where BridgedChain: ChainWithGrandpa, @@ -229,7 +288,9 @@ where // `submit_finality_proof` call size would be close to maximal expected (and refundable) let extra_bytes_required = maximal_expected_submit_finality_proof_call_size::() .saturating_sub(header.encoded_size()); - header.digest_mut().push(DigestItem::Other(vec![42; extra_bytes_required])); + if !is_minimal_call { + header.digest_mut().push(DigestItem::Other(vec![42; extra_bytes_required])); + } let justification = make_default_justification(&header); (header, justification) diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs index 932ba231239973db8b46ccea56faacc5628a4ffb..5d3cba4e53b5ec7ec9cd2e6141e6e95aa8928970 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs @@ -159,6 +159,52 @@ where } } +/// Prepare a call with message proof. +pub fn make_standalone_relayer_delivery_call( + message_proof: FromBridgedChainMessagesProof, + relayer_id_at_bridged_chain: InboundRelayer, +) -> Runtime::RuntimeCall where + Runtime: pallet_bridge_messages::Config< + MPI, + InboundPayload = XcmAsPlainPayload, + InboundRelayer = InboundRelayer, + >, + MPI: 'static, + Runtime::RuntimeCall: From>, + <>::SourceHeaderChain as SourceHeaderChain>::MessagesProof: + From>, +{ + pallet_bridge_messages::Call::::receive_messages_proof { + relayer_id_at_bridged_chain: relayer_id_at_bridged_chain.into(), + proof: message_proof.into(), + messages_count: 1, + dispatch_weight: Weight::from_parts(1000000000, 0), + } + .into() +} + +/// Prepare a call with message delivery proof. +pub fn make_standalone_relayer_confirmation_call( + message_delivery_proof: FromBridgedChainMessagesDeliveryProof, + relayers_state: UnrewardedRelayersState, +) -> Runtime::RuntimeCall +where + Runtime: pallet_bridge_messages::Config, + MPI: 'static, + Runtime::RuntimeCall: From>, + >::TargetHeaderChain: TargetHeaderChain< + XcmAsPlainPayload, + Runtime::AccountId, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof, + >, +{ + pallet_bridge_messages::Call::::receive_messages_delivery_proof { + proof: message_delivery_proof, + relayers_state, + } + .into() +} + /// Prepare storage proofs of messages, stored at the source chain. pub fn make_complex_relayer_delivery_proofs( lane_id: LaneId, @@ -168,6 +214,7 @@ pub fn make_complex_relayer_delivery_proofs ( HeaderOf, GrandpaJustification>, @@ -201,6 +248,7 @@ where para_header_number, relay_header_number, bridged_para_id, + is_minimal_call, ); let message_proof = FromBridgedChainMessagesProof { @@ -266,6 +314,7 @@ where para_header_number, relay_header_number, bridged_para_id, + false, ); let message_delivery_proof = FromBridgedChainMessagesDeliveryProof { @@ -290,6 +339,7 @@ pub fn make_complex_bridged_parachain_heads_proof( para_header_number: u32, relay_header_number: BlockNumberOf, bridged_para_id: u32, + is_minimal_call: bool, ) -> ( HeaderOf, GrandpaJustification>, @@ -319,9 +369,12 @@ where )]); assert_eq!(bridged_para_head.hash(), parachain_heads[0].1); - let (relay_chain_header, justification) = make_complex_bridged_grandpa_header_proof::< - BridgedRelayChain, - >(relay_state_root, relay_header_number); + let (relay_chain_header, justification) = + make_complex_bridged_grandpa_header_proof::( + relay_state_root, + relay_header_number, + is_minimal_call, + ); (relay_chain_header, justification, bridged_para_head, parachain_heads, para_heads_proof) } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/build.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/build.rs index 60f8a125129ff1344a1799246e931acdb1d139d5..239ccac19ec7778039fb1ee56f4e772b3ddd3711 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/build.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/build.rs @@ -15,11 +15,7 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs index 3816d2ed848ed51740283ffea31e9f7e53c01f1a..94765287637b57d47c588d9a4359666d1b54f509 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs @@ -21,13 +21,16 @@ mod tracks; use crate::{ weights, xcm_config::{FellowshipAdminBodyId, LocationToAccountId, TreasurerBodyId, UsdtAssetHub}, - AccountId, AssetRate, Balance, Balances, FellowshipReferenda, GovernanceLocation, Preimage, - Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Scheduler, WestendTreasuryAccount, DAYS, + AccountId, AssetRate, Balance, Balances, FellowshipReferenda, GovernanceLocation, + ParachainInfo, Preimage, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Scheduler, + WestendTreasuryAccount, DAYS, }; +use cumulus_primitives_core::ParaId; use frame_support::{ parameter_types, traits::{ - EitherOf, EitherOfDiverse, MapSuccess, NeverEnsureOrigin, OriginTrait, TryWithMorphedArg, + tokens::UnityOrOuterConversion, EitherOf, EitherOfDiverse, FromContains, MapSuccess, + NeverEnsureOrigin, OriginTrait, TryWithMorphedArg, }, PalletId, }; @@ -40,10 +43,10 @@ use pallet_ranked_collective::EnsureOfRank; use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; use parachains_common::impls::ToParentTreasury; use polkadot_runtime_common::impls::{ - LocatableAssetConverter, VersionedLocatableAsset, VersionedLocationConverter, + ContainsParts, LocatableAssetConverter, VersionedLocatableAsset, VersionedLocationConverter, }; use sp_arithmetic::Permill; -use sp_core::{ConstU128, ConstU32}; +use sp_core::{ConstU128, ConstU32, ConstU8}; use sp_runtime::traits::{ConstU16, ConvertToValue, IdentityLookup, Replace, TakeFirst}; use testnet_parachains_constants::westend::{account, currency::GRAND}; use westend_runtime_constants::time::HOURS; @@ -263,6 +266,7 @@ parameter_types! { // The asset's interior location for the paying account. This is the Fellowship Treasury // pallet instance (which sits at index 65). pub FellowshipTreasuryInteriorLocation: InteriorLocation = PalletInstance(65).into(); + pub SelfParaId: ParaId = ParachainInfo::parachain_id(); } #[cfg(feature = "runtime-benchmarks")] @@ -345,7 +349,15 @@ impl pallet_treasury::Config for Runtime { type Paymaster = FellowshipTreasuryPaymaster; #[cfg(feature = "runtime-benchmarks")] type Paymaster = PayWithEnsure>>; - type BalanceConverter = AssetRate; + type BalanceConverter = UnityOrOuterConversion< + ContainsParts< + FromContains< + xcm_builder::IsSiblingSystemParachain, + xcm_builder::IsParentsOnly>, + >, + >, + AssetRate, + >; type PayoutPeriod = ConstU32<{ 30 * DAYS }>; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = polkadot_runtime_common::impls::benchmarks::TreasuryArguments< diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index c599ba37f128bb01adaa1e4dc3203a1ae15def9d..7274e9acdcd6cc89eb48c820dd9cab8228cf6113 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -722,7 +722,7 @@ pub type UncheckedExtrinsic = /// `OnRuntimeUpgrade`. Included migrations must be idempotent. type Migrations = ( // unreleased - pallet_collator_selection::migration::v1::MigrateToV1, + pallet_collator_selection::migration::v2::MigrationToV2, // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, // permanent diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs index 0edd5dfff2b8b714c6de0a34dcd095787673d39b..5d427d850046ff030c6c5b6247426849227e7ea1 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -64,30 +64,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 21_911_000 picoseconds. - Weight::from_parts(22_431_000, 0) - .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) - /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) - /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn send_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 22_143_000 picoseconds. - Weight::from_parts(22_843_000, 0) + // Minimum execution time: 21_813_000 picoseconds. + Weight::from_parts(22_332_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) @@ -112,8 +90,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `214` // Estimated: `3679` - // Minimum execution time: 96_273_000 picoseconds. - Weight::from_parts(98_351_000, 0) + // Minimum execution time: 93_243_000 picoseconds. + Weight::from_parts(95_650_000, 0) .saturating_add(Weight::from_parts(0, 3679)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -148,8 +126,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `214` // Estimated: `3679` - // Minimum execution time: 95_571_000 picoseconds. - Weight::from_parts(96_251_000, 0) + // Minimum execution time: 96_199_000 picoseconds. + Weight::from_parts(98_620_000, 0) .saturating_add(Weight::from_parts(0, 3679)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -164,24 +142,14 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn execute_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_227_000 picoseconds. - Weight::from_parts(6_419_000, 0) + // Minimum execution time: 6_442_000 picoseconds. + Weight::from_parts(6_682_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -191,8 +159,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_851_000 picoseconds. - Weight::from_parts(1_940_000, 0) + // Minimum execution time: 1_833_000 picoseconds. + Weight::from_parts(1_973_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -218,8 +186,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 27_449_000 picoseconds. - Weight::from_parts(28_513_000, 0) + // Minimum execution time: 27_318_000 picoseconds. + Weight::from_parts(28_224_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -244,8 +212,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 29_477_000 picoseconds. - Weight::from_parts(30_251_000, 0) + // Minimum execution time: 29_070_000 picoseconds. + Weight::from_parts(30_205_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -256,8 +224,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_894_000 picoseconds. - Weight::from_parts(2_009_000, 0) + // Minimum execution time: 1_904_000 picoseconds. + Weight::from_parts(2_033_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -267,8 +235,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `159` // Estimated: `13524` - // Minimum execution time: 17_991_000 picoseconds. - Weight::from_parts(18_651_000, 0) + // Minimum execution time: 18_348_000 picoseconds. + Weight::from_parts(18_853_000, 0) .saturating_add(Weight::from_parts(0, 13524)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -279,8 +247,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `163` // Estimated: `13528` - // Minimum execution time: 18_321_000 picoseconds. - Weight::from_parts(18_701_000, 0) + // Minimum execution time: 17_964_000 picoseconds. + Weight::from_parts(18_548_000, 0) .saturating_add(Weight::from_parts(0, 13528)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -291,8 +259,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `173` // Estimated: `16013` - // Minimum execution time: 19_762_000 picoseconds. - Weight::from_parts(20_529_000, 0) + // Minimum execution time: 19_708_000 picoseconds. + Weight::from_parts(20_157_000, 0) .saturating_add(Weight::from_parts(0, 16013)) .saturating_add(T::DbWeight::get().reads(6)) } @@ -314,8 +282,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 26_927_000 picoseconds. - Weight::from_parts(27_629_000, 0) + // Minimum execution time: 26_632_000 picoseconds. + Weight::from_parts(27_314_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -326,8 +294,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `206` // Estimated: `11096` - // Minimum execution time: 11_957_000 picoseconds. - Weight::from_parts(12_119_000, 0) + // Minimum execution time: 11_929_000 picoseconds. + Weight::from_parts(12_304_000, 0) .saturating_add(Weight::from_parts(0, 11096)) .saturating_add(T::DbWeight::get().reads(4)) } @@ -337,8 +305,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `170` // Estimated: `13535` - // Minimum execution time: 17_942_000 picoseconds. - Weight::from_parts(18_878_000, 0) + // Minimum execution time: 18_599_000 picoseconds. + Weight::from_parts(19_195_000, 0) .saturating_add(Weight::from_parts(0, 13535)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -361,8 +329,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `212` // Estimated: `13577` - // Minimum execution time: 35_640_000 picoseconds. - Weight::from_parts(36_340_000, 0) + // Minimum execution time: 35_524_000 picoseconds. + Weight::from_parts(36_272_000, 0) .saturating_add(Weight::from_parts(0, 13577)) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(4)) @@ -376,7 +344,7 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Measured: `103` // Estimated: `1588` // Minimum execution time: 4_044_000 picoseconds. - Weight::from_parts(4_229_000, 0) + Weight::from_parts(4_238_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -387,8 +355,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 26_262_000 picoseconds. - Weight::from_parts(26_842_000, 0) + // Minimum execution time: 25_741_000 picoseconds. + Weight::from_parts(26_301_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -399,8 +367,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `160` // Estimated: `3625` - // Minimum execution time: 36_775_000 picoseconds. - Weight::from_parts(37_265_000, 0) + // Minimum execution time: 35_925_000 picoseconds. + Weight::from_parts(36_978_000, 0) .saturating_add(Weight::from_parts(0, 3625)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs index 21ccd3b9cdb0b0474eb2023cc2509dbee66b1861..84697c3e36341218ad959f575262c0903f5ae96d 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -35,16 +35,17 @@ use polkadot_runtime_common::xcm_sender::ExponentialPrice; use westend_runtime_constants::xcm as xcm_constants; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, - IsConcrete, LocatableAssetId, OriginToPluralityVoice, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, XcmFeeToAccount, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, + FrameTransactionalProcessor, FungibleAdapter, IsConcrete, LocatableAssetId, + OriginToPluralityVoice, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; -use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; +use xcm_executor::XcmExecutor; parameter_types! { pub const WndLocation: Location = Location::parent(); @@ -138,83 +139,6 @@ impl Contains for ParentOrParentsPlurality { } } -/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly -/// account for proof size weights. -/// -/// Calls that are allowed through this filter must: -/// 1. Have a fixed weight; -/// 2. Cannot lead to another call being made; -/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. -pub struct SafeCallFilter; -impl Contains for SafeCallFilter { - fn contains(call: &RuntimeCall) -> bool { - #[cfg(feature = "runtime-benchmarks")] - { - if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { - return true - } - } - - matches!( - call, - RuntimeCall::System( - frame_system::Call::set_heap_pages { .. } | - frame_system::Call::set_code { .. } | - frame_system::Call::set_code_without_checks { .. } | - frame_system::Call::authorize_upgrade { .. } | - frame_system::Call::authorize_upgrade_without_checks { .. } | - frame_system::Call::kill_prefix { .. }, - ) | RuntimeCall::ParachainSystem(..) | - RuntimeCall::Timestamp(..) | - RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection(..) | - RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | - RuntimeCall::PolkadotXcm( - pallet_xcm::Call::force_xcm_version { .. } | - pallet_xcm::Call::force_default_xcm_version { .. } - ) | RuntimeCall::XcmpQueue(..) | - RuntimeCall::MessageQueue(..) | - RuntimeCall::Alliance( - // `init_members` accepts unbounded vecs as arguments, - // but the call can be initiated only by root origin. - pallet_alliance::Call::init_members { .. } | - pallet_alliance::Call::vote { .. } | - pallet_alliance::Call::disband { .. } | - pallet_alliance::Call::set_rule { .. } | - pallet_alliance::Call::announce { .. } | - pallet_alliance::Call::remove_announcement { .. } | - pallet_alliance::Call::join_alliance { .. } | - pallet_alliance::Call::nominate_ally { .. } | - pallet_alliance::Call::elevate_ally { .. } | - pallet_alliance::Call::give_retirement_notice { .. } | - pallet_alliance::Call::retire { .. } | - pallet_alliance::Call::kick_member { .. } | - pallet_alliance::Call::close { .. } | - pallet_alliance::Call::abdicate_fellow_status { .. }, - ) | RuntimeCall::AllianceMotion( - pallet_collective::Call::vote { .. } | - pallet_collective::Call::disapprove_proposal { .. } | - pallet_collective::Call::close { .. }, - ) | RuntimeCall::FellowshipCollective( - pallet_ranked_collective::Call::add_member { .. } | - pallet_ranked_collective::Call::promote_member { .. } | - pallet_ranked_collective::Call::demote_member { .. } | - pallet_ranked_collective::Call::remove_member { .. }, - ) | RuntimeCall::FellowshipCore( - pallet_core_fellowship::Call::bump { .. } | - pallet_core_fellowship::Call::set_params { .. } | - pallet_core_fellowship::Call::set_active { .. } | - pallet_core_fellowship::Call::approve { .. } | - pallet_core_fellowship::Call::induct { .. } | - pallet_core_fellowship::Call::promote { .. } | - pallet_core_fellowship::Call::offboard { .. } | - pallet_core_fellowship::Call::submit_evidence { .. } | - pallet_core_fellowship::Call::import { .. }, - ) - ) - } -} - pub type Barrier = TrailingSetTopicAsId< DenyThenTry< DenyReserveTransferToRelayChain, @@ -233,6 +157,8 @@ pub type Barrier = TrailingSetTopicAsId< AllowExplicitUnpaidExecutionFrom, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, ), UniversalLocation, ConstU32<8>, @@ -287,8 +213,8 @@ impl xcm_executor::Config for XcmConfig { >; type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = WithOriginFilter; - type SafeCallFilter = SafeCallFilter; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; type Aliasers = Nothing; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/build.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/build.rs index 60f8a125129ff1344a1799246e931acdb1d139d5..239ccac19ec7778039fb1ee56f4e772b3ddd3711 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/build.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/build.rs @@ -15,11 +15,7 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs index 171ac6a9528f134d9c22548500805ef36e9504f9..fcd786711bbe90096f2ef5b8d427cec23879027b 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs @@ -71,7 +71,7 @@ impl Config for Runtime { type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; type MaxDelegateDependencies = ConstU32<32>; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Migrations = (); + type Migrations = (pallet_contracts::migration::v16::Migration,); type RuntimeHoldReason = RuntimeHoldReason; type Debug = (); type Environment = (); diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index efa26fcbc22d534dd4573ab15d8052a7179c77a0..988195d88d876c49941463e8103f08d6c8f3ab10 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -98,6 +98,8 @@ pub type UncheckedExtrinsic = /// Migrations to apply on runtime upgrade. pub type Migrations = ( + pallet_collator_selection::migration::v1::MigrateToV1, + pallet_collator_selection::migration::v2::MigrationToV2, cumulus_pallet_parachain_system::migration::Migration, cumulus_pallet_xcmp_queue::migration::v2::MigrationToV2, cumulus_pallet_xcmp_queue::migration::v3::MigrationToV3, diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs index 3863ea5022f9142b230f5dba9a3840f858daa127..ac15ac5b0f0f524c560f50dcd5ea7a73e2051ae3 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs @@ -38,22 +38,22 @@ use sp_runtime::traits::AccountIdConversion; use testnet_parachains_constants::rococo::currency::CENTS; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, - IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, + FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, ParentAsSuperuser, + ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; parameter_types! { pub const RelayLocation: Location = Location::parent(); - pub const RelayNetwork: Option = None; + pub const RelayNetwork: NetworkId = NetworkId::Rococo; pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - pub UniversalLocation: InteriorLocation = Parachain(ParachainInfo::parachain_id().into()).into(); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())].into(); pub const ExecutiveBody: BodyId = BodyId::Executive; pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); pub RelayTreasuryLocation: Location = (Parent, PalletInstance(rococo_runtime_constants::TREASURY_PALLET_ID)).into(); @@ -78,7 +78,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -pub type CurrencyTransactor = FungibleAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -149,6 +149,8 @@ pub type Barrier = TrailingSetTopicAsId< )>, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, ), UniversalLocation, ConstU32<8>, @@ -171,7 +173,7 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = NativeAsset; type IsTeleporter = TrustedTeleporter; diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index 9959c15b11107ecba1470ab52cbef16fc6f22c25..895890da7dd60dae5c5a5fd257184fa734a68808 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -108,7 +108,9 @@ pub type UncheckedExtrinsic = /// Migrations to apply on runtime upgrade. pub type Migrations = ( + pallet_collator_selection::migration::v2::MigrationToV2, cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + pallet_broker::migration::MigrateV0ToV1, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); @@ -719,11 +721,20 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { - type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< - xcm_config::XcmConfig, - ExistentialDepositAsset, - xcm_config::PriceForParentDelivery, - >; + type DeliveryHelper = ( + cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >, + polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + PriceForSiblingParachainDelivery, + RandomParaId, + ParachainSystem, + > + ); fn reachable_dest() -> Option { Some(Parent.into()) @@ -741,8 +752,21 @@ impl_runtime_apis! { } fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { - // Reserve transfers are disabled - None + // Coretime chain can reserve transfer regions to some random parachain. + + // Properties of a mock region: + let core = 0; + let begin = 0; + let end = 42; + + let region_id = pallet_broker::Pallet::::issue(core, begin, end, None, None); + Some(( + Asset { + fun: NonFungible(Index(region_id.into())), + id: AssetId(xcm_config::BrokerPalletLocation::get()) + }, + ParentThen(Parachain(RandomParaId::get().into()).into()).into(), + )) } fn get_asset() -> Asset { @@ -758,15 +782,25 @@ impl_runtime_apis! { RocRelayLocation::get(), ExistentialDeposit::get() ).into()); + pub const RandomParaId: ParaId = ParaId::new(43211234); } impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = xcm_config::XcmConfig; - type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< - xcm_config::XcmConfig, - ExistentialDepositAsset, - xcm_config::PriceForParentDelivery, - >; + type DeliveryHelper = ( + cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >, + polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + PriceForSiblingParachainDelivery, + RandomParaId, + ParachainSystem, + > + ); type AccountIdConverter = xcm_config::LocationToAccountId; fn valid_destination() -> Result { Ok(RocRelayLocation::get()) diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs index df0044089c8f6fb558d368e2f006a6a4fbd9fb97..c5d315467c1ed8b2aabf7ac18abe10931a02951b 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -62,28 +62,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 18_767_000 picoseconds. - Weight::from_parts(19_420_000, 0) - .saturating_add(Weight::from_parts(0, 3539)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) - /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) - /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn send_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `74` - // Estimated: `3539` - // Minimum execution time: 19_184_000 picoseconds. - Weight::from_parts(19_695_000, 0) + // Minimum execution time: 35_051_000 picoseconds. + Weight::from_parts(35_200_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -104,8 +84,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 58_120_000 picoseconds. - Weight::from_parts(59_533_000, 0) + // Minimum execution time: 56_235_000 picoseconds. + Weight::from_parts(58_178_000, 0) .saturating_add(Weight::from_parts(0, 3571)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) @@ -140,24 +120,14 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn execute_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_074_000 picoseconds. - Weight::from_parts(6_398_000, 0) + // Minimum execution time: 6_226_000 picoseconds. + Weight::from_parts(6_403_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -167,8 +137,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_036_000 picoseconds. - Weight::from_parts(2_180_000, 0) + // Minimum execution time: 2_020_000 picoseconds. + Weight::from_parts(2_100_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -192,8 +162,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 25_014_000 picoseconds. - Weight::from_parts(25_374_000, 0) + // Minimum execution time: 24_387_000 picoseconds. + Weight::from_parts(24_814_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -216,8 +186,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `292` // Estimated: `3757` - // Minimum execution time: 27_616_000 picoseconds. - Weight::from_parts(28_499_000, 0) + // Minimum execution time: 27_039_000 picoseconds. + Weight::from_parts(27_693_000, 0) .saturating_add(Weight::from_parts(0, 3757)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -228,8 +198,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_061_000 picoseconds. - Weight::from_parts(2_153_000, 0) + // Minimum execution time: 1_920_000 picoseconds. + Weight::from_parts(2_082_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -239,8 +209,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `89` // Estimated: `13454` - // Minimum execution time: 16_592_000 picoseconds. - Weight::from_parts(16_900_000, 0) + // Minimum execution time: 17_141_000 picoseconds. + Weight::from_parts(17_500_000, 0) .saturating_add(Weight::from_parts(0, 13454)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -251,8 +221,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `93` // Estimated: `13458` - // Minimum execution time: 16_694_000 picoseconds. - Weight::from_parts(16_905_000, 0) + // Minimum execution time: 17_074_000 picoseconds. + Weight::from_parts(17_431_000, 0) .saturating_add(Weight::from_parts(0, 13458)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -263,8 +233,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `15946` - // Minimum execution time: 17_779_000 picoseconds. - Weight::from_parts(18_490_000, 0) + // Minimum execution time: 19_139_000 picoseconds. + Weight::from_parts(19_474_000, 0) .saturating_add(Weight::from_parts(0, 15946)) .saturating_add(T::DbWeight::get().reads(6)) } @@ -284,8 +254,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `142` // Estimated: `6082` - // Minimum execution time: 24_526_000 picoseconds. - Weight::from_parts(25_182_000, 0) + // Minimum execution time: 24_346_000 picoseconds. + Weight::from_parts(25_318_000, 0) .saturating_add(Weight::from_parts(0, 6082)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) @@ -296,8 +266,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `136` // Estimated: `11026` - // Minimum execution time: 10_467_000 picoseconds. - Weight::from_parts(10_934_000, 0) + // Minimum execution time: 11_777_000 picoseconds. + Weight::from_parts(12_051_000, 0) .saturating_add(Weight::from_parts(0, 11026)) .saturating_add(T::DbWeight::get().reads(4)) } @@ -307,8 +277,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `100` // Estimated: `13465` - // Minimum execution time: 16_377_000 picoseconds. - Weight::from_parts(17_114_000, 0) + // Minimum execution time: 17_538_000 picoseconds. + Weight::from_parts(17_832_000, 0) .saturating_add(Weight::from_parts(0, 13465)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -329,8 +299,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `142` // Estimated: `13507` - // Minimum execution time: 32_575_000 picoseconds. - Weight::from_parts(33_483_000, 0) + // Minimum execution time: 33_623_000 picoseconds. + Weight::from_parts(34_186_000, 0) .saturating_add(Weight::from_parts(0, 13507)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) @@ -343,8 +313,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_604_000 picoseconds. - Weight::from_parts(3_744_000, 0) + // Minimum execution time: 3_363_000 picoseconds. + Weight::from_parts(3_511_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -355,8 +325,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 23_983_000 picoseconds. - Weight::from_parts(24_404_000, 0) + // Minimum execution time: 23_969_000 picoseconds. + Weight::from_parts(24_347_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -367,8 +337,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 34_446_000 picoseconds. - Weight::from_parts(35_465_000, 0) + // Minimum execution time: 34_071_000 picoseconds. + Weight::from_parts(35_031_000, 0) .saturating_add(Weight::from_parts(0, 3555)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs index b0b276128272b66f5b5866f3e40bec957d717e42..9095b5b1caaa6f7655ec1f2a0ab6302b20367679 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs @@ -39,16 +39,16 @@ use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, - NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, + FungibleAdapter, IsConcrete, NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; -use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; +use xcm_executor::XcmExecutor; parameter_types! { pub const RocRelayLocation: Location = Location::parent(); @@ -77,7 +77,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -pub type CurrencyTransactor = FungibleAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -106,7 +106,7 @@ pub type RegionTransactor = NonFungibleAdapter< >; /// Means for transacting assets on this chain. -pub type AssetTransactors = (CurrencyTransactor, RegionTransactor); +pub type AssetTransactors = (FungibleTransactor, RegionTransactor); /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, /// ready for dispatching a transaction with XCM's `Transact`. There is an `OriginKind` that can @@ -139,49 +139,6 @@ impl Contains for ParentOrParentsPlurality { } } -/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly -/// account for proof size weights. -/// -/// Calls that are allowed through this filter must: -/// 1. Have a fixed weight; -/// 2. Cannot lead to another call being made; -/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. -pub struct SafeCallFilter; -impl Contains for SafeCallFilter { - fn contains(call: &RuntimeCall) -> bool { - #[cfg(feature = "runtime-benchmarks")] - { - if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { - return true - } - } - - matches!( - call, - RuntimeCall::PolkadotXcm( - pallet_xcm::Call::force_xcm_version { .. } | - pallet_xcm::Call::force_default_xcm_version { .. } - ) | RuntimeCall::System( - frame_system::Call::set_heap_pages { .. } | - frame_system::Call::set_code { .. } | - frame_system::Call::set_code_without_checks { .. } | - frame_system::Call::authorize_upgrade { .. } | - frame_system::Call::authorize_upgrade_without_checks { .. } | - frame_system::Call::kill_prefix { .. } | - // Should not be in Polkadot/Kusama. Here in order to speed up testing. - frame_system::Call::set_storage { .. }, - ) | RuntimeCall::ParachainSystem(..) | - RuntimeCall::Timestamp(..) | - RuntimeCall::Balances(..) | - RuntimeCall::Sudo(..) | - RuntimeCall::CollatorSelection(..) | - RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | - RuntimeCall::XcmpQueue(..) | - RuntimeCall::Broker(..) - ) - } -} - pub type Barrier = TrailingSetTopicAsId< DenyThenTry< DenyReserveTransferToRelayChain, @@ -199,6 +156,8 @@ pub type Barrier = TrailingSetTopicAsId< AllowExplicitUnpaidExecutionFrom, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, ), UniversalLocation, ConstU32<8>, @@ -258,8 +217,8 @@ impl xcm_executor::Config for XcmConfig { >; type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = WithOriginFilter; - type SafeCallFilter = SafeCallFilter; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; type Aliasers = Nothing; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); @@ -293,7 +252,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecuteFilter = Everything; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = Nothing; // This parachain is not meant as a reserve location. + type XcmReserveTransferFilter = Everything; type Weigher = WeightInfoBounds< crate::weights::xcm::CoretimeRococoXcmWeight, RuntimeCall, diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 1ee8c3bc0ec3d4f9b3201659cca5f388a05e78e1..9d080087d5dbe07d8cb75e76dc203f6fd06b20da 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -108,7 +108,9 @@ pub type UncheckedExtrinsic = /// Migrations to apply on runtime upgrade. pub type Migrations = ( + pallet_collator_selection::migration::v2::MigrationToV2, cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + pallet_broker::migration::MigrateV0ToV1, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); @@ -218,6 +220,7 @@ impl pallet_authorship::Config for Runtime { parameter_types! { pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; + pub const RandomParaId: ParaId = ParaId::new(43211234); } impl pallet_balances::Config for Runtime { @@ -710,11 +713,20 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { - type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< - xcm_config::XcmConfig, - ExistentialDepositAsset, - xcm_config::PriceForParentDelivery, - >; + type DeliveryHelper = ( + cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >, + polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + PriceForSiblingParachainDelivery, + RandomParaId, + ParachainSystem, + > + ); fn reachable_dest() -> Option { Some(Parent.into()) @@ -732,8 +744,21 @@ impl_runtime_apis! { } fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { - // Reserve transfers are disabled - None + // Coretime chain can reserve transfer regions to some random parachain. + + // Properties of a mock region: + let core = 0; + let begin = 0; + let end = 42; + + let region_id = pallet_broker::Pallet::::issue(core, begin, end, None, None); + Some(( + Asset { + fun: NonFungible(Index(region_id.into())), + id: AssetId(xcm_config::BrokerPalletLocation::get()) + }, + ParentThen(Parachain(RandomParaId::get().into()).into()).into(), + )) } fn get_asset() -> Asset { @@ -753,11 +778,22 @@ impl_runtime_apis! { impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = xcm_config::XcmConfig; - type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< - xcm_config::XcmConfig, - ExistentialDepositAsset, - xcm_config::PriceForParentDelivery, - >; + + type DeliveryHelper = ( + cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >, + polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + PriceForSiblingParachainDelivery, + RandomParaId, + ParachainSystem, + > + ); + type AccountIdConverter = xcm_config::LocationToAccountId; fn valid_destination() -> Result { Ok(TokenRelayLocation::get()) diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs index a1701c5f1c2ced1bbdcac49863cc05ceb28a019f..0082db3099d029c976779af8600bcaf4410e8a2f 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -62,28 +62,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 17_681_000 picoseconds. - Weight::from_parts(18_350_000, 0) - .saturating_add(Weight::from_parts(0, 3539)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) - /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) - /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn send_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `74` - // Estimated: `3539` - // Minimum execution time: 18_091_000 picoseconds. - Weight::from_parts(18_327_000, 0) + // Minimum execution time: 18_410_000 picoseconds. + Weight::from_parts(18_657_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -104,8 +84,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 54_943_000 picoseconds. - Weight::from_parts(56_519_000, 0) + // Minimum execution time: 56_616_000 picoseconds. + Weight::from_parts(57_751_000, 0) .saturating_add(Weight::from_parts(0, 3571)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) @@ -140,24 +120,14 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn execute_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_887_000 picoseconds. - Weight::from_parts(6_101_000, 0) + // Minimum execution time: 6_014_000 picoseconds. + Weight::from_parts(6_412_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -167,8 +137,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_940_000 picoseconds. - Weight::from_parts(2_022_000, 0) + // Minimum execution time: 1_844_000 picoseconds. + Weight::from_parts(1_957_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -192,8 +162,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 23_165_000 picoseconds. - Weight::from_parts(23_800_000, 0) + // Minimum execution time: 24_067_000 picoseconds. + Weight::from_parts(24_553_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -216,8 +186,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `292` // Estimated: `3757` - // Minimum execution time: 26_506_000 picoseconds. - Weight::from_parts(27_180_000, 0) + // Minimum execution time: 27_023_000 picoseconds. + Weight::from_parts(27_620_000, 0) .saturating_add(Weight::from_parts(0, 3757)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -228,8 +198,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_868_000 picoseconds. - Weight::from_parts(2_002_000, 0) + // Minimum execution time: 1_866_000 picoseconds. + Weight::from_parts(1_984_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -239,8 +209,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `89` // Estimated: `13454` - // Minimum execution time: 16_138_000 picoseconds. - Weight::from_parts(16_447_000, 0) + // Minimum execution time: 16_425_000 picoseconds. + Weight::from_parts(16_680_000, 0) .saturating_add(Weight::from_parts(0, 13454)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -251,8 +221,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `93` // Estimated: `13458` - // Minimum execution time: 16_099_000 picoseconds. - Weight::from_parts(16_592_000, 0) + // Minimum execution time: 16_171_000 picoseconds. + Weight::from_parts(16_564_000, 0) .saturating_add(Weight::from_parts(0, 13458)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -263,8 +233,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `15946` - // Minimum execution time: 17_972_000 picoseconds. - Weight::from_parts(18_379_000, 0) + // Minimum execution time: 17_785_000 picoseconds. + Weight::from_parts(18_123_000, 0) .saturating_add(Weight::from_parts(0, 15946)) .saturating_add(T::DbWeight::get().reads(6)) } @@ -284,8 +254,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `142` // Estimated: `6082` - // Minimum execution time: 23_554_000 picoseconds. - Weight::from_parts(24_446_000, 0) + // Minimum execution time: 23_903_000 picoseconds. + Weight::from_parts(24_769_000, 0) .saturating_add(Weight::from_parts(0, 6082)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) @@ -296,8 +266,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `136` // Estimated: `11026` - // Minimum execution time: 10_541_000 picoseconds. - Weight::from_parts(10_894_000, 0) + // Minimum execution time: 10_617_000 picoseconds. + Weight::from_parts(10_843_000, 0) .saturating_add(Weight::from_parts(0, 11026)) .saturating_add(T::DbWeight::get().reads(4)) } @@ -307,8 +277,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `100` // Estimated: `13465` - // Minimum execution time: 16_404_000 picoseconds. - Weight::from_parts(16_818_000, 0) + // Minimum execution time: 16_656_000 picoseconds. + Weight::from_parts(17_106_000, 0) .saturating_add(Weight::from_parts(0, 13465)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -329,8 +299,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `142` // Estimated: `13507` - // Minimum execution time: 31_617_000 picoseconds. - Weight::from_parts(32_336_000, 0) + // Minimum execution time: 31_721_000 picoseconds. + Weight::from_parts(32_547_000, 0) .saturating_add(Weight::from_parts(0, 13507)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) @@ -343,8 +313,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_328_000 picoseconds. - Weight::from_parts(3_501_000, 0) + // Minimum execution time: 3_439_000 picoseconds. + Weight::from_parts(3_619_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -355,8 +325,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 23_571_000 picoseconds. - Weight::from_parts(24_312_000, 0) + // Minimum execution time: 24_657_000 picoseconds. + Weight::from_parts(24_971_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -367,8 +337,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 32_879_000 picoseconds. - Weight::from_parts(33_385_000, 0) + // Minimum execution time: 34_028_000 picoseconds. + Weight::from_parts(34_697_000, 0) .saturating_add(Weight::from_parts(0, 3555)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs index 346bdfa4d8c9ea2bd75260a2c6eeccad9b276eeb..defc57e2d7f552a86421359b25d64a2bacbd3572 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs @@ -39,16 +39,16 @@ use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, - NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, + FungibleAdapter, IsConcrete, NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; -use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; +use xcm_executor::XcmExecutor; parameter_types! { pub const TokenRelayLocation: Location = Location::parent(); @@ -146,48 +146,6 @@ impl Contains for FellowsPlurality { } } -/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly -/// account for proof size weights. -/// -/// Calls that are allowed through this filter must: -/// 1. Have a fixed weight; -/// 2. Cannot lead to another call being made; -/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. -pub struct SafeCallFilter; -impl Contains for SafeCallFilter { - fn contains(call: &RuntimeCall) -> bool { - #[cfg(feature = "runtime-benchmarks")] - { - if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { - return true - } - } - - matches!( - call, - RuntimeCall::PolkadotXcm( - pallet_xcm::Call::force_xcm_version { .. } | - pallet_xcm::Call::force_default_xcm_version { .. } - ) | RuntimeCall::System( - frame_system::Call::set_heap_pages { .. } | - frame_system::Call::set_code { .. } | - frame_system::Call::set_code_without_checks { .. } | - frame_system::Call::authorize_upgrade { .. } | - frame_system::Call::authorize_upgrade_without_checks { .. } | - frame_system::Call::kill_prefix { .. } | - // Should not be in Polkadot/Kusama. Here in order to speed up testing. - frame_system::Call::set_storage { .. }, - ) | RuntimeCall::ParachainSystem(..) | - RuntimeCall::Timestamp(..) | - RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection(..) | - RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | - RuntimeCall::XcmpQueue(..) | - RuntimeCall::Broker(..) - ) - } -} - pub type Barrier = TrailingSetTopicAsId< DenyThenTry< DenyReserveTransferToRelayChain, @@ -206,6 +164,8 @@ pub type Barrier = TrailingSetTopicAsId< AllowExplicitUnpaidExecutionFrom<(ParentOrParentsPlurality, FellowsPlurality)>, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, ), UniversalLocation, ConstU32<8>, @@ -265,8 +225,8 @@ impl xcm_executor::Config for XcmConfig { >; type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = WithOriginFilter; - type SafeCallFilter = SafeCallFilter; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; type Aliasers = Nothing; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); @@ -300,7 +260,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecuteFilter = Everything; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = Nothing; // This parachain is not meant as a reserve location. + type XcmReserveTransferFilter = Everything; type Weigher = WeightInfoBounds< crate::weights::xcm::CoretimeWestendXcmWeight, RuntimeCall, diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml index fe9cd25841bfc8c0b14278ed6ea167a46c3dd652..808bed38732758e546a6a513c4f19cf2b1d50fbc 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml @@ -22,8 +22,8 @@ frame-system-rpc-runtime-api = { path = "../../../../../substrate/frame/system/r frame-system-benchmarking = { path = "../../../../../substrate/frame/system/benchmarking", default-features = false, optional = true } frame-try-runtime = { path = "../../../../../substrate/frame/try-runtime", default-features = false, optional = true } pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false } -pallet-glutton = { path = "../../../../../substrate/frame/glutton", default-features = false, optional = true } -pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false, optional = true } +pallet-glutton = { path = "../../../../../substrate/frame/glutton", default-features = false } +pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false } pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } sp-api = { path = "../../../../../substrate/primitives/api", default-features = false } sp-block-builder = { path = "../../../../../substrate/primitives/block-builder", default-features = false } diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/build.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/build.rs index 1580e6f07bec466c644ccab1f4591d384632135e..2f311357403c0c179103a91d180c42d4e29f84a7 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/build.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/build.rs @@ -16,9 +16,5 @@ use substrate_wasm_builder::WasmBuilder; fn main() { - WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + WasmBuilder::build_using_defaults(); } diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/xcm_config.rs index 15bb519e115c5c2eaca25053a3d3e47c30fb21e0..9d438a41f8fe75037a4c9781653d5f65eee77f9c 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/xcm_config.rs @@ -30,8 +30,8 @@ use xcm_builder::{ parameter_types! { pub const WestendLocation: Location = Location::parent(); - pub const WestendNetwork: Option = Some(NetworkId::Westend); - pub UniversalLocation: InteriorLocation = [Parachain(ParachainInfo::parachain_id().into())].into(); + pub const WestendNetwork: NetworkId = NetworkId::Westend; + pub UniversalLocation: InteriorLocation = [GlobalConsensus(WestendNetwork::get()), Parachain(ParachainInfo::parachain_id().into())].into(); } /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 3cd085fec63248aeb872af7b9233eb0046f472c9..4a57bad01c8c9c4a77caa4d701ada4e149317607 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -102,6 +102,7 @@ pub type UncheckedExtrinsic = /// Migrations to apply on runtime upgrade. pub type Migrations = ( + pallet_collator_selection::migration::v2::MigrationToV2, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs index ac494fdc719f4139a79b9f92525bde8322267af2..fabce29b5fd9451f27364c38481d2dac98c430d8 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -62,28 +62,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 17_935_000 picoseconds. - Weight::from_parts(18_482_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) - /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) - /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn send_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 18_311_000 picoseconds. - Weight::from_parts(18_850_000, 0) + // Minimum execution time: 17_830_000 picoseconds. + Weight::from_parts(18_411_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -104,8 +84,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `70` // Estimated: `3535` - // Minimum execution time: 56_182_000 picoseconds. - Weight::from_parts(58_136_000, 0) + // Minimum execution time: 55_456_000 picoseconds. + Weight::from_parts(56_808_000, 0) .saturating_add(Weight::from_parts(0, 3535)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) @@ -140,24 +120,14 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn execute_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_979_000 picoseconds. - Weight::from_parts(6_289_000, 0) + // Minimum execution time: 5_996_000 picoseconds. + Weight::from_parts(6_154_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -167,8 +137,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_853_000 picoseconds. - Weight::from_parts(2_045_000, 0) + // Minimum execution time: 1_768_000 picoseconds. + Weight::from_parts(1_914_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -192,8 +162,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 23_827_000 picoseconds. - Weight::from_parts(24_493_000, 0) + // Minimum execution time: 24_120_000 picoseconds. + Weight::from_parts(24_745_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -216,8 +186,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 26_755_000 picoseconds. - Weight::from_parts(27_125_000, 0) + // Minimum execution time: 26_630_000 picoseconds. + Weight::from_parts(27_289_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -228,8 +198,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_898_000 picoseconds. - Weight::from_parts(2_028_000, 0) + // Minimum execution time: 1_821_000 picoseconds. + Weight::from_parts(1_946_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -239,8 +209,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `89` // Estimated: `13454` - // Minimum execution time: 16_300_000 picoseconds. - Weight::from_parts(16_995_000, 0) + // Minimum execution time: 16_586_000 picoseconds. + Weight::from_parts(16_977_000, 0) .saturating_add(Weight::from_parts(0, 13454)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -251,8 +221,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `93` // Estimated: `13458` - // Minimum execution time: 16_495_000 picoseconds. - Weight::from_parts(16_950_000, 0) + // Minimum execution time: 16_923_000 picoseconds. + Weight::from_parts(17_415_000, 0) .saturating_add(Weight::from_parts(0, 13458)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -263,8 +233,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `15946` - // Minimum execution time: 18_153_000 picoseconds. - Weight::from_parts(18_595_000, 0) + // Minimum execution time: 18_596_000 picoseconds. + Weight::from_parts(18_823_000, 0) .saturating_add(Weight::from_parts(0, 15946)) .saturating_add(T::DbWeight::get().reads(6)) } @@ -284,8 +254,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 23_387_000 picoseconds. - Weight::from_parts(24_677_000, 0) + // Minimum execution time: 23_817_000 picoseconds. + Weight::from_parts(24_520_000, 0) .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) @@ -296,8 +266,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `136` // Estimated: `11026` - // Minimum execution time: 10_939_000 picoseconds. - Weight::from_parts(11_210_000, 0) + // Minimum execution time: 11_042_000 picoseconds. + Weight::from_parts(11_578_000, 0) .saturating_add(Weight::from_parts(0, 11026)) .saturating_add(T::DbWeight::get().reads(4)) } @@ -307,8 +277,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `100` // Estimated: `13465` - // Minimum execution time: 16_850_000 picoseconds. - Weight::from_parts(17_195_000, 0) + // Minimum execution time: 17_306_000 picoseconds. + Weight::from_parts(17_817_000, 0) .saturating_add(Weight::from_parts(0, 13465)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -329,8 +299,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `13471` - // Minimum execution time: 31_931_000 picoseconds. - Weight::from_parts(32_494_000, 0) + // Minimum execution time: 32_141_000 picoseconds. + Weight::from_parts(32_954_000, 0) .saturating_add(Weight::from_parts(0, 13471)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) @@ -343,8 +313,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_514_000 picoseconds. - Weight::from_parts(3_709_000, 0) + // Minimum execution time: 3_410_000 picoseconds. + Weight::from_parts(3_556_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -355,8 +325,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 24_863_000 picoseconds. - Weight::from_parts(25_293_000, 0) + // Minimum execution time: 25_021_000 picoseconds. + Weight::from_parts(25_240_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -367,8 +337,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 33_799_000 picoseconds. - Weight::from_parts(34_665_000, 0) + // Minimum execution time: 33_801_000 picoseconds. + Weight::from_parts(34_655_000, 0) .saturating_add(Weight::from_parts(0, 3555)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs index 1a42adeafd1d863b33d677abf0e2d90bf6e9feb1..101d9a180e5feaa36d65afcd01276b86768f4ab0 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs @@ -36,16 +36,16 @@ use polkadot_parachain_primitives::primitives::Sibling; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, - HashedDescription, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeTerminus, EnsureXcmOrigin, + FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, ParentAsSuperuser, + ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; -use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; +use xcm_executor::XcmExecutor; parameter_types! { pub const RootLocation: Location = Location::here(); @@ -148,55 +148,6 @@ impl Contains for ParentOrParentsPlurality { } } -/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly -/// account for proof size weights. -/// -/// Calls that are allowed through this filter must: -/// 1. Have a fixed weight; -/// 2. Cannot lead to another call being made; -/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. -pub struct SafeCallFilter; -impl Contains for SafeCallFilter { - fn contains(call: &RuntimeCall) -> bool { - #[cfg(feature = "runtime-benchmarks")] - { - if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { - return true - } - } - - matches!( - call, - RuntimeCall::PolkadotXcm( - pallet_xcm::Call::force_xcm_version { .. } | - pallet_xcm::Call::force_default_xcm_version { .. } - ) | RuntimeCall::System( - frame_system::Call::set_heap_pages { .. } | - frame_system::Call::set_code { .. } | - frame_system::Call::set_code_without_checks { .. } | - frame_system::Call::authorize_upgrade { .. } | - frame_system::Call::authorize_upgrade_without_checks { .. } | - frame_system::Call::kill_prefix { .. }, - ) | RuntimeCall::ParachainSystem(..) | - RuntimeCall::Timestamp(..) | - RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection( - pallet_collator_selection::Call::set_desired_candidates { .. } | - pallet_collator_selection::Call::set_candidacy_bond { .. } | - pallet_collator_selection::Call::register_as_candidate { .. } | - pallet_collator_selection::Call::leave_intent { .. } | - pallet_collator_selection::Call::set_invulnerables { .. } | - pallet_collator_selection::Call::add_invulnerable { .. } | - pallet_collator_selection::Call::remove_invulnerable { .. }, - ) | RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | - RuntimeCall::XcmpQueue(..) | - RuntimeCall::MessageQueue(..) | - RuntimeCall::Identity(..) | - RuntimeCall::IdentityMigrator(..) - ) - } -} - pub type Barrier = TrailingSetTopicAsId< DenyThenTry< DenyReserveTransferToRelayChain, @@ -214,6 +165,8 @@ pub type Barrier = TrailingSetTopicAsId< AllowExplicitUnpaidExecutionFrom, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, ), UniversalLocation, ConstU32<8>, @@ -270,8 +223,8 @@ impl xcm_executor::Config for XcmConfig { >; type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = WithOriginFilter; - type SafeCallFilter = SafeCallFilter; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; type Aliasers = Nothing; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 307ab90a47723503725757d5862f5d0b21bdf9c0..22e8fd57d3ca735fa0e8490e94adf06bf2f91108 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -102,6 +102,7 @@ pub type UncheckedExtrinsic = /// Migrations to apply on runtime upgrade. pub type Migrations = ( + pallet_collator_selection::migration::v2::MigrationToV2, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs index 62a9c802808c0fdc2305f566f7b7f67b4d0a5748..c337289243b748d721b60421bccf3349044ef194 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -62,28 +62,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 17_450_000 picoseconds. - Weight::from_parts(17_913_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) - /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) - /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn send_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 18_082_000 picoseconds. - Weight::from_parts(18_293_000, 0) + // Minimum execution time: 17_856_000 picoseconds. + Weight::from_parts(18_473_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -104,8 +84,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `70` // Estimated: `3535` - // Minimum execution time: 54_939_000 picoseconds. - Weight::from_parts(55_721_000, 0) + // Minimum execution time: 56_112_000 picoseconds. + Weight::from_parts(57_287_000, 0) .saturating_add(Weight::from_parts(0, 3535)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) @@ -140,24 +120,14 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn execute_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_789_000 picoseconds. - Weight::from_parts(5_995_000, 0) + // Minimum execution time: 6_186_000 picoseconds. + Weight::from_parts(6_420_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -167,8 +137,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_795_000 picoseconds. - Weight::from_parts(1_924_000, 0) + // Minimum execution time: 1_824_000 picoseconds. + Weight::from_parts(1_999_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -192,8 +162,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 23_445_000 picoseconds. - Weight::from_parts(23_906_000, 0) + // Minimum execution time: 23_833_000 picoseconds. + Weight::from_parts(24_636_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -216,8 +186,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 26_590_000 picoseconds. - Weight::from_parts(27_056_000, 0) + // Minimum execution time: 26_557_000 picoseconds. + Weight::from_parts(27_275_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -228,8 +198,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_889_000 picoseconds. - Weight::from_parts(1_962_000, 0) + // Minimum execution time: 1_921_000 picoseconds. + Weight::from_parts(2_040_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -239,8 +209,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `89` // Estimated: `13454` - // Minimum execution time: 16_408_000 picoseconds. - Weight::from_parts(16_877_000, 0) + // Minimum execution time: 16_832_000 picoseconds. + Weight::from_parts(17_312_000, 0) .saturating_add(Weight::from_parts(0, 13454)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -251,8 +221,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `93` // Estimated: `13458` - // Minimum execution time: 16_791_000 picoseconds. - Weight::from_parts(17_111_000, 0) + // Minimum execution time: 16_687_000 picoseconds. + Weight::from_parts(17_123_000, 0) .saturating_add(Weight::from_parts(0, 13458)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -263,8 +233,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `15946` - // Minimum execution time: 18_355_000 picoseconds. - Weight::from_parts(19_110_000, 0) + // Minimum execution time: 18_164_000 picoseconds. + Weight::from_parts(18_580_000, 0) .saturating_add(Weight::from_parts(0, 15946)) .saturating_add(T::DbWeight::get().reads(6)) } @@ -284,8 +254,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 23_354_000 picoseconds. - Weight::from_parts(23_999_000, 0) + // Minimum execution time: 23_577_000 picoseconds. + Weight::from_parts(24_324_000, 0) .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) @@ -296,8 +266,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `136` // Estimated: `11026` - // Minimum execution time: 11_065_000 picoseconds. - Weight::from_parts(11_302_000, 0) + // Minimum execution time: 11_014_000 picoseconds. + Weight::from_parts(11_223_000, 0) .saturating_add(Weight::from_parts(0, 11026)) .saturating_add(T::DbWeight::get().reads(4)) } @@ -307,8 +277,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `100` // Estimated: `13465` - // Minimum execution time: 16_998_000 picoseconds. - Weight::from_parts(17_509_000, 0) + // Minimum execution time: 16_887_000 picoseconds. + Weight::from_parts(17_361_000, 0) .saturating_add(Weight::from_parts(0, 13465)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -329,8 +299,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `13471` - // Minimum execution time: 31_068_000 picoseconds. - Weight::from_parts(31_978_000, 0) + // Minimum execution time: 31_705_000 picoseconds. + Weight::from_parts(32_166_000, 0) .saturating_add(Weight::from_parts(0, 13471)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) @@ -343,8 +313,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_478_000 picoseconds. - Weight::from_parts(3_595_000, 0) + // Minimum execution time: 3_568_000 picoseconds. + Weight::from_parts(3_669_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -355,8 +325,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 24_962_000 picoseconds. - Weight::from_parts(25_404_000, 0) + // Minimum execution time: 24_823_000 picoseconds. + Weight::from_parts(25_344_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -367,8 +337,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 32_685_000 picoseconds. - Weight::from_parts(33_592_000, 0) + // Minimum execution time: 34_516_000 picoseconds. + Weight::from_parts(35_478_000, 0) .saturating_add(Weight::from_parts(0, 3555)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs index 114923270645589b0bb1b0d8d5a7abed4e315a8d..0a903f915056313c5650f030a3c0266eff1612e1 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs @@ -36,16 +36,16 @@ use polkadot_parachain_primitives::primitives::Sibling; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, - HashedDescription, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeTerminus, EnsureXcmOrigin, + FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, ParentAsSuperuser, + ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; -use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; +use xcm_executor::XcmExecutor; parameter_types! { pub const RootLocation: Location = Location::here(); @@ -155,55 +155,6 @@ impl Contains for FellowsPlurality { } } -/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly -/// account for proof size weights. -/// -/// Calls that are allowed through this filter must: -/// 1. Have a fixed weight; -/// 2. Cannot lead to another call being made; -/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. -pub struct SafeCallFilter; -impl Contains for SafeCallFilter { - fn contains(call: &RuntimeCall) -> bool { - #[cfg(feature = "runtime-benchmarks")] - { - if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { - return true - } - } - - matches!( - call, - RuntimeCall::PolkadotXcm( - pallet_xcm::Call::force_xcm_version { .. } | - pallet_xcm::Call::force_default_xcm_version { .. } - ) | RuntimeCall::System( - frame_system::Call::set_heap_pages { .. } | - frame_system::Call::set_code { .. } | - frame_system::Call::set_code_without_checks { .. } | - frame_system::Call::authorize_upgrade { .. } | - frame_system::Call::authorize_upgrade_without_checks { .. } | - frame_system::Call::kill_prefix { .. }, - ) | RuntimeCall::ParachainSystem(..) | - RuntimeCall::Timestamp(..) | - RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection( - pallet_collator_selection::Call::set_desired_candidates { .. } | - pallet_collator_selection::Call::set_candidacy_bond { .. } | - pallet_collator_selection::Call::register_as_candidate { .. } | - pallet_collator_selection::Call::leave_intent { .. } | - pallet_collator_selection::Call::set_invulnerables { .. } | - pallet_collator_selection::Call::add_invulnerable { .. } | - pallet_collator_selection::Call::remove_invulnerable { .. }, - ) | RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | - RuntimeCall::XcmpQueue(..) | - RuntimeCall::MessageQueue(..) | - RuntimeCall::Identity(..) | - RuntimeCall::IdentityMigrator(..) - ) - } -} - pub type Barrier = TrailingSetTopicAsId< DenyThenTry< DenyReserveTransferToRelayChain, @@ -222,6 +173,8 @@ pub type Barrier = TrailingSetTopicAsId< AllowExplicitUnpaidExecutionFrom<(ParentOrParentsPlurality, FellowsPlurality)>, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, ), UniversalLocation, ConstU32<8>, @@ -278,8 +231,8 @@ impl xcm_executor::Config for XcmConfig { >; type MessageExporter = (); type UniversalAliases = Nothing; - type CallDispatcher = WithOriginFilter; - type SafeCallFilter = SafeCallFilter; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; type Aliasers = Nothing; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); diff --git a/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs b/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs index df89158729cd9935911df3a1bff76557c6d5c900..7f9de0f64b35676f4684b15590a243e4550885d2 100644 --- a/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs @@ -30,8 +30,8 @@ use xcm_builder::{ parameter_types! { pub const RococoLocation: Location = Location::parent(); - pub const RococoNetwork: Option = Some(NetworkId::Rococo); - pub UniversalLocation: InteriorLocation = [Parachain(ParachainInfo::parachain_id().into())].into(); + pub const RococoNetwork: NetworkId = NetworkId::Rococo; + pub UniversalLocation: InteriorLocation = [GlobalConsensus(RococoNetwork::get()), Parachain(ParachainInfo::parachain_id().into())].into(); } /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, diff --git a/cumulus/parachains/runtimes/testing/penpal/build.rs b/cumulus/parachains/runtimes/testing/penpal/build.rs index 9c9cde9a25a1a8ab1780b7df7e068264459a31af..c2fa89aa7028510ab3f95a152ababcffbecd280f 100644 --- a/cumulus/parachains/runtimes/testing/penpal/build.rs +++ b/cumulus/parachains/runtimes/testing/penpal/build.rs @@ -16,11 +16,7 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 919bfe83e7d7aff8d90be3af0358dd28b93581e1..89885d77378ba2c1df13116b5f2d41f547002b30 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -72,18 +72,14 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use xcm_config::XcmOriginToTransactDispatchOrigin; +use xcm_config::{ForeignAssetsAssetId, XcmOriginToTransactDispatchOrigin}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; -// Polkadot imports +use parachains_common::{AccountId, Signature}; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; - use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; - -// XCM Imports -use parachains_common::{AccountId, Signature}; use xcm::latest::prelude::{AssetId as AssetLocationId, BodyId}; /// Balance of an account. @@ -474,8 +470,8 @@ pub type ForeignAssetsInstance = pallet_assets::Instance2; impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; - type AssetId = xcm::v3::Location; - type AssetIdParameter = xcm::v3::Location; + type AssetId = ForeignAssetsAssetId; + type AssetIdParameter = ForeignAssetsAssetId; type Currency = Balances; type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = EnsureRoot; diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index c12372abbe90f273f46f25863ccf08353d64e125..711041f6d6e28ccfc53d83ee2b903f06ae65de36 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -40,17 +40,18 @@ use pallet_xcm::XcmPassthrough; use parachains_common::{xcm_config::AssetFeeAsExistentialDepositMultiplier, TREASURY_PALLET_ID}; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::{impls::ToAuthor, xcm_sender::ExponentialPrice}; -use sp_runtime::traits::{AccountIdConversion, ConvertInto}; +use sp_runtime::traits::{AccountIdConversion, ConvertInto, Identity, TryConvertInto}; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, - AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, EnsureXcmOrigin, - FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsConcrete, - LocalMint, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, StartsWith, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, XcmFeeToAccount, + AccountId32Aliases, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, + ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, + FungibleAdapter, FungiblesAdapter, IsConcrete, LocalMint, NativeAsset, NoChecking, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, StartsWith, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::{traits::JustTry, XcmExecutor}; @@ -58,9 +59,16 @@ parameter_types! { pub const RelayLocation: Location = Location::parent(); // Local native currency which is stored in `pallet_balances`` pub const PenpalNativeCurrency: Location = Location::here(); - pub const RelayNetwork: Option = None; + // The Penpal runtime is utilized for testing with various environment setups. + // This storage item allows us to customize the `NetworkId` where Penpal is deployed. + // By default, it is set to `NetworkId::Rococo` and can be changed using `System::set_storage`. + pub storage RelayNetworkId: NetworkId = NetworkId::Westend; + pub RelayNetwork: Option = Some(RelayNetworkId::get()); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - pub UniversalLocation: InteriorLocation = [Parachain(ParachainInfo::parachain_id().into())].into(); + pub UniversalLocation: InteriorLocation = [ + GlobalConsensus(RelayNetworkId::get()), + Parachain(ParachainInfo::parachain_id().into()) + ].into(); pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); } @@ -77,7 +85,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. -pub type CurrencyTransactor = FungibleAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -124,7 +132,11 @@ pub type FungiblesTransactor = FungiblesAdapter< CheckingAccount, >; -pub type ForeignAssetsConvertedConcreteId = assets_common::LocationConvertedConcreteId< +// Using the latest `Location`, we don't need to worry about migrations for Penpal. +pub type ForeignAssetsAssetId = Location; +pub type ForeignAssetsConvertedConcreteId = xcm_builder::MatchedConvertedConcreteId< + Location, + Balance, EverythingBut<( // Here we rely on fact that something like this works: // assert!(Location::new(1, @@ -132,7 +144,8 @@ pub type ForeignAssetsConvertedConcreteId = assets_common::LocationConvertedConc // assert!([Parachain(100)].into().starts_with(&Here)); StartsWith, )>, - Balance, + Identity, + TryConvertInto, >; /// Means for transacting foreign assets from different global consensus. @@ -152,7 +165,7 @@ pub type ForeignFungiblesTransactor = FungiblesAdapter< >; /// Means for transacting assets on this chain. -pub type AssetTransactors = (CurrencyTransactor, ForeignFungiblesTransactor, FungiblesTransactor); +pub type AssetTransactors = (FungibleTransactor, ForeignFungiblesTransactor, FungiblesTransactor); /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, /// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can @@ -205,6 +218,8 @@ pub type Barrier = TrailingSetTopicAsId<( AllowTopLevelPaidExecutionFrom, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, ), UniversalLocation, ConstU32<8>, @@ -409,8 +424,8 @@ impl cumulus_pallet_xcm::Config for Runtime { /// Simple conversion of `u32` into an `AssetId` for use in benchmarking. pub struct XcmBenchmarkHelper; #[cfg(feature = "runtime-benchmarks")] -impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { - fn create_asset_id_parameter(id: u32) -> xcm::v3::Location { - xcm::v3::Location::new(1, [xcm::v3::Junction::Parachain(id)]) +impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { + fn create_asset_id_parameter(id: u32) -> ForeignAssetsAssetId { + Location::new(1, [Parachain(id)]) } } diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/build.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/build.rs index 60f8a125129ff1344a1799246e931acdb1d139d5..239ccac19ec7778039fb1ee56f4e772b3ddd3711 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/build.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/build.rs @@ -15,11 +15,7 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index 154c2c4600043cc0ceb1b0cd61a84949dcdd9616..11da6adb81905a79718213b3663c3ffb25bfe8cb 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -74,9 +74,9 @@ use parachains_common::{ AccountId, AssetIdForTrustBackedAssets, Signature, }; use xcm_builder::{ - AllowKnownQueryResponses, AllowSubscriptionsFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, - FrameTransactionalProcessor, FungiblesAdapter, LocalMint, TrailingSetTopicAsId, - WithUniqueTopic, + AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, + AsPrefixedGeneralIndex, ConvertedConcreteId, FrameTransactionalProcessor, FungiblesAdapter, + LocalMint, TrailingSetTopicAsId, WithUniqueTopic, }; use xcm_executor::traits::JustTry; @@ -327,9 +327,9 @@ impl cumulus_pallet_aura_ext::Config for Runtime {} parameter_types! { pub const RocLocation: Location = Location::parent(); - pub const RococoNetwork: Option = Some(NetworkId::Rococo); + pub const RococoNetwork: NetworkId = NetworkId::Rococo; pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - pub UniversalLocation: InteriorLocation = [Parachain(ParachainInfo::parachain_id().into())].into(); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(RococoNetwork::get()), Parachain(ParachainInfo::parachain_id().into())].into(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); } @@ -346,7 +346,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. -pub type CurrencyTransactor = FungibleAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -385,7 +385,7 @@ pub type FungiblesTransactor = FungiblesAdapter< CheckingAccount, >; /// Means for transacting assets on this chain. -pub type AssetTransactors = (CurrencyTransactor, FungiblesTransactor); +pub type AssetTransactors = (FungibleTransactor, FungiblesTransactor); /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, /// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can @@ -444,6 +444,8 @@ pub type Barrier = TrailingSetTopicAsId<( AllowKnownQueryResponses, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, + // HRMP notifications from the relay chain are OK. + AllowHrmpNotificationsFromRelayChain, )>; parameter_types! { diff --git a/cumulus/primitives/parachain-inherent/Cargo.toml b/cumulus/primitives/parachain-inherent/Cargo.toml index fcf4c93bc2f01dc9e60f5ad2d74e302f5247b955..4da561661b6b312d8d89a9f0f5ae5b7359e79a82 100644 --- a/cumulus/primitives/parachain-inherent/Cargo.toml +++ b/cumulus/primitives/parachain-inherent/Cargo.toml @@ -17,8 +17,8 @@ scale-info = { version = "2.11.1", default-features = false, features = ["derive # Substrate sp-core = { path = "../../../substrate/primitives/core", default-features = false } sp-inherents = { path = "../../../substrate/primitives/inherents", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", optional = true } -sp-state-machine = { path = "../../../substrate/primitives/state-machine", optional = true } +sp-runtime = { path = "../../../substrate/primitives/runtime", optional = true, default-features = false } +sp-state-machine = { path = "../../../substrate/primitives/state-machine", optional = true, default-features = false } sp-std = { path = "../../../substrate/primitives/std", default-features = false } sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } @@ -34,6 +34,8 @@ std = [ "scale-info/std", "sp-core/std", "sp-inherents/std", + "sp-runtime?/std", + "sp-state-machine?/std", "sp-std/std", "sp-trie/std", ] diff --git a/cumulus/primitives/utility/src/lib.rs b/cumulus/primitives/utility/src/lib.rs index d5d411356dc385948f31730ba65dcd26074d0336..54f40bd01097c99ab82520c7fe83d13c96c698b2 100644 --- a/cumulus/primitives/utility/src/lib.rs +++ b/cumulus/primitives/utility/src/lib.rs @@ -69,6 +69,9 @@ where let price = P::price_for_delivery((), &xcm); let versioned_xcm = W::wrap_version(&d, xcm).map_err(|()| SendError::DestinationUnsupported)?; + versioned_xcm + .validate_xcm_nesting() + .map_err(|()| SendError::ExceedsMaxMessageSize)?; let data = versioned_xcm.encode(); Ok((data, price)) @@ -526,6 +529,8 @@ impl< mod test_xcm_router { use super::*; use cumulus_primitives_core::UpwardMessage; + use frame_support::assert_ok; + use xcm::MAX_XCM_DECODE_DEPTH; /// Validates [`validate`] for required Some(destination) and Some(message) struct OkFixedXcmHashWithAssertingRequiredInputsSender; @@ -621,6 +626,29 @@ mod test_xcm_router { )>(dest.into(), message) ); } + + #[test] + fn parent_as_ump_validate_nested_xcm_works() { + let dest = Parent; + + type Router = ParentAsUmp<(), (), ()>; + + // Message that is not too deeply nested: + let mut good = Xcm(vec![ClearOrigin]); + for _ in 0..MAX_XCM_DECODE_DEPTH - 1 { + good = Xcm(vec![SetAppendix(good)]); + } + + // Check that the good message is validated: + assert_ok!(::validate(&mut Some(dest.into()), &mut Some(good.clone()))); + + // Nesting the message one more time should reject it: + let bad = Xcm(vec![SetAppendix(good)]); + assert_eq!( + Err(SendError::ExceedsMaxMessageSize), + ::validate(&mut Some(dest.into()), &mut Some(bad)) + ); + } } #[cfg(test)] mod test_trader { diff --git a/cumulus/test/runtime/build.rs b/cumulus/test/runtime/build.rs index 5e5f6a35a505a957a3494285b7b649e6ee4e61f7..ebd5c178cba07e2889b6501a9be490344467d228 100644 --- a/cumulus/test/runtime/build.rs +++ b/cumulus/test/runtime/build.rs @@ -18,16 +18,10 @@ fn main() { use substrate_wasm_builder::WasmBuilder; - WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build(); + WasmBuilder::build_using_defaults(); - WasmBuilder::new() - .with_current_project() + WasmBuilder::init_with_defaults() .enable_feature("increment-spec-version") - .import_memory() .set_file_name("wasm_binary_spec_version_incremented.rs") .build(); } diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile index 4bfb73acda05880ef49570594d0769d1e5e4b147..196ba861f503c0fc82b6eb0e428df600ce6bfd49 100644 --- a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile +++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile @@ -1,7 +1,7 @@ # this image is built on top of existing Zombienet image ARG ZOMBIENET_IMAGE # this image uses substrate-relay image built elsewhere -ARG SUBSTRATE_RELAY_IMAGE=docker.io/paritytech/substrate-relay:v2023-11-07-rococo-westend-initial-relayer +ARG SUBSTRATE_RELAY_IMAGE=docker.io/paritytech/substrate-relay:v1.5.0 # metadata ARG VCS_REF diff --git a/docs/contributor/container.md b/docs/contributor/container.md index 9c542f411c81f4237e69ffaf63d4686eeac204e4..ec51b8b9d7ccd1b4933c3977ce92c7f0f96966a6 100644 --- a/docs/contributor/container.md +++ b/docs/contributor/container.md @@ -24,7 +24,7 @@ The command below allows building a Linux binary without having to even install docker run --rm -it \ -w /polkadot-sdk \ -v $(pwd):/polkadot-sdk \ - paritytech/ci-unified:bullseye-1.75.0-2024-01-22-v20240222 \ + docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408 \ cargo build --release --locked -p polkadot-parachain-bin --bin polkadot-parachain sudo chown -R $(id -u):$(id -g) target/ ``` diff --git a/docs/contributor/prdoc.md b/docs/contributor/prdoc.md index af0ede5107a6cb1ea31d8b81fe16b06553ef64ac..0c8165af40f4d4f0438ab780c5f87eb5fe2ad62e 100644 --- a/docs/contributor/prdoc.md +++ b/docs/contributor/prdoc.md @@ -1,55 +1,31 @@ # PRDoc -## Intro - -With the merge of [PR #1946](https://github.com/paritytech/polkadot-sdk/pull/1946), a new method for -documenting changes has been introduced: `prdoc`. The [prdoc repository](https://github.com/paritytech/prdoc) -contains more documentation and tooling. - -The current document describes how to quickly get started authoring `PRDoc` files. +A [prdoc](https://github.com/paritytech/prdoc) is like a changelog but for a Pull Request. We use this approach to +record changes on a crate level. This information is then processed by the release team to apply the correct crate +version bumps and to generate the CHANGELOG of the next release. ## Requirements -When creating a PR, the author needs to decides with the `R0` label whether the change (PR) should -appear in the release notes or not. - -Labelling a PR with `R0` means that no `PRDoc` is required. - -A PR without the `R0` label **does** require a valid `PRDoc` file to be introduced in the PR. - -## PRDoc how-to - -A `.prdoc` file is a YAML file with a defined structure (ie JSON Schema). - -For significant changes, a `.prdoc` file is mandatory and the file must meet the following -requirements: -- file named `pr_NNNN.prdoc` where `NNNN` is the PR number. - For convenience, those file can also contain a short description: `pr_NNNN_foobar.prdoc`. -- located under the [`prdoc` folder](https://github.com/paritytech/polkadot-sdk/tree/master/prdoc) of the repository -- compliant with the [JSON schema](https://json-schema.org/) defined in `prdoc/schema_user.json` - -Those requirements can be fulfilled manually without any tooling but a text editor. - -## Tooling - -Users might find the following helpers convenient: -- Setup VSCode to be aware of the prdoc schema: see [using VSCode](https://github.com/paritytech/prdoc#using-vscode) -- Using the `prdoc` cli to: - - generate a `PRDoc` file from a [template defined in the Polkadot SDK - repo](https://github.com/paritytech/polkadot-sdk/blob/master/prdoc/.template.prdoc) simply providing a PR number - - check the validity of one or more `PRDoc` files +When creating a PR, the author needs to decide with the `R0-silent` label whether the PR has to contain a prdoc. The +`R0` label should only be placed for No-OP changes like correcting a typo in a comment or CI stuff. If unsure, ping +the [CODEOWNERS](../../.github/CODEOWNERS) for advice. -## `prdoc` cli usage +## PRDoc How-To -The `prdoc` cli documentation can be found at https://github.com/paritytech/prdoc#prdoc +A `.prdoc` file is a YAML file with a defined structure (ie JSON Schema). Please follow these steps to generate one: -tldr: -- `prdoc generate ` -- `prdoc check -n ` +1. Install the [`prdoc` CLI](https://github.com/paritytech/prdoc) by running `cargo install prdoc`. +1. Open a Pull Request and get the PR number. +1. Generate the file with `prdoc generate `. The output filename will be printed. +1. Optional: Install the `prdoc/schema_user.json` schema in your editor, for example +[VsCode](https://github.com/paritytech/prdoc?tab=readme-ov-file#schemas). +1. Edit your `.prdoc` file according to the [Audience](#pick-an-audience) and [SemVer](#record-semver-changes) sections. +1. Check your prdoc with `prdoc check -n `. This is optional since the CI will also check it. -where is the PR number. +> **Tip:** GitHub CLI and jq can be used to provide the number of your PR to generate the correct file: +> `prdoc generate $(gh pr view --json number | jq '.number') -o prdoc` -## Pick an audience +## Pick An Audience While describing a PR, the author needs to consider which audience(s) need to be addressed. The list of valid audiences is described and documented in the JSON schema as follow: @@ -65,7 +41,41 @@ The list of valid audiences is described and documented in the JSON schema as fo - `Runtime User`: Anyone using the runtime. This can be a token holder or a dev writing a front end for a chain. -## Tips +If you have a change that affects multiple audiences, you can either list them all, or write multiple sections and +re-phrase the changes for each audience. + +## Record SemVer Changes + +All published crates that got modified need to have an entry in the `crates` section of your `PRDoc`. This entry tells +the release team how to bump the crate version prior to the next release. It is very important that this information is +correct, otherwise it could break the code of downstream teams. + +The bump can either be `major`, `minor`, `patch` or `none`. The three first options are defined by +[rust-lang.org](https://doc.rust-lang.org/cargo/reference/semver.html), whereas `None` should be picked if no other +applies. The `None` option is equivalent to the `R0-silent` label, but on a crate level. Experimental and private APIs +are exempt from bumping and can be broken at any time. Please read the [Crate Section](../RELEASE.md) of the RELEASE doc +about them. + +> **Note**: There is currently no CI in place to sanity check this information, but should be added soon. + +### Example + +For example when you modified two crates and record the changes: + +```yaml +crates: +- name: frame-example + bump: major +- name: frame-example-pallet + bump: minor +``` + +It means that downstream code using `frame-example-pallet` is still guaranteed to work as before, while code using +`frame-example` might break. + +### Dependencies -The PRDoc schema is defined in each repo and usually is quite restrictive. -You cannot simply add a new property to a `PRDoc` file unless the Schema allows it. +A crate that depends on another crate will automatically inherit its `major` bumps. This means that you do not need to +bump a crate that had a SemVer breaking change only from re-exporting another crate with a breaking change. +`minor` an `patch` bumps do not need to be inherited, since `cargo` will automatically update them to the latest +compatible version. diff --git a/docs/sdk/src/reference_docs/development_environment_advice.rs b/docs/sdk/src/reference_docs/development_environment_advice.rs index 21bbe78836c44b8afd70cab68e4b9b2f929fb4a0..9ba95dfa032945ac684f13b1c3961d4f8d0f649e 100644 --- a/docs/sdk/src/reference_docs/development_environment_advice.rs +++ b/docs/sdk/src/reference_docs/development_environment_advice.rs @@ -38,7 +38,7 @@ //! // Use nightly formatting. //! // See the polkadot-sdk CI job that checks formatting for the current version used in //! // polkadot-sdk. -//! "rust-analyzer.rustfmt.extraArgs": ["+nightly-2024-01-22"], +//! "rust-analyzer.rustfmt.extraArgs": ["+nightly-2024-04-10"], //! } //! ``` //! @@ -79,7 +79,7 @@ //! # Use nightly formatting. //! # See the polkadot-sdk CI job that checks formatting for the current version used in //! # polkadot-sdk. -//! extraArgs = { "+nightly-2024-01-22" }, +//! extraArgs = { "+nightly-2024-04-10" }, //! }, //! }, //! ``` diff --git a/polkadot/cli/src/cli.rs b/polkadot/cli/src/cli.rs index 3737942e6e53fb61133cf66227129dedd4bf885c..3e5a6ccdd3c25519e7890f73c3d5a1a49e41c9c8 100644 --- a/polkadot/cli/src/cli.rs +++ b/polkadot/cli/src/cli.rs @@ -131,6 +131,23 @@ pub struct RunCmd { #[arg(long, value_name = "PATH")] pub workers_path: Option, + /// Override the maximum number of pvf execute workers. + /// + /// **Dangerous!** Do not touch unless explicitly advised to. + #[arg(long)] + pub execute_workers_max_num: Option, + /// Override the maximum number of pvf workers that can be spawned in the pvf prepare + /// pool for tasks with the priority below critical. + /// + /// **Dangerous!** Do not touch unless explicitly advised to. + + #[arg(long)] + pub prepare_workers_soft_max_num: Option, + /// Override the absolute number of pvf workers that can be spawned in the pvf prepare pool. + /// + /// **Dangerous!** Do not touch unless explicitly advised to. + #[arg(long)] + pub prepare_workers_hard_max_num: Option, /// TESTING ONLY: disable the version check between nodes and workers. #[arg(long, hide = true)] pub disable_worker_version_check: bool, diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 6af93a75638891f0499029574da93dc57a291212..f5ee538e8cec5f418c0f2bb06336f78a81425cae 100644 --- a/polkadot/cli/src/command.rs +++ b/polkadot/cli/src/command.rs @@ -253,6 +253,9 @@ where .overseer_channel_capacity_override, malus_finality_delay: maybe_malus_finality_delay, hwbench, + execute_workers_max_num: cli.run.execute_workers_max_num, + prepare_workers_hard_max_num: cli.run.prepare_workers_hard_max_num, + prepare_workers_soft_max_num: cli.run.prepare_workers_soft_max_num, }, ) .map(|full| full.task_manager)?; diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml index ced7706c40a288759dd0f5336a8389c60c8e533a..473bc67923b6f084f6f45f3864c22cfbae885ffe 100644 --- a/polkadot/node/core/approval-voting/Cargo.toml +++ b/polkadot/node/core/approval-voting/Cargo.toml @@ -53,3 +53,14 @@ kvdb-memorydb = "0.13.0" test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../../primitives/test-helpers" } log = { workspace = true, default-features = true } env_logger = "0.11" + +polkadot-subsystem-bench = { path = "../../subsystem-bench" } + +[[bench]] +name = "approval-voting-regression-bench" +path = "benches/approval-voting-regression-bench.rs" +harness = false +required-features = ["subsystem-benchmarks"] + +[features] +subsystem-benchmarks = [] diff --git a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs new file mode 100644 index 0000000000000000000000000000000000000000..9a5f0d29dbd3180676a01eba0b382dbb26ef11ea --- /dev/null +++ b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs @@ -0,0 +1,94 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! approval-voting throughput test +//! +//! Approval Voting benchmark based on Kusama parameters and scale. +//! +//! Subsystems involved: +//! - approval-distribution +//! - approval-voting + +use polkadot_subsystem_bench::{ + self, + approval::{bench_approvals, prepare_test, ApprovalsOptions}, + configuration::TestConfiguration, + usage::BenchmarkUsage, + utils::save_to_file, +}; +use std::io::Write; + +const BENCH_COUNT: usize = 10; + +fn main() -> Result<(), String> { + let mut messages = vec![]; + let mut config = TestConfiguration::default(); + config.n_cores = 100; + config.n_validators = 500; + config.num_blocks = 10; + config.peer_bandwidth = 524288000000; + config.bandwidth = 524288000000; + config.latency = None; + config.connectivity = 100; + config.generate_pov_sizes(); + let options = ApprovalsOptions { + last_considered_tranche: 89, + coalesce_mean: 3.0, + coalesce_std_dev: 1.0, + coalesce_tranche_diff: 12, + enable_assignments_v2: true, + stop_when_approved: false, + workdir_prefix: "/tmp".to_string(), + num_no_shows_per_candidate: 0, + }; + + println!("Benchmarking..."); + let usages: Vec = (0..BENCH_COUNT) + .map(|n| { + print!("\r[{}{}]", "#".repeat(n), "_".repeat(BENCH_COUNT - n)); + std::io::stdout().flush().unwrap(); + let (mut env, state) = prepare_test(config.clone(), options.clone(), false); + env.runtime().block_on(bench_approvals("approvals_throughput", &mut env, state)) + }) + .collect(); + println!("\rDone!{}", " ".repeat(BENCH_COUNT)); + + let average_usage = BenchmarkUsage::average(&usages); + save_to_file( + "charts/approval-voting-regression-bench.json", + average_usage.to_chart_json().map_err(|e| e.to_string())?, + ) + .map_err(|e| e.to_string())?; + println!("{}", average_usage); + + // We expect no variance for received and sent + // but use 0.001 because we operate with floats + messages.extend(average_usage.check_network_usage(&[ + ("Received from peers", 52942.4600, 0.001), + ("Sent to peers", 63547.0330, 0.001), + ])); + messages.extend(average_usage.check_cpu_usage(&[ + ("approval-distribution", 7.0317, 0.1), + ("approval-voting", 9.5751, 0.1), + ])); + + if messages.is_empty() { + Ok(()) + } else { + eprintln!("{}", messages.join("\n")); + Err("Regressions found".to_string()) + } +} diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 7ecc2b2595bce78c3c65569369a932c847552b1c..b5ed92fa39c873c0a1e5f40c52705a5803971b60 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -978,6 +978,7 @@ where woken_block, woken_candidate, &subsystem.metrics, + &wakeups, ).await? } next_msg = ctx.recv().fuse() => { @@ -1152,6 +1153,7 @@ async fn handle_actions( candidate_hash, delayed_approvals_timers, approval_request, + &wakeups, ) .await? .into_iter() @@ -1663,6 +1665,7 @@ async fn handle_from_overseer( |r| { let _ = res.send(r); }, + &wakeups, ) .await? .0, @@ -2477,6 +2480,7 @@ async fn check_and_import_approval( metrics: &Metrics, approval: IndirectSignedApprovalVoteV2, with_response: impl FnOnce(ApprovalCheckResult) -> T, + wakeups: &Wakeups, ) -> SubsystemResult<(Vec, T)> where Sender: SubsystemSender, @@ -2655,6 +2659,7 @@ where approved_candidate_hash, candidate_entry, ApprovalStateTransition::RemoteApproval(approval.validator), + wakeups, ) .await; actions.extend(new_actions); @@ -2689,6 +2694,10 @@ impl ApprovalStateTransition { ApprovalStateTransition::WakeupProcessed => false, } } + + fn is_remote_approval(&self) -> bool { + matches!(*self, ApprovalStateTransition::RemoteApproval(_)) + } } // Advance the approval state, either by importing an approval vote which is already checked to be @@ -2705,6 +2714,7 @@ async fn advance_approval_state( candidate_hash: CandidateHash, mut candidate_entry: CandidateEntry, transition: ApprovalStateTransition, + wakeups: &Wakeups, ) -> Vec where Sender: SubsystemSender, @@ -2835,6 +2845,43 @@ where status.required_tranches, )); + if is_approved && transition.is_remote_approval() { + // Make sure we wake other blocks in case they have + // a no-show that might be covered by this approval. + for (fork_block_hash, fork_approval_entry) in candidate_entry + .block_assignments + .iter() + .filter(|(hash, _)| **hash != block_hash) + { + let assigned_on_fork_block = validator_index + .as_ref() + .map(|validator_index| fork_approval_entry.is_assigned(*validator_index)) + .unwrap_or_default(); + if wakeups.wakeup_for(*fork_block_hash, candidate_hash).is_none() && + !fork_approval_entry.is_approved() && + assigned_on_fork_block + { + let fork_block_entry = db.load_block_entry(fork_block_hash); + if let Ok(Some(fork_block_entry)) = fork_block_entry { + actions.push(Action::ScheduleWakeup { + block_hash: *fork_block_hash, + block_number: fork_block_entry.block_number(), + candidate_hash, + // Schedule the wakeup next tick, since the assignment must be a + // no-show, because there is no-wakeup scheduled. + tick: tick_now + 1, + }) + } else { + gum::debug!( + target: LOG_TARGET, + ?fork_block_entry, + ?fork_block_hash, + "Failed to load block entry" + ) + } + } + } + } // We have no need to write the candidate entry if all of the following // is true: // @@ -2896,6 +2943,7 @@ async fn process_wakeup( relay_block: Hash, candidate_hash: CandidateHash, metrics: &Metrics, + wakeups: &Wakeups, ) -> SubsystemResult> { let mut span = state .spans @@ -3064,6 +3112,7 @@ async fn process_wakeup( candidate_hash, candidate_entry, ApprovalStateTransition::WakeupProcessed, + wakeups, ) .await, ); @@ -3294,6 +3343,7 @@ async fn issue_approval( candidate_hash: CandidateHash, delayed_approvals_timers: &mut DelayedApprovalTimer, ApprovalVoteRequest { validator_index, block_hash }: ApprovalVoteRequest, + wakeups: &Wakeups, ) -> SubsystemResult> { let mut issue_approval_span = state .spans @@ -3415,6 +3465,7 @@ async fn issue_approval( candidate_hash, candidate_entry, ApprovalStateTransition::LocalApproval(validator_index as _), + wakeups, ) .await; diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index f7bbbca4b8a1c0c5609898d714ad045dfd33f3ef..312d805bbefb7b2b2605093fa2cadde0a1f10511 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -834,7 +834,6 @@ impl ChainBuilder { cur_hash = cur_header.parent_hash; } ancestry.reverse(); - import_block( overseer, ancestry.as_ref(), @@ -1922,6 +1921,187 @@ fn subsystem_assignment_import_updates_candidate_entry_and_schedules_wakeup() { }); } +#[test] +fn subsystem_always_has_a_wakeup_when_pending() { + // Approvals sent after all assignments are no-show, the approval + // should be counted on the fork relay chain on the next tick. + test_approvals_on_fork_are_always_considered_after_no_show( + 30, + vec![(29, false), (30, false), (31, true)], + ); + // Approvals sent before fork no-shows, the approval + // should be counted on the fork relay chain when it no-shows. + test_approvals_on_fork_are_always_considered_after_no_show( + 8, // a tick smaller than the no-show tick which is 30. + vec![(7, false), (8, false), (29, false), (30, true), (31, true)], + ); +} + +fn test_approvals_on_fork_are_always_considered_after_no_show( + tick_to_send_approval: Tick, + expected_approval_status: Vec<(Tick, bool)>, +) { + let config = HarnessConfig::default(); + let store = config.backend(); + + test_harness(config, |test_harness| async move { + let TestHarness { + mut virtual_overseer, + clock, + sync_oracle_handle: _sync_oracle_handle, + .. + } = test_harness; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + let candidate_hash = Hash::repeat_byte(0x04); + + let candidate_descriptor = make_candidate(ParaId::from(1_u32), &candidate_hash); + let candidate_hash = candidate_descriptor.hash(); + + let block_hash = Hash::repeat_byte(0x01); + let block_hash_fork = Hash::repeat_byte(0x02); + + let candidate_index = 0; + let validator = ValidatorIndex(0); + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Eve, + ]; + // Add block hash 0x01 and for 0x02 + ChainBuilder::new() + .add_block( + block_hash, + ChainBuilder::GENESIS_HASH, + 1, + BlockConfig { + slot: Slot::from(1), + candidates: Some(vec![( + candidate_descriptor.clone(), + CoreIndex(0), + GroupIndex(0), + )]), + session_info: Some(SessionInfo { + validator_groups: IndexedVec::>::from( + vec![ + vec![ValidatorIndex(0), ValidatorIndex(1)], + vec![ValidatorIndex(2)], + vec![ValidatorIndex(3), ValidatorIndex(4)], + ], + ), + needed_approvals: 1, + ..session_info(&validators) + }), + end_syncing: false, + }, + ) + .add_block( + block_hash_fork, + ChainBuilder::GENESIS_HASH, + 1, + BlockConfig { + slot: Slot::from(1), + candidates: Some(vec![(candidate_descriptor, CoreIndex(0), GroupIndex(0))]), + session_info: Some(SessionInfo { + validator_groups: IndexedVec::>::from( + vec![ + vec![ValidatorIndex(0), ValidatorIndex(1)], + vec![ValidatorIndex(2)], + vec![ValidatorIndex(3), ValidatorIndex(4)], + ], + ), + needed_approvals: 1, + ..session_info(&validators) + }), + end_syncing: false, + }, + ) + .build(&mut virtual_overseer) + .await; + + // Send assignments for the same candidate on both forks + let rx = check_and_import_assignment( + &mut virtual_overseer, + block_hash, + candidate_index, + validator, + ) + .await; + assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted)); + + let rx = check_and_import_assignment( + &mut virtual_overseer, + block_hash_fork, + candidate_index, + validator, + ) + .await; + + assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted)); + // Wake on APPROVAL_DELAY first + assert!(clock.inner.lock().current_wakeup_is(2)); + clock.inner.lock().set_tick(2); + futures_timer::Delay::new(Duration::from_millis(100)).await; + + // Wake up on no-show + assert!(clock.inner.lock().current_wakeup_is(30)); + + for (tick, status) in expected_approval_status + .iter() + .filter(|(tick, _)| *tick < tick_to_send_approval) + { + // Wake up on no-show + clock.inner.lock().set_tick(*tick); + futures_timer::Delay::new(Duration::from_millis(100)).await; + let block_entry = store.load_block_entry(&block_hash).unwrap().unwrap(); + let block_entry_fork = store.load_block_entry(&block_hash_fork).unwrap().unwrap(); + assert!(!block_entry.is_fully_approved()); + assert_eq!(block_entry_fork.is_fully_approved(), *status); + } + + clock.inner.lock().set_tick(tick_to_send_approval); + futures_timer::Delay::new(Duration::from_millis(100)).await; + + // Send the approval for candidate just in the context of 0x01 block. + let rx = check_and_import_approval( + &mut virtual_overseer, + block_hash, + candidate_index, + validator, + candidate_hash, + 1, + false, + None, + ) + .await; + + assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted),); + + // Check approval status for the fork_block is correctly transitioned. + for (tick, status) in expected_approval_status + .iter() + .filter(|(tick, _)| *tick >= tick_to_send_approval) + { + // Wake up on no-show + clock.inner.lock().set_tick(*tick); + futures_timer::Delay::new(Duration::from_millis(100)).await; + let block_entry = store.load_block_entry(&block_hash).unwrap().unwrap(); + let block_entry_fork = store.load_block_entry(&block_hash_fork).unwrap().unwrap(); + assert!(block_entry.is_fully_approved()); + assert_eq!(block_entry_fork.is_fully_approved(), *status); + } + + virtual_overseer + }); +} + #[test] fn subsystem_process_wakeup_schedules_wakeup() { test_harness(HarnessConfig::default(), |test_harness| async move { diff --git a/polkadot/node/core/bitfield-signing/src/lib.rs b/polkadot/node/core/bitfield-signing/src/lib.rs index 0fc0bb3d27887d1257b3473d4ff2b80aff8db42d..89851c4a033b58428fa8eac366aafcd5f7144140 100644 --- a/polkadot/node/core/bitfield-signing/src/lib.rs +++ b/polkadot/node/core/bitfield-signing/src/lib.rs @@ -38,7 +38,7 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_util::{self as util, Validator}; use polkadot_primitives::{AvailabilityBitfield, CoreState, Hash, ValidatorIndex}; use sp_keystore::{Error as KeystoreError, KeystorePtr}; -use std::{collections::HashMap, iter::FromIterator, time::Duration}; +use std::{collections::HashMap, time::Duration}; use wasm_timer::{Delay, Instant}; mod metrics; diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs index ec24434db24c30713e106b99b3238f997ba4765f..08881dad1961f4beb4249c632f3a82c8f86cc68c 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -100,6 +100,13 @@ pub struct Config { pub prep_worker_path: PathBuf, /// Path to the execution worker binary pub exec_worker_path: PathBuf, + /// The maximum number of pvf execution workers. + pub pvf_execute_workers_max_num: usize, + /// The maximum number of pvf workers that can be spawned in the pvf prepare pool for tasks + /// with the priority below critical. + pub pvf_prepare_workers_soft_max_num: usize, + /// The absolute number of pvf workers that can be spawned in the pvf prepare pool. + pub pvf_prepare_workers_hard_max_num: usize, } /// The candidate validation subsystem. @@ -224,6 +231,9 @@ async fn run( secure_validator_mode, prep_worker_path, exec_worker_path, + pvf_execute_workers_max_num, + pvf_prepare_workers_soft_max_num, + pvf_prepare_workers_hard_max_num, }: Config, ) -> SubsystemResult<()> { let (validation_host, task) = polkadot_node_core_pvf::start( @@ -233,6 +243,9 @@ async fn run( secure_validator_mode, prep_worker_path, exec_worker_path, + pvf_execute_workers_max_num, + pvf_prepare_workers_soft_max_num, + pvf_prepare_workers_hard_max_num, ), pvf_metrics, ) @@ -657,7 +670,14 @@ async fn validate_candidate_exhaustive( PrepareJobKind::Compilation, ); - validation_backend.validate_candidate(pvf, exec_timeout, params.encode()).await + validation_backend + .validate_candidate( + pvf, + exec_timeout, + params.encode(), + polkadot_node_core_pvf::Priority::Normal, + ) + .await }, PvfExecKind::Approval => validation_backend @@ -667,6 +687,7 @@ async fn validate_candidate_exhaustive( params, executor_params, PVF_APPROVAL_EXECUTION_RETRY_DELAY, + polkadot_node_core_pvf::Priority::Critical, ) .await, }; @@ -749,10 +770,15 @@ trait ValidationBackend { pvf: PvfPrepData, exec_timeout: Duration, encoded_params: Vec, + // The priority for the preparation job. + prepare_priority: polkadot_node_core_pvf::Priority, ) -> Result; - /// Tries executing a PVF for the approval subsystem. Will retry once if an error is encountered - /// that may have been transient. + /// Tries executing a PVF. Will retry once if an error is encountered that may have + /// been transient. + /// + /// The `prepare_priority` is relevant in the context of the caller. Currently we expect + /// that `approval` context has priority over `backing` context. /// /// NOTE: Should retry only on errors that are a result of execution itself, and not of /// preparation. @@ -763,6 +789,8 @@ trait ValidationBackend { params: ValidationParams, executor_params: ExecutorParams, retry_delay: Duration, + // The priority for the preparation job. + prepare_priority: polkadot_node_core_pvf::Priority, ) -> Result { let prep_timeout = pvf_prep_timeout(&executor_params, PvfPrepKind::Prepare); // Construct the PVF a single time, since it is an expensive operation. Cloning it is cheap. @@ -776,8 +804,10 @@ trait ValidationBackend { // long. let total_time_start = Instant::now(); - let mut validation_result = - self.validate_candidate(pvf.clone(), exec_timeout, params.encode()).await; + // Use `Priority::Critical` as finality trumps parachain liveliness. + let mut validation_result = self + .validate_candidate(pvf.clone(), exec_timeout, params.encode(), prepare_priority) + .await; if validation_result.is_ok() { return validation_result } @@ -851,8 +881,9 @@ trait ValidationBackend { // Encode the params again when re-trying. We expect the retry case to be relatively // rare, and we want to avoid unconditionally cloning data. - validation_result = - self.validate_candidate(pvf.clone(), new_timeout, params.encode()).await; + validation_result = self + .validate_candidate(pvf.clone(), new_timeout, params.encode(), prepare_priority) + .await; } } @@ -870,11 +901,13 @@ impl ValidationBackend for ValidationHost { pvf: PvfPrepData, exec_timeout: Duration, encoded_params: Vec, + // The priority for the preparation job. + prepare_priority: polkadot_node_core_pvf::Priority, ) -> Result { - let priority = polkadot_node_core_pvf::Priority::Normal; - let (tx, rx) = oneshot::channel(); - if let Err(err) = self.execute_pvf(pvf, exec_timeout, encoded_params, priority, tx).await { + if let Err(err) = + self.execute_pvf(pvf, exec_timeout, encoded_params, prepare_priority, tx).await + { return Err(InternalValidationError::HostCommunication(format!( "cannot send pvf to the validation host, it might have shut down: {:?}", err diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs index f646f8535495b74128f829359214018c4fb01ac2..e492d51e239ed4bcdfd3a239f635fcd0b0f2154b 100644 --- a/polkadot/node/core/candidate-validation/src/tests.rs +++ b/polkadot/node/core/candidate-validation/src/tests.rs @@ -368,6 +368,7 @@ impl ValidationBackend for MockValidateCandidateBackend { _pvf: PvfPrepData, _timeout: Duration, _encoded_params: Vec, + _prepare_priority: polkadot_node_core_pvf::Priority, ) -> Result { // This is expected to panic if called more times than expected, indicating an error in the // test. @@ -1044,6 +1045,7 @@ impl ValidationBackend for MockPreCheckBackend { _pvf: PvfPrepData, _timeout: Duration, _encoded_params: Vec, + _prepare_priority: polkadot_node_core_pvf::Priority, ) -> Result { unreachable!() } diff --git a/polkadot/node/core/chain-selection/src/lib.rs b/polkadot/node/core/chain-selection/src/lib.rs index 6f864fefb6110184233d52620dea923f8f0b1e4a..07c245e839bf18a55f5ec61f02824fa87e9c6ed9 100644 --- a/polkadot/node/core/chain-selection/src/lib.rs +++ b/polkadot/node/core/chain-selection/src/lib.rs @@ -619,7 +619,7 @@ async fn handle_active_leaf( // Extract all reversion logs from a header in ascending order. // -// Ignores logs with number >= the block header number. +// Ignores logs with number > the block header number. fn extract_reversion_logs(header: &Header) -> Vec { let number = header.number; let mut logs = header @@ -639,14 +639,14 @@ fn extract_reversion_logs(header: &Header) -> Vec { None }, - Ok(Some(ConsensusLog::Revert(b))) if b < number => Some(b), + Ok(Some(ConsensusLog::Revert(b))) if b <= number => Some(b), Ok(Some(ConsensusLog::Revert(b))) => { gum::warn!( target: LOG_TARGET, revert_target = b, block_number = number, block_hash = ?header.hash(), - "Block issued invalid revert digest targeting itself or future" + "Block issued invalid revert digest targeting future" ); None diff --git a/polkadot/node/core/chain-selection/src/tests.rs b/polkadot/node/core/chain-selection/src/tests.rs index bc998f268a0da6a4ca3703f929be3e1c19677b35..1fe87f04cd585abc1674d3a20766e7172fd819fa 100644 --- a/polkadot/node/core/chain-selection/src/tests.rs +++ b/polkadot/node/core/chain-selection/src/tests.rs @@ -966,19 +966,54 @@ fn ancestor_of_unviable_is_not_leaf_if_has_children() { } #[test] -fn self_and_future_reversions_are_ignored() { +fn self_reversions_are_not_ignored() { test_harness(|backend, _, mut virtual_overseer| async move { let finalized_number = 0; let finalized_hash = Hash::repeat_byte(0); // F <- A1 <- A2 <- A3. // - // A3 reverts itself and future blocks. ignored. + // A3 reverts itself + + let (_, chain_a) = + construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { + if h.number == 3 { + add_reversions(h, vec![3]) + } + }); + + let a2_hash = chain_a.iter().rev().nth(1).unwrap().0.hash(); + + import_blocks_into( + &mut virtual_overseer, + &backend, + Some((finalized_number, finalized_hash)), + chain_a.clone(), + ) + .await; + + assert_backend_contains(&backend, chain_a.iter().map(|(h, _)| h)); + assert_leaves(&backend, vec![a2_hash]); + assert_leaves_query(&mut virtual_overseer, vec![a2_hash]).await; + + virtual_overseer + }); +} + +#[test] +fn future_reversions_are_ignored() { + test_harness(|backend, _, mut virtual_overseer| async move { + let finalized_number = 0; + let finalized_hash = Hash::repeat_byte(0); + + // F <- A1 <- A2 <- A3. + // + // A3 reverts future blocks. ignored. let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { if h.number == 3 { - add_reversions(h, vec![3, 4, 100]) + add_reversions(h, vec![4, 100]) } }); @@ -1006,7 +1041,7 @@ fn revert_finalized_is_ignored() { // F <- A1 <- A2 <- A3. // - // A3 reverts itself and future blocks. ignored. + // A3 reverts finalized F and its ancestors. ignored. let (a3_hash, chain_a) = construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |h| { diff --git a/polkadot/node/core/chain-selection/src/tree.rs b/polkadot/node/core/chain-selection/src/tree.rs index b4aba30368a621c1f1eaa5f1834fdb3343427d2f..1eb6c13a7f8232a66a2f0c4b89543f8e79690bce 100644 --- a/polkadot/node/core/chain-selection/src/tree.rs +++ b/polkadot/node/core/chain-selection/src/tree.rs @@ -236,7 +236,7 @@ fn propagate_viability_update( Ok(()) } -/// Imports a new block and applies any reversions to ancestors. +/// Imports a new block and applies any reversions to ancestors or the block itself. pub(crate) fn import_block( backend: &mut OverlayedBackend, block_hash: Hash, @@ -246,25 +246,29 @@ pub(crate) fn import_block( weight: BlockWeight, stagnant_at: Timestamp, ) -> Result<(), Error> { - add_block(backend, block_hash, block_number, parent_hash, weight, stagnant_at)?; - apply_ancestor_reversions(backend, block_hash, block_number, reversion_logs)?; + let block_entry = + add_block(backend, block_hash, block_number, parent_hash, weight, stagnant_at)?; + apply_reversions(backend, block_entry, reversion_logs)?; Ok(()) } // Load the given ancestor's block entry, in descending order from the `block_hash`. -// The ancestor_number must be at least one block less than the `block_number`. +// The ancestor_number must be not higher than the `block_entry`'s. // // The returned entry will be `None` if the range is invalid or any block in the path had // no entry present. If any block entry was missing, it can safely be assumed to // be finalized. fn load_ancestor( backend: &mut OverlayedBackend, - block_hash: Hash, - block_number: BlockNumber, + block_entry: &BlockEntry, ancestor_number: BlockNumber, ) -> Result, Error> { - if block_number <= ancestor_number { + let block_hash = block_entry.block_hash; + let block_number = block_entry.block_number; + if block_number == ancestor_number { + return Ok(Some(block_entry.clone())) + } else if block_number < ancestor_number { return Ok(None) } @@ -300,7 +304,7 @@ fn add_block( parent_hash: Hash, weight: BlockWeight, stagnant_at: Timestamp, -) -> Result<(), Error> { +) -> Result { let mut leaves = backend.load_leaves()?; let parent_entry = backend.load_block_entry(&parent_hash)?; @@ -308,7 +312,7 @@ fn add_block( parent_entry.as_ref().and_then(|parent| parent.non_viable_ancestor_for_child()); // 1. Add the block to the DB assuming it's not reverted. - backend.write_block_entry(BlockEntry { + let block_entry = BlockEntry { block_hash, block_number, parent_hash, @@ -319,7 +323,8 @@ fn add_block( approval: Approval::Unapproved, }, weight, - }); + }; + backend.write_block_entry(block_entry.clone()); // 2. Update leaves if inherited viability is fine. if inherited_viability.is_none() { @@ -344,26 +349,25 @@ fn add_block( stagnant_at_list.push(block_hash); backend.write_stagnant_at(stagnant_at, stagnant_at_list); - Ok(()) + Ok(block_entry) } /// Assuming that a block is already imported, accepts the number of the block /// as well as a list of reversions triggered by the block in ascending order. -fn apply_ancestor_reversions( +fn apply_reversions( backend: &mut OverlayedBackend, - block_hash: Hash, - block_number: BlockNumber, + block_entry: BlockEntry, reversions: Vec, ) -> Result<(), Error> { // Note: since revert numbers are in ascending order, the expensive propagation // of unviability is only heavy on the first log. for revert_number in reversions { - let maybe_block_entry = load_ancestor(backend, block_hash, block_number, revert_number)?; - if let Some(block_entry) = &maybe_block_entry { + let maybe_block_entry = load_ancestor(backend, &block_entry, revert_number)?; + if let Some(entry) = &maybe_block_entry { gum::trace!( target: LOG_TARGET, ?revert_number, - revert_hash = ?block_entry.block_hash, + revert_hash = ?entry.block_hash, "Block marked as reverted via scraped on-chain reversions" ); } @@ -372,8 +376,8 @@ fn apply_ancestor_reversions( maybe_block_entry, None, revert_number, - Some(block_hash), - Some(block_number), + Some(block_entry.block_hash), + Some(block_entry.block_number), )?; } diff --git a/polkadot/node/core/prospective-parachains/src/fragment_tree.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs similarity index 50% rename from polkadot/node/core/prospective-parachains/src/fragment_tree.rs rename to polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs index 8061dc82d8358ae9e7c741e3f785634601eb5d03..86814b976d13424281203aa5bca59b0a918b7251 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_tree.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs @@ -86,6 +86,9 @@ //! will still perform fairly well under these conditions, despite being somewhat wasteful of //! memory. +#[cfg(test)] +mod tests; + use std::{ borrow::Cow, collections::{ @@ -1145,1447 +1148,3 @@ impl FragmentNode { self.children.iter().find(|(_, c)| c == candidate_hash).map(|(p, _)| *p) } } - -#[cfg(test)] -mod tests { - use super::*; - use assert_matches::assert_matches; - use polkadot_node_subsystem_util::inclusion_emulator::InboundHrmpLimitations; - use polkadot_primitives::{BlockNumber, CandidateCommitments, CandidateDescriptor, HeadData}; - use polkadot_primitives_test_helpers as test_helpers; - use rstest::rstest; - use std::iter; - - impl NodePointer { - fn unwrap_idx(self) -> usize { - match self { - NodePointer::Root => panic!("Unexpected root"), - NodePointer::Storage(index) => index, - } - } - } - - fn make_constraints( - min_relay_parent_number: BlockNumber, - valid_watermarks: Vec, - required_parent: HeadData, - ) -> Constraints { - Constraints { - min_relay_parent_number, - max_pov_size: 1_000_000, - max_code_size: 1_000_000, - ump_remaining: 10, - ump_remaining_bytes: 1_000, - max_ump_num_per_candidate: 10, - dmp_remaining_messages: [0; 10].into(), - hrmp_inbound: InboundHrmpLimitations { valid_watermarks }, - hrmp_channels_out: HashMap::new(), - max_hrmp_num_per_candidate: 0, - required_parent, - validation_code_hash: Hash::repeat_byte(42).into(), - upgrade_restriction: None, - future_validation_code: None, - } - } - - fn make_committed_candidate( - para_id: ParaId, - relay_parent: Hash, - relay_parent_number: BlockNumber, - parent_head: HeadData, - para_head: HeadData, - hrmp_watermark: BlockNumber, - ) -> (PersistedValidationData, CommittedCandidateReceipt) { - let persisted_validation_data = PersistedValidationData { - parent_head, - relay_parent_number, - relay_parent_storage_root: Hash::repeat_byte(69), - max_pov_size: 1_000_000, - }; - - let candidate = CommittedCandidateReceipt { - descriptor: CandidateDescriptor { - para_id, - relay_parent, - collator: test_helpers::dummy_collator(), - persisted_validation_data_hash: persisted_validation_data.hash(), - pov_hash: Hash::repeat_byte(1), - erasure_root: Hash::repeat_byte(1), - signature: test_helpers::dummy_collator_signature(), - para_head: para_head.hash(), - validation_code_hash: Hash::repeat_byte(42).into(), - }, - commitments: CandidateCommitments { - upward_messages: Default::default(), - horizontal_messages: Default::default(), - new_validation_code: None, - head_data: para_head, - processed_downward_messages: 1, - hrmp_watermark, - }, - }; - - (persisted_validation_data, candidate) - } - - #[test] - fn scope_rejects_ancestors_that_skip_blocks() { - let para_id = ParaId::from(5u32); - let relay_parent = RelayChainBlockInfo { - number: 10, - hash: Hash::repeat_byte(10), - storage_root: Hash::repeat_byte(69), - }; - - let ancestors = vec![RelayChainBlockInfo { - number: 8, - hash: Hash::repeat_byte(8), - storage_root: Hash::repeat_byte(69), - }]; - - let max_depth = 2; - let base_constraints = make_constraints(8, vec![8, 9], vec![1, 2, 3].into()); - let pending_availability = Vec::new(); - - assert_matches!( - Scope::with_ancestors( - para_id, - relay_parent, - base_constraints, - pending_availability, - max_depth, - ancestors - ), - Err(UnexpectedAncestor { number: 8, prev: 10 }) - ); - } - - #[test] - fn scope_rejects_ancestor_for_0_block() { - let para_id = ParaId::from(5u32); - let relay_parent = RelayChainBlockInfo { - number: 0, - hash: Hash::repeat_byte(0), - storage_root: Hash::repeat_byte(69), - }; - - let ancestors = vec![RelayChainBlockInfo { - number: 99999, - hash: Hash::repeat_byte(99), - storage_root: Hash::repeat_byte(69), - }]; - - let max_depth = 2; - let base_constraints = make_constraints(0, vec![], vec![1, 2, 3].into()); - let pending_availability = Vec::new(); - - assert_matches!( - Scope::with_ancestors( - para_id, - relay_parent, - base_constraints, - pending_availability, - max_depth, - ancestors, - ), - Err(UnexpectedAncestor { number: 99999, prev: 0 }) - ); - } - - #[test] - fn scope_only_takes_ancestors_up_to_min() { - let para_id = ParaId::from(5u32); - let relay_parent = RelayChainBlockInfo { - number: 5, - hash: Hash::repeat_byte(0), - storage_root: Hash::repeat_byte(69), - }; - - let ancestors = vec![ - RelayChainBlockInfo { - number: 4, - hash: Hash::repeat_byte(4), - storage_root: Hash::repeat_byte(69), - }, - RelayChainBlockInfo { - number: 3, - hash: Hash::repeat_byte(3), - storage_root: Hash::repeat_byte(69), - }, - RelayChainBlockInfo { - number: 2, - hash: Hash::repeat_byte(2), - storage_root: Hash::repeat_byte(69), - }, - ]; - - let max_depth = 2; - let base_constraints = make_constraints(3, vec![2], vec![1, 2, 3].into()); - let pending_availability = Vec::new(); - - let scope = Scope::with_ancestors( - para_id, - relay_parent, - base_constraints, - pending_availability, - max_depth, - ancestors, - ) - .unwrap(); - - assert_eq!(scope.ancestors.len(), 2); - assert_eq!(scope.ancestors_by_hash.len(), 2); - } - - #[test] - fn storage_add_candidate() { - let mut storage = CandidateStorage::new(); - let relay_parent = Hash::repeat_byte(69); - - let (pvd, candidate) = make_committed_candidate( - ParaId::from(5u32), - relay_parent, - 8, - vec![4, 5, 6].into(), - vec![1, 2, 3].into(), - 7, - ); - - let candidate_hash = candidate.hash(); - let parent_head_hash = pvd.parent_head.hash(); - - storage.add_candidate(candidate, pvd).unwrap(); - assert!(storage.contains(&candidate_hash)); - assert_eq!(storage.iter_para_children(&parent_head_hash).count(), 1); - - assert_eq!(storage.relay_parent_by_candidate_hash(&candidate_hash), Some(relay_parent)); - } - - #[test] - fn storage_retain() { - let mut storage = CandidateStorage::new(); - - let (pvd, candidate) = make_committed_candidate( - ParaId::from(5u32), - Hash::repeat_byte(69), - 8, - vec![4, 5, 6].into(), - vec![1, 2, 3].into(), - 7, - ); - - let candidate_hash = candidate.hash(); - let output_head_hash = candidate.commitments.head_data.hash(); - let parent_head_hash = pvd.parent_head.hash(); - - storage.add_candidate(candidate, pvd).unwrap(); - storage.retain(|_| true); - assert!(storage.contains(&candidate_hash)); - assert_eq!(storage.iter_para_children(&parent_head_hash).count(), 1); - assert!(storage.head_data_by_hash(&output_head_hash).is_some()); - - storage.retain(|_| false); - assert!(!storage.contains(&candidate_hash)); - assert_eq!(storage.iter_para_children(&parent_head_hash).count(), 0); - assert!(storage.head_data_by_hash(&output_head_hash).is_none()); - } - - // [`FragmentTree::populate`] should pick up candidates that build on other candidates. - #[test] - fn populate_works_recursively() { - let mut storage = CandidateStorage::new(); - - let para_id = ParaId::from(5u32); - let relay_parent_a = Hash::repeat_byte(1); - let relay_parent_b = Hash::repeat_byte(2); - - let (pvd_a, candidate_a) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0a].into(), - vec![0x0b].into(), - 0, - ); - let candidate_a_hash = candidate_a.hash(); - - let (pvd_b, candidate_b) = make_committed_candidate( - para_id, - relay_parent_b, - 1, - vec![0x0b].into(), - vec![0x0c].into(), - 1, - ); - let candidate_b_hash = candidate_b.hash(); - - let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); - let pending_availability = Vec::new(); - - let ancestors = vec![RelayChainBlockInfo { - number: pvd_a.relay_parent_number, - hash: relay_parent_a, - storage_root: pvd_a.relay_parent_storage_root, - }]; - - let relay_parent_b_info = RelayChainBlockInfo { - number: pvd_b.relay_parent_number, - hash: relay_parent_b, - storage_root: pvd_b.relay_parent_storage_root, - }; - - storage.add_candidate(candidate_a, pvd_a).unwrap(); - storage.add_candidate(candidate_b, pvd_b).unwrap(); - let scope = Scope::with_ancestors( - para_id, - relay_parent_b_info, - base_constraints, - pending_availability, - 4, - ancestors, - ) - .unwrap(); - let tree = FragmentTree::populate(scope, &storage); - - let candidates: Vec<_> = tree.candidates().collect(); - assert_eq!(candidates.len(), 2); - assert!(candidates.contains(&candidate_a_hash)); - assert!(candidates.contains(&candidate_b_hash)); - - assert_eq!(tree.nodes.len(), 2); - assert_eq!(tree.nodes[0].parent, NodePointer::Root); - assert_eq!(tree.nodes[0].candidate_hash, candidate_a_hash); - assert_eq!(tree.nodes[0].depth, 0); - - assert_eq!(tree.nodes[1].parent, NodePointer::Storage(0)); - assert_eq!(tree.nodes[1].candidate_hash, candidate_b_hash); - assert_eq!(tree.nodes[1].depth, 1); - } - - #[test] - fn children_of_root_are_contiguous() { - let mut storage = CandidateStorage::new(); - - let para_id = ParaId::from(5u32); - let relay_parent_a = Hash::repeat_byte(1); - let relay_parent_b = Hash::repeat_byte(2); - - let (pvd_a, candidate_a) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0a].into(), - vec![0x0b].into(), - 0, - ); - - let (pvd_b, candidate_b) = make_committed_candidate( - para_id, - relay_parent_b, - 1, - vec![0x0b].into(), - vec![0x0c].into(), - 1, - ); - - let (pvd_a2, candidate_a2) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0a].into(), - vec![0x0b, 1].into(), - 0, - ); - let candidate_a2_hash = candidate_a2.hash(); - - let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); - let pending_availability = Vec::new(); - - let ancestors = vec![RelayChainBlockInfo { - number: pvd_a.relay_parent_number, - hash: relay_parent_a, - storage_root: pvd_a.relay_parent_storage_root, - }]; - - let relay_parent_b_info = RelayChainBlockInfo { - number: pvd_b.relay_parent_number, - hash: relay_parent_b, - storage_root: pvd_b.relay_parent_storage_root, - }; - - storage.add_candidate(candidate_a, pvd_a).unwrap(); - storage.add_candidate(candidate_b, pvd_b).unwrap(); - let scope = Scope::with_ancestors( - para_id, - relay_parent_b_info, - base_constraints, - pending_availability, - 4, - ancestors, - ) - .unwrap(); - let mut tree = FragmentTree::populate(scope, &storage); - - storage.add_candidate(candidate_a2, pvd_a2).unwrap(); - tree.add_and_populate(candidate_a2_hash, &storage); - let candidates: Vec<_> = tree.candidates().collect(); - assert_eq!(candidates.len(), 3); - - assert_eq!(tree.nodes[0].parent, NodePointer::Root); - assert_eq!(tree.nodes[1].parent, NodePointer::Root); - assert_eq!(tree.nodes[2].parent, NodePointer::Storage(0)); - } - - #[test] - fn add_candidate_child_of_root() { - let mut storage = CandidateStorage::new(); - - let para_id = ParaId::from(5u32); - let relay_parent_a = Hash::repeat_byte(1); - - let (pvd_a, candidate_a) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0a].into(), - vec![0x0b].into(), - 0, - ); - - let (pvd_b, candidate_b) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0a].into(), - vec![0x0c].into(), - 0, - ); - let candidate_b_hash = candidate_b.hash(); - - let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); - let pending_availability = Vec::new(); - - let relay_parent_a_info = RelayChainBlockInfo { - number: pvd_a.relay_parent_number, - hash: relay_parent_a, - storage_root: pvd_a.relay_parent_storage_root, - }; - - storage.add_candidate(candidate_a, pvd_a).unwrap(); - let scope = Scope::with_ancestors( - para_id, - relay_parent_a_info, - base_constraints, - pending_availability, - 4, - vec![], - ) - .unwrap(); - let mut tree = FragmentTree::populate(scope, &storage); - - storage.add_candidate(candidate_b, pvd_b).unwrap(); - tree.add_and_populate(candidate_b_hash, &storage); - let candidates: Vec<_> = tree.candidates().collect(); - assert_eq!(candidates.len(), 2); - - assert_eq!(tree.nodes[0].parent, NodePointer::Root); - assert_eq!(tree.nodes[1].parent, NodePointer::Root); - } - - #[test] - fn add_candidate_child_of_non_root() { - let mut storage = CandidateStorage::new(); - - let para_id = ParaId::from(5u32); - let relay_parent_a = Hash::repeat_byte(1); - - let (pvd_a, candidate_a) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0a].into(), - vec![0x0b].into(), - 0, - ); - - let (pvd_b, candidate_b) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0b].into(), - vec![0x0c].into(), - 0, - ); - let candidate_b_hash = candidate_b.hash(); - - let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); - let pending_availability = Vec::new(); - - let relay_parent_a_info = RelayChainBlockInfo { - number: pvd_a.relay_parent_number, - hash: relay_parent_a, - storage_root: pvd_a.relay_parent_storage_root, - }; - - storage.add_candidate(candidate_a, pvd_a).unwrap(); - let scope = Scope::with_ancestors( - para_id, - relay_parent_a_info, - base_constraints, - pending_availability, - 4, - vec![], - ) - .unwrap(); - let mut tree = FragmentTree::populate(scope, &storage); - - storage.add_candidate(candidate_b, pvd_b).unwrap(); - tree.add_and_populate(candidate_b_hash, &storage); - let candidates: Vec<_> = tree.candidates().collect(); - assert_eq!(candidates.len(), 2); - - assert_eq!(tree.nodes[0].parent, NodePointer::Root); - assert_eq!(tree.nodes[1].parent, NodePointer::Storage(0)); - } - - #[test] - fn test_find_ancestor_path_and_find_backable_chain_empty_tree() { - let para_id = ParaId::from(5u32); - let relay_parent = Hash::repeat_byte(1); - let required_parent: HeadData = vec![0xff].into(); - let max_depth = 10; - - // Empty tree - let storage = CandidateStorage::new(); - let base_constraints = make_constraints(0, vec![0], required_parent.clone()); - - let relay_parent_info = - RelayChainBlockInfo { number: 0, hash: relay_parent, storage_root: Hash::zero() }; - - let scope = Scope::with_ancestors( - para_id, - relay_parent_info, - base_constraints, - vec![], - max_depth, - vec![], - ) - .unwrap(); - let tree = FragmentTree::populate(scope, &storage); - assert_eq!(tree.candidates().collect::>().len(), 0); - assert_eq!(tree.nodes.len(), 0); - - assert_eq!(tree.find_ancestor_path(Ancestors::new()).unwrap(), NodePointer::Root); - assert_eq!(tree.find_backable_chain(Ancestors::new(), 2, |_| true), vec![]); - // Invalid candidate. - let ancestors: Ancestors = [CandidateHash::default()].into_iter().collect(); - assert_eq!(tree.find_ancestor_path(ancestors.clone()), Some(NodePointer::Root)); - assert_eq!(tree.find_backable_chain(ancestors, 2, |_| true), vec![]); - } - - #[rstest] - #[case(true, 13)] - #[case(false, 8)] - // The tree with no cycles looks like: - // Make a tree that looks like this (note that there's no cycle): - // +-(root)-+ - // | | - // +----0---+ 7 - // | | - // 1----+ 5 - // | | - // | | - // 2 6 - // | - // 3 - // | - // 4 - // - // The tree with cycles is the same as the first but has a cycle from 4 back to the state - // produced by 0 (It's bounded by the max_depth + 1). - // +-(root)-+ - // | | - // +----0---+ 7 - // | | - // 1----+ 5 - // | | - // | | - // 2 6 - // | - // 3 - // | - // 4---+ - // | | - // 1 5 - // | - // 2 - // | - // 3 - fn test_find_ancestor_path_and_find_backable_chain( - #[case] has_cycle: bool, - #[case] expected_node_count: usize, - ) { - let para_id = ParaId::from(5u32); - let relay_parent = Hash::repeat_byte(1); - let required_parent: HeadData = vec![0xff].into(); - let max_depth = 7; - let relay_parent_number = 0; - let relay_parent_storage_root = Hash::repeat_byte(69); - - let mut candidates = vec![]; - - // Candidate 0 - candidates.push(make_committed_candidate( - para_id, - relay_parent, - 0, - required_parent.clone(), - vec![0].into(), - 0, - )); - // Candidate 1 - candidates.push(make_committed_candidate( - para_id, - relay_parent, - 0, - vec![0].into(), - vec![1].into(), - 0, - )); - // Candidate 2 - candidates.push(make_committed_candidate( - para_id, - relay_parent, - 0, - vec![1].into(), - vec![2].into(), - 0, - )); - // Candidate 3 - candidates.push(make_committed_candidate( - para_id, - relay_parent, - 0, - vec![2].into(), - vec![3].into(), - 0, - )); - // Candidate 4 - candidates.push(make_committed_candidate( - para_id, - relay_parent, - 0, - vec![3].into(), - vec![4].into(), - 0, - )); - // Candidate 5 - candidates.push(make_committed_candidate( - para_id, - relay_parent, - 0, - vec![0].into(), - vec![5].into(), - 0, - )); - // Candidate 6 - candidates.push(make_committed_candidate( - para_id, - relay_parent, - 0, - vec![1].into(), - vec![6].into(), - 0, - )); - // Candidate 7 - candidates.push(make_committed_candidate( - para_id, - relay_parent, - 0, - required_parent.clone(), - vec![7].into(), - 0, - )); - - if has_cycle { - candidates[4] = make_committed_candidate( - para_id, - relay_parent, - 0, - vec![3].into(), - vec![0].into(), // put the cycle here back to the output state of 0. - 0, - ); - } - - let base_constraints = make_constraints(0, vec![0], required_parent.clone()); - let mut storage = CandidateStorage::new(); - - let relay_parent_info = RelayChainBlockInfo { - number: relay_parent_number, - hash: relay_parent, - storage_root: relay_parent_storage_root, - }; - - for (pvd, candidate) in candidates.iter() { - storage.add_candidate(candidate.clone(), pvd.clone()).unwrap(); - } - let candidates = - candidates.into_iter().map(|(_pvd, candidate)| candidate).collect::>(); - let scope = Scope::with_ancestors( - para_id, - relay_parent_info, - base_constraints, - vec![], - max_depth, - vec![], - ) - .unwrap(); - let tree = FragmentTree::populate(scope, &storage); - - assert_eq!(tree.candidates().collect::>().len(), candidates.len()); - assert_eq!(tree.nodes.len(), expected_node_count); - - // Do some common tests on both trees. - { - // No ancestors supplied. - assert_eq!(tree.find_ancestor_path(Ancestors::new()).unwrap(), NodePointer::Root); - assert_eq!( - tree.find_backable_chain(Ancestors::new(), 4, |_| true), - [0, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() - ); - // Ancestor which is not part of the tree. Will be ignored. - let ancestors: Ancestors = [CandidateHash::default()].into_iter().collect(); - assert_eq!(tree.find_ancestor_path(ancestors.clone()).unwrap(), NodePointer::Root); - assert_eq!( - tree.find_backable_chain(ancestors, 4, |_| true), - [0, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() - ); - // A chain fork. - let ancestors: Ancestors = - [(candidates[0].hash()), (candidates[7].hash())].into_iter().collect(); - assert_eq!(tree.find_ancestor_path(ancestors.clone()), None); - assert_eq!(tree.find_backable_chain(ancestors, 1, |_| true), vec![]); - - // Ancestors which are part of the tree but don't form a path. Will be ignored. - let ancestors: Ancestors = - [candidates[1].hash(), candidates[2].hash()].into_iter().collect(); - assert_eq!(tree.find_ancestor_path(ancestors.clone()).unwrap(), NodePointer::Root); - assert_eq!( - tree.find_backable_chain(ancestors, 4, |_| true), - [0, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() - ); - - // Valid ancestors. - let ancestors: Ancestors = [candidates[7].hash()].into_iter().collect(); - let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); - let candidate = &tree.nodes[res.unwrap_idx()]; - assert_eq!(candidate.candidate_hash, candidates[7].hash()); - assert_eq!(tree.find_backable_chain(ancestors, 1, |_| true), vec![]); - - let ancestors: Ancestors = - [candidates[2].hash(), candidates[0].hash(), candidates[1].hash()] - .into_iter() - .collect(); - let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); - let candidate = &tree.nodes[res.unwrap_idx()]; - assert_eq!(candidate.candidate_hash, candidates[2].hash()); - assert_eq!( - tree.find_backable_chain(ancestors.clone(), 2, |_| true), - [3, 4].into_iter().map(|i| candidates[i].hash()).collect::>() - ); - - // Valid ancestors with candidates which have been omitted due to timeouts - let ancestors: Ancestors = - [candidates[0].hash(), candidates[2].hash()].into_iter().collect(); - let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); - let candidate = &tree.nodes[res.unwrap_idx()]; - assert_eq!(candidate.candidate_hash, candidates[0].hash()); - assert_eq!( - tree.find_backable_chain(ancestors, 3, |_| true), - [1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() - ); - - let ancestors: Ancestors = - [candidates[0].hash(), candidates[1].hash(), candidates[3].hash()] - .into_iter() - .collect(); - let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); - let candidate = &tree.nodes[res.unwrap_idx()]; - assert_eq!(candidate.candidate_hash, candidates[1].hash()); - if has_cycle { - assert_eq!( - tree.find_backable_chain(ancestors, 2, |_| true), - [2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() - ); - } else { - assert_eq!( - tree.find_backable_chain(ancestors, 4, |_| true), - [2, 3, 4].into_iter().map(|i| candidates[i].hash()).collect::>() - ); - } - - let ancestors: Ancestors = - [candidates[1].hash(), candidates[2].hash()].into_iter().collect(); - let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); - assert_eq!(res, NodePointer::Root); - assert_eq!( - tree.find_backable_chain(ancestors, 4, |_| true), - [0, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() - ); - - // Requested count is 0. - assert_eq!(tree.find_backable_chain(Ancestors::new(), 0, |_| true), vec![]); - - let ancestors: Ancestors = - [candidates[2].hash(), candidates[0].hash(), candidates[1].hash()] - .into_iter() - .collect(); - assert_eq!(tree.find_backable_chain(ancestors, 0, |_| true), vec![]); - - let ancestors: Ancestors = - [candidates[2].hash(), candidates[0].hash()].into_iter().collect(); - assert_eq!(tree.find_backable_chain(ancestors, 0, |_| true), vec![]); - } - - // Now do some tests only on the tree with cycles - if has_cycle { - // Exceeds the maximum tree depth. 0-1-2-3-4-1-2-3-4, when the tree stops at - // 0-1-2-3-4-1-2-3. - let ancestors: Ancestors = [ - candidates[0].hash(), - candidates[1].hash(), - candidates[2].hash(), - candidates[3].hash(), - candidates[4].hash(), - ] - .into_iter() - .collect(); - let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); - let candidate = &tree.nodes[res.unwrap_idx()]; - assert_eq!(candidate.candidate_hash, candidates[4].hash()); - assert_eq!( - tree.find_backable_chain(ancestors, 4, |_| true), - [1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() - ); - - // 0-1-2. - let ancestors: Ancestors = - [candidates[0].hash(), candidates[1].hash(), candidates[2].hash()] - .into_iter() - .collect(); - let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); - let candidate = &tree.nodes[res.unwrap_idx()]; - assert_eq!(candidate.candidate_hash, candidates[2].hash()); - assert_eq!( - tree.find_backable_chain(ancestors.clone(), 1, |_| true), - [3].into_iter().map(|i| candidates[i].hash()).collect::>() - ); - assert_eq!( - tree.find_backable_chain(ancestors, 5, |_| true), - [3, 4, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() - ); - - // 0-1 - let ancestors: Ancestors = - [candidates[0].hash(), candidates[1].hash()].into_iter().collect(); - let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); - let candidate = &tree.nodes[res.unwrap_idx()]; - assert_eq!(candidate.candidate_hash, candidates[1].hash()); - assert_eq!( - tree.find_backable_chain(ancestors, 6, |_| true), - [2, 3, 4, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>(), - ); - - // For 0-1-2-3-4-5, there's more than 1 way of finding this path in - // the tree. `None` should be returned. The runtime should not have accepted this. - let ancestors: Ancestors = [ - candidates[0].hash(), - candidates[1].hash(), - candidates[2].hash(), - candidates[3].hash(), - candidates[4].hash(), - candidates[5].hash(), - ] - .into_iter() - .collect(); - let res = tree.find_ancestor_path(ancestors.clone()); - assert_eq!(res, None); - assert_eq!(tree.find_backable_chain(ancestors, 1, |_| true), vec![]); - } - } - - #[test] - fn graceful_cycle_of_0() { - let mut storage = CandidateStorage::new(); - - let para_id = ParaId::from(5u32); - let relay_parent_a = Hash::repeat_byte(1); - - let (pvd_a, candidate_a) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0a].into(), - vec![0x0a].into(), // input same as output - 0, - ); - let candidate_a_hash = candidate_a.hash(); - let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); - let pending_availability = Vec::new(); - - let relay_parent_a_info = RelayChainBlockInfo { - number: pvd_a.relay_parent_number, - hash: relay_parent_a, - storage_root: pvd_a.relay_parent_storage_root, - }; - - let max_depth = 4; - storage.add_candidate(candidate_a, pvd_a).unwrap(); - let scope = Scope::with_ancestors( - para_id, - relay_parent_a_info, - base_constraints, - pending_availability, - max_depth, - vec![], - ) - .unwrap(); - let tree = FragmentTree::populate(scope, &storage); - - let candidates: Vec<_> = tree.candidates().collect(); - assert_eq!(candidates.len(), 1); - assert_eq!(tree.nodes.len(), max_depth + 1); - - assert_eq!(tree.nodes[0].parent, NodePointer::Root); - assert_eq!(tree.nodes[1].parent, NodePointer::Storage(0)); - assert_eq!(tree.nodes[2].parent, NodePointer::Storage(1)); - assert_eq!(tree.nodes[3].parent, NodePointer::Storage(2)); - assert_eq!(tree.nodes[4].parent, NodePointer::Storage(3)); - - assert_eq!(tree.nodes[0].candidate_hash, candidate_a_hash); - assert_eq!(tree.nodes[1].candidate_hash, candidate_a_hash); - assert_eq!(tree.nodes[2].candidate_hash, candidate_a_hash); - assert_eq!(tree.nodes[3].candidate_hash, candidate_a_hash); - assert_eq!(tree.nodes[4].candidate_hash, candidate_a_hash); - - for count in 1..10 { - assert_eq!( - tree.find_backable_chain(Ancestors::new(), count, |_| true), - iter::repeat(candidate_a_hash) - .take(std::cmp::min(count as usize, max_depth + 1)) - .collect::>() - ); - assert_eq!( - tree.find_backable_chain( - [candidate_a_hash].into_iter().collect(), - count - 1, - |_| true - ), - iter::repeat(candidate_a_hash) - .take(std::cmp::min(count as usize - 1, max_depth)) - .collect::>() - ); - } - } - - #[test] - fn graceful_cycle_of_1() { - let mut storage = CandidateStorage::new(); - - let para_id = ParaId::from(5u32); - let relay_parent_a = Hash::repeat_byte(1); - - let (pvd_a, candidate_a) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0a].into(), - vec![0x0b].into(), // input same as output - 0, - ); - let candidate_a_hash = candidate_a.hash(); - - let (pvd_b, candidate_b) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0b].into(), - vec![0x0a].into(), // input same as output - 0, - ); - let candidate_b_hash = candidate_b.hash(); - - let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); - let pending_availability = Vec::new(); - - let relay_parent_a_info = RelayChainBlockInfo { - number: pvd_a.relay_parent_number, - hash: relay_parent_a, - storage_root: pvd_a.relay_parent_storage_root, - }; - - let max_depth = 4; - storage.add_candidate(candidate_a, pvd_a).unwrap(); - storage.add_candidate(candidate_b, pvd_b).unwrap(); - let scope = Scope::with_ancestors( - para_id, - relay_parent_a_info, - base_constraints, - pending_availability, - max_depth, - vec![], - ) - .unwrap(); - let tree = FragmentTree::populate(scope, &storage); - - let candidates: Vec<_> = tree.candidates().collect(); - assert_eq!(candidates.len(), 2); - assert_eq!(tree.nodes.len(), max_depth + 1); - - assert_eq!(tree.nodes[0].parent, NodePointer::Root); - assert_eq!(tree.nodes[1].parent, NodePointer::Storage(0)); - assert_eq!(tree.nodes[2].parent, NodePointer::Storage(1)); - assert_eq!(tree.nodes[3].parent, NodePointer::Storage(2)); - assert_eq!(tree.nodes[4].parent, NodePointer::Storage(3)); - - assert_eq!(tree.nodes[0].candidate_hash, candidate_a_hash); - assert_eq!(tree.nodes[1].candidate_hash, candidate_b_hash); - assert_eq!(tree.nodes[2].candidate_hash, candidate_a_hash); - assert_eq!(tree.nodes[3].candidate_hash, candidate_b_hash); - assert_eq!(tree.nodes[4].candidate_hash, candidate_a_hash); - - assert_eq!(tree.find_backable_chain(Ancestors::new(), 1, |_| true), vec![candidate_a_hash],); - assert_eq!( - tree.find_backable_chain(Ancestors::new(), 2, |_| true), - vec![candidate_a_hash, candidate_b_hash], - ); - assert_eq!( - tree.find_backable_chain(Ancestors::new(), 3, |_| true), - vec![candidate_a_hash, candidate_b_hash, candidate_a_hash], - ); - assert_eq!( - tree.find_backable_chain([candidate_a_hash].into_iter().collect(), 2, |_| true), - vec![candidate_b_hash, candidate_a_hash], - ); - - assert_eq!( - tree.find_backable_chain(Ancestors::new(), 6, |_| true), - vec![ - candidate_a_hash, - candidate_b_hash, - candidate_a_hash, - candidate_b_hash, - candidate_a_hash - ], - ); - - for count in 3..7 { - assert_eq!( - tree.find_backable_chain( - [candidate_a_hash, candidate_b_hash].into_iter().collect(), - count, - |_| true - ), - vec![candidate_a_hash, candidate_b_hash, candidate_a_hash], - ); - } - } - - #[test] - fn hypothetical_depths_known_and_unknown() { - let mut storage = CandidateStorage::new(); - - let para_id = ParaId::from(5u32); - let relay_parent_a = Hash::repeat_byte(1); - - let (pvd_a, candidate_a) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0a].into(), - vec![0x0b].into(), // input same as output - 0, - ); - let candidate_a_hash = candidate_a.hash(); - - let (pvd_b, candidate_b) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0b].into(), - vec![0x0a].into(), // input same as output - 0, - ); - let candidate_b_hash = candidate_b.hash(); - - let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); - let pending_availability = Vec::new(); - - let relay_parent_a_info = RelayChainBlockInfo { - number: pvd_a.relay_parent_number, - hash: relay_parent_a, - storage_root: pvd_a.relay_parent_storage_root, - }; - - let max_depth = 4; - storage.add_candidate(candidate_a, pvd_a).unwrap(); - storage.add_candidate(candidate_b, pvd_b).unwrap(); - let scope = Scope::with_ancestors( - para_id, - relay_parent_a_info, - base_constraints, - pending_availability, - max_depth, - vec![], - ) - .unwrap(); - let tree = FragmentTree::populate(scope, &storage); - - let candidates: Vec<_> = tree.candidates().collect(); - assert_eq!(candidates.len(), 2); - assert_eq!(tree.nodes.len(), max_depth + 1); - - assert_eq!( - tree.hypothetical_depths( - candidate_a_hash, - HypotheticalCandidate::Incomplete { - parent_head_data_hash: HeadData::from(vec![0x0a]).hash(), - relay_parent: relay_parent_a, - }, - &storage, - false, - ), - vec![0, 2, 4], - ); - - assert_eq!( - tree.hypothetical_depths( - candidate_b_hash, - HypotheticalCandidate::Incomplete { - parent_head_data_hash: HeadData::from(vec![0x0b]).hash(), - relay_parent: relay_parent_a, - }, - &storage, - false, - ), - vec![1, 3], - ); - - assert_eq!( - tree.hypothetical_depths( - CandidateHash(Hash::repeat_byte(21)), - HypotheticalCandidate::Incomplete { - parent_head_data_hash: HeadData::from(vec![0x0a]).hash(), - relay_parent: relay_parent_a, - }, - &storage, - false, - ), - vec![0, 2, 4], - ); - - assert_eq!( - tree.hypothetical_depths( - CandidateHash(Hash::repeat_byte(22)), - HypotheticalCandidate::Incomplete { - parent_head_data_hash: HeadData::from(vec![0x0b]).hash(), - relay_parent: relay_parent_a, - }, - &storage, - false, - ), - vec![1, 3] - ); - } - - #[test] - fn hypothetical_depths_stricter_on_complete() { - let storage = CandidateStorage::new(); - - let para_id = ParaId::from(5u32); - let relay_parent_a = Hash::repeat_byte(1); - - let (pvd_a, candidate_a) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0a].into(), - vec![0x0b].into(), - 1000, // watermark is illegal - ); - - let candidate_a_hash = candidate_a.hash(); - - let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); - let pending_availability = Vec::new(); - - let relay_parent_a_info = RelayChainBlockInfo { - number: pvd_a.relay_parent_number, - hash: relay_parent_a, - storage_root: pvd_a.relay_parent_storage_root, - }; - - let max_depth = 4; - let scope = Scope::with_ancestors( - para_id, - relay_parent_a_info, - base_constraints, - pending_availability, - max_depth, - vec![], - ) - .unwrap(); - let tree = FragmentTree::populate(scope, &storage); - - assert_eq!( - tree.hypothetical_depths( - candidate_a_hash, - HypotheticalCandidate::Incomplete { - parent_head_data_hash: HeadData::from(vec![0x0a]).hash(), - relay_parent: relay_parent_a, - }, - &storage, - false, - ), - vec![0], - ); - - assert!(tree - .hypothetical_depths( - candidate_a_hash, - HypotheticalCandidate::Complete { - receipt: Cow::Owned(candidate_a), - persisted_validation_data: Cow::Owned(pvd_a), - }, - &storage, - false, - ) - .is_empty()); - } - - #[test] - fn hypothetical_depths_backed_in_path() { - let mut storage = CandidateStorage::new(); - - let para_id = ParaId::from(5u32); - let relay_parent_a = Hash::repeat_byte(1); - - let (pvd_a, candidate_a) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0a].into(), - vec![0x0b].into(), - 0, - ); - let candidate_a_hash = candidate_a.hash(); - - let (pvd_b, candidate_b) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0b].into(), - vec![0x0c].into(), - 0, - ); - let candidate_b_hash = candidate_b.hash(); - - let (pvd_c, candidate_c) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0b].into(), - vec![0x0d].into(), - 0, - ); - - let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); - let pending_availability = Vec::new(); - - let relay_parent_a_info = RelayChainBlockInfo { - number: pvd_a.relay_parent_number, - hash: relay_parent_a, - storage_root: pvd_a.relay_parent_storage_root, - }; - - let max_depth = 4; - storage.add_candidate(candidate_a, pvd_a).unwrap(); - storage.add_candidate(candidate_b, pvd_b).unwrap(); - storage.add_candidate(candidate_c, pvd_c).unwrap(); - - // `A` and `B` are backed, `C` is not. - storage.mark_backed(&candidate_a_hash); - storage.mark_backed(&candidate_b_hash); - - let scope = Scope::with_ancestors( - para_id, - relay_parent_a_info, - base_constraints, - pending_availability, - max_depth, - vec![], - ) - .unwrap(); - let tree = FragmentTree::populate(scope, &storage); - - let candidates: Vec<_> = tree.candidates().collect(); - assert_eq!(candidates.len(), 3); - assert_eq!(tree.nodes.len(), 3); - - let candidate_d_hash = CandidateHash(Hash::repeat_byte(0xAA)); - - assert_eq!( - tree.hypothetical_depths( - candidate_d_hash, - HypotheticalCandidate::Incomplete { - parent_head_data_hash: HeadData::from(vec![0x0a]).hash(), - relay_parent: relay_parent_a, - }, - &storage, - true, - ), - vec![0], - ); - - assert_eq!( - tree.hypothetical_depths( - candidate_d_hash, - HypotheticalCandidate::Incomplete { - parent_head_data_hash: HeadData::from(vec![0x0c]).hash(), - relay_parent: relay_parent_a, - }, - &storage, - true, - ), - vec![2], - ); - - assert_eq!( - tree.hypothetical_depths( - candidate_d_hash, - HypotheticalCandidate::Incomplete { - parent_head_data_hash: HeadData::from(vec![0x0d]).hash(), - relay_parent: relay_parent_a, - }, - &storage, - true, - ), - Vec::::new(), - ); - - assert_eq!( - tree.hypothetical_depths( - candidate_d_hash, - HypotheticalCandidate::Incomplete { - parent_head_data_hash: HeadData::from(vec![0x0d]).hash(), - relay_parent: relay_parent_a, - }, - &storage, - false, - ), - vec![2], // non-empty if `false`. - ); - } - - #[test] - fn pending_availability_in_scope() { - let mut storage = CandidateStorage::new(); - - let para_id = ParaId::from(5u32); - let relay_parent_a = Hash::repeat_byte(1); - let relay_parent_b = Hash::repeat_byte(2); - let relay_parent_c = Hash::repeat_byte(3); - - let (pvd_a, candidate_a) = make_committed_candidate( - para_id, - relay_parent_a, - 0, - vec![0x0a].into(), - vec![0x0b].into(), - 0, - ); - let candidate_a_hash = candidate_a.hash(); - - let (pvd_b, candidate_b) = make_committed_candidate( - para_id, - relay_parent_b, - 1, - vec![0x0b].into(), - vec![0x0c].into(), - 1, - ); - - // Note that relay parent `a` is not allowed. - let base_constraints = make_constraints(1, vec![], vec![0x0a].into()); - - let relay_parent_a_info = RelayChainBlockInfo { - number: pvd_a.relay_parent_number, - hash: relay_parent_a, - storage_root: pvd_a.relay_parent_storage_root, - }; - let pending_availability = vec![PendingAvailability { - candidate_hash: candidate_a_hash, - relay_parent: relay_parent_a_info, - }]; - - let relay_parent_b_info = RelayChainBlockInfo { - number: pvd_b.relay_parent_number, - hash: relay_parent_b, - storage_root: pvd_b.relay_parent_storage_root, - }; - let relay_parent_c_info = RelayChainBlockInfo { - number: pvd_b.relay_parent_number + 1, - hash: relay_parent_c, - storage_root: Hash::zero(), - }; - - let max_depth = 4; - storage.add_candidate(candidate_a, pvd_a).unwrap(); - storage.add_candidate(candidate_b, pvd_b).unwrap(); - storage.mark_backed(&candidate_a_hash); - - let scope = Scope::with_ancestors( - para_id, - relay_parent_c_info, - base_constraints, - pending_availability, - max_depth, - vec![relay_parent_b_info], - ) - .unwrap(); - let tree = FragmentTree::populate(scope, &storage); - - let candidates: Vec<_> = tree.candidates().collect(); - assert_eq!(candidates.len(), 2); - assert_eq!(tree.nodes.len(), 2); - - let candidate_d_hash = CandidateHash(Hash::repeat_byte(0xAA)); - - assert_eq!( - tree.hypothetical_depths( - candidate_d_hash, - HypotheticalCandidate::Incomplete { - parent_head_data_hash: HeadData::from(vec![0x0b]).hash(), - relay_parent: relay_parent_c, - }, - &storage, - false, - ), - vec![1], - ); - - assert_eq!( - tree.hypothetical_depths( - candidate_d_hash, - HypotheticalCandidate::Incomplete { - parent_head_data_hash: HeadData::from(vec![0x0c]).hash(), - relay_parent: relay_parent_b, - }, - &storage, - false, - ), - vec![2], - ); - } -} diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..fd41be55f7f960367ee0a57ccd4a4c2251b90d13 --- /dev/null +++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs @@ -0,0 +1,1451 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use super::*; +use assert_matches::assert_matches; +use polkadot_node_subsystem_util::inclusion_emulator::InboundHrmpLimitations; +use polkadot_primitives::{BlockNumber, CandidateCommitments, CandidateDescriptor, HeadData}; +use polkadot_primitives_test_helpers as test_helpers; +use rstest::rstest; +use std::iter; + +impl NodePointer { + fn unwrap_idx(self) -> usize { + match self { + NodePointer::Root => panic!("Unexpected root"), + NodePointer::Storage(index) => index, + } + } +} + +fn make_constraints( + min_relay_parent_number: BlockNumber, + valid_watermarks: Vec, + required_parent: HeadData, +) -> Constraints { + Constraints { + min_relay_parent_number, + max_pov_size: 1_000_000, + max_code_size: 1_000_000, + ump_remaining: 10, + ump_remaining_bytes: 1_000, + max_ump_num_per_candidate: 10, + dmp_remaining_messages: [0; 10].into(), + hrmp_inbound: InboundHrmpLimitations { valid_watermarks }, + hrmp_channels_out: HashMap::new(), + max_hrmp_num_per_candidate: 0, + required_parent, + validation_code_hash: Hash::repeat_byte(42).into(), + upgrade_restriction: None, + future_validation_code: None, + } +} + +fn make_committed_candidate( + para_id: ParaId, + relay_parent: Hash, + relay_parent_number: BlockNumber, + parent_head: HeadData, + para_head: HeadData, + hrmp_watermark: BlockNumber, +) -> (PersistedValidationData, CommittedCandidateReceipt) { + let persisted_validation_data = PersistedValidationData { + parent_head, + relay_parent_number, + relay_parent_storage_root: Hash::repeat_byte(69), + max_pov_size: 1_000_000, + }; + + let candidate = CommittedCandidateReceipt { + descriptor: CandidateDescriptor { + para_id, + relay_parent, + collator: test_helpers::dummy_collator(), + persisted_validation_data_hash: persisted_validation_data.hash(), + pov_hash: Hash::repeat_byte(1), + erasure_root: Hash::repeat_byte(1), + signature: test_helpers::dummy_collator_signature(), + para_head: para_head.hash(), + validation_code_hash: Hash::repeat_byte(42).into(), + }, + commitments: CandidateCommitments { + upward_messages: Default::default(), + horizontal_messages: Default::default(), + new_validation_code: None, + head_data: para_head, + processed_downward_messages: 1, + hrmp_watermark, + }, + }; + + (persisted_validation_data, candidate) +} + +#[test] +fn scope_rejects_ancestors_that_skip_blocks() { + let para_id = ParaId::from(5u32); + let relay_parent = RelayChainBlockInfo { + number: 10, + hash: Hash::repeat_byte(10), + storage_root: Hash::repeat_byte(69), + }; + + let ancestors = vec![RelayChainBlockInfo { + number: 8, + hash: Hash::repeat_byte(8), + storage_root: Hash::repeat_byte(69), + }]; + + let max_depth = 2; + let base_constraints = make_constraints(8, vec![8, 9], vec![1, 2, 3].into()); + let pending_availability = Vec::new(); + + assert_matches!( + Scope::with_ancestors( + para_id, + relay_parent, + base_constraints, + pending_availability, + max_depth, + ancestors + ), + Err(UnexpectedAncestor { number: 8, prev: 10 }) + ); +} + +#[test] +fn scope_rejects_ancestor_for_0_block() { + let para_id = ParaId::from(5u32); + let relay_parent = RelayChainBlockInfo { + number: 0, + hash: Hash::repeat_byte(0), + storage_root: Hash::repeat_byte(69), + }; + + let ancestors = vec![RelayChainBlockInfo { + number: 99999, + hash: Hash::repeat_byte(99), + storage_root: Hash::repeat_byte(69), + }]; + + let max_depth = 2; + let base_constraints = make_constraints(0, vec![], vec![1, 2, 3].into()); + let pending_availability = Vec::new(); + + assert_matches!( + Scope::with_ancestors( + para_id, + relay_parent, + base_constraints, + pending_availability, + max_depth, + ancestors, + ), + Err(UnexpectedAncestor { number: 99999, prev: 0 }) + ); +} + +#[test] +fn scope_only_takes_ancestors_up_to_min() { + let para_id = ParaId::from(5u32); + let relay_parent = RelayChainBlockInfo { + number: 5, + hash: Hash::repeat_byte(0), + storage_root: Hash::repeat_byte(69), + }; + + let ancestors = vec![ + RelayChainBlockInfo { + number: 4, + hash: Hash::repeat_byte(4), + storage_root: Hash::repeat_byte(69), + }, + RelayChainBlockInfo { + number: 3, + hash: Hash::repeat_byte(3), + storage_root: Hash::repeat_byte(69), + }, + RelayChainBlockInfo { + number: 2, + hash: Hash::repeat_byte(2), + storage_root: Hash::repeat_byte(69), + }, + ]; + + let max_depth = 2; + let base_constraints = make_constraints(3, vec![2], vec![1, 2, 3].into()); + let pending_availability = Vec::new(); + + let scope = Scope::with_ancestors( + para_id, + relay_parent, + base_constraints, + pending_availability, + max_depth, + ancestors, + ) + .unwrap(); + + assert_eq!(scope.ancestors.len(), 2); + assert_eq!(scope.ancestors_by_hash.len(), 2); +} + +#[test] +fn storage_add_candidate() { + let mut storage = CandidateStorage::new(); + let relay_parent = Hash::repeat_byte(69); + + let (pvd, candidate) = make_committed_candidate( + ParaId::from(5u32), + relay_parent, + 8, + vec![4, 5, 6].into(), + vec![1, 2, 3].into(), + 7, + ); + + let candidate_hash = candidate.hash(); + let parent_head_hash = pvd.parent_head.hash(); + + storage.add_candidate(candidate, pvd).unwrap(); + assert!(storage.contains(&candidate_hash)); + assert_eq!(storage.iter_para_children(&parent_head_hash).count(), 1); + + assert_eq!(storage.relay_parent_by_candidate_hash(&candidate_hash), Some(relay_parent)); +} + +#[test] +fn storage_retain() { + let mut storage = CandidateStorage::new(); + + let (pvd, candidate) = make_committed_candidate( + ParaId::from(5u32), + Hash::repeat_byte(69), + 8, + vec![4, 5, 6].into(), + vec![1, 2, 3].into(), + 7, + ); + + let candidate_hash = candidate.hash(); + let output_head_hash = candidate.commitments.head_data.hash(); + let parent_head_hash = pvd.parent_head.hash(); + + storage.add_candidate(candidate, pvd).unwrap(); + storage.retain(|_| true); + assert!(storage.contains(&candidate_hash)); + assert_eq!(storage.iter_para_children(&parent_head_hash).count(), 1); + assert!(storage.head_data_by_hash(&output_head_hash).is_some()); + + storage.retain(|_| false); + assert!(!storage.contains(&candidate_hash)); + assert_eq!(storage.iter_para_children(&parent_head_hash).count(), 0); + assert!(storage.head_data_by_hash(&output_head_hash).is_none()); +} + +// [`FragmentTree::populate`] should pick up candidates that build on other candidates. +#[test] +fn populate_works_recursively() { + let mut storage = CandidateStorage::new(); + + let para_id = ParaId::from(5u32); + let relay_parent_a = Hash::repeat_byte(1); + let relay_parent_b = Hash::repeat_byte(2); + + let (pvd_a, candidate_a) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0a].into(), + vec![0x0b].into(), + 0, + ); + let candidate_a_hash = candidate_a.hash(); + + let (pvd_b, candidate_b) = make_committed_candidate( + para_id, + relay_parent_b, + 1, + vec![0x0b].into(), + vec![0x0c].into(), + 1, + ); + let candidate_b_hash = candidate_b.hash(); + + let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); + let pending_availability = Vec::new(); + + let ancestors = vec![RelayChainBlockInfo { + number: pvd_a.relay_parent_number, + hash: relay_parent_a, + storage_root: pvd_a.relay_parent_storage_root, + }]; + + let relay_parent_b_info = RelayChainBlockInfo { + number: pvd_b.relay_parent_number, + hash: relay_parent_b, + storage_root: pvd_b.relay_parent_storage_root, + }; + + storage.add_candidate(candidate_a, pvd_a).unwrap(); + storage.add_candidate(candidate_b, pvd_b).unwrap(); + let scope = Scope::with_ancestors( + para_id, + relay_parent_b_info, + base_constraints, + pending_availability, + 4, + ancestors, + ) + .unwrap(); + let tree = FragmentTree::populate(scope, &storage); + + let candidates: Vec<_> = tree.candidates().collect(); + assert_eq!(candidates.len(), 2); + assert!(candidates.contains(&candidate_a_hash)); + assert!(candidates.contains(&candidate_b_hash)); + + assert_eq!(tree.nodes.len(), 2); + assert_eq!(tree.nodes[0].parent, NodePointer::Root); + assert_eq!(tree.nodes[0].candidate_hash, candidate_a_hash); + assert_eq!(tree.nodes[0].depth, 0); + + assert_eq!(tree.nodes[1].parent, NodePointer::Storage(0)); + assert_eq!(tree.nodes[1].candidate_hash, candidate_b_hash); + assert_eq!(tree.nodes[1].depth, 1); +} + +#[test] +fn children_of_root_are_contiguous() { + let mut storage = CandidateStorage::new(); + + let para_id = ParaId::from(5u32); + let relay_parent_a = Hash::repeat_byte(1); + let relay_parent_b = Hash::repeat_byte(2); + + let (pvd_a, candidate_a) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0a].into(), + vec![0x0b].into(), + 0, + ); + + let (pvd_b, candidate_b) = make_committed_candidate( + para_id, + relay_parent_b, + 1, + vec![0x0b].into(), + vec![0x0c].into(), + 1, + ); + + let (pvd_a2, candidate_a2) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0a].into(), + vec![0x0b, 1].into(), + 0, + ); + let candidate_a2_hash = candidate_a2.hash(); + + let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); + let pending_availability = Vec::new(); + + let ancestors = vec![RelayChainBlockInfo { + number: pvd_a.relay_parent_number, + hash: relay_parent_a, + storage_root: pvd_a.relay_parent_storage_root, + }]; + + let relay_parent_b_info = RelayChainBlockInfo { + number: pvd_b.relay_parent_number, + hash: relay_parent_b, + storage_root: pvd_b.relay_parent_storage_root, + }; + + storage.add_candidate(candidate_a, pvd_a).unwrap(); + storage.add_candidate(candidate_b, pvd_b).unwrap(); + let scope = Scope::with_ancestors( + para_id, + relay_parent_b_info, + base_constraints, + pending_availability, + 4, + ancestors, + ) + .unwrap(); + let mut tree = FragmentTree::populate(scope, &storage); + + storage.add_candidate(candidate_a2, pvd_a2).unwrap(); + tree.add_and_populate(candidate_a2_hash, &storage); + let candidates: Vec<_> = tree.candidates().collect(); + assert_eq!(candidates.len(), 3); + + assert_eq!(tree.nodes[0].parent, NodePointer::Root); + assert_eq!(tree.nodes[1].parent, NodePointer::Root); + assert_eq!(tree.nodes[2].parent, NodePointer::Storage(0)); +} + +#[test] +fn add_candidate_child_of_root() { + let mut storage = CandidateStorage::new(); + + let para_id = ParaId::from(5u32); + let relay_parent_a = Hash::repeat_byte(1); + + let (pvd_a, candidate_a) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0a].into(), + vec![0x0b].into(), + 0, + ); + + let (pvd_b, candidate_b) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0a].into(), + vec![0x0c].into(), + 0, + ); + let candidate_b_hash = candidate_b.hash(); + + let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); + let pending_availability = Vec::new(); + + let relay_parent_a_info = RelayChainBlockInfo { + number: pvd_a.relay_parent_number, + hash: relay_parent_a, + storage_root: pvd_a.relay_parent_storage_root, + }; + + storage.add_candidate(candidate_a, pvd_a).unwrap(); + let scope = Scope::with_ancestors( + para_id, + relay_parent_a_info, + base_constraints, + pending_availability, + 4, + vec![], + ) + .unwrap(); + let mut tree = FragmentTree::populate(scope, &storage); + + storage.add_candidate(candidate_b, pvd_b).unwrap(); + tree.add_and_populate(candidate_b_hash, &storage); + let candidates: Vec<_> = tree.candidates().collect(); + assert_eq!(candidates.len(), 2); + + assert_eq!(tree.nodes[0].parent, NodePointer::Root); + assert_eq!(tree.nodes[1].parent, NodePointer::Root); +} + +#[test] +fn add_candidate_child_of_non_root() { + let mut storage = CandidateStorage::new(); + + let para_id = ParaId::from(5u32); + let relay_parent_a = Hash::repeat_byte(1); + + let (pvd_a, candidate_a) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0a].into(), + vec![0x0b].into(), + 0, + ); + + let (pvd_b, candidate_b) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0b].into(), + vec![0x0c].into(), + 0, + ); + let candidate_b_hash = candidate_b.hash(); + + let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); + let pending_availability = Vec::new(); + + let relay_parent_a_info = RelayChainBlockInfo { + number: pvd_a.relay_parent_number, + hash: relay_parent_a, + storage_root: pvd_a.relay_parent_storage_root, + }; + + storage.add_candidate(candidate_a, pvd_a).unwrap(); + let scope = Scope::with_ancestors( + para_id, + relay_parent_a_info, + base_constraints, + pending_availability, + 4, + vec![], + ) + .unwrap(); + let mut tree = FragmentTree::populate(scope, &storage); + + storage.add_candidate(candidate_b, pvd_b).unwrap(); + tree.add_and_populate(candidate_b_hash, &storage); + let candidates: Vec<_> = tree.candidates().collect(); + assert_eq!(candidates.len(), 2); + + assert_eq!(tree.nodes[0].parent, NodePointer::Root); + assert_eq!(tree.nodes[1].parent, NodePointer::Storage(0)); +} + +#[test] +fn test_find_ancestor_path_and_find_backable_chain_empty_tree() { + let para_id = ParaId::from(5u32); + let relay_parent = Hash::repeat_byte(1); + let required_parent: HeadData = vec![0xff].into(); + let max_depth = 10; + + // Empty tree + let storage = CandidateStorage::new(); + let base_constraints = make_constraints(0, vec![0], required_parent.clone()); + + let relay_parent_info = + RelayChainBlockInfo { number: 0, hash: relay_parent, storage_root: Hash::zero() }; + + let scope = Scope::with_ancestors( + para_id, + relay_parent_info, + base_constraints, + vec![], + max_depth, + vec![], + ) + .unwrap(); + let tree = FragmentTree::populate(scope, &storage); + assert_eq!(tree.candidates().collect::>().len(), 0); + assert_eq!(tree.nodes.len(), 0); + + assert_eq!(tree.find_ancestor_path(Ancestors::new()).unwrap(), NodePointer::Root); + assert_eq!(tree.find_backable_chain(Ancestors::new(), 2, |_| true), vec![]); + // Invalid candidate. + let ancestors: Ancestors = [CandidateHash::default()].into_iter().collect(); + assert_eq!(tree.find_ancestor_path(ancestors.clone()), Some(NodePointer::Root)); + assert_eq!(tree.find_backable_chain(ancestors, 2, |_| true), vec![]); +} + +#[rstest] +#[case(true, 13)] +#[case(false, 8)] +// The tree with no cycles looks like: +// Make a tree that looks like this (note that there's no cycle): +// +-(root)-+ +// | | +// +----0---+ 7 +// | | +// 1----+ 5 +// | | +// | | +// 2 6 +// | +// 3 +// | +// 4 +// +// The tree with cycles is the same as the first but has a cycle from 4 back to the state +// produced by 0 (It's bounded by the max_depth + 1). +// +-(root)-+ +// | | +// +----0---+ 7 +// | | +// 1----+ 5 +// | | +// | | +// 2 6 +// | +// 3 +// | +// 4---+ +// | | +// 1 5 +// | +// 2 +// | +// 3 +fn test_find_ancestor_path_and_find_backable_chain( + #[case] has_cycle: bool, + #[case] expected_node_count: usize, +) { + let para_id = ParaId::from(5u32); + let relay_parent = Hash::repeat_byte(1); + let required_parent: HeadData = vec![0xff].into(); + let max_depth = 7; + let relay_parent_number = 0; + let relay_parent_storage_root = Hash::repeat_byte(69); + + let mut candidates = vec![]; + + // Candidate 0 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + required_parent.clone(), + vec![0].into(), + 0, + )); + // Candidate 1 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + vec![0].into(), + vec![1].into(), + 0, + )); + // Candidate 2 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + vec![1].into(), + vec![2].into(), + 0, + )); + // Candidate 3 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + vec![2].into(), + vec![3].into(), + 0, + )); + // Candidate 4 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + vec![3].into(), + vec![4].into(), + 0, + )); + // Candidate 5 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + vec![0].into(), + vec![5].into(), + 0, + )); + // Candidate 6 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + vec![1].into(), + vec![6].into(), + 0, + )); + // Candidate 7 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + required_parent.clone(), + vec![7].into(), + 0, + )); + + if has_cycle { + candidates[4] = make_committed_candidate( + para_id, + relay_parent, + 0, + vec![3].into(), + vec![0].into(), // put the cycle here back to the output state of 0. + 0, + ); + } + + let base_constraints = make_constraints(0, vec![0], required_parent.clone()); + let mut storage = CandidateStorage::new(); + + let relay_parent_info = RelayChainBlockInfo { + number: relay_parent_number, + hash: relay_parent, + storage_root: relay_parent_storage_root, + }; + + for (pvd, candidate) in candidates.iter() { + storage.add_candidate(candidate.clone(), pvd.clone()).unwrap(); + } + let candidates = candidates.into_iter().map(|(_pvd, candidate)| candidate).collect::>(); + let scope = Scope::with_ancestors( + para_id, + relay_parent_info, + base_constraints, + vec![], + max_depth, + vec![], + ) + .unwrap(); + let tree = FragmentTree::populate(scope, &storage); + + assert_eq!(tree.candidates().collect::>().len(), candidates.len()); + assert_eq!(tree.nodes.len(), expected_node_count); + + // Do some common tests on both trees. + { + // No ancestors supplied. + assert_eq!(tree.find_ancestor_path(Ancestors::new()).unwrap(), NodePointer::Root); + assert_eq!( + tree.find_backable_chain(Ancestors::new(), 4, |_| true), + [0, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + // Ancestor which is not part of the tree. Will be ignored. + let ancestors: Ancestors = [CandidateHash::default()].into_iter().collect(); + assert_eq!(tree.find_ancestor_path(ancestors.clone()).unwrap(), NodePointer::Root); + assert_eq!( + tree.find_backable_chain(ancestors, 4, |_| true), + [0, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + // A chain fork. + let ancestors: Ancestors = + [(candidates[0].hash()), (candidates[7].hash())].into_iter().collect(); + assert_eq!(tree.find_ancestor_path(ancestors.clone()), None); + assert_eq!(tree.find_backable_chain(ancestors, 1, |_| true), vec![]); + + // Ancestors which are part of the tree but don't form a path. Will be ignored. + let ancestors: Ancestors = + [candidates[1].hash(), candidates[2].hash()].into_iter().collect(); + assert_eq!(tree.find_ancestor_path(ancestors.clone()).unwrap(), NodePointer::Root); + assert_eq!( + tree.find_backable_chain(ancestors, 4, |_| true), + [0, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + + // Valid ancestors. + let ancestors: Ancestors = [candidates[7].hash()].into_iter().collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[7].hash()); + assert_eq!(tree.find_backable_chain(ancestors, 1, |_| true), vec![]); + + let ancestors: Ancestors = + [candidates[2].hash(), candidates[0].hash(), candidates[1].hash()] + .into_iter() + .collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[2].hash()); + assert_eq!( + tree.find_backable_chain(ancestors.clone(), 2, |_| true), + [3, 4].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + + // Valid ancestors with candidates which have been omitted due to timeouts + let ancestors: Ancestors = + [candidates[0].hash(), candidates[2].hash()].into_iter().collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[0].hash()); + assert_eq!( + tree.find_backable_chain(ancestors, 3, |_| true), + [1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + + let ancestors: Ancestors = + [candidates[0].hash(), candidates[1].hash(), candidates[3].hash()] + .into_iter() + .collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[1].hash()); + if has_cycle { + assert_eq!( + tree.find_backable_chain(ancestors, 2, |_| true), + [2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + } else { + assert_eq!( + tree.find_backable_chain(ancestors, 4, |_| true), + [2, 3, 4].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + } + + let ancestors: Ancestors = + [candidates[1].hash(), candidates[2].hash()].into_iter().collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + assert_eq!(res, NodePointer::Root); + assert_eq!( + tree.find_backable_chain(ancestors, 4, |_| true), + [0, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + + // Requested count is 0. + assert_eq!(tree.find_backable_chain(Ancestors::new(), 0, |_| true), vec![]); + + let ancestors: Ancestors = + [candidates[2].hash(), candidates[0].hash(), candidates[1].hash()] + .into_iter() + .collect(); + assert_eq!(tree.find_backable_chain(ancestors, 0, |_| true), vec![]); + + let ancestors: Ancestors = + [candidates[2].hash(), candidates[0].hash()].into_iter().collect(); + assert_eq!(tree.find_backable_chain(ancestors, 0, |_| true), vec![]); + } + + // Now do some tests only on the tree with cycles + if has_cycle { + // Exceeds the maximum tree depth. 0-1-2-3-4-1-2-3-4, when the tree stops at + // 0-1-2-3-4-1-2-3. + let ancestors: Ancestors = [ + candidates[0].hash(), + candidates[1].hash(), + candidates[2].hash(), + candidates[3].hash(), + candidates[4].hash(), + ] + .into_iter() + .collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[4].hash()); + assert_eq!( + tree.find_backable_chain(ancestors, 4, |_| true), + [1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + + // 0-1-2. + let ancestors: Ancestors = + [candidates[0].hash(), candidates[1].hash(), candidates[2].hash()] + .into_iter() + .collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[2].hash()); + assert_eq!( + tree.find_backable_chain(ancestors.clone(), 1, |_| true), + [3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + assert_eq!( + tree.find_backable_chain(ancestors, 5, |_| true), + [3, 4, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + + // 0-1 + let ancestors: Ancestors = + [candidates[0].hash(), candidates[1].hash()].into_iter().collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[1].hash()); + assert_eq!( + tree.find_backable_chain(ancestors, 6, |_| true), + [2, 3, 4, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>(), + ); + + // For 0-1-2-3-4-5, there's more than 1 way of finding this path in + // the tree. `None` should be returned. The runtime should not have accepted this. + let ancestors: Ancestors = [ + candidates[0].hash(), + candidates[1].hash(), + candidates[2].hash(), + candidates[3].hash(), + candidates[4].hash(), + candidates[5].hash(), + ] + .into_iter() + .collect(); + let res = tree.find_ancestor_path(ancestors.clone()); + assert_eq!(res, None); + assert_eq!(tree.find_backable_chain(ancestors, 1, |_| true), vec![]); + } +} + +#[test] +fn graceful_cycle_of_0() { + let mut storage = CandidateStorage::new(); + + let para_id = ParaId::from(5u32); + let relay_parent_a = Hash::repeat_byte(1); + + let (pvd_a, candidate_a) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0a].into(), + vec![0x0a].into(), // input same as output + 0, + ); + let candidate_a_hash = candidate_a.hash(); + let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); + let pending_availability = Vec::new(); + + let relay_parent_a_info = RelayChainBlockInfo { + number: pvd_a.relay_parent_number, + hash: relay_parent_a, + storage_root: pvd_a.relay_parent_storage_root, + }; + + let max_depth = 4; + storage.add_candidate(candidate_a, pvd_a).unwrap(); + let scope = Scope::with_ancestors( + para_id, + relay_parent_a_info, + base_constraints, + pending_availability, + max_depth, + vec![], + ) + .unwrap(); + let tree = FragmentTree::populate(scope, &storage); + + let candidates: Vec<_> = tree.candidates().collect(); + assert_eq!(candidates.len(), 1); + assert_eq!(tree.nodes.len(), max_depth + 1); + + assert_eq!(tree.nodes[0].parent, NodePointer::Root); + assert_eq!(tree.nodes[1].parent, NodePointer::Storage(0)); + assert_eq!(tree.nodes[2].parent, NodePointer::Storage(1)); + assert_eq!(tree.nodes[3].parent, NodePointer::Storage(2)); + assert_eq!(tree.nodes[4].parent, NodePointer::Storage(3)); + + assert_eq!(tree.nodes[0].candidate_hash, candidate_a_hash); + assert_eq!(tree.nodes[1].candidate_hash, candidate_a_hash); + assert_eq!(tree.nodes[2].candidate_hash, candidate_a_hash); + assert_eq!(tree.nodes[3].candidate_hash, candidate_a_hash); + assert_eq!(tree.nodes[4].candidate_hash, candidate_a_hash); + + for count in 1..10 { + assert_eq!( + tree.find_backable_chain(Ancestors::new(), count, |_| true), + iter::repeat(candidate_a_hash) + .take(std::cmp::min(count as usize, max_depth + 1)) + .collect::>() + ); + assert_eq!( + tree.find_backable_chain([candidate_a_hash].into_iter().collect(), count - 1, |_| true), + iter::repeat(candidate_a_hash) + .take(std::cmp::min(count as usize - 1, max_depth)) + .collect::>() + ); + } +} + +#[test] +fn graceful_cycle_of_1() { + let mut storage = CandidateStorage::new(); + + let para_id = ParaId::from(5u32); + let relay_parent_a = Hash::repeat_byte(1); + + let (pvd_a, candidate_a) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0a].into(), + vec![0x0b].into(), // input same as output + 0, + ); + let candidate_a_hash = candidate_a.hash(); + + let (pvd_b, candidate_b) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0b].into(), + vec![0x0a].into(), // input same as output + 0, + ); + let candidate_b_hash = candidate_b.hash(); + + let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); + let pending_availability = Vec::new(); + + let relay_parent_a_info = RelayChainBlockInfo { + number: pvd_a.relay_parent_number, + hash: relay_parent_a, + storage_root: pvd_a.relay_parent_storage_root, + }; + + let max_depth = 4; + storage.add_candidate(candidate_a, pvd_a).unwrap(); + storage.add_candidate(candidate_b, pvd_b).unwrap(); + let scope = Scope::with_ancestors( + para_id, + relay_parent_a_info, + base_constraints, + pending_availability, + max_depth, + vec![], + ) + .unwrap(); + let tree = FragmentTree::populate(scope, &storage); + + let candidates: Vec<_> = tree.candidates().collect(); + assert_eq!(candidates.len(), 2); + assert_eq!(tree.nodes.len(), max_depth + 1); + + assert_eq!(tree.nodes[0].parent, NodePointer::Root); + assert_eq!(tree.nodes[1].parent, NodePointer::Storage(0)); + assert_eq!(tree.nodes[2].parent, NodePointer::Storage(1)); + assert_eq!(tree.nodes[3].parent, NodePointer::Storage(2)); + assert_eq!(tree.nodes[4].parent, NodePointer::Storage(3)); + + assert_eq!(tree.nodes[0].candidate_hash, candidate_a_hash); + assert_eq!(tree.nodes[1].candidate_hash, candidate_b_hash); + assert_eq!(tree.nodes[2].candidate_hash, candidate_a_hash); + assert_eq!(tree.nodes[3].candidate_hash, candidate_b_hash); + assert_eq!(tree.nodes[4].candidate_hash, candidate_a_hash); + + assert_eq!(tree.find_backable_chain(Ancestors::new(), 1, |_| true), vec![candidate_a_hash],); + assert_eq!( + tree.find_backable_chain(Ancestors::new(), 2, |_| true), + vec![candidate_a_hash, candidate_b_hash], + ); + assert_eq!( + tree.find_backable_chain(Ancestors::new(), 3, |_| true), + vec![candidate_a_hash, candidate_b_hash, candidate_a_hash], + ); + assert_eq!( + tree.find_backable_chain([candidate_a_hash].into_iter().collect(), 2, |_| true), + vec![candidate_b_hash, candidate_a_hash], + ); + + assert_eq!( + tree.find_backable_chain(Ancestors::new(), 6, |_| true), + vec![ + candidate_a_hash, + candidate_b_hash, + candidate_a_hash, + candidate_b_hash, + candidate_a_hash + ], + ); + + for count in 3..7 { + assert_eq!( + tree.find_backable_chain( + [candidate_a_hash, candidate_b_hash].into_iter().collect(), + count, + |_| true + ), + vec![candidate_a_hash, candidate_b_hash, candidate_a_hash], + ); + } +} + +#[test] +fn hypothetical_depths_known_and_unknown() { + let mut storage = CandidateStorage::new(); + + let para_id = ParaId::from(5u32); + let relay_parent_a = Hash::repeat_byte(1); + + let (pvd_a, candidate_a) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0a].into(), + vec![0x0b].into(), // input same as output + 0, + ); + let candidate_a_hash = candidate_a.hash(); + + let (pvd_b, candidate_b) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0b].into(), + vec![0x0a].into(), // input same as output + 0, + ); + let candidate_b_hash = candidate_b.hash(); + + let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); + let pending_availability = Vec::new(); + + let relay_parent_a_info = RelayChainBlockInfo { + number: pvd_a.relay_parent_number, + hash: relay_parent_a, + storage_root: pvd_a.relay_parent_storage_root, + }; + + let max_depth = 4; + storage.add_candidate(candidate_a, pvd_a).unwrap(); + storage.add_candidate(candidate_b, pvd_b).unwrap(); + let scope = Scope::with_ancestors( + para_id, + relay_parent_a_info, + base_constraints, + pending_availability, + max_depth, + vec![], + ) + .unwrap(); + let tree = FragmentTree::populate(scope, &storage); + + let candidates: Vec<_> = tree.candidates().collect(); + assert_eq!(candidates.len(), 2); + assert_eq!(tree.nodes.len(), max_depth + 1); + + assert_eq!( + tree.hypothetical_depths( + candidate_a_hash, + HypotheticalCandidate::Incomplete { + parent_head_data_hash: HeadData::from(vec![0x0a]).hash(), + relay_parent: relay_parent_a, + }, + &storage, + false, + ), + vec![0, 2, 4], + ); + + assert_eq!( + tree.hypothetical_depths( + candidate_b_hash, + HypotheticalCandidate::Incomplete { + parent_head_data_hash: HeadData::from(vec![0x0b]).hash(), + relay_parent: relay_parent_a, + }, + &storage, + false, + ), + vec![1, 3], + ); + + assert_eq!( + tree.hypothetical_depths( + CandidateHash(Hash::repeat_byte(21)), + HypotheticalCandidate::Incomplete { + parent_head_data_hash: HeadData::from(vec![0x0a]).hash(), + relay_parent: relay_parent_a, + }, + &storage, + false, + ), + vec![0, 2, 4], + ); + + assert_eq!( + tree.hypothetical_depths( + CandidateHash(Hash::repeat_byte(22)), + HypotheticalCandidate::Incomplete { + parent_head_data_hash: HeadData::from(vec![0x0b]).hash(), + relay_parent: relay_parent_a, + }, + &storage, + false, + ), + vec![1, 3] + ); +} + +#[test] +fn hypothetical_depths_stricter_on_complete() { + let storage = CandidateStorage::new(); + + let para_id = ParaId::from(5u32); + let relay_parent_a = Hash::repeat_byte(1); + + let (pvd_a, candidate_a) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0a].into(), + vec![0x0b].into(), + 1000, // watermark is illegal + ); + + let candidate_a_hash = candidate_a.hash(); + + let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); + let pending_availability = Vec::new(); + + let relay_parent_a_info = RelayChainBlockInfo { + number: pvd_a.relay_parent_number, + hash: relay_parent_a, + storage_root: pvd_a.relay_parent_storage_root, + }; + + let max_depth = 4; + let scope = Scope::with_ancestors( + para_id, + relay_parent_a_info, + base_constraints, + pending_availability, + max_depth, + vec![], + ) + .unwrap(); + let tree = FragmentTree::populate(scope, &storage); + + assert_eq!( + tree.hypothetical_depths( + candidate_a_hash, + HypotheticalCandidate::Incomplete { + parent_head_data_hash: HeadData::from(vec![0x0a]).hash(), + relay_parent: relay_parent_a, + }, + &storage, + false, + ), + vec![0], + ); + + assert!(tree + .hypothetical_depths( + candidate_a_hash, + HypotheticalCandidate::Complete { + receipt: Cow::Owned(candidate_a), + persisted_validation_data: Cow::Owned(pvd_a), + }, + &storage, + false, + ) + .is_empty()); +} + +#[test] +fn hypothetical_depths_backed_in_path() { + let mut storage = CandidateStorage::new(); + + let para_id = ParaId::from(5u32); + let relay_parent_a = Hash::repeat_byte(1); + + let (pvd_a, candidate_a) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0a].into(), + vec![0x0b].into(), + 0, + ); + let candidate_a_hash = candidate_a.hash(); + + let (pvd_b, candidate_b) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0b].into(), + vec![0x0c].into(), + 0, + ); + let candidate_b_hash = candidate_b.hash(); + + let (pvd_c, candidate_c) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0b].into(), + vec![0x0d].into(), + 0, + ); + + let base_constraints = make_constraints(0, vec![0], vec![0x0a].into()); + let pending_availability = Vec::new(); + + let relay_parent_a_info = RelayChainBlockInfo { + number: pvd_a.relay_parent_number, + hash: relay_parent_a, + storage_root: pvd_a.relay_parent_storage_root, + }; + + let max_depth = 4; + storage.add_candidate(candidate_a, pvd_a).unwrap(); + storage.add_candidate(candidate_b, pvd_b).unwrap(); + storage.add_candidate(candidate_c, pvd_c).unwrap(); + + // `A` and `B` are backed, `C` is not. + storage.mark_backed(&candidate_a_hash); + storage.mark_backed(&candidate_b_hash); + + let scope = Scope::with_ancestors( + para_id, + relay_parent_a_info, + base_constraints, + pending_availability, + max_depth, + vec![], + ) + .unwrap(); + let tree = FragmentTree::populate(scope, &storage); + + let candidates: Vec<_> = tree.candidates().collect(); + assert_eq!(candidates.len(), 3); + assert_eq!(tree.nodes.len(), 3); + + let candidate_d_hash = CandidateHash(Hash::repeat_byte(0xAA)); + + assert_eq!( + tree.hypothetical_depths( + candidate_d_hash, + HypotheticalCandidate::Incomplete { + parent_head_data_hash: HeadData::from(vec![0x0a]).hash(), + relay_parent: relay_parent_a, + }, + &storage, + true, + ), + vec![0], + ); + + assert_eq!( + tree.hypothetical_depths( + candidate_d_hash, + HypotheticalCandidate::Incomplete { + parent_head_data_hash: HeadData::from(vec![0x0c]).hash(), + relay_parent: relay_parent_a, + }, + &storage, + true, + ), + vec![2], + ); + + assert_eq!( + tree.hypothetical_depths( + candidate_d_hash, + HypotheticalCandidate::Incomplete { + parent_head_data_hash: HeadData::from(vec![0x0d]).hash(), + relay_parent: relay_parent_a, + }, + &storage, + true, + ), + Vec::::new(), + ); + + assert_eq!( + tree.hypothetical_depths( + candidate_d_hash, + HypotheticalCandidate::Incomplete { + parent_head_data_hash: HeadData::from(vec![0x0d]).hash(), + relay_parent: relay_parent_a, + }, + &storage, + false, + ), + vec![2], // non-empty if `false`. + ); +} + +#[test] +fn pending_availability_in_scope() { + let mut storage = CandidateStorage::new(); + + let para_id = ParaId::from(5u32); + let relay_parent_a = Hash::repeat_byte(1); + let relay_parent_b = Hash::repeat_byte(2); + let relay_parent_c = Hash::repeat_byte(3); + + let (pvd_a, candidate_a) = make_committed_candidate( + para_id, + relay_parent_a, + 0, + vec![0x0a].into(), + vec![0x0b].into(), + 0, + ); + let candidate_a_hash = candidate_a.hash(); + + let (pvd_b, candidate_b) = make_committed_candidate( + para_id, + relay_parent_b, + 1, + vec![0x0b].into(), + vec![0x0c].into(), + 1, + ); + + // Note that relay parent `a` is not allowed. + let base_constraints = make_constraints(1, vec![], vec![0x0a].into()); + + let relay_parent_a_info = RelayChainBlockInfo { + number: pvd_a.relay_parent_number, + hash: relay_parent_a, + storage_root: pvd_a.relay_parent_storage_root, + }; + let pending_availability = vec![PendingAvailability { + candidate_hash: candidate_a_hash, + relay_parent: relay_parent_a_info, + }]; + + let relay_parent_b_info = RelayChainBlockInfo { + number: pvd_b.relay_parent_number, + hash: relay_parent_b, + storage_root: pvd_b.relay_parent_storage_root, + }; + let relay_parent_c_info = RelayChainBlockInfo { + number: pvd_b.relay_parent_number + 1, + hash: relay_parent_c, + storage_root: Hash::zero(), + }; + + let max_depth = 4; + storage.add_candidate(candidate_a, pvd_a).unwrap(); + storage.add_candidate(candidate_b, pvd_b).unwrap(); + storage.mark_backed(&candidate_a_hash); + + let scope = Scope::with_ancestors( + para_id, + relay_parent_c_info, + base_constraints, + pending_availability, + max_depth, + vec![relay_parent_b_info], + ) + .unwrap(); + let tree = FragmentTree::populate(scope, &storage); + + let candidates: Vec<_> = tree.candidates().collect(); + assert_eq!(candidates.len(), 2); + assert_eq!(tree.nodes.len(), 2); + + let candidate_d_hash = CandidateHash(Hash::repeat_byte(0xAA)); + + assert_eq!( + tree.hypothetical_depths( + candidate_d_hash, + HypotheticalCandidate::Incomplete { + parent_head_data_hash: HeadData::from(vec![0x0b]).hash(), + relay_parent: relay_parent_c, + }, + &storage, + false, + ), + vec![1], + ); + + assert_eq!( + tree.hypothetical_depths( + candidate_d_hash, + HypotheticalCandidate::Incomplete { + parent_head_data_hash: HeadData::from(vec![0x0c]).hash(), + relay_parent: relay_parent_b, + }, + &storage, + false, + ), + vec![2], + ); +} diff --git a/polkadot/node/core/prospective-parachains/src/lib.rs b/polkadot/node/core/prospective-parachains/src/lib.rs index f5d50fb74faca5fec78c47aa6a653a1a9fe48699..0b1a2e034a2893e6397b986405df28a91776d819 100644 --- a/polkadot/node/core/prospective-parachains/src/lib.rs +++ b/polkadot/node/core/prospective-parachains/src/lib.rs @@ -55,13 +55,13 @@ use polkadot_primitives::{ use crate::{ error::{FatalError, FatalResult, JfyiError, JfyiErrorResult, Result}, - fragment_tree::{ + fragment_chain::{ CandidateStorage, CandidateStorageInsertionError, FragmentTree, Scope as TreeScope, }, }; mod error; -mod fragment_tree; +mod fragment_chain; #[cfg(test)] mod tests; @@ -349,7 +349,7 @@ fn prune_view_candidate_storage(view: &mut View, metrics: &Metrics) { struct ImportablePendingAvailability { candidate: CommittedCandidateReceipt, persisted_validation_data: PersistedValidationData, - compact: crate::fragment_tree::PendingAvailability, + compact: crate::fragment_chain::PendingAvailability, } #[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)] @@ -394,7 +394,7 @@ async fn preprocess_candidates_pending_availability( relay_parent_number: relay_parent.number, relay_parent_storage_root: relay_parent.storage_root, }, - compact: crate::fragment_tree::PendingAvailability { + compact: crate::fragment_chain::PendingAvailability { candidate_hash: pending.candidate_hash, relay_parent, }, @@ -675,7 +675,7 @@ fn answer_hypothetical_frontier_request( let candidate_hash = c.candidate_hash(); let hypothetical = match c { HypotheticalCandidate::Complete { receipt, persisted_validation_data, .. } => - fragment_tree::HypotheticalCandidate::Complete { + fragment_chain::HypotheticalCandidate::Complete { receipt: Cow::Borrowed(receipt), persisted_validation_data: Cow::Borrowed(persisted_validation_data), }, @@ -683,7 +683,7 @@ fn answer_hypothetical_frontier_request( parent_head_data_hash, candidate_relay_parent, .. - } => fragment_tree::HypotheticalCandidate::Incomplete { + } => fragment_chain::HypotheticalCandidate::Incomplete { relay_parent: *candidate_relay_parent, parent_head_data_hash: *parent_head_data_hash, }, diff --git a/polkadot/node/core/pvf/Cargo.toml b/polkadot/node/core/pvf/Cargo.toml index a0233d6b751769a4f51f3eda0f7ca0ec75fa1cb7..8bfe2baa42fd8baafbf435fab244b2f27252d544 100644 --- a/polkadot/node/core/pvf/Cargo.toml +++ b/polkadot/node/core/pvf/Cargo.toml @@ -17,8 +17,7 @@ cfg-if = "1.0" futures = "0.3.30" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../../gum" } -is_executable = "1.0.1" -libc = "0.2.152" +is_executable = { version = "1.0.1", optional = true } pin-project = "1.0.9" rand = "0.8.5" slotmap = "1.0" @@ -26,7 +25,9 @@ tempfile = "3.3.0" thiserror = { workspace = true } tokio = { version = "1.24.2", features = ["fs", "process"] } -parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } +parity-scale-codec = { version = "3.6.1", default-features = false, features = [ + "derive", +] } polkadot-parachain-primitives = { path = "../../../parachain" } polkadot-core-primitives = { path = "../../../core-primitives" } @@ -37,14 +38,16 @@ polkadot-node-subsystem = { path = "../../subsystem" } polkadot-primitives = { path = "../../../primitives" } sp-core = { path = "../../../../substrate/primitives/core" } -sp-wasm-interface = { path = "../../../../substrate/primitives/wasm-interface" } -sp-maybe-compressed-blob = { path = "../../../../substrate/primitives/maybe-compressed-blob" } +sp-maybe-compressed-blob = { path = "../../../../substrate/primitives/maybe-compressed-blob", optional = true } polkadot-node-core-pvf-prepare-worker = { path = "prepare-worker", optional = true } polkadot-node-core-pvf-execute-worker = { path = "execute-worker", optional = true } [dev-dependencies] assert_matches = "1.4.0" -criterion = { version = "0.4.0", default-features = false, features = ["async_tokio", "cargo_bench_support"] } +criterion = { version = "0.4.0", default-features = false, features = [ + "async_tokio", + "cargo_bench_support", +] } hex-literal = "0.4.1" polkadot-node-core-pvf-common = { path = "common", features = ["test-utils"] } @@ -57,6 +60,7 @@ adder = { package = "test-parachain-adder", path = "../../../parachain/test-para halt = { package = "test-parachain-halt", path = "../../../parachain/test-parachains/halt" } [target.'cfg(target_os = "linux")'.dev-dependencies] +libc = "0.2.153" procfs = "0.16.0" rusty-fork = "0.3.0" sc-sysinfo = { path = "../../../../substrate/client/sysinfo" } @@ -70,6 +74,8 @@ ci-only-tests = [] jemalloc-allocator = ["polkadot-node-core-pvf-common/jemalloc-allocator"] # This feature is used to export test code to other crates without putting it in the production build. test-utils = [ - "polkadot-node-core-pvf-execute-worker", - "polkadot-node-core-pvf-prepare-worker", + "dep:is_executable", + "dep:polkadot-node-core-pvf-execute-worker", + "dep:polkadot-node-core-pvf-prepare-worker", + "dep:sp-maybe-compressed-blob", ] diff --git a/polkadot/node/core/pvf/benches/host_prepare_rococo_runtime.rs b/polkadot/node/core/pvf/benches/host_prepare_rococo_runtime.rs index 2aea21361a3e8fcb4114eb533539f09b1d660fc7..97a03e6596d16e2d10219331e58dd2d19647b531 100644 --- a/polkadot/node/core/pvf/benches/host_prepare_rococo_runtime.rs +++ b/polkadot/node/core/pvf/benches/host_prepare_rococo_runtime.rs @@ -48,6 +48,9 @@ impl TestHost { false, prepare_worker_path, execute_worker_path, + 2, + 1, + 2, ); f(&mut config); let (host, task) = start(config, Metrics::default()).await.unwrap(); diff --git a/polkadot/node/core/pvf/common/Cargo.toml b/polkadot/node/core/pvf/common/Cargo.toml index f3eb9d919aae6ea6ded7df58a088d22ebbe66325..e1ce6e79cb99038bcdd183c32096432e8e8428be 100644 --- a/polkadot/node/core/pvf/common/Cargo.toml +++ b/polkadot/node/core/pvf/common/Cargo.toml @@ -10,14 +10,16 @@ license.workspace = true workspace = true [dependencies] -cfg-if = "1.0" cpu-time = "1.0.0" futures = "0.3.30" gum = { package = "tracing-gum", path = "../../../gum" } libc = "0.2.152" +nix = { version = "0.27.1", features = ["resource", "sched"] } thiserror = { workspace = true } -parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } +parity-scale-codec = { version = "3.6.1", default-features = false, features = [ + "derive", +] } polkadot-parachain-primitives = { path = "../../../../parachain" } polkadot-primitives = { path = "../../../../primitives" } @@ -34,7 +36,6 @@ sp-tracing = { path = "../../../../../substrate/primitives/tracing" } [target.'cfg(target_os = "linux")'.dependencies] landlock = "0.3.0" -nix = { version = "0.27.1", features = ["sched"] } [target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] seccompiler = "0.4.0" diff --git a/polkadot/node/core/pvf/common/src/error.rs b/polkadot/node/core/pvf/common/src/error.rs index cf274044456f3ea2db6770fbd61b3319f6420996..adeb40c0b1958df624662f8e7533b6d06f49f543 100644 --- a/polkadot/node/core/pvf/common/src/error.rs +++ b/polkadot/node/core/pvf/common/src/error.rs @@ -136,6 +136,9 @@ pub enum InternalValidationError { /// Could not find or open compiled artifact file. #[error("validation: could not find or open compiled artifact file: {0}")] CouldNotOpenFile(String), + /// Could not create a pipe between the worker and a child process. + #[error("validation: could not create pipe: {0}")] + CouldNotCreatePipe(String), /// Host could not clear the worker cache after a job. #[error("validation: host could not clear the worker cache ({path:?}) after a job: {err}")] CouldNotClearWorkerDir { diff --git a/polkadot/node/core/pvf/common/src/execute.rs b/polkadot/node/core/pvf/common/src/execute.rs index 18c97b03cbcd6fccc03cb27cb9f9e3aba9c7cf2a..ae6096cacec457a209d656541999f915f4dbbc3b 100644 --- a/polkadot/node/core/pvf/common/src/execute.rs +++ b/polkadot/node/core/pvf/common/src/execute.rs @@ -30,35 +30,36 @@ pub struct Handshake { /// The response from the execution worker. #[derive(Debug, Encode, Decode)] -pub enum WorkerResponse { - /// The job completed successfully. - Ok { - /// The result of parachain validation. - result_descriptor: ValidationResult, - /// The amount of CPU time taken by the job. - duration: Duration, - }, - /// The candidate is invalid. - InvalidCandidate(String), - /// Instantiation of the WASM module instance failed during an execution. - /// Possibly related to local issues or dirty node update. May be retried with re-preparation. - RuntimeConstruction(String), +pub struct WorkerResponse { + /// The response from the execute job process. + pub job_response: JobResponse, + /// The amount of CPU time taken by the job. + pub duration: Duration, +} + +/// An error occurred in the worker process. +#[derive(thiserror::Error, Debug, Clone, Encode, Decode)] +pub enum WorkerError { /// The job timed out. + #[error("The job timed out")] JobTimedOut, /// The job process has died. We must kill the worker just in case. /// /// We cannot treat this as an internal error because malicious code may have killed the job. /// We still retry it, because in the non-malicious case it is likely spurious. + #[error("The job process (pid {job_pid}) has died: {err}")] JobDied { err: String, job_pid: i32 }, /// An unexpected error occurred in the job process, e.g. failing to spawn a thread, panic, /// etc. /// /// Because malicious code can cause a job error, we must not treat it as an internal error. We /// still retry it, because in the non-malicious case it is likely spurious. - JobError(String), + #[error("An unexpected error occurred in the job process: {0}")] + JobError(#[from] JobError), /// Some internal error occurred. - InternalError(InternalValidationError), + #[error("An internal error occurred: {0}")] + InternalError(#[from] InternalValidationError), } /// The result of a job on the execution worker. @@ -101,7 +102,7 @@ impl JobResponse { /// An unexpected error occurred in the execution job process. Because this comes from the job, /// which executes untrusted code, this error must likewise be treated as untrusted. That is, we /// cannot raise an internal error based on this. -#[derive(thiserror::Error, Debug, Encode, Decode)] +#[derive(thiserror::Error, Clone, Debug, Encode, Decode)] pub enum JobError { #[error("The job timed out")] TimedOut, @@ -114,4 +115,7 @@ pub enum JobError { CouldNotSpawnThread(String), #[error("An error occurred in the CPU time monitor thread: {0}")] CpuTimeMonitorThread(String), + /// Since the job can return any exit status it wants, we have to treat this as untrusted. + #[error("Unexpected exit status: {0}")] + UnexpectedExitStatus(i32), } diff --git a/polkadot/node/core/pvf/common/src/lib.rs b/polkadot/node/core/pvf/common/src/lib.rs index 15097dbd3af5c29d1fceabc8d04f6ee9a395ef30..0cd928201639636bd1e5725685e97ea57361313a 100644 --- a/polkadot/node/core/pvf/common/src/lib.rs +++ b/polkadot/node/core/pvf/common/src/lib.rs @@ -15,6 +15,7 @@ // along with Polkadot. If not, see . //! Contains functionality related to PVFs that is shared by the PVF host and the PVF workers. +#![deny(unused_crate_dependencies)] pub mod error; pub mod execute; diff --git a/polkadot/node/core/pvf/common/src/pvf.rs b/polkadot/node/core/pvf/common/src/pvf.rs index 340dffe07c3fd3e8b049f1a126a348a7ff4e326f..5f248f49b9a381c9f250f911bc1989518280c6de 100644 --- a/polkadot/node/core/pvf/common/src/pvf.rs +++ b/polkadot/node/core/pvf/common/src/pvf.rs @@ -18,12 +18,7 @@ use crate::prepare::PrepareJobKind; use parity_scale_codec::{Decode, Encode}; use polkadot_parachain_primitives::primitives::ValidationCodeHash; use polkadot_primitives::ExecutorParams; -use std::{ - cmp::{Eq, PartialEq}, - fmt, - sync::Arc, - time::Duration, -}; +use std::{fmt, sync::Arc, time::Duration}; /// A struct that carries the exhaustive set of data to prepare an artifact out of plain /// Wasm binary diff --git a/polkadot/node/core/pvf/common/src/worker/mod.rs b/polkadot/node/core/pvf/common/src/worker/mod.rs index d7c95d9e7047f940a236e6d9abf1146ad8073566..67e7bece407d13b012d85ee28bd369fc585a6c6f 100644 --- a/polkadot/node/core/pvf/common/src/worker/mod.rs +++ b/polkadot/node/core/pvf/common/src/worker/mod.rs @@ -18,10 +18,13 @@ pub mod security; -use crate::{framed_recv_blocking, SecurityStatus, WorkerHandshake, LOG_TARGET}; +use crate::{ + framed_recv_blocking, framed_send_blocking, SecurityStatus, WorkerHandshake, LOG_TARGET, +}; use cpu_time::ProcessTime; use futures::never::Never; -use parity_scale_codec::Decode; +use nix::{errno::Errno, sys::resource::Usage}; +use parity_scale_codec::{Decode, Encode}; use std::{ any::Any, fmt::{self}, @@ -58,8 +61,6 @@ macro_rules! decl_worker_main { $crate::sp_tracing::try_init_simple(); - let worker_pid = std::process::id(); - let args = std::env::args().collect::>(); if args.len() == 1 { print_help($expected_command); @@ -548,6 +549,81 @@ fn recv_worker_handshake(stream: &mut UnixStream) -> io::Result Ok(worker_handshake) } +/// Calculate the total CPU time from the given `usage` structure, returned from +/// [`nix::sys::resource::getrusage`], and calculates the total CPU time spent, including both user +/// and system time. +/// +/// # Arguments +/// +/// - `rusage`: Contains resource usage information. +/// +/// # Returns +/// +/// Returns a `Duration` representing the total CPU time. +pub fn get_total_cpu_usage(rusage: Usage) -> Duration { + let micros = (((rusage.user_time().tv_sec() + rusage.system_time().tv_sec()) * 1_000_000) + + (rusage.system_time().tv_usec() + rusage.user_time().tv_usec()) as i64) as u64; + + return Duration::from_micros(micros) +} + +/// Get a job response. +pub fn recv_child_response( + received_data: &mut io::BufReader<&[u8]>, + context: &'static str, +) -> io::Result +where + T: Decode, +{ + let response_bytes = framed_recv_blocking(received_data)?; + T::decode(&mut response_bytes.as_slice()).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("{} pvf recv_child_response: decode error: {}", context, e), + ) + }) +} + +pub fn send_result( + stream: &mut UnixStream, + result: Result, + worker_info: &WorkerInfo, +) -> io::Result<()> +where + T: std::fmt::Debug, + E: std::fmt::Debug + std::fmt::Display, + Result: Encode, +{ + if let Err(ref err) = result { + gum::warn!( + target: LOG_TARGET, + ?worker_info, + "worker: error occurred: {}", + err + ); + } + gum::trace!( + target: LOG_TARGET, + ?worker_info, + "worker: sending result to host: {:?}", + result + ); + + framed_send_blocking(stream, &result.encode()).map_err(|err| { + gum::warn!( + target: LOG_TARGET, + ?worker_info, + "worker: error occurred sending result to host: {}", + err + ); + err + }) +} + +pub fn stringify_errno(context: &'static str, errno: Errno) -> String { + format!("{}: {}: {}", context, errno, io::Error::last_os_error()) +} + /// Functionality related to threads spawned by the workers. /// /// The motivation for this module is to coordinate worker threads without using async Rust. diff --git a/polkadot/node/core/pvf/execute-worker/src/lib.rs b/polkadot/node/core/pvf/execute-worker/src/lib.rs index bd7e76010a6dfedb339fd2fc1c17aa7f4007babe..55f5290bd87ee88ce55157a1988c63a2c9670bbf 100644 --- a/polkadot/node/core/pvf/execute-worker/src/lib.rs +++ b/polkadot/node/core/pvf/execute-worker/src/lib.rs @@ -16,6 +16,9 @@ //! Contains the logic for executing PVFs. Used by the polkadot-execute-worker binary. +#![deny(unused_crate_dependencies)] +#![warn(missing_docs)] + pub use polkadot_node_core_pvf_common::{ error::ExecuteError, executor_interface::execute_artifact, }; @@ -36,11 +39,12 @@ use nix::{ use parity_scale_codec::{Decode, Encode}; use polkadot_node_core_pvf_common::{ error::InternalValidationError, - execute::{Handshake, JobError, JobResponse, JobResult, WorkerResponse}, + execute::{Handshake, JobError, JobResponse, JobResult, WorkerError, WorkerResponse}, executor_interface::params_to_wasmtime_semantics, framed_recv_blocking, framed_send_blocking, worker::{ - cpu_time_monitor_loop, pipe2_cloexec, run_worker, stringify_panic_payload, + cpu_time_monitor_loop, get_total_cpu_usage, pipe2_cloexec, recv_child_response, run_worker, + send_result, stringify_errno, stringify_panic_payload, thread::{self, WaitOutcome}, PipeFd, WorkerInfo, WorkerKind, }, @@ -93,8 +97,14 @@ fn recv_request(stream: &mut UnixStream) -> io::Result<(Vec, Duration)> { Ok((params, execution_timeout)) } -fn send_response(stream: &mut UnixStream, response: WorkerResponse) -> io::Result<()> { - framed_send_blocking(stream, &response.encode()) +/// Sends an error to the host and returns the original error wrapped in `io::Error`. +macro_rules! map_and_send_err { + ($error:expr, $err_constructor:expr, $stream:expr, $worker_info:expr) => {{ + let err: WorkerError = $err_constructor($error.to_string()).into(); + let io_err = io::Error::new(io::ErrorKind::Other, err.to_string()); + let _ = send_result::($stream, Err(err), $worker_info); + io_err + }}; } /// The entrypoint that the spawned execute worker should start with. @@ -110,8 +120,6 @@ fn send_response(stream: &mut UnixStream, response: WorkerResponse) -> io::Resul /// check is not necessary. /// /// - `worker_version`: see above -/// -/// - `security_status`: contains the detected status of security features. pub fn worker_entrypoint( socket_path: PathBuf, worker_dir_path: PathBuf, @@ -127,13 +135,28 @@ pub fn worker_entrypoint( |mut stream, worker_info, security_status| { let artifact_path = worker_dir::execute_artifact(&worker_info.worker_dir_path); - let Handshake { executor_params } = recv_execute_handshake(&mut stream)?; + let Handshake { executor_params } = + recv_execute_handshake(&mut stream).map_err(|e| { + map_and_send_err!( + e, + InternalValidationError::HostCommunication, + &mut stream, + worker_info + ) + })?; let executor_params: Arc = Arc::new(executor_params); let execute_thread_stack_size = max_stack_size(&executor_params); loop { - let (params, execution_timeout) = recv_request(&mut stream)?; + let (params, execution_timeout) = recv_request(&mut stream).map_err(|e| { + map_and_send_err!( + e, + InternalValidationError::HostCommunication, + &mut stream, + worker_info + ) + })?; gum::debug!( target: LOG_TARGET, ?worker_info, @@ -143,27 +166,34 @@ pub fn worker_entrypoint( ); // Get the artifact bytes. - let compiled_artifact_blob = match std::fs::read(&artifact_path) { - Ok(bytes) => bytes, - Err(err) => { - let response = WorkerResponse::InternalError( - InternalValidationError::CouldNotOpenFile(err.to_string()), - ); - send_response(&mut stream, response)?; - continue - }, - }; - - let (pipe_read_fd, pipe_write_fd) = pipe2_cloexec()?; - - let usage_before = match nix::sys::resource::getrusage(UsageWho::RUSAGE_CHILDREN) { - Ok(usage) => usage, - Err(errno) => { - let response = internal_error_from_errno("getrusage before", errno); - send_response(&mut stream, response)?; - continue - }, - }; + let compiled_artifact_blob = std::fs::read(&artifact_path).map_err(|e| { + map_and_send_err!( + e, + InternalValidationError::CouldNotOpenFile, + &mut stream, + worker_info + ) + })?; + + let (pipe_read_fd, pipe_write_fd) = pipe2_cloexec().map_err(|e| { + map_and_send_err!( + e, + InternalValidationError::CouldNotCreatePipe, + &mut stream, + worker_info + ) + })?; + + let usage_before = nix::sys::resource::getrusage(UsageWho::RUSAGE_CHILDREN) + .map_err(|errno| { + let e = stringify_errno("getrusage before", errno); + map_and_send_err!( + e, + InternalValidationError::Kernel, + &mut stream, + worker_info + ) + })?; let stream_fd = stream.as_raw_fd(); let compiled_artifact_blob = Arc::new(compiled_artifact_blob); @@ -222,7 +252,7 @@ pub fn worker_entrypoint( "worker: sending result to host: {:?}", result ); - send_response(&mut stream, result)?; + send_result(&mut stream, result, worker_info)?; } }, ); @@ -270,7 +300,7 @@ fn handle_clone( worker_info: &WorkerInfo, have_unshare_newuser: bool, usage_before: Usage, -) -> io::Result { +) -> io::Result> { use polkadot_node_core_pvf_common::worker::security; // SAFETY: new process is spawned within a single threaded process. This invariant @@ -301,7 +331,8 @@ fn handle_clone( usage_before, execution_timeout, ), - Err(security::clone::Error::Clone(errno)) => Ok(internal_error_from_errno("clone", errno)), + Err(security::clone::Error::Clone(errno)) => + Ok(Err(internal_error_from_errno("clone", errno))), } } @@ -316,7 +347,7 @@ fn handle_fork( execute_worker_stack_size: usize, worker_info: &WorkerInfo, usage_before: Usage, -) -> io::Result { +) -> io::Result> { // SAFETY: new process is spawned within a single threaded process. This invariant // is enforced by tests. match unsafe { nix::unistd::fork() } { @@ -338,7 +369,7 @@ fn handle_fork( usage_before, execution_timeout, ), - Err(errno) => Ok(internal_error_from_errno("fork", errno)), + Err(errno) => Ok(Err(internal_error_from_errno("fork", errno))), } } @@ -483,11 +514,11 @@ fn handle_parent_process( job_pid: Pid, usage_before: Usage, timeout: Duration, -) -> io::Result { +) -> io::Result> { // the read end will wait until all write ends have been closed, // this drop is necessary to avoid deadlock if let Err(errno) = nix::unistd::close(pipe_write_fd) { - return Ok(internal_error_from_errno("closing pipe write fd", errno)); + return Ok(Err(internal_error_from_errno("closing pipe write fd", errno))); }; // SAFETY: pipe_read_fd is an open and owned file descriptor at this point. @@ -512,7 +543,7 @@ fn handle_parent_process( let usage_after = match nix::sys::resource::getrusage(UsageWho::RUSAGE_CHILDREN) { Ok(usage) => usage, - Err(errno) => return Ok(internal_error_from_errno("getrusage after", errno)), + Err(errno) => return Ok(Err(internal_error_from_errno("getrusage after", errno))), }; // Using `getrusage` is needed to check whether child has timedout since we cannot rely on @@ -530,32 +561,25 @@ fn handle_parent_process( cpu_tv.as_millis(), timeout.as_millis(), ); - return Ok(WorkerResponse::JobTimedOut) + return Ok(Err(WorkerError::JobTimedOut)) } match status { Ok(WaitStatus::Exited(_, exit_status)) => { let mut reader = io::BufReader::new(received_data.as_slice()); - let result = match recv_child_response(&mut reader) { - Ok(result) => result, - Err(err) => return Ok(WorkerResponse::JobError(err.to_string())), - }; + let result = recv_child_response(&mut reader, "execute")?; match result { - Ok(JobResponse::Ok { result_descriptor }) => { + Ok(job_response) => { // The exit status should have been zero if no error occurred. if exit_status != 0 { - return Ok(WorkerResponse::JobError(format!( - "unexpected exit status: {}", - exit_status - ))) + return Ok(Err(WorkerError::JobError(JobError::UnexpectedExitStatus( + exit_status, + )))); } - Ok(WorkerResponse::Ok { result_descriptor, duration: cpu_tv }) + Ok(Ok(WorkerResponse { job_response, duration: cpu_tv })) }, - Ok(JobResponse::InvalidCandidate(err)) => Ok(WorkerResponse::InvalidCandidate(err)), - Ok(JobResponse::RuntimeConstruction(err)) => - Ok(WorkerResponse::RuntimeConstruction(err)), Err(job_error) => { gum::warn!( target: LOG_TARGET, @@ -565,9 +589,9 @@ fn handle_parent_process( job_error, ); if matches!(job_error, JobError::TimedOut) { - Ok(WorkerResponse::JobTimedOut) + Ok(Err(WorkerError::JobTimedOut)) } else { - Ok(WorkerResponse::JobError(job_error.to_string())) + Ok(Err(WorkerError::JobError(job_error.into()))) } }, } @@ -576,50 +600,21 @@ fn handle_parent_process( // // The job gets SIGSYS on seccomp violations, but this signal may have been sent for some // other reason, so we still need to check for seccomp violations elsewhere. - Ok(WaitStatus::Signaled(_pid, signal, _core_dump)) => Ok(WorkerResponse::JobDied { + Ok(WaitStatus::Signaled(_pid, signal, _core_dump)) => Ok(Err(WorkerError::JobDied { err: format!("received signal: {signal:?}"), job_pid: job_pid.as_raw(), - }), - Err(errno) => Ok(internal_error_from_errno("waitpid", errno)), + })), + Err(errno) => Ok(Err(internal_error_from_errno("waitpid", errno))), // It is within an attacker's power to send an unexpected exit status. So we cannot treat // this as an internal error (which would make us abstain), but must vote against. - Ok(unexpected_wait_status) => Ok(WorkerResponse::JobDied { + Ok(unexpected_wait_status) => Ok(Err(WorkerError::JobDied { err: format!("unexpected status from wait: {unexpected_wait_status:?}"), job_pid: job_pid.as_raw(), - }), + })), } } -/// Calculate the total CPU time from the given `usage` structure, returned from -/// [`nix::sys::resource::getrusage`], and calculates the total CPU time spent, including both user -/// and system time. -/// -/// # Arguments -/// -/// - `rusage`: Contains resource usage information. -/// -/// # Returns -/// -/// Returns a `Duration` representing the total CPU time. -fn get_total_cpu_usage(rusage: Usage) -> Duration { - let micros = (((rusage.user_time().tv_sec() + rusage.system_time().tv_sec()) * 1_000_000) + - (rusage.system_time().tv_usec() + rusage.user_time().tv_usec()) as i64) as u64; - - return Duration::from_micros(micros) -} - -/// Get a job response. -fn recv_child_response(received_data: &mut io::BufReader<&[u8]>) -> io::Result { - let response_bytes = framed_recv_blocking(received_data)?; - JobResult::decode(&mut response_bytes.as_slice()).map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("execute pvf recv_child_response: decode error: {}", e), - ) - }) -} - /// Write a job response to the pipe and exit process after. /// /// # Arguments @@ -638,15 +633,10 @@ fn send_child_response(pipe_write: &mut PipeFd, response: JobResult) -> ! { } } -fn internal_error_from_errno(context: &'static str, errno: Errno) -> WorkerResponse { - WorkerResponse::InternalError(InternalValidationError::Kernel(format!( - "{}: {}: {}", - context, - errno, - io::Error::last_os_error() - ))) +fn internal_error_from_errno(context: &'static str, errno: Errno) -> WorkerError { + WorkerError::InternalError(InternalValidationError::Kernel(stringify_errno(context, errno))) } fn job_error_from_errno(context: &'static str, errno: Errno) -> JobResult { - Err(JobError::Kernel(format!("{}: {}: {}", context, errno, io::Error::last_os_error()))) + Err(JobError::Kernel(stringify_errno(context, errno))) } diff --git a/polkadot/node/core/pvf/prepare-worker/src/lib.rs b/polkadot/node/core/pvf/prepare-worker/src/lib.rs index 82a56107ef535743a1838d50358241e3cc64b711..d1b218f48ae897fb663f8285521f251f8a6817d3 100644 --- a/polkadot/node/core/pvf/prepare-worker/src/lib.rs +++ b/polkadot/node/core/pvf/prepare-worker/src/lib.rs @@ -26,7 +26,6 @@ const LOG_TARGET: &str = "parachain::pvf-prepare-worker"; use crate::memory_stats::max_rss_stat::{extract_max_rss_stat, get_max_rss_thread}; #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] use crate::memory_stats::memory_tracker::{get_memory_tracker_loop_stats, memory_tracker_loop}; -use libc; use nix::{ errno::Errno, sys::{ @@ -48,7 +47,8 @@ use polkadot_node_core_pvf_common::{ prepare::{MemoryStats, PrepareJobKind, PrepareStats, PrepareWorkerSuccess}, pvf::PvfPrepData, worker::{ - cpu_time_monitor_loop, run_worker, stringify_panic_payload, + cpu_time_monitor_loop, get_total_cpu_usage, recv_child_response, run_worker, send_result, + stringify_errno, stringify_panic_payload, thread::{self, spawn_worker_thread, WaitOutcome}, WorkerKind, }, @@ -117,11 +117,6 @@ fn recv_request(stream: &mut UnixStream) -> io::Result { Ok(pvf) } -/// Send a worker response. -fn send_response(stream: &mut UnixStream, result: PrepareWorkerResult) -> io::Result<()> { - framed_send_blocking(stream, &result.encode()) -} - fn start_memory_tracking(fd: RawFd, limit: Option) { unsafe { // SAFETY: Inside the failure handler, the allocator is locked and no allocations or @@ -178,8 +173,6 @@ fn end_memory_tracking() -> isize { /// /// - `worker_version`: see above /// -/// - `security_status`: contains the detected status of security features. -/// /// # Flow /// /// This runs the following in a loop: @@ -233,8 +226,9 @@ pub fn worker_entrypoint( let usage_before = match nix::sys::resource::getrusage(UsageWho::RUSAGE_CHILDREN) { Ok(usage) => usage, Err(errno) => { - let result = Err(error_from_errno("getrusage before", errno)); - send_response(&mut stream, result)?; + let result: PrepareWorkerResult = + Err(error_from_errno("getrusage before", errno)); + send_result(&mut stream, result, worker_info)?; continue }, }; @@ -294,7 +288,7 @@ pub fn worker_entrypoint( "worker: sending result to host: {:?}", result ); - send_response(&mut stream, result)?; + send_result(&mut stream, result, worker_info)?; } }, ); @@ -666,7 +660,7 @@ fn handle_parent_process( match status { Ok(WaitStatus::Exited(_pid, exit_status)) => { let mut reader = io::BufReader::new(received_data.as_slice()); - let result = recv_child_response(&mut reader) + let result = recv_child_response(&mut reader, "prepare") .map_err(|err| PrepareError::JobError(err.to_string()))?; match result { @@ -726,35 +720,6 @@ fn handle_parent_process( } } -/// Calculate the total CPU time from the given `usage` structure, returned from -/// [`nix::sys::resource::getrusage`], and calculates the total CPU time spent, including both user -/// and system time. -/// -/// # Arguments -/// -/// - `rusage`: Contains resource usage information. -/// -/// # Returns -/// -/// Returns a `Duration` representing the total CPU time. -fn get_total_cpu_usage(rusage: Usage) -> Duration { - let micros = (((rusage.user_time().tv_sec() + rusage.system_time().tv_sec()) * 1_000_000) + - (rusage.system_time().tv_usec() + rusage.user_time().tv_usec()) as i64) as u64; - - return Duration::from_micros(micros) -} - -/// Get a job response. -fn recv_child_response(received_data: &mut io::BufReader<&[u8]>) -> io::Result { - let response_bytes = framed_recv_blocking(received_data)?; - JobResult::decode(&mut response_bytes.as_slice()).map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("prepare pvf recv_child_response: decode error: {:?}", e), - ) - }) -} - /// Write a job response to the pipe and exit process after. /// /// # Arguments @@ -774,7 +739,7 @@ fn send_child_response(pipe_write: &mut PipeFd, response: JobResult) -> ! { } fn error_from_errno(context: &'static str, errno: Errno) -> PrepareError { - PrepareError::Kernel(format!("{}: {}: {}", context, errno, io::Error::last_os_error())) + PrepareError::Kernel(stringify_errno(context, errno)) } type JobResult = Result; diff --git a/polkadot/node/core/pvf/src/artifacts.rs b/polkadot/node/core/pvf/src/artifacts.rs index 6288755526d497812027f7bf04e11e5a67ab799e..a3a48b61acb1743d2ed5df62457c6f9f4ba5fa79 100644 --- a/polkadot/node/core/pvf/src/artifacts.rs +++ b/polkadot/node/core/pvf/src/artifacts.rs @@ -58,7 +58,7 @@ use crate::{host::PrecheckResultSender, worker_interface::WORKER_DIR_PREFIX}; use always_assert::always; use polkadot_node_core_pvf_common::{error::PrepareError, prepare::PrepareStats, pvf::PvfPrepData}; use polkadot_parachain_primitives::primitives::ValidationCodeHash; -use polkadot_primitives::ExecutorParamsHash; +use polkadot_primitives::ExecutorParamsPrepHash; use std::{ collections::HashMap, fs, @@ -85,22 +85,27 @@ pub fn generate_artifact_path(cache_path: &Path) -> PathBuf { artifact_path } -/// Identifier of an artifact. Encodes a code hash of the PVF and a hash of executor parameter set. +/// Identifier of an artifact. Encodes a code hash of the PVF and a hash of preparation-related +/// executor parameter set. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ArtifactId { pub(crate) code_hash: ValidationCodeHash, - pub(crate) executor_params_hash: ExecutorParamsHash, + pub(crate) executor_params_prep_hash: ExecutorParamsPrepHash, } impl ArtifactId { /// Creates a new artifact ID with the given hash. - pub fn new(code_hash: ValidationCodeHash, executor_params_hash: ExecutorParamsHash) -> Self { - Self { code_hash, executor_params_hash } + pub fn new( + code_hash: ValidationCodeHash, + executor_params_prep_hash: ExecutorParamsPrepHash, + ) -> Self { + Self { code_hash, executor_params_prep_hash } } - /// Returns an artifact ID that corresponds to the PVF with given executor params. + /// Returns an artifact ID that corresponds to the PVF with given preparation-related + /// executor parameters. pub fn from_pvf_prep_data(pvf: &PvfPrepData) -> Self { - Self::new(pvf.code_hash(), pvf.executor_params().hash()) + Self::new(pvf.code_hash(), pvf.executor_params().prep_hash()) } } diff --git a/polkadot/node/core/pvf/src/execute/queue.rs b/polkadot/node/core/pvf/src/execute/queue.rs index bdc3c7327b06079eaaeebf737ffa7ca2c8c8270c..bb00a5a652d6434cd06327803d9ce51a3d5c1e93 100644 --- a/polkadot/node/core/pvf/src/execute/queue.rs +++ b/polkadot/node/core/pvf/src/execute/queue.rs @@ -16,7 +16,7 @@ //! A queue that handles requests for PVF execution. -use super::worker_interface::Outcome; +use super::worker_interface::{Error as WorkerInterfaceError, Response as WorkerInterfaceResponse}; use crate::{ artifacts::{ArtifactId, ArtifactPathId}, host::ResultSender, @@ -30,7 +30,10 @@ use futures::{ stream::{FuturesUnordered, StreamExt as _}, Future, FutureExt, }; -use polkadot_node_core_pvf_common::SecurityStatus; +use polkadot_node_core_pvf_common::{ + execute::{JobResponse, WorkerError, WorkerResponse}, + SecurityStatus, +}; use polkadot_primitives::{ExecutorParams, ExecutorParamsHash}; use slotmap::HopSlotMap; use std::{ @@ -133,7 +136,12 @@ impl Workers { enum QueueEvent { Spawn(IdleWorker, WorkerHandle, ExecuteJob), - StartWork(Worker, Outcome, ArtifactId, ResultSender), + StartWork( + Worker, + Result, + ArtifactId, + ResultSender, + ), } type Mux = FuturesUnordered>; @@ -340,23 +348,34 @@ fn handle_worker_spawned( async fn handle_job_finish( queue: &mut Queue, worker: Worker, - outcome: Outcome, + worker_result: Result, artifact_id: ArtifactId, result_tx: ResultSender, ) { - let (idle_worker, result, duration, sync_channel) = match outcome { - Outcome::Ok { result_descriptor, duration, idle_worker } => { + let (idle_worker, result, duration, sync_channel) = match worker_result { + Ok(WorkerInterfaceResponse { + worker_response: + WorkerResponse { job_response: JobResponse::Ok { result_descriptor }, duration }, + idle_worker, + }) => { // TODO: propagate the soft timeout (Some(idle_worker), Ok(result_descriptor), Some(duration), None) }, - Outcome::InvalidCandidate { err, idle_worker } => ( + Ok(WorkerInterfaceResponse { + worker_response: WorkerResponse { job_response: JobResponse::InvalidCandidate(err), .. }, + idle_worker, + }) => ( Some(idle_worker), Err(ValidationError::Invalid(InvalidCandidate::WorkerReportedInvalid(err))), None, None, ), - Outcome::RuntimeConstruction { err, idle_worker } => { + Ok(WorkerInterfaceResponse { + worker_response: + WorkerResponse { job_response: JobResponse::RuntimeConstruction(err), .. }, + idle_worker, + }) => { // The task for artifact removal is executed concurrently with // the message to the host on the execution result. let (result_tx, result_rx) = oneshot::channel(); @@ -376,27 +395,31 @@ async fn handle_job_finish( Some(result_rx), ) }, - Outcome::InternalError { err } => (None, Err(ValidationError::Internal(err)), None, None), + + Err(WorkerInterfaceError::InternalError(err)) | + Err(WorkerInterfaceError::WorkerError(WorkerError::InternalError(err))) => + (None, Err(ValidationError::Internal(err)), None, None), // Either the worker or the job timed out. Kill the worker in either case. Treated as // definitely-invalid, because if we timed out, there's no time left for a retry. - Outcome::HardTimeout => + Err(WorkerInterfaceError::HardTimeout) | + Err(WorkerInterfaceError::WorkerError(WorkerError::JobTimedOut)) => (None, Err(ValidationError::Invalid(InvalidCandidate::HardTimeout)), None, None), // "Maybe invalid" errors (will retry). - Outcome::WorkerIntfErr => ( + Err(WorkerInterfaceError::CommunicationErr(_err)) => ( None, Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)), None, None, ), - Outcome::JobDied { err } => ( + Err(WorkerInterfaceError::WorkerError(WorkerError::JobDied { err, .. })) => ( None, Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousJobDeath(err))), None, None, ), - Outcome::JobError { err } => ( + Err(WorkerInterfaceError::WorkerError(WorkerError::JobError(err))) => ( None, - Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::JobError(err))), + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::JobError(err.to_string()))), None, None, ), @@ -539,18 +562,21 @@ fn assign(queue: &mut Queue, worker: Worker, job: ExecuteJob) { thus claim_idle cannot return None; qed.", ); + queue + .metrics + .observe_execution_queued_time(job.waiting_since.elapsed().as_millis() as u32); let execution_timer = queue.metrics.time_execution(); queue.mux.push( async move { let _timer = execution_timer; - let outcome = super::worker_interface::start_work( + let result = super::worker_interface::start_work( idle, job.artifact.clone(), job.exec_timeout, job.params, ) .await; - QueueEvent::StartWork(worker, outcome, job.artifact.id, job.result_tx) + QueueEvent::StartWork(worker, result, job.artifact.id, job.result_tx) } .boxed(), ); diff --git a/polkadot/node/core/pvf/src/execute/worker_interface.rs b/polkadot/node/core/pvf/src/execute/worker_interface.rs index db81da118d7b5563ea7b4bec1b6c76bd3bf842e2..9dcadfb4c2a73fbbb27532709de8dd410146fd11 100644 --- a/polkadot/node/core/pvf/src/execute/worker_interface.rs +++ b/polkadot/node/core/pvf/src/execute/worker_interface.rs @@ -29,10 +29,9 @@ use futures_timer::Delay; use parity_scale_codec::{Decode, Encode}; use polkadot_node_core_pvf_common::{ error::InternalValidationError, - execute::{Handshake, WorkerResponse}, + execute::{Handshake, WorkerError, WorkerResponse}, worker_dir, SecurityStatus, }; -use polkadot_parachain_primitives::primitives::ValidationResult; use polkadot_primitives::ExecutorParams; use std::{path::Path, time::Duration}; use tokio::{io, net::UnixStream}; @@ -69,7 +68,8 @@ pub async fn spawn( gum::warn!( target: LOG_TARGET, worker_pid = %idle_worker.pid, - %err + "failed to send a handshake to the spawned worker: {}", + error ); err })?; @@ -78,39 +78,40 @@ pub async fn spawn( /// Outcome of PVF execution. /// -/// If the idle worker token is not returned, it means the worker must be terminated. -pub enum Outcome { - /// PVF execution completed successfully and the result is returned. The worker is ready for - /// another job. - Ok { result_descriptor: ValidationResult, duration: Duration, idle_worker: IdleWorker }, - /// The candidate validation failed. It may be for example because the wasm execution triggered - /// a trap. Errors related to the preparation process are not expected to be encountered by the - /// execution workers. - InvalidCandidate { err: String, idle_worker: IdleWorker }, - /// The error is probably transient. It may be for example - /// because the artifact was prepared with a Wasmtime version different from the version - /// in the current execution environment. - RuntimeConstruction { err: String, idle_worker: IdleWorker }, +/// PVF execution completed and the result is returned. The worker is ready for +/// another job. +pub struct Response { + /// The response (valid/invalid) from the worker. + pub worker_response: WorkerResponse, + /// Returning the idle worker token means the worker can be reused. + pub idle_worker: IdleWorker, +} +/// The idle worker token is not returned for any of these cases, meaning the worker must be +/// terminated. +/// +/// NOTE: Errors related to the preparation process are not expected to be encountered by the +/// execution workers. +#[derive(thiserror::Error, Debug)] +pub enum Error { /// The execution time exceeded the hard limit. The worker is terminated. + #[error("The communication with the worker exceeded the hard limit")] HardTimeout, /// An I/O error happened during communication with the worker. This may mean that the worker /// process already died. The token is not returned in any case. - WorkerIntfErr, - /// The job process has died. We must kill the worker just in case. - /// - /// We cannot treat this as an internal error because malicious code may have caused this. - JobDied { err: String }, - /// An unexpected error occurred in the job process. - /// - /// Because malicious code can cause a job error, we must not treat it as an internal error. - JobError { err: String }, + #[error("An I/O error happened during communication with the worker: {0}")] + CommunicationErr(#[from] io::Error), + /// The worker reported an error (can be from itself or from the job). The worker should not be + /// reused. + #[error("The worker reported an error: {0}")] + WorkerError(#[from] WorkerError), /// An internal error happened during the validation. Such an error is most likely related to /// some transient glitch. /// /// Should only ever be used for errors independent of the candidate and PVF. Therefore it may /// be a problem with the worker, so we terminate it. - InternalError { err: InternalValidationError }, + #[error("An internal error occurred: {0}")] + InternalError(#[from] InternalValidationError), } /// Given the idle token of a worker and parameters of work, communicates with the worker and @@ -123,7 +124,7 @@ pub async fn start_work( artifact: ArtifactPathId, execution_timeout: Duration, validation_params: Vec, -) -> Outcome { +) -> Result { let IdleWorker { mut stream, pid, worker_dir } = worker; gum::debug!( @@ -136,16 +137,18 @@ pub async fn start_work( ); with_worker_dir_setup(worker_dir, pid, &artifact.path, |worker_dir| async move { - if let Err(error) = send_request(&mut stream, &validation_params, execution_timeout).await { - gum::warn!( - target: LOG_TARGET, - worker_pid = %pid, - validation_code_hash = ?artifact.id.code_hash, - ?error, - "failed to send an execute request", - ); - return Outcome::WorkerIntfErr - } + send_request(&mut stream, &validation_params, execution_timeout).await.map_err( + |error| { + gum::warn!( + target: LOG_TARGET, + worker_pid = %pid, + validation_code_hash = ?artifact.id.code_hash, + "failed to send an execute request: {}", + error, + ); + Error::InternalError(InternalValidationError::HostCommunication(error.to_string())) + }, + )?; // We use a generous timeout here. This is in addition to the one in the child process, in // case the child stalls. We have a wall clock timeout here in the host, but a CPU timeout @@ -153,12 +156,12 @@ pub async fn start_work( // load, but the CPU resources of the child can only be measured from the parent after the // child process terminates. let timeout = execution_timeout * JOB_TIMEOUT_WALL_CLOCK_FACTOR; - let response = futures::select! { - response = recv_response(&mut stream).fuse() => { - match response { - Ok(response) => - handle_response( - response, + let worker_result = futures::select! { + worker_result = recv_result(&mut stream).fuse() => { + match worker_result { + Ok(result) => + handle_result( + result, pid, execution_timeout, ) @@ -168,11 +171,11 @@ pub async fn start_work( target: LOG_TARGET, worker_pid = %pid, validation_code_hash = ?artifact.id.code_hash, - ?error, - "failed to recv an execute response", + "failed to recv an execute result: {}", + error, ); - return Outcome::WorkerIntfErr + return Err(Error::CommunicationErr(error)) }, } }, @@ -183,29 +186,16 @@ pub async fn start_work( validation_code_hash = ?artifact.id.code_hash, "execution worker exceeded lenient timeout for execution, child worker likely stalled", ); - WorkerResponse::JobTimedOut + return Err(Error::HardTimeout) }, }; - match response { - WorkerResponse::Ok { result_descriptor, duration } => Outcome::Ok { - result_descriptor, - duration, - idle_worker: IdleWorker { stream, pid, worker_dir }, - }, - WorkerResponse::InvalidCandidate(err) => Outcome::InvalidCandidate { - err, - idle_worker: IdleWorker { stream, pid, worker_dir }, - }, - WorkerResponse::RuntimeConstruction(err) => Outcome::RuntimeConstruction { - err, + match worker_result { + Ok(worker_response) => Ok(Response { + worker_response, idle_worker: IdleWorker { stream, pid, worker_dir }, - }, - WorkerResponse::JobTimedOut => Outcome::HardTimeout, - WorkerResponse::JobDied { err, job_pid: _ } => Outcome::JobDied { err }, - WorkerResponse::JobError(err) => Outcome::JobError { err }, - - WorkerResponse::InternalError(err) => Outcome::InternalError { err }, + }), + Err(worker_error) => Err(worker_error.into()), } }) .await @@ -215,12 +205,12 @@ pub async fn start_work( /// /// Here we know the artifact exists, but is still located in a temporary file which will be cleared /// by [`with_worker_dir_setup`]. -async fn handle_response( - response: WorkerResponse, +async fn handle_result( + worker_result: Result, worker_pid: u32, execution_timeout: Duration, -) -> WorkerResponse { - if let WorkerResponse::Ok { duration, .. } = response { +) -> Result { + if let Ok(WorkerResponse { duration, .. }) = worker_result { if duration > execution_timeout { // The job didn't complete within the timeout. gum::warn!( @@ -232,11 +222,11 @@ async fn handle_response( ); // Return a timeout error. - return WorkerResponse::JobTimedOut + return Err(WorkerError::JobTimedOut) } } - response + worker_result } /// Create a temporary file for an artifact in the worker cache, execute the given future/closure @@ -249,9 +239,9 @@ async fn with_worker_dir_setup( pid: u32, artifact_path: &Path, f: F, -) -> Outcome +) -> Result where - Fut: futures::Future, + Fut: futures::Future>, F: FnOnce(WorkerDir) -> Fut, { // Cheaply create a hard link to the artifact. The artifact is always at a known location in the @@ -263,16 +253,14 @@ where target: LOG_TARGET, worker_pid = %pid, ?worker_dir, - "failed to clear worker cache after the job: {:?}", + "failed to clear worker cache after the job: {}", err, ); - return Outcome::InternalError { - err: InternalValidationError::CouldNotCreateLink(format!("{:?}", err)), - } + return Err(InternalValidationError::CouldNotCreateLink(format!("{:?}", err)).into()); } let worker_dir_path = worker_dir.path().to_owned(); - let outcome = f(worker_dir).await; + let result = f(worker_dir).await; // Try to clear the worker dir. if let Err(err) = clear_worker_dir_path(&worker_dir_path) { @@ -283,15 +271,14 @@ where "failed to clear worker cache after the job: {:?}", err, ); - return Outcome::InternalError { - err: InternalValidationError::CouldNotClearWorkerDir { - err: format!("{:?}", err), - path: worker_dir_path.to_str().map(String::from), - }, + return Err(InternalValidationError::CouldNotClearWorkerDir { + err: format!("{:?}", err), + path: worker_dir_path.to_str().map(String::from), } + .into()) } - outcome + result } /// Sends a handshake with information specific to the execute worker. @@ -308,12 +295,12 @@ async fn send_request( framed_send(stream, &execution_timeout.encode()).await } -async fn recv_response(stream: &mut UnixStream) -> io::Result { - let response_bytes = framed_recv(stream).await?; - WorkerResponse::decode(&mut response_bytes.as_slice()).map_err(|e| { +async fn recv_result(stream: &mut UnixStream) -> io::Result> { + let result_bytes = framed_recv(stream).await?; + Result::::decode(&mut result_bytes.as_slice()).map_err(|e| { io::Error::new( io::ErrorKind::Other, - format!("execute pvf recv_response: decode error: {:?}", e), + format!("execute pvf recv_result: decode error: {:?}", e), ) }) } diff --git a/polkadot/node/core/pvf/src/host.rs b/polkadot/node/core/pvf/src/host.rs index 59d5a7e20a887ef35282f0a8ae8c0998625d825a..4065598a3ac46c32b4d821aac7712f62767a973e 100644 --- a/polkadot/node/core/pvf/src/host.rs +++ b/polkadot/node/core/pvf/src/host.rs @@ -188,6 +188,9 @@ impl Config { secure_validator_mode: bool, prepare_worker_program_path: PathBuf, execute_worker_program_path: PathBuf, + execute_workers_max_num: usize, + prepare_workers_soft_max_num: usize, + prepare_workers_hard_max_num: usize, ) -> Self { Self { cache_path, @@ -196,12 +199,12 @@ impl Config { prepare_worker_program_path, prepare_worker_spawn_timeout: Duration::from_secs(3), - prepare_workers_soft_max_num: 1, - prepare_workers_hard_max_num: 1, + prepare_workers_soft_max_num, + prepare_workers_hard_max_num, execute_worker_program_path, execute_worker_spawn_timeout: Duration::from_secs(3), - execute_workers_max_num: 2, + execute_workers_max_num, } } } @@ -959,10 +962,7 @@ pub(crate) mod tests { use crate::{artifacts::generate_artifact_path, PossiblyInvalidError}; use assert_matches::assert_matches; use futures::future::BoxFuture; - use polkadot_node_core_pvf_common::{ - error::PrepareError, - prepare::{PrepareStats, PrepareSuccess}, - }; + use polkadot_node_core_pvf_common::prepare::PrepareStats; const TEST_EXECUTION_TIMEOUT: Duration = Duration::from_secs(3); pub(crate) const TEST_PREPARATION_TIMEOUT: Duration = Duration::from_secs(30); diff --git a/polkadot/node/core/pvf/src/metrics.rs b/polkadot/node/core/pvf/src/metrics.rs index 7fd876cf17405f4815173ab289d34f41d7e1e1cc..bc8d300037fe8e1d7c70b575d99b101b8343e94b 100644 --- a/polkadot/node/core/pvf/src/metrics.rs +++ b/polkadot/node/core/pvf/src/metrics.rs @@ -74,6 +74,12 @@ impl Metrics { self.0.as_ref().map(|metrics| metrics.execution_time.start_timer()) } + pub(crate) fn observe_execution_queued_time(&self, queued_for_millis: u32) { + self.0.as_ref().map(|metrics| { + metrics.execution_queued_time.observe(queued_for_millis as f64 / 1000 as f64) + }); + } + /// Observe memory stats for preparation. #[allow(unused_variables)] pub(crate) fn observe_preparation_memory_metrics(&self, memory_stats: MemoryStats) { @@ -112,6 +118,7 @@ struct MetricsInner { execute_finished: prometheus::Counter, preparation_time: prometheus::Histogram, execution_time: prometheus::Histogram, + execution_queued_time: prometheus::Histogram, #[cfg(target_os = "linux")] preparation_max_rss: prometheus::Histogram, // Max. allocated memory, tracked by Jemallocator, polling-based @@ -240,6 +247,31 @@ impl metrics::Metrics for Metrics { )?, registry, )?, + execution_queued_time: prometheus::register( + prometheus::Histogram::with_opts( + prometheus::HistogramOpts::new( + "polkadot_pvf_execution_queued_time", + "Time spent in queue waiting for PVFs execution job to be assigned", + ).buckets(vec![ + 0.01, + 0.025, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 12.0, + 24.0, + 48.0, + ]), + )?, + registry, + )?, #[cfg(target_os = "linux")] preparation_max_rss: prometheus::register( prometheus::Histogram::with_opts( diff --git a/polkadot/node/core/pvf/src/priority.rs b/polkadot/node/core/pvf/src/priority.rs index d1ef9c604b117d10aca5b99cc8ccd1953fa522f5..0d18d4b484cabbde70361019e7008887e419b350 100644 --- a/polkadot/node/core/pvf/src/priority.rs +++ b/polkadot/node/core/pvf/src/priority.rs @@ -14,17 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -/// A priority assigned to execution of a PVF. +/// A priority assigned to preparation of a PVF. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Priority { /// Normal priority for things that do not require immediate response, but still need to be /// done pretty quick. /// - /// Approvals and disputes fall into this category. + /// Backing falls into this category. Normal, /// This priority is used for requests that are required to be processed as soon as possible. /// - /// For example, backing is on a critical path and requires execution as soon as possible. + /// Disputes and approvals are on a critical path and require execution as soon as + /// possible to not delay finality. Critical, } diff --git a/polkadot/node/core/pvf/tests/it/main.rs b/polkadot/node/core/pvf/tests/it/main.rs index 16ef23c69cad9d40d6cd79d2a754d8a347bfac3c..6961b93832abed0d8608e8363a1fac3d2d510fdf 100644 --- a/polkadot/node/core/pvf/tests/it/main.rs +++ b/polkadot/node/core/pvf/tests/it/main.rs @@ -26,7 +26,7 @@ use polkadot_node_core_pvf::{ ValidationHost, JOB_TIMEOUT_WALL_CLOCK_FACTOR, }; use polkadot_parachain_primitives::primitives::{BlockData, ValidationParams, ValidationResult}; -use polkadot_primitives::{ExecutorParam, ExecutorParams}; +use polkadot_primitives::{ExecutorParam, ExecutorParams, PvfExecKind, PvfPrepKind}; use std::{io::Write, time::Duration}; use tokio::sync::Mutex; @@ -63,6 +63,9 @@ impl TestHost { false, prepare_worker_path, execute_worker_path, + 2, + 1, + 2, ); f(&mut config); let (host, task) = start(config, Metrics::default()).await.unwrap(); @@ -556,3 +559,73 @@ async fn nonexistent_cache_dir() { .await .unwrap(); } + +// Checks the the artifact is not re-prepared when the executor environment parameters change +// in a way not affecting the preparation +#[tokio::test] +async fn artifact_does_not_reprepare_on_non_meaningful_exec_parameter_change() { + let host = TestHost::new_with_config(|cfg| { + cfg.prepare_workers_hard_max_num = 1; + }) + .await; + let cache_dir = host.cache_dir.path(); + + let set1 = ExecutorParams::default(); + let set2 = + ExecutorParams::from(&[ExecutorParam::PvfExecTimeout(PvfExecKind::Backing, 2500)][..]); + + let _stats = host.precheck_pvf(halt::wasm_binary_unwrap(), set1).await.unwrap(); + + let md1 = { + let mut cache_dir: Vec<_> = std::fs::read_dir(cache_dir).unwrap().collect(); + assert_eq!(cache_dir.len(), 2); + let mut artifact_path = cache_dir.pop().unwrap().unwrap(); + if artifact_path.path().is_dir() { + artifact_path = cache_dir.pop().unwrap().unwrap(); + } + std::fs::metadata(artifact_path.path()).unwrap() + }; + + // FS times are not monotonical so we wait 2 secs here to be sure that the creation time of the + // second attifact will be different + tokio::time::sleep(Duration::from_secs(2)).await; + + let _stats = host.precheck_pvf(halt::wasm_binary_unwrap(), set2).await.unwrap(); + + let md2 = { + let mut cache_dir: Vec<_> = std::fs::read_dir(cache_dir).unwrap().collect(); + assert_eq!(cache_dir.len(), 2); + let mut artifact_path = cache_dir.pop().unwrap().unwrap(); + if artifact_path.path().is_dir() { + artifact_path = cache_dir.pop().unwrap().unwrap(); + } + std::fs::metadata(artifact_path.path()).unwrap() + }; + + assert_eq!(md1.created().unwrap(), md2.created().unwrap()); +} + +// Checks if the artifact is re-prepared if the re-preparation is needed by the nature of +// the execution environment parameters change +#[tokio::test] +async fn artifact_does_reprepare_on_meaningful_exec_parameter_change() { + let host = TestHost::new_with_config(|cfg| { + cfg.prepare_workers_hard_max_num = 1; + }) + .await; + let cache_dir = host.cache_dir.path(); + + let set1 = ExecutorParams::default(); + let set2 = + ExecutorParams::from(&[ExecutorParam::PvfPrepTimeout(PvfPrepKind::Prepare, 60000)][..]); + + let _stats = host.precheck_pvf(halt::wasm_binary_unwrap(), set1).await.unwrap(); + let cache_dir_contents: Vec<_> = std::fs::read_dir(cache_dir).unwrap().collect(); + + assert_eq!(cache_dir_contents.len(), 2); + + let _stats = host.precheck_pvf(halt::wasm_binary_unwrap(), set2).await.unwrap(); + let cache_dir_contents: Vec<_> = std::fs::read_dir(cache_dir).unwrap().collect(); + + assert_eq!(cache_dir_contents.len(), 3); // new artifact has been added +} diff --git a/polkadot/node/core/runtime-api/src/cache.rs b/polkadot/node/core/runtime-api/src/cache.rs index acdb256ab36ca7392c0947237df6d7feb449247d..05efbc533d0204f25d4e55cc36e4a354f655f437 100644 --- a/polkadot/node/core/runtime-api/src/cache.rs +++ b/polkadot/node/core/runtime-api/src/cache.rs @@ -24,8 +24,8 @@ use polkadot_primitives::{ CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreIndex, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, NodeFeatures, OccupiedCoreAssumption, - PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo, - ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, + PersistedValidationData, ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, + ValidationCodeHash, ValidatorId, ValidatorIndex, }; /// For consistency we have the same capacity for all caches. We use 128 as we'll only need that @@ -48,6 +48,7 @@ pub(crate) struct RequestResultCache { validation_code: LruMap<(Hash, ParaId, OccupiedCoreAssumption), Option>, validation_code_by_hash: LruMap>, candidate_pending_availability: LruMap<(Hash, ParaId), Option>, + candidates_pending_availability: LruMap<(Hash, ParaId), Vec>, candidate_events: LruMap>, session_executor_params: LruMap>, session_info: LruMap, @@ -86,6 +87,7 @@ impl Default for RequestResultCache { validation_code: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), validation_code_by_hash: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), candidate_pending_availability: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), + candidates_pending_availability: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), candidate_events: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), session_executor_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), session_info: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), @@ -261,6 +263,21 @@ impl RequestResultCache { self.candidate_pending_availability.insert(key, value); } + pub(crate) fn candidates_pending_availability( + &mut self, + key: (Hash, ParaId), + ) -> Option<&Vec> { + self.candidates_pending_availability.get(&key).map(|v| &*v) + } + + pub(crate) fn cache_candidates_pending_availability( + &mut self, + key: (Hash, ParaId), + value: Vec, + ) { + self.candidates_pending_availability.insert(key, value); + } + pub(crate) fn candidate_events(&mut self, relay_parent: &Hash) -> Option<&Vec> { self.candidate_events.get(relay_parent).map(|v| &*v) } @@ -544,7 +561,7 @@ pub(crate) enum RequestResult { // The structure of each variant is (relay_parent, [params,]*, result) Authorities(Hash, Vec), Validators(Hash, Vec), - MinimumBackingVotes(Hash, SessionIndex, u32), + MinimumBackingVotes(SessionIndex, u32), ValidatorGroups(Hash, (Vec>, GroupRotationInfo)), AvailabilityCores(Hash, Vec), PersistedValidationData(Hash, ParaId, OccupiedCoreAssumption, Option), @@ -572,23 +589,21 @@ pub(crate) enum RequestResult { FetchOnChainVotes(Hash, Option), PvfsRequirePrecheck(Hash, Vec), // This is a request with side-effects and no result, hence (). - SubmitPvfCheckStatement(Hash, PvfCheckStatement, ValidatorSignature, ()), + #[allow(dead_code)] + SubmitPvfCheckStatement(()), ValidationCodeHash(Hash, ParaId, OccupiedCoreAssumption, Option), Version(Hash, u32), Disputes(Hash, Vec<(SessionIndex, CandidateHash, DisputeState)>), UnappliedSlashes(Hash, Vec<(SessionIndex, CandidateHash, slashing::PendingSlashes)>), KeyOwnershipProof(Hash, ValidatorId, Option), // This is a request with side-effects. - SubmitReportDisputeLost( - Hash, - slashing::DisputeProof, - slashing::OpaqueKeyOwnershipProof, - Option<()>, - ), + #[allow(dead_code)] + SubmitReportDisputeLost(Option<()>), ApprovalVotingParams(Hash, SessionIndex, ApprovalVotingParams), DisabledValidators(Hash, Vec), ParaBackingState(Hash, ParaId, Option), AsyncBackingParams(Hash, async_backing::AsyncBackingParams), NodeFeatures(SessionIndex, NodeFeatures), ClaimQueue(Hash, BTreeMap>), + CandidatesPendingAvailability(Hash, ParaId, Vec), } diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs index 2b7f6fc2d609f4dfcdf4e94fb63bf499376f6d07..c8b1d61e7be720734a65484a86f4685fc61580b8 100644 --- a/polkadot/node/core/runtime-api/src/lib.rs +++ b/polkadot/node/core/runtime-api/src/lib.rs @@ -101,7 +101,7 @@ where self.requests_cache.cache_authorities(relay_parent, authorities), Validators(relay_parent, validators) => self.requests_cache.cache_validators(relay_parent, validators), - MinimumBackingVotes(_, session_index, minimum_backing_votes) => self + MinimumBackingVotes(session_index, minimum_backing_votes) => self .requests_cache .cache_minimum_backing_votes(session_index, minimum_backing_votes), ValidatorGroups(relay_parent, groups) => @@ -133,6 +133,9 @@ where CandidatePendingAvailability(relay_parent, para_id, candidate) => self .requests_cache .cache_candidate_pending_availability((relay_parent, para_id), candidate), + CandidatesPendingAvailability(relay_parent, para_id, candidates) => self + .requests_cache + .cache_candidates_pending_availability((relay_parent, para_id), candidates), CandidateEvents(relay_parent, events) => self.requests_cache.cache_candidate_events(relay_parent, events), SessionExecutorParams(_relay_parent, session_index, index) => @@ -152,7 +155,7 @@ where self.requests_cache.cache_on_chain_votes(relay_parent, scraped), PvfsRequirePrecheck(relay_parent, pvfs) => self.requests_cache.cache_pvfs_require_precheck(relay_parent, pvfs), - SubmitPvfCheckStatement(_, _, _, ()) => {}, + SubmitPvfCheckStatement(()) => {}, ValidationCodeHash(relay_parent, para_id, assumption, hash) => self .requests_cache .cache_validation_code_hash((relay_parent, para_id, assumption), hash), @@ -167,7 +170,7 @@ where .cache_key_ownership_proof((relay_parent, validator_id), key_ownership_proof), RequestResult::ApprovalVotingParams(_relay_parent, session_index, params) => self.requests_cache.cache_approval_voting_params(session_index, params), - SubmitReportDisputeLost(_, _, _, _) => {}, + SubmitReportDisputeLost(_) => {}, DisabledValidators(relay_parent, disabled_validators) => self.requests_cache.cache_disabled_validators(relay_parent, disabled_validators), ParaBackingState(relay_parent, para_id, constraints) => self @@ -252,6 +255,9 @@ where Request::CandidatePendingAvailability(para, sender) => query!(candidate_pending_availability(para), sender) .map(|sender| Request::CandidatePendingAvailability(para, sender)), + Request::CandidatesPendingAvailability(para, sender) => + query!(candidates_pending_availability(para), sender) + .map(|sender| Request::CandidatesPendingAvailability(para, sender)), Request::CandidateEvents(sender) => query!(candidate_events(), sender).map(|sender| Request::CandidateEvents(sender)), Request::SessionExecutorParams(session_index, sender) => { @@ -364,7 +370,7 @@ where async fn poll_requests(&mut self) { // If there are no active requests, this future should be pending forever. if self.active_requests.len() == 0 { - return futures::pending!() + return futures::pending!(); } // If there are active requests, this will always resolve to `Some(_)` when a request is @@ -433,7 +439,7 @@ where }}; ($req_variant:ident, $api_name:ident ($($param:expr),*), ver = $version:expr, $sender:expr, result = ( $($results:expr),* ) ) => {{ let sender = $sender; - let version: u32 = $version; // enforce type for the version expression + let version: u32 = $version; // enforce type for the version expression let runtime_version = client.api_version_parachain_host(relay_parent).await .unwrap_or_else(|e| { gum::warn!( @@ -531,6 +537,12 @@ where ver = 1, sender ), + Request::CandidatesPendingAvailability(para, sender) => query!( + CandidatesPendingAvailability, + candidates_pending_availability(para), + ver = Request::CANDIDATES_PENDING_AVAILABILITY_RUNTIME_REQUIREMENT, + sender + ), Request::CandidateEvents(sender) => { query!(CandidateEvents, candidate_events(), ver = 1, sender) }, @@ -558,7 +570,8 @@ where SubmitPvfCheckStatement, submit_pvf_check_statement(stmt, signature), ver = 2, - sender + sender, + result = () ) }, Request::PvfsRequirePrecheck(sender) => { @@ -594,13 +607,15 @@ where SubmitReportDisputeLost, submit_report_dispute_lost(dispute_proof, key_ownership_proof), ver = Request::SUBMIT_REPORT_DISPUTE_LOST_RUNTIME_REQUIREMENT, - sender + sender, + result = () ), Request::MinimumBackingVotes(index, sender) => query!( MinimumBackingVotes, minimum_backing_votes(index), ver = Request::MINIMUM_BACKING_VOTES_RUNTIME_REQUIREMENT, - sender + sender, + result = (index) ), Request::DisabledValidators(sender) => query!( DisabledValidators, diff --git a/polkadot/node/core/runtime-api/src/tests.rs b/polkadot/node/core/runtime-api/src/tests.rs index 73c661c40762effc2efbbfa66f7f46a47bbe2d16..0113de83c89ec56403fec0e42207bc5d4e0c552e 100644 --- a/polkadot/node/core/runtime-api/src/tests.rs +++ b/polkadot/node/core/runtime-api/src/tests.rs @@ -47,6 +47,7 @@ struct MockSubsystemClient { validation_outputs_results: HashMap, session_index_for_child: SessionIndex, candidate_pending_availability: HashMap, + candidates_pending_availability: HashMap>, dmq_contents: HashMap>, hrmp_channels: HashMap>>, validation_code_by_hash: HashMap, @@ -140,6 +141,14 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient { Ok(self.candidate_pending_availability.get(¶_id).cloned()) } + async fn candidates_pending_availability( + &self, + _: Hash, + para_id: ParaId, + ) -> Result>, ApiError> { + Ok(self.candidates_pending_availability.get(¶_id).cloned().unwrap_or_default()) + } + async fn candidate_events(&self, _: Hash) -> Result>, ApiError> { Ok(self.candidate_events.clone()) } diff --git a/polkadot/node/network/approval-distribution/src/lib.rs b/polkadot/node/network/approval-distribution/src/lib.rs index d360a18423e6bdea62ee0609cf5ea67f16e2e68e..369d82b45b094b30f0b65d72d54d5c14c1e28440 100644 --- a/polkadot/node/network/approval-distribution/src/lib.rs +++ b/polkadot/node/network/approval-distribution/src/lib.rs @@ -148,6 +148,7 @@ enum ApprovalEntryError { InvalidCandidateIndex, DuplicateApproval, UnknownAssignment, + #[allow(dead_code)] AssignmentsFollowedDifferentPaths(RequiredRouting, RequiredRouting), } diff --git a/polkadot/node/network/availability-distribution/benches/availability-distribution-regression-bench.rs b/polkadot/node/network/availability-distribution/benches/availability-distribution-regression-bench.rs index 0d4f4f49e31f4d77342a3c084431e75f3c9e0725..5e3072be3a8c13d08bfec7fc5840320fcc33c6c5 100644 --- a/polkadot/node/network/availability-distribution/benches/availability-distribution-regression-bench.rs +++ b/polkadot/node/network/availability-distribution/benches/availability-distribution-regression-bench.rs @@ -31,7 +31,7 @@ use polkadot_subsystem_bench::{ }; use std::io::Write; -const BENCH_COUNT: usize = 5; +const BENCH_COUNT: usize = 50; fn main() -> Result<(), String> { let mut messages = vec![]; @@ -40,8 +40,6 @@ fn main() -> Result<(), String> { config.n_cores = 10; config.n_validators = 500; config.num_blocks = 3; - config.connectivity = 100; - config.latency = None; config.generate_pov_sizes(); let state = TestState::new(&config); @@ -75,13 +73,13 @@ fn main() -> Result<(), String> { // We expect no variance for received and sent // but use 0.001 because we operate with floats messages.extend(average_usage.check_network_usage(&[ - ("Received from peers", 433.3, 0.001), - ("Sent to peers", 18480.0, 0.001), + ("Received from peers", 433.3333, 0.001), + ("Sent to peers", 18479.9000, 0.001), ])); messages.extend(average_usage.check_cpu_usage(&[ - ("availability-distribution", 0.012, 0.05), - ("availability-store", 0.153, 0.05), - ("bitfield-distribution", 0.026, 0.05), + ("availability-distribution", 0.0123, 0.1), + ("availability-store", 0.1597, 0.1), + ("bitfield-distribution", 0.0223, 0.1), ])); if messages.is_empty() { diff --git a/polkadot/node/network/availability-recovery/benches/availability-recovery-regression-bench.rs b/polkadot/node/network/availability-recovery/benches/availability-recovery-regression-bench.rs index 9be147bda93a8bdd3efac4e4ccce61d79d6501b8..d9bdc1a2d944d71231d0679fd5e5ef79a52cc782 100644 --- a/polkadot/node/network/availability-recovery/benches/availability-recovery-regression-bench.rs +++ b/polkadot/node/network/availability-recovery/benches/availability-recovery-regression-bench.rs @@ -32,7 +32,7 @@ use polkadot_subsystem_bench::{ }; use std::io::Write; -const BENCH_COUNT: usize = 5; +const BENCH_COUNT: usize = 10; fn main() -> Result<(), String> { let mut messages = vec![]; @@ -40,8 +40,6 @@ fn main() -> Result<(), String> { let options = DataAvailabilityReadOptions { fetch_from_backers: true }; let mut config = TestConfiguration::default(); config.num_blocks = 3; - config.connectivity = 100; - config.latency = None; config.generate_pov_sizes(); let state = TestState::new(&config); @@ -73,10 +71,10 @@ fn main() -> Result<(), String> { // We expect no variance for received and sent // but use 0.001 because we operate with floats messages.extend(average_usage.check_network_usage(&[ - ("Received from peers", 307200.000, 0.001), - ("Sent to peers", 1.667, 0.001), + ("Received from peers", 307203.0000, 0.001), + ("Sent to peers", 1.6667, 0.001), ])); - messages.extend(average_usage.check_cpu_usage(&[("availability-recovery", 11.500, 0.05)])); + messages.extend(average_usage.check_cpu_usage(&[("availability-recovery", 12.8338, 0.1)])); if messages.is_empty() { Ok(()) diff --git a/polkadot/node/network/bitfield-distribution/src/tests.rs b/polkadot/node/network/bitfield-distribution/src/tests.rs index 188b51ebcccae89fd6f69410fd054d25eeabbb01..dc37f73ec8a12fdf2ae3a44b41fef6902e0e9998 100644 --- a/polkadot/node/network/bitfield-distribution/src/tests.rs +++ b/polkadot/node/network/bitfield-distribution/src/tests.rs @@ -40,7 +40,7 @@ use sp_core::Pair as PairT; use sp_keyring::Sr25519Keyring; use sp_keystore::{testing::MemoryKeystore, Keystore, KeystorePtr}; -use std::{iter::FromIterator as _, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; const TIMEOUT: Duration = Duration::from_millis(50); macro_rules! launch { diff --git a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs index e6aa55235b7a8094d54fff1c6b5d15a9d0c198ab..879caf923285b3341a0bc28bc52371fb9c81ff69 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs @@ -16,7 +16,6 @@ use std::{ collections::{HashMap, HashSet}, - convert::TryInto, time::Duration, }; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index d23279e875419b19052380d8d25a1cda6f5d4dfa..ac8c060827f5a4519cd8e4930d1447a8bf4cbe47 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -20,9 +20,7 @@ use futures::{ use futures_timer::Delay; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, - convert::TryInto, future::Future, - iter::FromIterator, time::{Duration, Instant}, }; use tokio_util::sync::CancellationToken; @@ -953,6 +951,7 @@ enum AdvertisementError { /// parent. ProtocolMisuse, /// Advertisement is invalid. + #[allow(dead_code)] Invalid(InsertAdvertisementError), } diff --git a/polkadot/node/network/dispute-distribution/src/sender/send_task.rs b/polkadot/node/network/dispute-distribution/src/sender/send_task.rs index 18c66066d162ec16880b9391aa2e59e1f6b0ea1a..54ccd10789d0aec283748286bc4f69b6a9ae379a 100644 --- a/polkadot/node/network/dispute-distribution/src/sender/send_task.rs +++ b/polkadot/node/network/dispute-distribution/src/sender/send_task.rs @@ -16,7 +16,7 @@ use std::collections::{HashMap, HashSet}; -use futures::{future::RemoteHandle, Future, FutureExt}; +use futures::{Future, FutureExt}; use polkadot_node_network_protocol::{ request_response::{ @@ -64,7 +64,7 @@ pub struct SendTask { /// Status of a particular vote/statement delivery to a particular validator. enum DeliveryStatus { /// Request is still in flight. - Pending(RemoteHandle<()>), + Pending, /// Succeeded - no need to send request to this peer anymore. Succeeded, } @@ -297,9 +297,8 @@ async fn send_requests( metrics.time_dispute_request(), ); - let (remote, remote_handle) = fut.remote_handle(); - ctx.spawn("dispute-sender", remote.boxed()).map_err(FatalError::SpawnTask)?; - statuses.insert(receiver, DeliveryStatus::Pending(remote_handle)); + ctx.spawn("dispute-sender", fut.boxed()).map_err(FatalError::SpawnTask)?; + statuses.insert(receiver, DeliveryStatus::Pending); } let msg = NetworkBridgeTxMessage::SendRequests(reqs, IfDisconnected::ImmediateError); diff --git a/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs b/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs index 0dea5ad0996e787553bfb597d455460208cc5e21..d4c5f95034ae8061b81f8019c86fcd44b0530cf5 100644 --- a/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs +++ b/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs @@ -55,7 +55,7 @@ use sp_application_crypto::{sr25519::Pair, AppCrypto, Pair as TraitPair}; use sp_authority_discovery::AuthorityPair; use sp_keyring::Sr25519Keyring; use sp_keystore::{Keystore, KeystorePtr}; -use std::{iter::FromIterator as _, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use util::reputation::add_reputation; // Some deterministic genesis hash for protocol names diff --git a/polkadot/node/network/statement-distribution/src/v2/mod.rs b/polkadot/node/network/statement-distribution/src/v2/mod.rs index 68caa5f0e70099d1cdb192cde4307edaa56660f0..118e34e92063e5571f637d9e44460b5b78c4fd8a 100644 --- a/polkadot/node/network/statement-distribution/src/v2/mod.rs +++ b/polkadot/node/network/statement-distribution/src/v2/mod.rs @@ -826,7 +826,16 @@ pub(crate) fn handle_deactivate_leaves(state: &mut State, leaves: &[Hash]) { // clean up sessions based on everything remaining. let sessions: HashSet<_> = state.per_relay_parent.values().map(|r| r.session).collect(); state.per_session.retain(|s, _| sessions.contains(s)); - state.unused_topologies.retain(|s, _| sessions.contains(s)); + + let last_session_index = state.unused_topologies.keys().max().copied(); + // Do not clean-up the last saved toplogy unless we moved to the next session + // This is needed because handle_deactive_leaves, gets also called when + // prospective_parachains APIs are not present, so we would actually remove + // the topology without using it because `per_relay_parent` is empty until + // prospective_parachains gets enabled + state + .unused_topologies + .retain(|s, _| sessions.contains(s) || last_session_index == Some(*s)); } #[overseer::contextbounds(StatementDistribution, prefix=self::overseer)] diff --git a/polkadot/node/network/statement-distribution/src/v2/requests.rs b/polkadot/node/network/statement-distribution/src/v2/requests.rs index fe270c8a58e81fc5ebee9a42c38a1cedd1a0b516..1ed18ffd42a9fa6eca92284b097f69b9caa12bb1 100644 --- a/polkadot/node/network/statement-distribution/src/v2/requests.rs +++ b/polkadot/node/network/statement-distribution/src/v2/requests.rs @@ -288,7 +288,7 @@ impl RequestManager { /// Returns an instant at which the next request to be retried will be ready. pub fn next_retry_time(&mut self) -> Option { let mut next = None; - for (_id, request) in &self.requests { + for (_id, request) in self.requests.iter().filter(|(_id, request)| !request.in_flight) { if let Some(next_retry_time) = request.next_retry_time { if next.map_or(true, |next| next_retry_time < next) { next = Some(next_retry_time); diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs b/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs index 8dda7219cd1251ccd7869408c51e80ca01513c2b..3d987d3fc433fbf62c1b05675da864b82f019536 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs @@ -509,6 +509,12 @@ async fn setup_test_and_connect_peers( // Send gossip topology and activate leaf. if send_topology_before_leaf { send_new_topology(overseer, state.make_dummy_topology()).await; + // Send cleaning up of a leaf to make sure it does not clear the save topology as well. + overseer + .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::stop_work(Hash::random()), + ))) + .await; activate_leaf(overseer, &test_leaf, &state, true, vec![]).await; } else { activate_leaf(overseer, &test_leaf, &state, true, vec![]).await; diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/requests.rs b/polkadot/node/network/statement-distribution/src/v2/tests/requests.rs index dc2c8f55290b43e5dce172730d3c5027dddc4b03..8cf139802148c0a6e720f6918c61824d5ef96575 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/requests.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/requests.rs @@ -2606,7 +2606,31 @@ fn should_delay_before_retrying_dropped_requests() { // Sleep for the given amount of time. This should reset the delay for the first candidate. futures_timer::Delay::new(REQUEST_RETRY_DELAY).await; - // We re-try the first request. + // We re-try the first request the second time drop it again. + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendRequests(mut requests, IfDisconnected::ImmediateError)) => { + assert_eq!(requests.len(), 1); + assert_matches!( + requests.pop().unwrap(), + Requests::AttestedCandidateV2(outgoing) => { + assert_eq!(outgoing.peer, Recipient::Peer(peer_c)); + assert_eq!(outgoing.payload.candidate_hash, candidate_hash_1); + assert_eq!(outgoing.payload.mask, mask); + } + ); + } + ); + + assert_matches!( + overseer_recv_with_timeout(&mut overseer, Duration::from_millis(100)).await, + None + ); + + // Sleep for the given amount of time. This should reset the delay for the first candidate. + futures_timer::Delay::new(REQUEST_RETRY_DELAY).await; + + // We re-try the first request, for the third time, so let's answer to it. { let statements = vec![ state diff --git a/polkadot/node/service/chain-specs/paseo.json b/polkadot/node/service/chain-specs/paseo.json index 2e659716766ede8cf4ec2b183013b5e925dff998..19eefd328994ed67c269fafc06acefaf28c751bf 100644 --- a/polkadot/node/service/chain-specs/paseo.json +++ b/polkadot/node/service/chain-specs/paseo.json @@ -16,7 +16,9 @@ "/dns/boot.gatotech.network/tcp/33400/p2p/12D3KooWEvz5Ygv3MhCUNTVQbUTVhzhvf4KKcNoe5M5YbVLPBeeW", "/dns/boot.gatotech.network/tcp/35400/wss/p2p/12D3KooWEvz5Ygv3MhCUNTVQbUTVhzhvf4KKcNoe5M5YbVLPBeeW", "/dns/paseo-bootnode.turboflakes.io/tcp/30630/p2p/12D3KooWMjCN2CrnN71hAdehn6M2iYKeGdGbZ1A3SKhf4hxrgG9e", - "/dns/paseo-bootnode.turboflakes.io/tcp/30730/wss/p2p/12D3KooWMjCN2CrnN71hAdehn6M2iYKeGdGbZ1A3SKhf4hxrgG9e" + "/dns/paseo-bootnode.turboflakes.io/tcp/30730/wss/p2p/12D3KooWMjCN2CrnN71hAdehn6M2iYKeGdGbZ1A3SKhf4hxrgG9e", + "/dns/paseo-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWBLLFKDGBxCwq3QmU3YwWKXUx953WwprRshJQicYu4Cfr", + "/dns/paseo-boot-ng.dwellir.com/tcp/30354/p2p/12D3KooWBLLFKDGBxCwq3QmU3YwWKXUx953WwprRshJQicYu4Cfr" ], "telemetryEndpoints": null, "protocolId": "pas", @@ -419,4 +421,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} diff --git a/polkadot/node/service/chain-specs/polkadot.json b/polkadot/node/service/chain-specs/polkadot.json index 5f8d88102d7edd9d9d640a21b43a3c37982430d5..035705437072f2bbf87c2e1790f97e15d302a85a 100644 --- a/polkadot/node/service/chain-specs/polkadot.json +++ b/polkadot/node/service/chain-specs/polkadot.json @@ -37,7 +37,8 @@ "/dns/dot14.rotko.net/tcp/33214/p2p/12D3KooWPyEvPEXghnMC67Gff6PuZiSvfx3fmziKiPZcGStZ5xff", "/dns/ibp-boot-polkadot.luckyfriday.io/tcp/30333/p2p/12D3KooWEjk6QXrZJ26fLpaajisJGHiz6WiQsR8k7mkM9GmWKnRZ", "/dns/ibp-boot-polkadot.luckyfriday.io/tcp/30334/wss/p2p/12D3KooWEjk6QXrZJ26fLpaajisJGHiz6WiQsR8k7mkM9GmWKnRZ", - "/dns/boot-polkadot.luckyfriday.io/tcp/443/wss/p2p/12D3KooWAdyiVAaeGdtBt6vn5zVetwA4z4qfm9Fi2QCSykN1wTBJ" + "/dns/boot-polkadot.luckyfriday.io/tcp/443/wss/p2p/12D3KooWAdyiVAaeGdtBt6vn5zVetwA4z4qfm9Fi2QCSykN1wTBJ", + "/dns4/polkadot-0.boot.onfinality.io/tcp/24446/ws/p2p/12D3KooWT1PWaNdAwYrSr89dvStnoGdH3t4LNRbcVNN4JCtsotkR" ], "telemetryEndpoints": [ [ diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index 22231c84b1d9c377c24bf689e7c5a88d40dba62b..e5c29172099b4d7b73b4da089a901c63ee0e3a43 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -643,6 +643,13 @@ pub struct NewFullParams { pub workers_path: Option, /// Optional custom names for the prepare and execute workers. pub workers_names: Option<(String, String)>, + /// An optional number of the maximum number of pvf execute workers. + pub execute_workers_max_num: Option, + /// An optional maximum number of pvf workers that can be spawned in the pvf prepare pool for + /// tasks with the priority below critical. + pub prepare_workers_soft_max_num: Option, + /// An optional absolute number of pvf workers that can be spawned in the pvf prepare pool. + pub prepare_workers_hard_max_num: Option, pub overseer_gen: OverseerGenerator, pub overseer_message_channel_capacity_override: Option, #[allow(dead_code)] @@ -738,6 +745,9 @@ pub fn new_full< overseer_message_channel_capacity_override, malus_finality_delay: _malus_finality_delay, hwbench, + execute_workers_max_num, + prepare_workers_soft_max_num, + prepare_workers_hard_max_num, }: NewFullParams, ) -> Result { use polkadot_node_network_protocol::request_response::IncomingRequest; @@ -943,6 +953,16 @@ pub fn new_full< secure_validator_mode, prep_worker_path, exec_worker_path, + pvf_execute_workers_max_num: execute_workers_max_num.unwrap_or_else( + || match config.chain_spec.identify_chain() { + // The intention is to use this logic for gradual increasing from 2 to 4 + // of this configuration chain by chain untill it reaches production chain. + Chain::Polkadot | Chain::Kusama => 2, + Chain::Rococo | Chain::Westend | Chain::Unknown => 4, + }, + ), + pvf_prepare_workers_soft_max_num: prepare_workers_soft_max_num.unwrap_or(1), + pvf_prepare_workers_hard_max_num: prepare_workers_hard_max_num.unwrap_or(2), }) } else { None diff --git a/polkadot/node/subsystem-bench/src/lib/usage.rs b/polkadot/node/subsystem-bench/src/lib/usage.rs index 59296746ec3d4154274ce68d9ee910bb61d0f9f8..bfaac3265a2e3741aaa2fa4811785bd90e8ea016 100644 --- a/polkadot/node/subsystem-bench/src/lib/usage.rs +++ b/polkadot/node/subsystem-bench/src/lib/usage.rs @@ -161,6 +161,13 @@ impl ResourceUsage { for (resource_name, values) in by_name { let total = values.iter().map(|v| v.total).sum::() / values.len() as f64; let per_block = values.iter().map(|v| v.per_block).sum::() / values.len() as f64; + let per_block_sd = + standard_deviation(&values.iter().map(|v| v.per_block).collect::>()); + println!( + "[{}] standart_deviation {:.2}%", + resource_name, + per_block_sd / per_block * 100.0 + ); average.push(Self { resource_name, total, per_block }); } average @@ -179,3 +186,11 @@ pub struct ChartItem { pub unit: String, pub value: f64, } + +fn standard_deviation(values: &[f64]) -> f64 { + let n = values.len() as f64; + let mean = values.iter().sum::() / n; + let variance = values.iter().map(|v| (v - mean).powi(2)).sum::() / (n - 1.0); + + variance.sqrt() +} diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index 2ca6728af012166649ccfee136b098cff9b9fe4a..e75d80395c4ba0371b58ce1383db2a1f364eaad8 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -670,7 +670,7 @@ pub enum RuntimeApiRequest { /// Get validation code by its hash, either past, current or future code can be returned, as /// long as state is still available. ValidationCodeByHash(ValidationCodeHash, RuntimeApiSender>), - /// Get a the candidate pending availability for a particular parachain by parachain / core + /// Get the candidate pending availability for a particular parachain by parachain / core /// index CandidatePendingAvailability(ParaId, RuntimeApiSender>), /// Get all events concerning candidates (backing, inclusion, time-out) in the parent of @@ -739,6 +739,9 @@ pub enum RuntimeApiRequest { /// Fetch the `ClaimQueue` from scheduler pallet /// `V11` ClaimQueue(RuntimeApiSender>>), + /// Get the candidates pending availability for a particular parachain + /// `V11` + CandidatesPendingAvailability(ParaId, RuntimeApiSender>), } impl RuntimeApiRequest { @@ -776,6 +779,9 @@ impl RuntimeApiRequest { /// `ClaimQueue` pub const CLAIM_QUEUE_RUNTIME_REQUIREMENT: u32 = 11; + + /// `candidates_pending_availability` + pub const CANDIDATES_PENDING_AVAILABILITY_RUNTIME_REQUIREMENT: u32 = 11; } /// A message to the Runtime API subsystem. diff --git a/polkadot/node/subsystem-types/src/messages/network_bridge_event.rs b/polkadot/node/subsystem-types/src/messages/network_bridge_event.rs index fa2c7687b38a52ed1a02cdd1373ab5fd41552470..29798c785b9c97816017d8f3d4bbaf5cdc3585fe 100644 --- a/polkadot/node/subsystem-types/src/messages/network_bridge_event.rs +++ b/polkadot/node/subsystem-types/src/messages/network_bridge_event.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use std::{collections::HashSet, convert::TryFrom}; +use std::collections::HashSet; pub use sc_network::ReputationChange; pub use sc_network_types::PeerId; diff --git a/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs index 664d10ed1af5fdb77c3105ab995ab5ff89df2220..e5e1e4d24ef96e8fa7689a41cd7897832b5aa271 100644 --- a/polkadot/node/subsystem-types/src/runtime_client.rs +++ b/polkadot/node/subsystem-types/src/runtime_client.rs @@ -333,6 +333,14 @@ pub trait RuntimeApiSubsystemClient { // == v11: Claim queue == /// Fetch the `ClaimQueue` from scheduler pallet async fn claim_queue(&self, at: Hash) -> Result>, ApiError>; + + // == v11: Elastic scaling support == + /// Get the receipts of all candidates pending availability for a `ParaId`. + async fn candidates_pending_availability( + &self, + at: Hash, + para_id: Id, + ) -> Result>, ApiError>; } /// Default implementation of [`RuntimeApiSubsystemClient`] using the client. @@ -428,6 +436,14 @@ where self.client.runtime_api().candidate_pending_availability(at, para_id) } + async fn candidates_pending_availability( + &self, + at: Hash, + para_id: Id, + ) -> Result>, ApiError> { + self.client.runtime_api().candidates_pending_availability(at, para_id) + } + async fn candidate_events(&self, at: Hash) -> Result>, ApiError> { self.client.runtime_api().candidate_events(at) } diff --git a/polkadot/node/subsystem-util/src/lib.rs b/polkadot/node/subsystem-util/src/lib.rs index 83b046f0bf0ac54533958da36dc230e8c8ce2922..b93818070a183e43e71f327bcc1770a71fae1cb6 100644 --- a/polkadot/node/subsystem-util/src/lib.rs +++ b/polkadot/node/subsystem-util/src/lib.rs @@ -296,6 +296,7 @@ specialize_requests! { fn request_validation_code(para_id: ParaId, assumption: OccupiedCoreAssumption) -> Option; ValidationCode; fn request_validation_code_by_hash(validation_code_hash: ValidationCodeHash) -> Option; ValidationCodeByHash; fn request_candidate_pending_availability(para_id: ParaId) -> Option; CandidatePendingAvailability; + fn request_candidates_pending_availability(para_id: ParaId) -> Vec; CandidatesPendingAvailability; fn request_candidate_events() -> Vec; CandidateEvents; fn request_session_info(index: SessionIndex) -> Option; SessionInfo; fn request_validation_code_hash(para_id: ParaId, assumption: OccupiedCoreAssumption) diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index d313c19333483095291b09c79103d681961dbff6..87fbc7c20f318c6a791276f7fb509e8c33c4f909 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -97,6 +97,9 @@ pub fn new_full( overseer_message_channel_capacity_override: None, malus_finality_delay: None, hwbench: None, + execute_workers_max_num: None, + prepare_workers_hard_max_num: None, + prepare_workers_soft_max_num: None, }, ), sc_network::config::NetworkBackendType::Litep2p => @@ -116,6 +119,9 @@ pub fn new_full( overseer_message_channel_capacity_override: None, malus_finality_delay: None, hwbench: None, + execute_workers_max_num: None, + prepare_workers_hard_max_num: None, + prepare_workers_soft_max_num: None, }, ), } diff --git a/polkadot/parachain/test-parachains/adder/collator/src/main.rs b/polkadot/parachain/test-parachains/adder/collator/src/main.rs index fec90fc41cdb160f1a90a8ceab7a15da81e96bb2..e8588274df27aad40820d0a73e70ec5d24a89fb6 100644 --- a/polkadot/parachain/test-parachains/adder/collator/src/main.rs +++ b/polkadot/parachain/test-parachains/adder/collator/src/main.rs @@ -95,6 +95,9 @@ fn main() -> Result<()> { overseer_message_channel_capacity_override: None, malus_finality_delay: None, hwbench: None, + execute_workers_max_num: None, + prepare_workers_hard_max_num: None, + prepare_workers_soft_max_num: None, }, ) .map_err(|e| e.to_string())?; diff --git a/polkadot/parachain/test-parachains/undying/collator/src/main.rs b/polkadot/parachain/test-parachains/undying/collator/src/main.rs index 45f21e7b859631ec8763304c18f01cf6f3d04d10..7198a831a4771b1a70de4180149ce81ffe6f5412 100644 --- a/polkadot/parachain/test-parachains/undying/collator/src/main.rs +++ b/polkadot/parachain/test-parachains/undying/collator/src/main.rs @@ -97,6 +97,9 @@ fn main() -> Result<()> { overseer_message_channel_capacity_override: None, malus_finality_delay: None, hwbench: None, + execute_workers_max_num: None, + prepare_workers_hard_max_num: None, + prepare_workers_soft_max_num: None, }, ) .map_err(|e| e.to_string())?; diff --git a/polkadot/primitives/Cargo.toml b/polkadot/primitives/Cargo.toml index 004fa62acf34595c548e0e36da1d05f0a52abb53..99800afc37fe0133b78dd6f1b7432014b923c0ca 100644 --- a/polkadot/primitives/Cargo.toml +++ b/polkadot/primitives/Cargo.toml @@ -26,7 +26,7 @@ sp-arithmetic = { path = "../../substrate/primitives/arithmetic", default-featur sp-authority-discovery = { path = "../../substrate/primitives/authority-discovery", default-features = false, features = ["serde"] } sp-consensus-slots = { path = "../../substrate/primitives/consensus/slots", default-features = false, features = ["serde"] } sp-io = { path = "../../substrate/primitives/io", default-features = false } -sp-keystore = { path = "../../substrate/primitives/keystore", optional = true } +sp-keystore = { path = "../../substrate/primitives/keystore", optional = true, default-features = false } sp-staking = { path = "../../substrate/primitives/staking", default-features = false, features = ["serde"] } sp-std = { package = "sp-std", path = "../../substrate/primitives/std", default-features = false } @@ -53,6 +53,7 @@ std = [ "sp-consensus-slots/std", "sp-io/std", "sp-keystore", + "sp-keystore?/std", "sp-staking/std", "sp-std/std", ] diff --git a/polkadot/primitives/src/lib.rs b/polkadot/primitives/src/lib.rs index d4eeb3cc3d2951089213b924777cbcd9a979c1c9..01f393086a668f43d2e18abc10b4491f54aae2cf 100644 --- a/polkadot/primitives/src/lib.rs +++ b/polkadot/primitives/src/lib.rs @@ -44,7 +44,7 @@ pub use v7::{ CandidateReceipt, CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, CollatorId, CollatorSignature, CommittedCandidateReceipt, CompactStatement, ConsensusLog, CoreIndex, CoreState, DisputeState, DisputeStatement, DisputeStatementSet, DownwardMessage, EncodeAs, - ExecutorParam, ExecutorParamError, ExecutorParams, ExecutorParamsHash, + ExecutorParam, ExecutorParamError, ExecutorParams, ExecutorParamsHash, ExecutorParamsPrepHash, ExplicitDisputeStatement, GroupIndex, GroupRotationInfo, Hash, HashT, HeadData, Header, HorizontalMessages, HrmpChannelId, Id, InboundDownwardMessage, InboundHrmpMessage, IndexedVec, InherentData, InvalidDisputeStatementKind, Moment, MultiDisputeStatementSet, NodeFeatures, diff --git a/polkadot/primitives/src/runtime_api.rs b/polkadot/primitives/src/runtime_api.rs index f611936f2701d367283503e761176e9b61e72cbe..7bd92be35c159db35b3db7b9dd91afcc73eebd81 100644 --- a/polkadot/primitives/src/runtime_api.rs +++ b/polkadot/primitives/src/runtime_api.rs @@ -288,5 +288,10 @@ sp_api::decl_runtime_apis! { /// Claim queue #[api_version(11)] fn claim_queue() -> BTreeMap>; + + /***** Added in v11 *****/ + /// Elastic scaling support + #[api_version(11)] + fn candidates_pending_availability(para_id: ppp::Id) -> Vec>; } } diff --git a/polkadot/primitives/src/v7/executor_params.rs b/polkadot/primitives/src/v7/executor_params.rs index 1e19f3b23fec9b9889d5976380f52ab6b66072b4..918a7f17a7e3ba466bd8e06cb635cfbf2f9df839 100644 --- a/polkadot/primitives/src/v7/executor_params.rs +++ b/polkadot/primitives/src/v7/executor_params.rs @@ -152,13 +152,42 @@ impl sp_std::fmt::LowerHex for ExecutorParamsHash { } } +/// Unit type wrapper around [`type@Hash`] that represents a hash of preparation-related +/// executor parameters. +/// +/// This type is produced by [`ExecutorParams::prep_hash`]. +#[derive(Clone, Copy, Encode, Decode, Hash, Eq, PartialEq, PartialOrd, Ord, TypeInfo)] +pub struct ExecutorParamsPrepHash(Hash); + +impl sp_std::fmt::Display for ExecutorParamsPrepHash { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + self.0.fmt(f) + } +} + +impl sp_std::fmt::Debug for ExecutorParamsPrepHash { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl sp_std::fmt::LowerHex for ExecutorParamsPrepHash { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + sp_std::fmt::LowerHex::fmt(&self.0, f) + } +} + /// # Deterministically serialized execution environment semantics /// Represents an arbitrary semantics of an arbitrary execution environment, so should be kept as /// abstract as possible. +// // ADR: For mandatory entries, mandatoriness should be enforced in code rather than separating them // into individual fields of the structure. Thus, complex migrations shall be avoided when adding // new entries and removing old ones. At the moment, there's no mandatory parameters defined. If // they show up, they must be clearly documented as mandatory ones. +// +// !!! Any new parameter that does not affect the prepared artifact must be added to the exclusion +// !!! list in `prep_hash()` to avoid unneccessary artifact rebuilds. #[derive( Clone, Debug, Default, Encode, Decode, PartialEq, Eq, TypeInfo, Serialize, Deserialize, )] @@ -175,6 +204,28 @@ impl ExecutorParams { ExecutorParamsHash(BlakeTwo256::hash(&self.encode())) } + /// Returns hash of preparation-related executor parameters + pub fn prep_hash(&self) -> ExecutorParamsPrepHash { + use ExecutorParam::*; + + let mut enc = b"prep".to_vec(); + + self.0 + .iter() + .flat_map(|param| match param { + MaxMemoryPages(..) => None, + StackLogicalMax(..) => Some(param), + StackNativeMax(..) => None, + PrecheckingMaxMemory(..) => None, + PvfPrepTimeout(..) => Some(param), + PvfExecTimeout(..) => None, + WasmExtBulkMemory => Some(param), + }) + .for_each(|p| enc.extend(p.encode())); + + ExecutorParamsPrepHash(BlakeTwo256::hash(&enc)) + } + /// Returns a PVF preparation timeout, if any pub fn pvf_prep_timeout(&self, kind: PvfPrepKind) -> Option { for param in &self.0 { @@ -336,3 +387,51 @@ impl From<&[ExecutorParam]> for ExecutorParams { ExecutorParams(arr.to_vec()) } } + +// This test ensures the hash generated by `prep_hash()` changes if any preparation-related +// executor parameter changes. If you're adding a new executor parameter, you must add it into +// this test, and if changing that parameter may not affect the artifact produced on the +// preparation step, it must be added to the list of exlusions in `pre_hash()` as well. +// See also `prep_hash()` comments. +#[test] +fn ensure_prep_hash_changes() { + use ExecutorParam::*; + let ep = ExecutorParams::from( + &[ + MaxMemoryPages(0), + StackLogicalMax(0), + StackNativeMax(0), + PrecheckingMaxMemory(0), + PvfPrepTimeout(PvfPrepKind::Precheck, 0), + PvfPrepTimeout(PvfPrepKind::Prepare, 0), + PvfExecTimeout(PvfExecKind::Backing, 0), + PvfExecTimeout(PvfExecKind::Approval, 0), + WasmExtBulkMemory, + ][..], + ); + + for p in ep.iter() { + let (ep1, ep2) = match p { + MaxMemoryPages(_) => continue, + StackLogicalMax(_) => ( + ExecutorParams::from(&[StackLogicalMax(1)][..]), + ExecutorParams::from(&[StackLogicalMax(2)][..]), + ), + StackNativeMax(_) => continue, + PrecheckingMaxMemory(_) => continue, + PvfPrepTimeout(PvfPrepKind::Precheck, _) => ( + ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Precheck, 1)][..]), + ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Precheck, 2)][..]), + ), + PvfPrepTimeout(PvfPrepKind::Prepare, _) => ( + ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Prepare, 1)][..]), + ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Prepare, 2)][..]), + ), + PvfExecTimeout(_, _) => continue, + WasmExtBulkMemory => + (ExecutorParams::default(), ExecutorParams::from(&[WasmExtBulkMemory][..])), + }; + + assert_ne!(ep1.prep_hash(), ep2.prep_hash()); + } +} diff --git a/polkadot/primitives/src/v7/mod.rs b/polkadot/primitives/src/v7/mod.rs index 5647bfe68d5667c17f23a684ba97d8ce271fcb26..8a059408496c0f87e2d1394beb84263f48c4fbda 100644 --- a/polkadot/primitives/src/v7/mod.rs +++ b/polkadot/primitives/src/v7/mod.rs @@ -62,7 +62,9 @@ pub mod executor_params; pub mod slashing; pub use async_backing::AsyncBackingParams; -pub use executor_params::{ExecutorParam, ExecutorParamError, ExecutorParams, ExecutorParamsHash}; +pub use executor_params::{ + ExecutorParam, ExecutorParamError, ExecutorParams, ExecutorParamsHash, ExecutorParamsPrepHash, +}; mod metrics; pub use metrics::{ diff --git a/polkadot/roadmap/implementers-guide/src/runtime-api/candidate-pending-availability.md b/polkadot/roadmap/implementers-guide/src/runtime-api/candidate-pending-availability.md index e118757d83cede12468be2d8d444958ca847eadf..b9f03748d89bafaad448b7515f08eb73a44b0996 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime-api/candidate-pending-availability.md +++ b/polkadot/roadmap/implementers-guide/src/runtime-api/candidate-pending-availability.md @@ -4,5 +4,8 @@ Get the receipt of a candidate pending availability. This returns `Some` for any `availability_cores` and `None` otherwise. ```rust +// Deprectated. fn candidate_pending_availability(at: Block, ParaId) -> Option; +// Use this one +fn candidates_pending_availability(at: Block, ParaId) -> Vec; ``` diff --git a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md index fd74f33253b72bd629313b5763e2cbbed75a6314..0700a781d426324c2c35e72628caa65b577ef979 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md @@ -154,6 +154,8 @@ All failed checks should lead to an unrecoverable error making the block invalid where the changes to the state are expected to be discarded directly after. * `candidate_pending_availability(ParaId) -> Option`: returns the `CommittedCandidateReceipt` pending availability for the para provided, if any. +* `candidates_pending_availability(ParaId) -> Vec`: returns the `CommittedCandidateReceipt`s + pending availability for the para provided, if any. * `pending_availability(ParaId) -> Option`: returns the metadata around the candidate pending availability for the para, if any. * `free_disputed(disputed: Vec) -> Vec`: Sweeps through all paras pending availability. If @@ -164,10 +166,10 @@ These functions were formerly part of the UMP pallet: * `check_upward_messages(P: ParaId, Vec)`: 1. Checks that the parachain is not currently offboarding and error otherwise. - 1. Checks that there are at most `config.max_upward_message_num_per_candidate` messages to be enqueued. - 1. Checks that no message exceeds `config.max_upward_message_size`. - 1. Checks that the total resulting queue size would not exceed `co`. - 1. Verify that queuing up the messages could not result in exceeding the queue's footprint according to the config + 2. Checks that there are at most `config.max_upward_message_num_per_candidate` messages to be enqueued. + 3. Checks that no message exceeds `config.max_upward_message_size`. + 4. Checks that the total resulting queue size would not exceed `co`. + 5. Verify that queuing up the messages could not result in exceeding the queue's footprint according to the config items `config.max_upward_queue_count` and `config.max_upward_queue_size`. The queue's current footprint is provided in `well_known_keys` in order to facilitate oraclisation on to the para. diff --git a/polkadot/runtime/common/src/crowdloan/mod.rs b/polkadot/runtime/common/src/crowdloan/mod.rs index 12078871a1957215fd209402894fc0434fba6ba5..477530467fa105d93b41569d851b5699aadc09b0 100644 --- a/polkadot/runtime/common/src/crowdloan/mod.rs +++ b/polkadot/runtime/common/src/crowdloan/mod.rs @@ -866,7 +866,7 @@ mod tests { use sp_core::H256; use std::{cell::RefCell, collections::BTreeMap, sync::Arc}; // The testing primitives are very useful for avoiding having to work with signatures - // or public keys. `u64` is used as the `AccountId` and no `Signature`s are requried. + // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. use crate::{ crowdloan, mock::TestRegistrar, diff --git a/polkadot/runtime/common/src/impls.rs b/polkadot/runtime/common/src/impls.rs index cc1243790c2e58b23c169de918f28d75397420ba..85531e9c04fc47f83fb007802e30e87799c2150c 100644 --- a/polkadot/runtime/common/src/impls.rs +++ b/polkadot/runtime/common/src/impls.rs @@ -19,7 +19,7 @@ use frame_support::traits::{ fungible::{Balanced, Credit}, tokens::imbalance::ResolveTo, - Imbalance, OnUnbalanced, + Contains, ContainsPair, Imbalance, OnUnbalanced, }; use pallet_treasury::TreasuryAccountId; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; @@ -156,6 +156,26 @@ impl TryConvert<&VersionedLocation, xcm::latest::Location> for VersionedLocation } } +/// Adapter for [`Contains`] trait to match [`VersionedLocatableAsset`] type converted to the latest +/// version of itself where it's location matched by `L` and it's asset id by `A` parameter types. +pub struct ContainsParts(core::marker::PhantomData); +impl Contains for ContainsParts +where + C: ContainsPair, +{ + fn contains(asset: &VersionedLocatableAsset) -> bool { + use VersionedLocatableAsset::*; + let (location, asset_id) = match asset.clone() { + V3 { location, asset_id } => match (location.try_into(), asset_id.try_into()) { + (Ok(l), Ok(a)) => (l, a), + _ => return false, + }, + V4 { location, asset_id } => (location, asset_id), + }; + C::contains(&location, &asset_id.0) + } +} + #[cfg(feature = "runtime-benchmarks")] pub mod benchmarks { use super::VersionedLocatableAsset; diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index 91b64ef7259c3c260ca39e30a8e4dc63362bf339..3e9ac1fc1b152574ea86d42c7d48f51da47ddc39 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -39,7 +39,7 @@ use primitives::{ MAX_CODE_SIZE, }; use runtime_parachains::{ - configuration, origin, paras, shared, Origin as ParaOrigin, ParaLifecycle, + configuration, dmp, origin, paras, shared, Origin as ParaOrigin, ParaLifecycle, }; use sp_core::H256; use sp_io::TestExternalities; @@ -84,6 +84,7 @@ frame_support::construct_runtime!( Paras: paras, ParasShared: shared, ParachainsOrigin: origin, + Dmp: dmp, // Para Onboarding Pallets Registrar: paras_registrar, @@ -201,6 +202,8 @@ impl shared::Config for Test { type DisabledValidators = (); } +impl dmp::Config for Test {} + impl origin::Config for Test {} parameter_types! { diff --git a/polkadot/runtime/common/src/lib.rs b/polkadot/runtime/common/src/lib.rs index 65161764ccd7bf363a85238fbf7a708421561743..60cc684149b48fe7bb4a102cbad7f96f3c7b5826 100644 --- a/polkadot/runtime/common/src/lib.rs +++ b/polkadot/runtime/common/src/lib.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Common runtime code for Polkadot and Kusama. +//! Common runtime code for the Relay Chain, e.g. Rococo, Westend, Polkadot, Kusama ... #![cfg_attr(not(feature = "std"), no_std)] diff --git a/polkadot/runtime/common/src/paras_registrar/mod.rs b/polkadot/runtime/common/src/paras_registrar/mod.rs index cc949c9d3f626407b77590b35d44177a847fedae..a49ebab3e26a8df5ecfdf7615849b75d9e3c4671 100644 --- a/polkadot/runtime/common/src/paras_registrar/mod.rs +++ b/polkadot/runtime/common/src/paras_registrar/mod.rs @@ -412,7 +412,7 @@ pub mod pallet { /// validators have reported on the validity of the code, the code will either be enacted /// or the upgrade will be rejected. If the code will be enacted, the current code of the /// parachain will be overwritten directly. This means that any PoV will be checked by this - /// new code. The parachain itself will not be informed explictely that the validation code + /// new code. The parachain itself will not be informed explicitly that the validation code /// has changed. /// /// Can be called by Root, the parachain, or the parachain manager if the parachain is diff --git a/polkadot/runtime/common/src/xcm_sender.rs b/polkadot/runtime/common/src/xcm_sender.rs index 0cbc2e603c8e776b2377f15569a2c38934753967..a712d4381f75f586ada72b3046925d97a1fba1db 100644 --- a/polkadot/runtime/common/src/xcm_sender.rs +++ b/polkadot/runtime/common/src/xcm_sender.rs @@ -119,7 +119,9 @@ where let config = configuration::ActiveConfig::::get(); let para = id.into(); let price = P::price_for_delivery(para, &xcm); - let blob = W::wrap_version(&d, xcm).map_err(|()| DestinationUnsupported)?.encode(); + let versioned_xcm = W::wrap_version(&d, xcm).map_err(|()| DestinationUnsupported)?; + versioned_xcm.validate_xcm_nesting().map_err(|()| ExceedsMaxMessageSize)?; + let blob = versioned_xcm.encode(); dmp::Pallet::::can_queue_downward_message(&config, ¶, &blob) .map_err(Into::::into)?; @@ -236,9 +238,11 @@ impl EnsureForParachain for () { #[cfg(test)] mod tests { use super::*; - use frame_support::parameter_types; + use crate::integration_tests::new_test_ext; + use frame_support::{assert_ok, parameter_types}; use runtime_parachains::FeeTracker; use sp_runtime::FixedU128; + use xcm::MAX_XCM_DECODE_DEPTH; parameter_types! { pub const BaseDeliveryFee: u128 = 300_000_000; @@ -297,4 +301,40 @@ mod tests { (FeeAssetId::get(), result).into() ); } + + #[test] + fn child_parachain_router_validate_nested_xcm_works() { + let dest = Parachain(5555); + + type Router = ChildParachainRouter< + crate::integration_tests::Test, + (), + NoPriceForMessageDelivery, + >; + + // Message that is not too deeply nested: + let mut good = Xcm(vec![ClearOrigin]); + for _ in 0..MAX_XCM_DECODE_DEPTH - 1 { + good = Xcm(vec![SetAppendix(good)]); + } + + new_test_ext().execute_with(|| { + configuration::ActiveConfig::::mutate(|c| { + c.max_downward_message_size = u32::MAX; + }); + + // Check that the good message is validated: + assert_ok!(::validate( + &mut Some(dest.into()), + &mut Some(good.clone()) + )); + + // Nesting the message one more time should reject it: + let bad = Xcm(vec![SetAppendix(good)]); + assert_eq!( + Err(ExceedsMaxMessageSize), + ::validate(&mut Some(dest.into()), &mut Some(bad)) + ); + }); + } } diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index dff8549f29f3ad38748c3c2cc5b21c328356196a..402c6e487a1f8b2b2ad9f535ca60bdc160c69895 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -28,7 +28,7 @@ sp-runtime = { path = "../../../substrate/primitives/runtime", default-features sp-session = { path = "../../../substrate/primitives/session", default-features = false } sp-staking = { path = "../../../substrate/primitives/staking", default-features = false, features = ["serde"] } sp-core = { path = "../../../substrate/primitives/core", default-features = false, features = ["serde"] } -sp-keystore = { path = "../../../substrate/primitives/keystore", optional = true } +sp-keystore = { path = "../../../substrate/primitives/keystore", optional = true, default-features = false } sp-application-crypto = { path = "../../../substrate/primitives/application-crypto", default-features = false, optional = true } sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false, optional = true } sp-arithmetic = { path = "../../../substrate/primitives/arithmetic", default-features = false } @@ -108,6 +108,7 @@ std = [ "sp-core/std", "sp-io/std", "sp-keystore", + "sp-keystore?/std", "sp-runtime/std", "sp-session/std", "sp-staking/std", diff --git a/polkadot/runtime/parachains/src/coretime/migration.rs b/polkadot/runtime/parachains/src/coretime/migration.rs index 72eda1ea3f3cd03a50c595a4b36b6188cca81aaa..4f52fc99ec308a7f3dc21aa37495dbbfe8b9464b 100644 --- a/polkadot/runtime/parachains/src/coretime/migration.rs +++ b/polkadot/runtime/parachains/src/coretime/migration.rs @@ -222,7 +222,7 @@ mod v_coretime { mask: CoreMask::complete(), assignment: CoreAssignment::Task(p.into()), }]); - mk_coretime_call(crate::coretime::CoretimeCalls::Reserve(schedule)) + mk_coretime_call::(crate::coretime::CoretimeCalls::Reserve(schedule)) }); let leases = lease_holding.into_iter().filter_map(|p| { @@ -243,14 +243,14 @@ mod v_coretime { let round_up = if valid_until % TIME_SLICE_PERIOD > 0 { 1 } else { 0 }; let time_slice = valid_until / TIME_SLICE_PERIOD + TIME_SLICE_PERIOD * round_up; log::trace!(target: "coretime-migration", "Sending of lease holding para {:?}, valid_until: {:?}, time_slice: {:?}", p, valid_until, time_slice); - Some(mk_coretime_call(crate::coretime::CoretimeCalls::SetLease(p.into(), time_slice))) + Some(mk_coretime_call::(crate::coretime::CoretimeCalls::SetLease(p.into(), time_slice))) }); let core_count: u16 = configuration::ActiveConfig::::get() .scheduler_params .num_cores .saturated_into(); - let set_core_count = iter::once(mk_coretime_call( + let set_core_count = iter::once(mk_coretime_call::( crate::coretime::CoretimeCalls::NotifyCoreCount(core_count), )); @@ -261,7 +261,7 @@ mod v_coretime { }]); // Reserved cores will come before lease cores, so cores will change their assignments // when coretime chain sends us their assign_core calls -> Good test. - mk_coretime_call(crate::coretime::CoretimeCalls::Reserve(schedule)) + mk_coretime_call::(crate::coretime::CoretimeCalls::Reserve(schedule)) }); let message_content = iter::once(Instruction::UnpaidExecution { diff --git a/polkadot/runtime/parachains/src/coretime/mod.rs b/polkadot/runtime/parachains/src/coretime/mod.rs index 9095cd90ae0cfea6a72fde2d4730cad28cf97d29..a30f7336f692f28d43b17f1ab6aef5fcdf7f2e96 100644 --- a/polkadot/runtime/parachains/src/coretime/mod.rs +++ b/polkadot/runtime/parachains/src/coretime/mod.rs @@ -110,6 +110,11 @@ pub mod pallet { /// Something that provides the weight of this pallet. type WeightInfo: WeightInfo; type SendXcm: SendXcm; + + /// Maximum weight for any XCM transact call that should be executed on the coretime chain. + /// + /// Basically should be `max_weight(set_leases, reserve, notify_core_count)`. + type MaxXcmTransactWeight: Get; } #[pallet::event] @@ -225,7 +230,7 @@ impl Pallet { weight_limit: WeightLimit::Unlimited, check_origin: None, }, - mk_coretime_call(crate::coretime::CoretimeCalls::NotifyCoreCount(core_count)), + mk_coretime_call::(crate::coretime::CoretimeCalls::NotifyCoreCount(core_count)), ]); if let Err(err) = send_xcm::( Location::new(0, [Junction::Parachain(T::BrokerId::get())]), @@ -244,7 +249,7 @@ impl Pallet { weight_limit: WeightLimit::Unlimited, check_origin: None, }, - mk_coretime_call(crate::coretime::CoretimeCalls::SwapLeases(one, other)), + mk_coretime_call::(crate::coretime::CoretimeCalls::SwapLeases(one, other)), ]); if let Err(err) = send_xcm::( Location::new(0, [Junction::Parachain(T::BrokerId::get())]), @@ -261,12 +266,10 @@ impl OnNewSession> for Pallet { } } -fn mk_coretime_call(call: crate::coretime::CoretimeCalls) -> Instruction<()> { +fn mk_coretime_call(call: crate::coretime::CoretimeCalls) -> Instruction<()> { Instruction::Transact { origin_kind: OriginKind::Superuser, - // Largest call is set_lease with 1526 byte: - // Longest call is reserve() with 31_000_000 - require_weight_at_most: Weight::from_parts(170_000_000, 20_000), + require_weight_at_most: T::MaxXcmTransactWeight::get(), call: BrokerRuntimePallets::Broker(call).encode().into(), } } diff --git a/polkadot/runtime/parachains/src/disputes/slashing.rs b/polkadot/runtime/parachains/src/disputes/slashing.rs index d0c74e4bc958320fca78231129dd6e8e135e0179..a61d0c8998364c111f6e3a49fc03d5d39a5d76af 100644 --- a/polkadot/runtime/parachains/src/disputes/slashing.rs +++ b/polkadot/runtime/parachains/src/disputes/slashing.rs @@ -64,7 +64,7 @@ use sp_runtime::{ KeyTypeId, Perbill, }; use sp_session::{GetSessionNumber, GetValidatorCount}; -use sp_staking::offence::{DisableStrategy, Kind, Offence, OffenceError, ReportOffence}; +use sp_staking::offence::{Kind, Offence, OffenceError, ReportOffence}; use sp_std::{ collections::{btree_map::Entry, btree_set::BTreeSet}, prelude::*, @@ -134,15 +134,6 @@ where self.time_slot.clone() } - fn disable_strategy(&self) -> DisableStrategy { - match self.kind { - SlashingOffenceKind::ForInvalid => DisableStrategy::Always, - // in the future we might change it based on number of disputes initiated: - // - SlashingOffenceKind::AgainstValid => DisableStrategy::Never, - } - } - fn slash_fraction(&self, _offenders: u32) -> Perbill { self.slash_fraction } diff --git a/polkadot/runtime/parachains/src/hrmp.rs b/polkadot/runtime/parachains/src/hrmp.rs index 05a540aef828b95b602439c89d6e44ad95c3174a..65652b38577b361353aca1920255e9788c838028 100644 --- a/polkadot/runtime/parachains/src/hrmp.rs +++ b/polkadot/runtime/parachains/src/hrmp.rs @@ -65,6 +65,7 @@ pub trait WeightInfo { fn force_open_hrmp_channel(c: u32) -> Weight; fn establish_system_channel() -> Weight; fn poke_channel_deposits() -> Weight; + fn establish_channel_with_system() -> Weight; } /// A weight info that is only suitable for testing. @@ -104,6 +105,9 @@ impl WeightInfo for TestWeightInfo { fn poke_channel_deposits() -> Weight { Weight::MAX } + fn establish_channel_with_system() -> Weight { + Weight::MAX + } } /// A description of a request to open an HRMP channel. @@ -270,6 +274,10 @@ pub mod pallet { /// implementation should be the same as `Balance` as used in the `Configuration`. type Currency: ReservableCurrency; + /// The default channel size and capacity to use when opening a channel to a system + /// parachain. + type DefaultChannelSizeAndCapacityWithSystem: Get<(u32, u32)>; + /// Something that provides the weight of this pallet. type WeightInfo: WeightInfo; } @@ -297,7 +305,7 @@ pub mod pallet { proposed_max_capacity: u32, proposed_max_message_size: u32, }, - /// An HRMP channel was opened between two system chains. + /// An HRMP channel was opened with a system chain. HrmpSystemChannelOpened { sender: ParaId, recipient: ParaId, @@ -836,6 +844,50 @@ pub mod pallet { Ok(()) } + + /// Establish a bidirectional HRMP channel between a parachain and a system chain. + /// + /// Arguments: + /// + /// - `target_system_chain`: A system chain, `ParaId`. + /// + /// The origin needs to be the parachain origin. + #[pallet::call_index(10)] + #[pallet::weight(::WeightInfo::establish_channel_with_system())] + pub fn establish_channel_with_system( + origin: OriginFor, + target_system_chain: ParaId, + ) -> DispatchResultWithPostInfo { + let sender = ensure_parachain(::RuntimeOrigin::from(origin))?; + + ensure!(target_system_chain.is_system(), Error::::ChannelCreationNotAuthorized); + + let (max_message_size, max_capacity) = + T::DefaultChannelSizeAndCapacityWithSystem::get(); + + // create bidirectional channel + Self::init_open_channel(sender, target_system_chain, max_capacity, max_message_size)?; + Self::accept_open_channel(target_system_chain, sender)?; + + Self::init_open_channel(target_system_chain, sender, max_capacity, max_message_size)?; + Self::accept_open_channel(sender, target_system_chain)?; + + Self::deposit_event(Event::HrmpSystemChannelOpened { + sender, + recipient: target_system_chain, + proposed_max_capacity: max_capacity, + proposed_max_message_size: max_message_size, + }); + + Self::deposit_event(Event::HrmpSystemChannelOpened { + sender: target_system_chain, + recipient: sender, + proposed_max_capacity: max_capacity, + proposed_max_message_size: max_message_size, + }); + + Ok(Pays::No.into()) + } } } diff --git a/polkadot/runtime/parachains/src/hrmp/benchmarking.rs b/polkadot/runtime/parachains/src/hrmp/benchmarking.rs index 13f4cdfe3eeaf767f6c04bab6a627610db16939d..8bf444e647e2475cd9d24193c6bdb8d268c5228a 100644 --- a/polkadot/runtime/parachains/src/hrmp/benchmarking.rs +++ b/polkadot/runtime/parachains/src/hrmp/benchmarking.rs @@ -50,6 +50,13 @@ fn assert_last_event(generic_event: ::RuntimeEvent) { assert_eq!(event, &system_event); } +fn assert_has_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + + assert!(events.iter().any(|record| record.event == system_event)); +} + /// Enumerates the phase in the setup process of a channel between two parachains. enum ParachainSetupStep { /// A channel open has been requested @@ -517,6 +524,43 @@ mod benchmarks { ); } + #[benchmark] + fn establish_channel_with_system() { + let sender_id = 1u32; + let recipient_id: ParaId = 2u32.into(); + + let sender_origin: crate::Origin = sender_id.into(); + + // make sure para is registered, and has zero balance. + register_parachain_with_balance::(sender_id.into(), Zero::zero()); + register_parachain_with_balance::(recipient_id, Zero::zero()); + + #[extrinsic_call] + _(sender_origin, recipient_id); + + let (max_message_size, max_capacity) = T::DefaultChannelSizeAndCapacityWithSystem::get(); + + assert_has_event::( + Event::::HrmpSystemChannelOpened { + sender: sender_id.into(), + recipient: recipient_id, + proposed_max_capacity: max_capacity, + proposed_max_message_size: max_message_size, + } + .into(), + ); + + assert_has_event::( + Event::::HrmpSystemChannelOpened { + sender: recipient_id, + recipient: sender_id.into(), + proposed_max_capacity: max_capacity, + proposed_max_message_size: max_message_size, + } + .into(), + ); + } + impl_benchmark_test_suite!( Hrmp, crate::mock::new_test_ext(crate::hrmp::tests::GenesisConfigBuilder::default().build()), diff --git a/polkadot/runtime/parachains/src/hrmp/tests.rs b/polkadot/runtime/parachains/src/hrmp/tests.rs index 8d43b866bc19767ad3308c4f9730c1ad894d4c0c..2f767ab7e1b19d311f53d194638bb23066c37d31 100644 --- a/polkadot/runtime/parachains/src/hrmp/tests.rs +++ b/polkadot/runtime/parachains/src/hrmp/tests.rs @@ -27,7 +27,7 @@ use crate::{ }, shared, }; -use frame_support::{assert_noop, assert_ok}; +use frame_support::{assert_noop, assert_ok, error::BadOrigin}; use primitives::BlockNumber; use std::collections::BTreeMap; @@ -935,3 +935,72 @@ fn watermark_maxed_out_at_relay_parent() { Hrmp::assert_storage_consistency_exhaustive(); }); } + +#[test] +fn establish_channel_with_system_works() { + let para_a = 2000.into(); + let para_a_origin: crate::Origin = 2000.into(); + let para_b = 3.into(); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + // We need both A & B to be registered and live parachains. + register_parachain(para_a); + register_parachain(para_b); + + run_to_block(5, Some(vec![4, 5])); + Hrmp::establish_channel_with_system(para_a_origin.into(), para_b).unwrap(); + Hrmp::assert_storage_consistency_exhaustive(); + assert!(System::events().iter().any(|record| record.event == + MockEvent::Hrmp(Event::HrmpSystemChannelOpened { + sender: para_a, + recipient: para_b, + proposed_max_capacity: 1, + proposed_max_message_size: 4 + }))); + + assert!(System::events().iter().any(|record| record.event == + MockEvent::Hrmp(Event::HrmpSystemChannelOpened { + sender: para_b, + recipient: para_a, + proposed_max_capacity: 1, + proposed_max_message_size: 4 + }))); + + // Advance to a block 6, but without session change. That means that the channel has + // not been created yet. + run_to_block(6, None); + assert!(!channel_exists(para_a, para_b)); + assert!(!channel_exists(para_b, para_a)); + Hrmp::assert_storage_consistency_exhaustive(); + + // Now let the session change happen and thus open the channel. + run_to_block(8, Some(vec![8])); + assert!(channel_exists(para_a, para_b)); + assert!(channel_exists(para_b, para_a)); + Hrmp::assert_storage_consistency_exhaustive(); + }); +} + +#[test] +fn establish_channel_with_system_with_invalid_args() { + let para_a = 2001.into(); + let para_a_origin: crate::Origin = 2000.into(); + let para_b = 2003.into(); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + // We need both A & B to be registered and live parachains. + register_parachain(para_a); + register_parachain(para_b); + + run_to_block(5, Some(vec![4, 5])); + assert_noop!( + Hrmp::establish_channel_with_system(RuntimeOrigin::signed(1), para_b), + BadOrigin + ); + assert_noop!( + Hrmp::establish_channel_with_system(para_a_origin.into(), para_b), + Error::::ChannelCreationNotAuthorized + ); + Hrmp::assert_storage_consistency_exhaustive(); + }); +} diff --git a/polkadot/runtime/parachains/src/inclusion/mod.rs b/polkadot/runtime/parachains/src/inclusion/mod.rs index 9d60bbb23b6fae7f34b5b3152d01afe824c5936f..31befefa32201b4bf343301c2956abcfbfb6a896 100644 --- a/polkadot/runtime/parachains/src/inclusion/mod.rs +++ b/polkadot/runtime/parachains/src/inclusion/mod.rs @@ -245,7 +245,7 @@ pub enum AggregateMessageOrigin { /// Identifies a UMP queue inside the `MessageQueue` pallet. /// /// It is written in verbose form since future variants like `Here` and `Bridged` are already -/// forseeable. +/// foreseeable. #[derive(Encode, Decode, Clone, MaxEncodedLen, Eq, PartialEq, RuntimeDebug, TypeInfo)] pub enum UmpQueueId { /// The message originated from this parachain. @@ -377,22 +377,45 @@ pub mod pallet { const LOG_TARGET: &str = "runtime::inclusion"; /// The reason that a candidate's outputs were rejected for. -#[derive(derive_more::From)] #[cfg_attr(feature = "std", derive(Debug))] -enum AcceptanceCheckErr { +enum AcceptanceCheckErr { HeadDataTooLarge, /// Code upgrades are not permitted at the current time. PrematureCodeUpgrade, /// The new runtime blob is too large. NewCodeTooLarge, /// The candidate violated this DMP acceptance criteria. - ProcessedDownwardMessages(dmp::ProcessedDownwardMessagesAcceptanceErr), + ProcessedDownwardMessages, /// The candidate violated this UMP acceptance criteria. - UpwardMessages(UmpAcceptanceCheckErr), + UpwardMessages, /// The candidate violated this HRMP watermark acceptance criteria. - HrmpWatermark(hrmp::HrmpWatermarkAcceptanceErr), + HrmpWatermark, /// The candidate violated this outbound HRMP acceptance criteria. - OutboundHrmp(hrmp::OutboundHrmpAcceptanceErr), + OutboundHrmp, +} + +impl From for AcceptanceCheckErr { + fn from(_: dmp::ProcessedDownwardMessagesAcceptanceErr) -> Self { + Self::ProcessedDownwardMessages + } +} + +impl From for AcceptanceCheckErr { + fn from(_: UmpAcceptanceCheckErr) -> Self { + Self::UpwardMessages + } +} + +impl From> for AcceptanceCheckErr { + fn from(_: hrmp::HrmpWatermarkAcceptanceErr) -> Self { + Self::HrmpWatermark + } +} + +impl From for AcceptanceCheckErr { + fn from(_: hrmp::OutboundHrmpAcceptanceErr) -> Self { + Self::OutboundHrmp + } } /// An error returned by [`Pallet::check_upward_messages`] that indicates a violation of one of @@ -1104,6 +1127,24 @@ impl Pallet { }) } + /// Returns all the `CommittedCandidateReceipt` pending availability for the para provided, if + /// any. + pub(crate) fn candidates_pending_availability( + para: ParaId, + ) -> Vec> { + >::get(¶) + .map(|candidates| { + candidates + .into_iter() + .map(|candidate| CommittedCandidateReceipt { + descriptor: candidate.descriptor.clone(), + commitments: candidate.commitments.clone(), + }) + .collect() + }) + .unwrap_or_default() + } + /// Returns the metadata around the first candidate pending availability for the /// para provided, if any. pub(crate) fn pending_availability( @@ -1127,7 +1168,7 @@ const fn availability_threshold(n_validators: usize) -> usize { supermajority_threshold(n_validators) } -impl AcceptanceCheckErr { +impl AcceptanceCheckErr { /// Returns the same error so that it can be threaded through a needle of `DispatchError` and /// ultimately returned from a `Dispatchable`. fn strip_into_dispatch_err(self) -> Error { @@ -1136,10 +1177,10 @@ impl AcceptanceCheckErr { HeadDataTooLarge => Error::::HeadDataTooLarge, PrematureCodeUpgrade => Error::::PrematureCodeUpgrade, NewCodeTooLarge => Error::::NewCodeTooLarge, - ProcessedDownwardMessages(_) => Error::::IncorrectDownwardMessageHandling, - UpwardMessages(_) => Error::::InvalidUpwardMessages, - HrmpWatermark(_) => Error::::HrmpWatermarkMishandling, - OutboundHrmp(_) => Error::::InvalidOutboundHrmp, + ProcessedDownwardMessages => Error::::IncorrectDownwardMessageHandling, + UpwardMessages => Error::::InvalidUpwardMessages, + HrmpWatermark => Error::::HrmpWatermarkMishandling, + OutboundHrmp => Error::::InvalidOutboundHrmp, } } } @@ -1282,7 +1323,7 @@ impl CandidateCheckContext { upward_messages: &[primitives::UpwardMessage], hrmp_watermark: BlockNumberFor, horizontal_messages: &[primitives::OutboundHrmpMessage], - ) -> Result<(), AcceptanceCheckErr>> { + ) -> Result<(), AcceptanceCheckErr> { ensure!( head_data.0.len() <= self.config.max_head_data_size as _, AcceptanceCheckErr::HeadDataTooLarge, diff --git a/polkadot/runtime/parachains/src/mock.rs b/polkadot/runtime/parachains/src/mock.rs index 461b9f4b431acea91614b295d47e3d4c273adccf..97a75d47ff775401ec549c1fcfd7a247664449fb 100644 --- a/polkadot/runtime/parachains/src/mock.rs +++ b/polkadot/runtime/parachains/src/mock.rs @@ -248,6 +248,7 @@ impl crate::dmp::Config for Test {} parameter_types! { pub const FirstMessageFactorPercent: u64 = 100; + pub const DefaultChannelSizeAndCapacityWithSystem: (u32, u32) = (4, 1); } impl crate::hrmp::Config for Test { @@ -255,6 +256,7 @@ impl crate::hrmp::Config for Test { type RuntimeEvent = RuntimeEvent; type ChannelManager = frame_system::EnsureRoot; type Currency = pallet_balances::Pallet; + type DefaultChannelSizeAndCapacityWithSystem = DefaultChannelSizeAndCapacityWithSystem; type WeightInfo = crate::hrmp::TestWeightInfo; } @@ -385,6 +387,7 @@ impl assigner_coretime::Config for Test {} parameter_types! { pub const BrokerId: u32 = 10u32; + pub MaxXcmTransactWeight: Weight = Weight::from_parts(10_000_000, 10_000); } impl coretime::Config for Test { @@ -394,6 +397,7 @@ impl coretime::Config for Test { type BrokerId = BrokerId; type WeightInfo = crate::coretime::TestWeightInfo; type SendXcm = DummyXcmSender; + type MaxXcmTransactWeight = MaxXcmTransactWeight; } pub struct DummyXcmSender; diff --git a/polkadot/runtime/parachains/src/paras/mod.rs b/polkadot/runtime/parachains/src/paras/mod.rs index 6f67c4b8c03da1278f45532b8c9b1a1db4cf427e..36a693bcc8e29a7a2cbce1cb75a430322b88b58b 100644 --- a/polkadot/runtime/parachains/src/paras/mod.rs +++ b/polkadot/runtime/parachains/src/paras/mod.rs @@ -641,7 +641,7 @@ pub mod pallet { /// /// This is only used at genesis or by root. /// - /// TODO: Remove once coretime is the standard accross all chains. + /// TODO: Remove once coretime is the standard across all chains. type AssignCoretime: AssignCoretime; } diff --git a/polkadot/runtime/parachains/src/paras_inherent/mod.rs b/polkadot/runtime/parachains/src/paras_inherent/mod.rs index 2c6c48acc6d47d0e8176fe0b4702b9cfa6781af7..ac4cf5dc8d413397b6a740c6503f69e8d592e20d 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/mod.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/mod.rs @@ -1099,7 +1099,7 @@ fn limit_and_sanitize_disputes< } // Helper function for filtering candidates which don't pass the given predicate. When/if the first -// candidate which failes the predicate is found, all the other candidates that follow are dropped. +// candidate which failed the predicate is found, all the other candidates that follow are dropped. fn retain_candidates< T: inclusion::Config + paras::Config + inclusion::Config, F: FnMut(ParaId, &mut C) -> bool, diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs index 39d4f520994a9993e64f448b456086d4aca99a83..3dca38050a0ac8c43bbb44cdd605572fd6760a82 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs @@ -301,6 +301,10 @@ pub fn validation_code( } /// Implementation for the `candidate_pending_availability` function of the runtime API. +#[deprecated( + note = "`candidate_pending_availability` will be removed. Use `candidates_pending_availability` to query + all candidates pending availability" +)] pub fn candidate_pending_availability( para_id: ParaId, ) -> Option> { diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs index 9ea29a2d374007fa54337fcc8d0fd2f8092553be..32bbdca84a3cce4946ce39edb4e3a3f8e5211188 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs @@ -16,8 +16,8 @@ //! Put implementations of functions from staging APIs here. -use crate::scheduler; -use primitives::{CoreIndex, Id as ParaId}; +use crate::{inclusion, initializer, scheduler}; +use primitives::{CommittedCandidateReceipt, CoreIndex, Id as ParaId}; use sp_runtime::traits::One; use sp_std::{ collections::{btree_map::BTreeMap, vec_deque::VecDeque}, @@ -41,3 +41,11 @@ pub fn claim_queue() -> BTreeMap( + para_id: ParaId, +) -> Vec> { + >::candidates_pending_availability(para_id) +} diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 20a914fb8085fcc917b83fbf5f879e603609904b..bbe19310f970ade37e25f5c1e2c983758c617412 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -70,6 +70,7 @@ pallet-mmr = { path = "../../../substrate/frame/merkle-mountain-range", default- pallet-multisig = { path = "../../../substrate/frame/multisig", default-features = false } pallet-nis = { path = "../../../substrate/frame/nis", default-features = false } pallet-offences = { path = "../../../substrate/frame/offences", default-features = false } +pallet-parameters = { path = "../../../substrate/frame/parameters", default-features = false } pallet-preimage = { path = "../../../substrate/frame/preimage", default-features = false } pallet-proxy = { path = "../../../substrate/frame/proxy", default-features = false } pallet-ranked-collective = { path = "../../../substrate/frame/ranked-collective", default-features = false } @@ -164,6 +165,7 @@ std = [ "pallet-multisig/std", "pallet-nis/std", "pallet-offences/std", + "pallet-parameters/std", "pallet-preimage/std", "pallet-proxy/std", "pallet-ranked-collective/std", @@ -239,6 +241,7 @@ runtime-benchmarks = [ "pallet-multisig/runtime-benchmarks", "pallet-nis/runtime-benchmarks", "pallet-offences/runtime-benchmarks", + "pallet-parameters/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-ranked-collective/runtime-benchmarks", @@ -294,6 +297,7 @@ try-runtime = [ "pallet-multisig/try-runtime", "pallet-nis/try-runtime", "pallet-offences/try-runtime", + "pallet-parameters/try-runtime", "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", "pallet-ranked-collective/try-runtime", diff --git a/polkadot/runtime/rococo/build.rs b/polkadot/runtime/rococo/build.rs index 0b7ee77b0d0d3b984a168f8cefe02a9e00e8f86c..403c31ff21c70f679059fa5b7e65478d309ba6a3 100644 --- a/polkadot/runtime/rococo/build.rs +++ b/polkadot/runtime/rococo/build.rs @@ -16,18 +16,11 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .import_memory() - .export_heap_base() - .build(); + substrate_wasm_builder::WasmBuilder::build_using_defaults(); - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() + substrate_wasm_builder::WasmBuilder::init_with_defaults() .set_file_name("fast_runtime_binary.rs") .enable_feature("fast-runtime") - .import_memory() - .export_heap_base() .build(); } diff --git a/polkadot/runtime/rococo/src/impls.rs b/polkadot/runtime/rococo/src/impls.rs index cf364b6ac7942753b6fd5f47dbf621d70c7ebcf7..ac7100d7858377dca5991e0d0308dc64577b9350 100644 --- a/polkadot/runtime/rococo/src/impls.rs +++ b/polkadot/runtime/rococo/src/impls.rs @@ -167,16 +167,11 @@ where }, ]); - let encoded_versioned_xcm = - VersionedXcm::V4(program).encode().try_into().map_err(|error| { - log::error!(target: "runtime::on_reap_identity", "XCM too large, error: {:?}", error); - pallet_xcm::Error::::XcmTooLarge - })?; // send - let _ = >::send_blob( + let _ = >::send( RawOrigin::Root.into(), Box::new(VersionedLocation::V4(destination)), - encoded_versioned_xcm, + Box::new(VersionedXcm::V4(program)), )?; Ok(()) } diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 894d7fac2f0a4d0e07f1b97f19b3f6d73147dd23..1cfe9adfe13d1c315402c95ff89b86404546a1ce 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -25,6 +25,10 @@ use beefy_primitives::{ ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature}, mmr::{BeefyDataProvider, MmrLeafVersion}, }; +use frame_support::{ + dynamic_params::{dynamic_pallet_params, dynamic_params}, + traits::FromContains, +}; use pallet_nis::WithMaximumOf; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::{ @@ -39,7 +43,8 @@ use rococo_runtime_constants::system_parachain::BROKER_ID; use runtime_common::{ assigned_slots, auctions, claims, crowdloan, identity_migrator, impl_runtime_weights, impls::{ - LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, VersionedLocationConverter, + ContainsParts, LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, + VersionedLocationConverter, }, paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, traits::{Leaser, OnSwap}, @@ -73,9 +78,10 @@ use frame_support::{ genesis_builder_helper::{build_state, get_preset}, parameter_types, traits::{ - fungible::HoldConsideration, Contains, EitherOf, EitherOfDiverse, EverythingBut, - InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, PrivilegeCmp, ProcessMessage, - ProcessMessageError, StorageMapShim, WithdrawReasons, + fungible::HoldConsideration, tokens::UnityOrOuterConversion, Contains, EitherOf, + EitherOfDiverse, EnsureOrigin, EnsureOriginWithArg, EverythingBut, InstanceFilter, + KeyOwnerProofSystem, LinearStoragePrice, PrivilegeCmp, ProcessMessage, ProcessMessageError, + StorageMapShim, WithdrawReasons, }, weights::{ConstantMultiplier, WeightMeter, WeightToFee as _}, PalletId, @@ -85,7 +91,7 @@ use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId}; use pallet_identity::legacy::IdentityInfo; use pallet_session::historical as session_historical; use pallet_transaction_payment::{FeeDetails, FungibleAdapter, RuntimeDispatchInfo}; -use sp_core::{ConstU128, OpaqueMetadata, H256}; +use sp_core::{ConstU128, ConstU8, OpaqueMetadata, H256}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{ @@ -234,6 +240,72 @@ impl PrivilegeCmp for OriginPrivilegeCmp { } } +/// Dynamic params that can be adjusted at runtime. +#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + #[dynamic_pallet_params] + #[codec(index = 0)] + pub mod nis { + use super::*; + + #[codec(index = 0)] + pub static Target: Perquintill = Perquintill::zero(); + + #[codec(index = 1)] + pub static MinBid: Balance = 100 * UNITS; + } + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod preimage { + use super::*; + + #[codec(index = 0)] + pub static BaseDeposit: Balance = deposit(2, 64); + + #[codec(index = 1)] + pub static ByteDeposit: Balance = deposit(0, 1); + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl Default for RuntimeParameters { + fn default() -> Self { + RuntimeParameters::Preimage(dynamic_params::preimage::Parameters::BaseDeposit( + dynamic_params::preimage::BaseDeposit, + Some(1u32.into()), + )) + } +} + +/// Defines what origin can modify which dynamic parameters. +pub struct DynamicParameterOrigin; +impl EnsureOriginWithArg for DynamicParameterOrigin { + type Success = (); + + fn try_origin( + origin: RuntimeOrigin, + key: &RuntimeParametersKey, + ) -> Result { + use crate::{dynamic_params::*, governance::*, RuntimeParametersKey::*}; + + match key { + Nis(nis::ParametersKey::MinBid(_)) => StakingAdmin::ensure_origin(origin.clone()), + Nis(nis::ParametersKey::Target(_)) => GeneralAdmin::ensure_origin(origin.clone()), + Preimage(_) => frame_system::ensure_root(origin.clone()), + } + .map_err(|_| origin) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(_key: &RuntimeParametersKey) -> Result { + // Provide the origin for the parameter returned by `Default`: + Ok(RuntimeOrigin::root()) + } +} + impl pallet_scheduler::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; type RuntimeEvent = RuntimeEvent; @@ -250,8 +322,6 @@ impl pallet_scheduler::Config for Runtime { } parameter_types! { - pub const PreimageBaseDeposit: Balance = deposit(2, 64); - pub const PreimageByteDeposit: Balance = deposit(0, 1); pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); } @@ -264,7 +334,11 @@ impl pallet_preimage::Config for Runtime { AccountId, Balances, PreimageHoldReason, - LinearStoragePrice, + LinearStoragePrice< + dynamic_params::preimage::BaseDeposit, + dynamic_params::preimage::ByteDeposit, + Balance, + >, >; } @@ -453,7 +527,15 @@ impl pallet_treasury::Config for Runtime { LocatableAssetConverter, VersionedLocationConverter, >; - type BalanceConverter = AssetRate; + type BalanceConverter = UnityOrOuterConversion< + ContainsParts< + FromContains< + xcm_builder::IsChildSystemParachain, + xcm_builder::IsParentsOnly>, + >, + >, + AssetRate, + >; type PayoutPeriod = PayoutSpendPeriod; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = runtime_common::impls::benchmarks::TreasuryArguments; @@ -950,11 +1032,16 @@ impl pallet_message_queue::Config for Runtime { impl parachains_dmp::Config for Runtime {} +parameter_types! { + pub const DefaultChannelSizeAndCapacityWithSystem: (u32, u32) = (51200, 500); +} + impl parachains_hrmp::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; type RuntimeEvent = RuntimeEvent; type ChannelManager = EnsureRoot; type Currency = Balances; + type DefaultChannelSizeAndCapacityWithSystem = DefaultChannelSizeAndCapacityWithSystem; type WeightInfo = weights::runtime_parachains_hrmp::WeightInfo; } @@ -970,6 +1057,7 @@ impl parachains_scheduler::Config for Runtime { parameter_types! { pub const BrokerId: u32 = BROKER_ID; + pub MaxXcmTransactWeight: Weight = Weight::from_parts(200_000_000, 20_000); } impl coretime::Config for Runtime { @@ -979,6 +1067,7 @@ impl coretime::Config for Runtime { type BrokerId = BrokerId; type WeightInfo = weights::runtime_parachains_coretime::WeightInfo; type SendXcm = crate::xcm_config::XcmRouter; + type MaxXcmTransactWeight = MaxXcmTransactWeight; } parameter_types! { @@ -1123,12 +1212,10 @@ impl pallet_balances::Config for Runtime { parameter_types! { pub const NisBasePeriod: BlockNumber = 30 * DAYS; - pub const MinBid: Balance = 100 * UNITS; pub MinReceipt: Perquintill = Perquintill::from_rational(1u64, 10_000_000u64); pub const IntakePeriod: BlockNumber = 5 * MINUTES; pub MaxIntakeWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 10; pub const ThawThrottle: (Perquintill, BlockNumber) = (Perquintill::from_percent(25), 5); - pub storage NisTarget: Perquintill = Perquintill::zero(); pub const NisPalletId: PalletId = PalletId(*b"py/nis "); } @@ -1142,13 +1229,13 @@ impl pallet_nis::Config for Runtime { type CounterpartAmount = WithMaximumOf>; type Deficit = (); // Mint type IgnoredIssuance = (); - type Target = NisTarget; + type Target = dynamic_params::nis::Target; type PalletId = NisPalletId; type QueueCount = ConstU32<300>; type MaxQueueLen = ConstU32<1000>; type FifoQueueLen = ConstU32<250>; type BasePeriod = NisBasePeriod; - type MinBid = MinBid; + type MinBid = dynamic_params::nis::MinBid; type MinReceipt = MinReceipt; type IntakePeriod = IntakePeriod; type MaxIntakeWeight = MaxIntakeWeight; @@ -1158,6 +1245,13 @@ impl pallet_nis::Config for Runtime { type BenchmarkSetup = (); } +impl pallet_parameters::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeParameters = RuntimeParameters; + type AdminOrigin = DynamicParameterOrigin; + type WeightInfo = weights::pallet_parameters::WeightInfo; +} + parameter_types! { pub BeefySetIdSessionEntries: u32 = BondingDuration::get() * SessionsPerEra::get(); } @@ -1190,6 +1284,7 @@ impl pallet_mmr::Config for Runtime { type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest; type WeightInfo = (); type LeafData = pallet_beefy_mmr::Pallet; + type BlockHashProvider = pallet_mmr::DefaultBlockHashProvider; } parameter_types! { @@ -1285,6 +1380,7 @@ construct_runtime! { Timestamp: pallet_timestamp = 2, Indices: pallet_indices = 3, Balances: pallet_balances = 4, + Parameters: pallet_parameters = 6, TransactionPayment: pallet_transaction_payment = 33, // Consensus support. @@ -1625,6 +1721,7 @@ mod benches { [pallet_indices, Indices] [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] + [pallet_parameters, Parameters] [pallet_preimage, Preimage] [pallet_proxy, Proxy] [pallet_ranked_collective, FellowshipCollective] @@ -1790,6 +1887,7 @@ sp_api::impl_runtime_apis! { } fn candidate_pending_availability(para_id: ParaId) -> Option> { + #[allow(deprecated)] parachains_runtime_api_impl::candidate_pending_availability::(para_id) } @@ -1903,6 +2001,10 @@ sp_api::impl_runtime_apis! { fn claim_queue() -> BTreeMap> { vstaging_parachains_runtime_api_impl::claim_queue::() } + + fn candidates_pending_availability(para_id: ParaId) -> Vec> { + vstaging_parachains_runtime_api_impl::candidates_pending_availability::(para_id) + } } #[api_version(3)] diff --git a/polkadot/runtime/rococo/src/weights/mod.rs b/polkadot/runtime/rococo/src/weights/mod.rs index 7328dca9393693e335b49dc317d6754f9fd6e840..3c6845dfb43e65e85dc33b2e288f35a9a8742e1e 100644 --- a/polkadot/runtime/rococo/src/weights/mod.rs +++ b/polkadot/runtime/rococo/src/weights/mod.rs @@ -27,6 +27,7 @@ pub mod pallet_indices; pub mod pallet_message_queue; pub mod pallet_multisig; pub mod pallet_nis; +pub mod pallet_parameters; pub mod pallet_preimage; pub mod pallet_proxy; pub mod pallet_ranked_collective; diff --git a/polkadot/runtime/rococo/src/weights/pallet_parameters.rs b/polkadot/runtime/rococo/src/weights/pallet_parameters.rs new file mode 100644 index 0000000000000000000000000000000000000000..bd2bcf960e9baae96f156b6424852b02a1536b00 --- /dev/null +++ b/polkadot/runtime/rococo/src/weights/pallet_parameters.rs @@ -0,0 +1,63 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Autogenerated weights for `pallet_parameters` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-04-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_parameters +// --chain=rococo-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_parameters`. +pub struct WeightInfo(PhantomData); +impl pallet_parameters::WeightInfo for WeightInfo { + /// Storage: `Parameters::Parameters` (r:1 w:1) + /// Proof: `Parameters::Parameters` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + fn set_parameter() -> Weight { + // Proof Size summary in bytes: + // Measured: `4` + // Estimated: `3493` + // Minimum execution time: 6_937_000 picoseconds. + Weight::from_parts(7_242_000, 0) + .saturating_add(Weight::from_parts(0, 3493)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/polkadot/runtime/rococo/src/weights/pallet_xcm.rs b/polkadot/runtime/rococo/src/weights/pallet_xcm.rs index 42972baa1c830baa820fc5c72b35f346daaf27d0..5544ca44658cc09fa51cd75679ba90132eb16444 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_xcm.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -60,26 +60,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `180` // Estimated: `3645` - // Minimum execution time: 24_724_000 picoseconds. - Weight::from_parts(25_615_000, 0) - .saturating_add(Weight::from_parts(0, 3645)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) - /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) - /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) - /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) - /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn send_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `180` - // Estimated: `3645` - // Minimum execution time: 24_709_000 picoseconds. - Weight::from_parts(25_326_000, 0) + // Minimum execution time: 25_043_000 picoseconds. + Weight::from_parts(25_682_000, 0) .saturating_add(Weight::from_parts(0, 3645)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -98,8 +80,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `180` // Estimated: `3645` - // Minimum execution time: 106_600_000 picoseconds. - Weight::from_parts(110_781_000, 0) + // Minimum execution time: 107_570_000 picoseconds. + Weight::from_parts(109_878_000, 0) .saturating_add(Weight::from_parts(0, 3645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) @@ -118,8 +100,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `232` // Estimated: `3697` - // Minimum execution time: 103_030_000 picoseconds. - Weight::from_parts(106_018_000, 0) + // Minimum execution time: 106_341_000 picoseconds. + Weight::from_parts(109_135_000, 0) .saturating_add(Weight::from_parts(0, 3697)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) @@ -138,8 +120,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `180` // Estimated: `3645` - // Minimum execution time: 107_017_000 picoseconds. - Weight::from_parts(109_214_000, 0) + // Minimum execution time: 108_372_000 picoseconds. + Weight::from_parts(112_890_000, 0) .saturating_add(Weight::from_parts(0, 3645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) @@ -148,16 +130,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_864_000 picoseconds. - Weight::from_parts(7_135_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } - fn execute_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 6_955_000 picoseconds. - Weight::from_parts(7_165_000, 0) + // Minimum execution time: 6_957_000 picoseconds. + Weight::from_parts(7_417_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `XcmPallet::SupportedVersion` (r:0 w:1) @@ -166,8 +140,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_827_000 picoseconds. - Weight::from_parts(7_211_000, 0) + // Minimum execution time: 7_053_000 picoseconds. + Weight::from_parts(7_462_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -175,8 +149,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_788_000 picoseconds. - Weight::from_parts(2_021_000, 0) + // Minimum execution time: 1_918_000 picoseconds. + Weight::from_parts(2_037_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) @@ -197,8 +171,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `180` // Estimated: `3645` - // Minimum execution time: 30_627_000 picoseconds. - Weight::from_parts(31_350_000, 0) + // Minimum execution time: 30_417_000 picoseconds. + Weight::from_parts(31_191_000, 0) .saturating_add(Weight::from_parts(0, 3645)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) @@ -219,8 +193,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `360` // Estimated: `3825` - // Minimum execution time: 36_688_000 picoseconds. - Weight::from_parts(37_345_000, 0) + // Minimum execution time: 36_666_000 picoseconds. + Weight::from_parts(37_779_000, 0) .saturating_add(Weight::from_parts(0, 3825)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) @@ -231,8 +205,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_829_000 picoseconds. - Weight::from_parts(1_986_000, 0) + // Minimum execution time: 1_869_000 picoseconds. + Weight::from_parts(2_003_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -242,8 +216,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `22` // Estimated: `13387` - // Minimum execution time: 16_104_000 picoseconds. - Weight::from_parts(16_464_000, 0) + // Minimum execution time: 16_188_000 picoseconds. + Weight::from_parts(16_435_000, 0) .saturating_add(Weight::from_parts(0, 13387)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -254,8 +228,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `26` // Estimated: `13391` - // Minimum execution time: 16_267_000 picoseconds. - Weight::from_parts(16_675_000, 0) + // Minimum execution time: 16_431_000 picoseconds. + Weight::from_parts(16_935_000, 0) .saturating_add(Weight::from_parts(0, 13391)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -266,8 +240,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `40` // Estimated: `15880` - // Minimum execution time: 18_487_000 picoseconds. - Weight::from_parts(19_102_000, 0) + // Minimum execution time: 18_460_000 picoseconds. + Weight::from_parts(18_885_000, 0) .saturating_add(Weight::from_parts(0, 15880)) .saturating_add(T::DbWeight::get().reads(6)) } @@ -285,8 +259,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `216` // Estimated: `6156` - // Minimum execution time: 29_603_000 picoseconds. - Weight::from_parts(31_002_000, 0) + // Minimum execution time: 29_623_000 picoseconds. + Weight::from_parts(30_661_000, 0) .saturating_add(Weight::from_parts(0, 6156)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) @@ -297,8 +271,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `69` // Estimated: `10959` - // Minimum execution time: 12_183_000 picoseconds. - Weight::from_parts(12_587_000, 0) + // Minimum execution time: 12_043_000 picoseconds. + Weight::from_parts(12_360_000, 0) .saturating_add(Weight::from_parts(0, 10959)) .saturating_add(T::DbWeight::get().reads(4)) } @@ -308,8 +282,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `33` // Estimated: `13398` - // Minimum execution time: 16_372_000 picoseconds. - Weight::from_parts(16_967_000, 0) + // Minimum execution time: 16_511_000 picoseconds. + Weight::from_parts(17_011_000, 0) .saturating_add(Weight::from_parts(0, 13398)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -328,8 +302,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `216` // Estimated: `13581` - // Minimum execution time: 38_904_000 picoseconds. - Weight::from_parts(39_983_000, 0) + // Minimum execution time: 39_041_000 picoseconds. + Weight::from_parts(39_883_000, 0) .saturating_add(Weight::from_parts(0, 13581)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) @@ -342,8 +316,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1485` - // Minimum execution time: 2_067_000 picoseconds. - Weight::from_parts(2_195_000, 0) + // Minimum execution time: 2_030_000 picoseconds. + Weight::from_parts(2_150_000, 0) .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -354,8 +328,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7576` // Estimated: `11041` - // Minimum execution time: 23_982_000 picoseconds. - Weight::from_parts(24_409_000, 0) + // Minimum execution time: 22_615_000 picoseconds. + Weight::from_parts(23_008_000, 0) .saturating_add(Weight::from_parts(0, 11041)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -366,8 +340,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `23` // Estimated: `3488` - // Minimum execution time: 33_430_000 picoseconds. - Weight::from_parts(34_433_000, 0) + // Minimum execution time: 34_438_000 picoseconds. + Weight::from_parts(35_514_000, 0) .saturating_add(Weight::from_parts(0, 3488)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_hrmp.rs b/polkadot/runtime/rococo/src/weights/runtime_parachains_hrmp.rs index 417820e6627f6e39c4107df722f4b83f9cca6a4b..97b84155b36a6adb2258a714e65bf5c70bc9aa50 100644 --- a/polkadot/runtime/rococo/src/weights/runtime_parachains_hrmp.rs +++ b/polkadot/runtime/rococo/src/weights/runtime_parachains_hrmp.rs @@ -331,4 +331,14 @@ impl runtime_parachains::hrmp::WeightInfo for WeightInf .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + fn establish_channel_with_system() -> Weight { + // Proof Size summary in bytes: + // Measured: `417` + // Estimated: `6357` + // Minimum execution time: 629_674_000 picoseconds. + Weight::from_parts(640_174_000, 0) + .saturating_add(Weight::from_parts(0, 6357)) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(8)) + } } diff --git a/polkadot/runtime/test-runtime/Cargo.toml b/polkadot/runtime/test-runtime/Cargo.toml index 35fb684597e7d3f8082478353a643ee2adf2481d..6552ed4ef8aeaa2e36aa1f410879cc4cf0eeaf07 100644 --- a/polkadot/runtime/test-runtime/Cargo.toml +++ b/polkadot/runtime/test-runtime/Cargo.toml @@ -11,14 +11,10 @@ license.workspace = true workspace = true [dependencies] -bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } log = { workspace = true } -rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } serde = { workspace = true } -serde_derive = { optional = true, workspace = true } -smallvec = "1.8.0" authority-discovery-primitives = { package = "sp-authority-discovery", path = "../../../substrate/primitives/authority-discovery", default-features = false } babe-primitives = { package = "sp-consensus-babe", path = "../../../substrate/primitives/consensus/babe", default-features = false } @@ -63,7 +59,6 @@ pallet-vesting = { path = "../../../substrate/frame/vesting", default-features = runtime-common = { package = "polkadot-runtime-common", path = "../common", default-features = false } primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false } pallet-xcm = { path = "../../xcm/pallet-xcm", default-features = false } -polkadot-parachain-primitives = { path = "../../parachain", default-features = false } polkadot-runtime-parachains = { path = "../parachains", default-features = false } xcm-builder = { package = "staging-xcm-builder", path = "../../xcm/xcm-builder", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = "../../xcm/xcm-executor", default-features = false } @@ -92,7 +87,6 @@ std = [ "authority-discovery-primitives/std", "babe-primitives/std", "beefy-primitives/std", - "bitvec/std", "block-builder-api/std", "frame-election-provider-support/std", "frame-executive/std", @@ -118,14 +112,11 @@ std = [ "pallet-vesting/std", "pallet-xcm/std", "parity-scale-codec/std", - "polkadot-parachain-primitives/std", "polkadot-runtime-parachains/std", "primitives/std", "runtime-common/std", - "rustc-hex/std", "scale-info/std", "serde/std", - "serde_derive", "sp-api/std", "sp-core/std", "sp-genesis-builder/std", @@ -157,7 +148,6 @@ runtime-benchmarks = [ "pallet-timestamp/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", - "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "primitives/runtime-benchmarks", "runtime-common/runtime-benchmarks", diff --git a/polkadot/runtime/test-runtime/build.rs b/polkadot/runtime/test-runtime/build.rs index 404ba3f2fdbdfdc68d35d0dd08958448f30d90c6..caf24317d0b3e1e30bdc63ba2a5e6388b820be57 100644 --- a/polkadot/runtime/test-runtime/build.rs +++ b/polkadot/runtime/test-runtime/build.rs @@ -17,9 +17,5 @@ use substrate_wasm_builder::WasmBuilder; fn main() { - WasmBuilder::new() - .with_current_project() - .import_memory() - .export_heap_base() - .build() + WasmBuilder::build_using_defaults(); } diff --git a/polkadot/runtime/test-runtime/constants/Cargo.toml b/polkadot/runtime/test-runtime/constants/Cargo.toml index 2b387bbd3072a91bc00afa7326d93f66a644f39e..5b8a4d7a051af84701065a3836d813630011bd0c 100644 --- a/polkadot/runtime/test-runtime/constants/Cargo.toml +++ b/polkadot/runtime/test-runtime/constants/Cargo.toml @@ -14,18 +14,12 @@ smallvec = "1.8.0" frame-support = { path = "../../../../substrate/frame/support", default-features = false } primitives = { package = "polkadot-primitives", path = "../../../primitives", default-features = false } -runtime-common = { package = "polkadot-runtime-common", path = "../../common", default-features = false } sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } -sp-weights = { path = "../../../../substrate/primitives/weights", default-features = false } -sp-core = { path = "../../../../substrate/primitives/core", default-features = false } [features] default = ["std"] std = [ "frame-support/std", "primitives/std", - "runtime-common/std", - "sp-core/std", "sp-runtime/std", - "sp-weights/std", ] diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index f01382509501186bbe6efbdad67eea536ce59990..d0f1ff0035fcf37cb57c8be5f7effb5d1742c847 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -22,15 +22,19 @@ use pallet_transaction_payment::FungibleAdapter; use parity_scale_codec::Encode; -use sp_std::{collections::btree_map::BTreeMap, prelude::*}; +use sp_std::{ + collections::{btree_map::BTreeMap, vec_deque::VecDeque}, + prelude::*, +}; use polkadot_runtime_parachains::{ assigner_parachains as parachains_assigner_parachains, configuration as parachains_configuration, disputes as parachains_disputes, - disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp, - inclusion as parachains_inclusion, initializer as parachains_initializer, - origin as parachains_origin, paras as parachains_paras, - paras_inherent as parachains_paras_inherent, runtime_api_impl::v10 as runtime_impl, + disputes::slashing as parachains_slashing, + dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion, + initializer as parachains_initializer, origin as parachains_origin, paras as parachains_paras, + paras_inherent as parachains_paras_inherent, + runtime_api_impl::{v10 as runtime_impl, vstaging as vstaging_parachains_runtime_api_impl}, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -53,9 +57,9 @@ use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use polkadot_runtime_parachains::reward_points::RewardValidatorsWithEraPoints; use primitives::{ slashing, AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash, - CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, - Hash as HashT, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, Moment, Nonce, - OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes, + CommittedCandidateReceipt, CoreIndex, CoreState, DisputeState, ExecutorParams, + GroupRotationInfo, Hash as HashT, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, + Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes, SessionInfo as SessionInfoData, Signature, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, PARACHAIN_KEY_TYPE_ID, }; @@ -309,7 +313,6 @@ parameter_types! { pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub const MaxExposurePageSize: u32 = 64; pub const MaxNominators: u32 = 256; - pub storage OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); pub const MaxAuthorities: u32 = 100_000; pub const OnChainMaxWinners: u32 = u32::MAX; // Unbounded number of election targets and voters. @@ -345,7 +348,6 @@ impl pallet_staking::Config for Runtime { type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type MaxExposurePageSize = MaxExposurePageSize; - type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = onchain::OnChainExecution; @@ -360,6 +362,7 @@ impl pallet_staking::Config for Runtime { type BenchmarkingConfig = runtime_common::StakingBenchmarkingConfig; type EventListeners = (); type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } parameter_types! { @@ -554,6 +557,7 @@ impl parachains_dmp::Config for Runtime {} parameter_types! { pub const FirstMessageFactorPercent: u64 = 100; + pub const DefaultChannelSizeAndCapacityWithSystem: (u32, u32) = (51200, 500); } impl parachains_hrmp::Config for Runtime { @@ -561,6 +565,7 @@ impl parachains_hrmp::Config for Runtime { type RuntimeEvent = RuntimeEvent; type ChannelManager = frame_system::EnsureRoot; type Currency = Balances; + type DefaultChannelSizeAndCapacityWithSystem = DefaultChannelSizeAndCapacityWithSystem; type WeightInfo = parachains_hrmp::TestWeightInfo; } @@ -829,7 +834,7 @@ sp_api::impl_runtime_apis! { } } - #[api_version(10)] + #[api_version(11)] impl primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { runtime_impl::validators::() @@ -877,6 +882,7 @@ sp_api::impl_runtime_apis! { } fn candidate_pending_availability(para_id: ParaId) -> Option> { + #[allow(deprecated)] runtime_impl::candidate_pending_availability::(para_id) } @@ -981,6 +987,14 @@ sp_api::impl_runtime_apis! { fn node_features() -> primitives::NodeFeatures { runtime_impl::node_features::() } + + fn claim_queue() -> BTreeMap> { + vstaging_parachains_runtime_api_impl::claim_queue::() + } + + fn candidates_pending_availability(para_id: ParaId) -> Vec> { + vstaging_parachains_runtime_api_impl::candidates_pending_availability::(para_id) + } } impl beefy_primitives::BeefyApi for Runtime { diff --git a/polkadot/runtime/westend/build.rs b/polkadot/runtime/westend/build.rs index 428c971bc132a5c7e856e6485246c4fd2ff57822..0b3e12c78c746517a32538c8c1e5f9da63747fc5 100644 --- a/polkadot/runtime/westend/build.rs +++ b/polkadot/runtime/westend/build.rs @@ -17,9 +17,5 @@ use substrate_wasm_builder::WasmBuilder; fn main() { - WasmBuilder::new() - .with_current_project() - .import_memory() - .export_heap_base() - .build() + WasmBuilder::build_using_defaults(); } diff --git a/polkadot/runtime/westend/src/impls.rs b/polkadot/runtime/westend/src/impls.rs index d8741c939a507d49c181b3620d322c08692d730b..71e6b696a20a0feb89e669067d02b12e6eeb89fd 100644 --- a/polkadot/runtime/westend/src/impls.rs +++ b/polkadot/runtime/westend/src/impls.rs @@ -167,16 +167,11 @@ where }, ]); - let encoded_versioned_xcm = - VersionedXcm::V4(program).encode().try_into().map_err(|error| { - log::error!(target: "runtime::on_reap_identity", "XCM too large, error: {:?}", error); - pallet_xcm::Error::::XcmTooLarge - })?; // send - let _ = >::send_blob( + let _ = >::send( RawOrigin::Root.into(), Box::new(VersionedLocation::V4(destination)), - encoded_versioned_xcm, + Box::new(VersionedXcm::V4(program)), )?; Ok(()) } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 4930610c1d80a3e632232b2f72c81ece97ae1955..de961bb4c3987746585f772466941183b7b3e0b5 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -31,9 +31,9 @@ use frame_support::{ genesis_builder_helper::{build_state, get_preset}, parameter_types, traits::{ - fungible::HoldConsideration, ConstU32, Contains, EitherOf, EitherOfDiverse, EverythingBut, - InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, ProcessMessage, - ProcessMessageError, WithdrawReasons, + fungible::HoldConsideration, tokens::UnityOrOuterConversion, ConstU32, Contains, EitherOf, + EitherOfDiverse, EverythingBut, FromContains, InstanceFilter, KeyOwnerProofSystem, + LinearStoragePrice, ProcessMessage, ProcessMessageError, WithdrawReasons, }, weights::{ConstantMultiplier, WeightMeter, WeightToFee as _}, PalletId, @@ -57,7 +57,8 @@ use runtime_common::{ elections::OnChainAccuracy, identity_migrator, impl_runtime_weights, impls::{ - LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, VersionedLocationConverter, + ContainsParts, LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, + VersionedLocationConverter, }, paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, traits::{Leaser, OnSwap}, @@ -80,7 +81,7 @@ use runtime_parachains::{ shared as parachains_shared, }; use scale_info::TypeInfo; -use sp_core::{OpaqueMetadata, RuntimeDebug, H256}; +use sp_core::{ConstU8, OpaqueMetadata, RuntimeDebug, H256}; use sp_runtime::{ create_runtime_str, curve::PiecewiseLinear, @@ -333,6 +334,7 @@ impl pallet_mmr::Config for Runtime { type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest; type WeightInfo = (); type LeafData = pallet_beefy_mmr::Pallet; + type BlockHashProvider = pallet_mmr::DefaultBlockHashProvider; } /// MMR helper types. @@ -611,7 +613,6 @@ parameter_types! { // this is an unbounded number. We just set it to a reasonably high value, 1 full page // of nominators. pub const MaxNominators: u32 = 64; - pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); pub const MaxNominations: u32 = ::LIMIT as u32; pub const MaxControllersInDeprecationBatch: u32 = 751; } @@ -632,7 +633,6 @@ impl pallet_staking::Config for Runtime { type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type MaxExposurePageSize = MaxExposurePageSize; - type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainExecution; @@ -645,6 +645,7 @@ impl pallet_staking::Config for Runtime { type BenchmarkingConfig = runtime_common::StakingBenchmarkingConfig; type EventListeners = NominationPools; type WeightInfo = weights::pallet_staking::WeightInfo; + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } impl pallet_fast_unstake::Config for Runtime { @@ -711,7 +712,15 @@ impl pallet_treasury::Config for Runtime { LocatableAssetConverter, VersionedLocationConverter, >; - type BalanceConverter = AssetRate; + type BalanceConverter = UnityOrOuterConversion< + ContainsParts< + FromContains< + xcm_builder::IsChildSystemParachain, + xcm_builder::IsParentsOnly>, + >, + >, + AssetRate, + >; type PayoutPeriod = PayoutSpendPeriod; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = runtime_common::impls::benchmarks::TreasuryArguments; @@ -1153,11 +1162,16 @@ impl pallet_message_queue::Config for Runtime { impl parachains_dmp::Config for Runtime {} +parameter_types! { + pub const DefaultChannelSizeAndCapacityWithSystem: (u32, u32) = (4096, 4); +} + impl parachains_hrmp::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; type RuntimeEvent = RuntimeEvent; type ChannelManager = EnsureRoot; type Currency = Balances; + type DefaultChannelSizeAndCapacityWithSystem = DefaultChannelSizeAndCapacityWithSystem; type WeightInfo = weights::runtime_parachains_hrmp::WeightInfo; } @@ -1173,6 +1187,7 @@ impl parachains_scheduler::Config for Runtime { parameter_types! { pub const BrokerId: u32 = BROKER_ID; + pub MaxXcmTransactWeight: Weight = Weight::from_parts(200_000_000, 20_000); } impl coretime::Config for Runtime { @@ -1182,6 +1197,7 @@ impl coretime::Config for Runtime { type BrokerId = BrokerId; type WeightInfo = weights::runtime_parachains_coretime::WeightInfo; type SendXcm = crate::xcm_config::XcmRouter; + type MaxXcmTransactWeight = MaxXcmTransactWeight; } parameter_types! { @@ -1631,36 +1647,8 @@ pub mod migrations { } } - // We don't have a limit in the Relay Chain. - const IDENTITY_MIGRATION_KEY_LIMIT: u64 = u64::MAX; - /// Unreleased migrations. Add new ones here: - pub type Unreleased = ( - parachains_configuration::migration::v7::MigrateToV7, - pallet_staking::migrations::v14::MigrateToV14, - assigned_slots::migration::v1::MigrateToV1, - parachains_scheduler::migration::MigrateV1ToV2, - parachains_configuration::migration::v8::MigrateToV8, - parachains_configuration::migration::v9::MigrateToV9, - paras_registrar::migration::MigrateToV1, - pallet_referenda::migration::v1::MigrateV0ToV1, - pallet_grandpa::migrations::MigrateV4ToV5, - parachains_configuration::migration::v10::MigrateToV10, - pallet_nomination_pools::migration::unversioned::TotalValueLockedSync, - // Migrate Identity pallet for Usernames - pallet_identity::migration::versioned::V0ToV1, - parachains_configuration::migration::v11::MigrateToV11, - parachains_configuration::migration::v12::MigrateToV12, - // permanent - pallet_xcm::migration::MigrateToLatestXcmVersion, - // Migrate from legacy lease to coretime. Needs to run after configuration v11 - coretime::migration::MigrateToCoretime< - Runtime, - crate::xcm_config::XcmRouter, - GetLegacyLeaseImpl, - >, - parachains_inclusion::migration::MigrateToV1, - ); + pub type Unreleased = (pallet_staking::migrations::v15::MigrateV14ToV15,); } /// Unchecked extrinsic type as expected by this runtime. @@ -1848,6 +1836,7 @@ sp_api::impl_runtime_apis! { } fn candidate_pending_availability(para_id: ParaId) -> Option> { + #[allow(deprecated)] parachains_runtime_api_impl::candidate_pending_availability::(para_id) } @@ -1961,6 +1950,10 @@ sp_api::impl_runtime_apis! { fn claim_queue() -> BTreeMap> { vstaging_parachains_runtime_api_impl::claim_queue::() } + + fn candidates_pending_availability(para_id: ParaId) -> Vec> { + vstaging_parachains_runtime_api_impl::candidates_pending_availability::(para_id) + } } impl beefy_primitives::BeefyApi for Runtime { @@ -2257,6 +2250,10 @@ sp_api::impl_runtime_apis! { fn eras_stakers_page_count(era: sp_staking::EraIndex, account: AccountId) -> sp_staking::Page { Staking::api_eras_stakers_page_count(era, account) } + + fn pending_rewards(era: sp_staking::EraIndex, account: AccountId) -> bool { + Staking::api_pending_rewards(era, account) + } } #[cfg(feature = "try-runtime")] diff --git a/polkadot/runtime/westend/src/weights/pallet_xcm.rs b/polkadot/runtime/westend/src/weights/pallet_xcm.rs index 80bc551ba1e264f9703f5fd307ec7075642bc3bd..10725cecf24995e34b3254baa23535cb91dd9981 100644 --- a/polkadot/runtime/westend/src/weights/pallet_xcm.rs +++ b/polkadot/runtime/westend/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -60,26 +60,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `147` // Estimated: `3612` - // Minimum execution time: 24_535_000 picoseconds. - Weight::from_parts(25_618_000, 0) - .saturating_add(Weight::from_parts(0, 3612)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) - /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) - /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) - /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) - /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn send_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `147` - // Estimated: `3612` - // Minimum execution time: 25_376_000 picoseconds. - Weight::from_parts(26_180_000, 0) + // Minimum execution time: 25_725_000 picoseconds. + Weight::from_parts(26_174_000, 0) .saturating_add(Weight::from_parts(0, 3612)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -98,8 +80,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `250` // Estimated: `6196` - // Minimum execution time: 108_786_000 picoseconds. - Weight::from_parts(112_208_000, 0) + // Minimum execution time: 113_140_000 picoseconds. + Weight::from_parts(116_204_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -118,8 +100,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `302` // Estimated: `6196` - // Minimum execution time: 105_190_000 picoseconds. - Weight::from_parts(107_140_000, 0) + // Minimum execution time: 108_571_000 picoseconds. + Weight::from_parts(110_650_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -138,8 +120,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `250` // Estimated: `6196` - // Minimum execution time: 109_027_000 picoseconds. - Weight::from_parts(111_404_000, 0) + // Minimum execution time: 111_836_000 picoseconds. + Weight::from_parts(114_435_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -154,24 +136,14 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn execute_blob() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } /// Storage: `XcmPallet::SupportedVersion` (r:0 w:1) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_668_000 picoseconds. - Weight::from_parts(7_013_000, 0) + // Minimum execution time: 7_160_000 picoseconds. + Weight::from_parts(7_477_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -179,8 +151,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_740_000 picoseconds. - Weight::from_parts(1_884_000, 0) + // Minimum execution time: 1_934_000 picoseconds. + Weight::from_parts(2_053_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) @@ -201,8 +173,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `147` // Estimated: `3612` - // Minimum execution time: 30_200_000 picoseconds. - Weight::from_parts(30_768_000, 0) + // Minimum execution time: 31_123_000 picoseconds. + Weight::from_parts(31_798_000, 0) .saturating_add(Weight::from_parts(0, 3612)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) @@ -223,8 +195,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `327` // Estimated: `3792` - // Minimum execution time: 33_928_000 picoseconds. - Weight::from_parts(35_551_000, 0) + // Minimum execution time: 35_175_000 picoseconds. + Weight::from_parts(36_098_000, 0) .saturating_add(Weight::from_parts(0, 3792)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) @@ -235,8 +207,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_759_000 picoseconds. - Weight::from_parts(1_880_000, 0) + // Minimum execution time: 1_974_000 picoseconds. + Weight::from_parts(2_096_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -246,8 +218,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `22` // Estimated: `13387` - // Minimum execution time: 16_507_000 picoseconds. - Weight::from_parts(17_219_000, 0) + // Minimum execution time: 16_626_000 picoseconds. + Weight::from_parts(17_170_000, 0) .saturating_add(Weight::from_parts(0, 13387)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -258,8 +230,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `26` // Estimated: `13391` - // Minimum execution time: 16_633_000 picoseconds. - Weight::from_parts(16_889_000, 0) + // Minimum execution time: 16_937_000 picoseconds. + Weight::from_parts(17_447_000, 0) .saturating_add(Weight::from_parts(0, 13391)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -270,8 +242,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `40` // Estimated: `15880` - // Minimum execution time: 19_297_000 picoseconds. - Weight::from_parts(19_820_000, 0) + // Minimum execution time: 19_157_000 picoseconds. + Weight::from_parts(19_659_000, 0) .saturating_add(Weight::from_parts(0, 15880)) .saturating_add(T::DbWeight::get().reads(6)) } @@ -289,8 +261,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `183` // Estimated: `6123` - // Minimum execution time: 30_364_000 picoseconds. - Weight::from_parts(31_122_000, 0) + // Minimum execution time: 30_699_000 picoseconds. + Weight::from_parts(31_537_000, 0) .saturating_add(Weight::from_parts(0, 6123)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) @@ -301,8 +273,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `69` // Estimated: `10959` - // Minimum execution time: 11_997_000 picoseconds. - Weight::from_parts(12_392_000, 0) + // Minimum execution time: 12_303_000 picoseconds. + Weight::from_parts(12_670_000, 0) .saturating_add(Weight::from_parts(0, 10959)) .saturating_add(T::DbWeight::get().reads(4)) } @@ -312,8 +284,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `33` // Estimated: `13398` - // Minimum execution time: 16_894_000 picoseconds. - Weight::from_parts(17_452_000, 0) + // Minimum execution time: 17_129_000 picoseconds. + Weight::from_parts(17_668_000, 0) .saturating_add(Weight::from_parts(0, 13398)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -332,8 +304,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `183` // Estimated: `13548` - // Minimum execution time: 39_864_000 picoseconds. - Weight::from_parts(40_859_000, 0) + // Minimum execution time: 39_960_000 picoseconds. + Weight::from_parts(41_068_000, 0) .saturating_add(Weight::from_parts(0, 13548)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) @@ -346,8 +318,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1485` - // Minimum execution time: 2_363_000 picoseconds. - Weight::from_parts(2_519_000, 0) + // Minimum execution time: 2_333_000 picoseconds. + Weight::from_parts(2_504_000, 0) .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -358,8 +330,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7576` // Estimated: `11041` - // Minimum execution time: 22_409_000 picoseconds. - Weight::from_parts(22_776_000, 0) + // Minimum execution time: 22_932_000 picoseconds. + Weight::from_parts(23_307_000, 0) .saturating_add(Weight::from_parts(0, 11041)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -370,8 +342,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `23` // Estimated: `3488` - // Minimum execution time: 33_551_000 picoseconds. - Weight::from_parts(34_127_000, 0) + // Minimum execution time: 34_558_000 picoseconds. + Weight::from_parts(35_299_000, 0) .saturating_add(Weight::from_parts(0, 3488)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_hrmp.rs b/polkadot/runtime/westend/src/weights/runtime_parachains_hrmp.rs index 9beb15303d871cd4ae0b6b0ce3148291d745395b..3d2ab827b8fd125b74a2c892803038557998b8be 100644 --- a/polkadot/runtime/westend/src/weights/runtime_parachains_hrmp.rs +++ b/polkadot/runtime/westend/src/weights/runtime_parachains_hrmp.rs @@ -324,4 +324,14 @@ impl runtime_parachains::hrmp::WeightInfo for WeightInf .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + fn establish_channel_with_system() -> Weight { + // Proof Size summary in bytes: + // Measured: `417` + // Estimated: `6357` + // Minimum execution time: 629_674_000 picoseconds. + Weight::from_parts(640_174_000, 0) + .saturating_add(Weight::from_parts(0, 6357)) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(8)) + } } diff --git a/polkadot/tests/benchmark_block.rs b/polkadot/tests/benchmark_block.rs index 99f95ef611a48266105ba6b413641867fab21101..bc268025985035a74996ba444e02b98471bb1125 100644 --- a/polkadot/tests/benchmark_block.rs +++ b/polkadot/tests/benchmark_block.rs @@ -58,7 +58,13 @@ async fn build_chain(runtime: &str, base_path: &Path) { let mut cmd = Command::new(cargo_bin("polkadot")) .stdout(process::Stdio::piped()) .stderr(process::Stdio::piped()) - .args(["--chain", runtime, "--force-authoring", "--alice"]) + .args([ + "--chain", + runtime, + "--force-authoring", + "--alice", + "--unsafe-force-node-key-generation", + ]) .arg("-d") .arg(base_path) .arg("--no-hardware-benchmarks") diff --git a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml index 8c71426a6faee49023603caa250f37bcbb42f631..9691ddd48168e5e8339353a10c475ad263ce5f14 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml +++ b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml @@ -29,7 +29,6 @@ log = { workspace = true, default-features = true } [dev-dependencies] pallet-balances = { path = "../../../substrate/frame/balances" } pallet-assets = { path = "../../../substrate/frame/assets" } -sp-core = { path = "../../../substrate/primitives/core" } sp-tracing = { path = "../../../substrate/primitives/tracing" } xcm = { package = "staging-xcm", path = ".." } # temp diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs index 4b77199069d341320a67f196719604cedcc35157..d99da9184b5d8c7f2680b23140df1083d487b452 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs @@ -146,8 +146,6 @@ benchmarks_instance_pallet! { initiate_reserve_withdraw { let (sender_account, sender_location) = account_and_location::(1); - let holding = T::worst_case_holding(1); - let assets_filter = AssetFilter::Definite(holding.clone().into_inner().into_iter().take(MAX_ITEMS_IN_ASSETS).collect::>().into()); let reserve = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( @@ -157,15 +155,29 @@ benchmarks_instance_pallet! { ); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); + // generate holding and add possible required fees + let holding = if let Some(expected_assets_in_holding) = expected_assets_in_holding { + let mut holding = T::worst_case_holding(1 + expected_assets_in_holding.len() as u32); + for a in expected_assets_in_holding.into_inner() { + holding.push(a); + } + holding + } else { + T::worst_case_holding(1) + }; + let mut executor = new_executor::(sender_location); - executor.set_holding(holding.into()); + executor.set_holding(holding.clone().into()); if let Some(expected_fees_mode) = expected_fees_mode { executor.set_fees_mode(expected_fees_mode); } - if let Some(expected_assets_in_holding) = expected_assets_in_holding { - executor.set_holding(expected_assets_in_holding.into()); - } - let instruction = Instruction::InitiateReserveWithdraw { assets: assets_filter, reserve, xcm: Xcm(vec![]) }; + + let instruction = Instruction::InitiateReserveWithdraw { + // Worst case is looking through all holdings for every asset explicitly - respecting the limit `MAX_ITEMS_IN_ASSETS`. + assets: Definite(holding.into_inner().into_iter().take(MAX_ITEMS_IN_ASSETS).collect::>().into()), + reserve, + xcm: Xcm(vec![]) + }; let xcm = Xcm(vec![instruction]); }: { executor.bench_process(xcm)?; diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs index c831cd024659135fd3fd5d85a4406351945d887f..7233b46d0cd64d2f649fce9c007da1c1753bcff9 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs @@ -16,17 +16,16 @@ //! A mock runtime for XCM benchmarking. -use crate::{fungible as xcm_balances_benchmark, mock::*}; +use crate::{fungible as xcm_balances_benchmark, generate_holding_assets, mock::*}; use frame_benchmarking::BenchmarkError; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, Everything, Nothing}, - weights::Weight, + traits::{Everything, Nothing}, }; -use sp_core::H256; -use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; use xcm::latest::prelude::*; -use xcm_builder::{AllowUnpaidExecutionFrom, FrameTransactionalProcessor, MintLocation}; +use xcm_builder::{ + AllowUnpaidExecutionFrom, EnsureDecodableXcm, FrameTransactionalProcessor, MintLocation, +}; type Block = frame_system::mocking::MockBlock; @@ -40,37 +39,10 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, u64::MAX)); -} - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { @@ -121,7 +93,7 @@ parameter_types! { pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; - type XcmSender = DevNull; + type XcmSender = EnsureDecodableXcm; type AssetTransactor = AssetTransactor; type OriginConverter = (); type IsReserve = TrustedReserves; @@ -160,9 +132,8 @@ impl crate::Config for Test { Ok(valid_destination) } fn worst_case_holding(depositable_count: u32) -> Assets { - crate::mock_worst_case_holding( - depositable_count, - ::MaxAssetsIntoHolding::get(), + generate_holding_assets( + ::MaxAssetsIntoHolding::get() - depositable_count, ) } } diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs index 8c6ed4b5d0e0245a1758820bf5ab9ec9f7d095e9..760b21f93566e12b77a00f2c7b9b744f6db91d68 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs @@ -19,9 +19,9 @@ use crate::{account_and_location, new_executor, EnsureDelivery, XcmCallOf}; use codec::Encode; use frame_benchmarking::{benchmarks, BenchmarkError}; use frame_support::{dispatch::GetDispatchInfo, traits::fungible::Inspect}; -use sp_std::vec; +use sp_std::{prelude::*, vec}; use xcm::{ - latest::{prelude::*, MaxDispatchErrorLen, MaybeErrorCode, Weight}, + latest::{prelude::*, MaxDispatchErrorLen, MaybeErrorCode, Weight, MAX_ITEMS_IN_ASSETS}, DoubleEncoded, }; use xcm_executor::{ @@ -32,7 +32,6 @@ use xcm_executor::{ benchmarks! { report_holding { let (sender_account, sender_location) = account_and_location::(1); - let holding = T::worst_case_holding(0); let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( @@ -42,14 +41,22 @@ benchmarks! { ); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); + // generate holding and add possible required fees + let holding = if let Some(expected_assets_in_holding) = expected_assets_in_holding { + let mut holding = T::worst_case_holding(expected_assets_in_holding.len() as u32); + for a in expected_assets_in_holding.into_inner() { + holding.push(a); + } + holding + } else { + T::worst_case_holding(0) + }; + let mut executor = new_executor::(sender_location); executor.set_holding(holding.clone().into()); if let Some(expected_fees_mode) = expected_fees_mode { executor.set_fees_mode(expected_fees_mode); } - if let Some(expected_assets_in_holding) = expected_assets_in_holding { - executor.set_holding(expected_assets_in_holding.into()); - } let instruction = Instruction::>::ReportHolding { response_info: QueryResponseInfo { @@ -57,8 +64,8 @@ benchmarks! { query_id: Default::default(), max_weight: Weight::MAX, }, - // Worst case is looking through all holdings for every asset explicitly. - assets: Definite(holding), + // Worst case is looking through all holdings for every asset explicitly - respecting the limit `MAX_ITEMS_IN_ASSETS`. + assets: Definite(holding.into_inner().into_iter().take(MAX_ITEMS_IN_ASSETS).collect::>().into()), }; let xcm = Xcm(vec![instruction]); @@ -612,14 +619,19 @@ benchmarks! { let sender_account = T::AccountIdConverter::convert_location(&owner).unwrap(); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); + // generate holding and add possible required fees + let mut holding: Assets = asset.clone().into(); + if let Some(expected_assets_in_holding) = expected_assets_in_holding { + for a in expected_assets_in_holding.into_inner() { + holding.push(a); + } + }; + let mut executor = new_executor::(owner); - executor.set_holding(asset.clone().into()); + executor.set_holding(holding.into()); if let Some(expected_fees_mode) = expected_fees_mode { executor.set_fees_mode(expected_fees_mode); } - if let Some(expected_assets_in_holding) = expected_assets_in_holding { - executor.set_holding(expected_assets_in_holding.into()); - } let instruction = Instruction::LockAsset { asset, unlocker }; let xcm = Xcm(vec![instruction]); diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mock.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mock.rs index 534f7d85ea2e9aa9759aca4bda9e4759dbf49517..a9f4d37d7a55f5fa50cd6767447c2578774a7017 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mock.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mock.rs @@ -21,16 +21,15 @@ use codec::Decode; use frame_support::{ derive_impl, parameter_types, traits::{Contains, Everything, OriginTrait}, - weights::Weight, }; -use sp_core::H256; -use sp_runtime::traits::{BlakeTwo256, IdentityLookup, TrailingZeroInput}; +use sp_runtime::traits::TrailingZeroInput; use xcm_builder::{ test_utils::{ AssetsInHolding, TestAssetExchanger, TestAssetLocker, TestAssetTrap, TestSubscriptionService, TestUniversalAliases, }, - AliasForeignAccountId32, AllowUnpaidExecutionFrom, FrameTransactionalProcessor, + AliasForeignAccountId32, AllowUnpaidExecutionFrom, EnsureDecodableXcm, + FrameTransactionalProcessor, }; use xcm_executor::traits::ConvertOrigin; @@ -45,37 +44,10 @@ frame_support::construct_runtime!( } ); -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, u64::MAX)); -} - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } /// The benchmarks in this pallet should never need an asset transactor to begin with. @@ -110,7 +82,7 @@ type Aliasers = AliasForeignAccountId32; pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; - type XcmSender = DevNull; + type XcmSender = EnsureDecodableXcm; type AssetTransactor = NoAssetTransactor; type OriginConverter = AlwaysSignedByDefault; type IsReserve = AllAssetLocationsPass; @@ -161,9 +133,8 @@ impl crate::Config for Test { Ok(valid_destination) } fn worst_case_holding(depositable_count: u32) -> Assets { - crate::mock_worst_case_holding( - depositable_count, - ::MaxAssetsIntoHolding::get(), + generate_holding_assets( + ::MaxAssetsIntoHolding::get() - depositable_count, ) } } diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs index 63ed0ac0ca736450077a4677d29d65a81e9eaf3b..a43f27bf47e7242308adc1b53d146508f14e36b2 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs @@ -50,6 +50,8 @@ pub trait Config: frame_system::Config { fn valid_destination() -> Result; /// Worst case scenario for a holding account in this runtime. + /// - `depositable_count` specifies the count of assets we plan to add to the holding on top of + /// those generated by the `worst_case_holding` implementation. fn worst_case_holding(depositable_count: u32) -> Assets; } @@ -64,19 +66,22 @@ pub type AssetTransactorOf = <::XcmConfig as XcmConfig>::AssetTr /// The call type of executor's config. Should eventually resolve to the same overarching call type. pub type XcmCallOf = <::XcmConfig as XcmConfig>::RuntimeCall; -pub fn mock_worst_case_holding(depositable_count: u32, max_assets: u32) -> Assets { +pub fn generate_holding_assets(max_assets: u32) -> Assets { let fungibles_amount: u128 = 100; - let holding_fungibles = max_assets / 2 - depositable_count; - let holding_non_fungibles = holding_fungibles; + let holding_fungibles = max_assets / 2; + let holding_non_fungibles = max_assets - holding_fungibles - 1; // -1 because of adding `Here` asset + // add count of `holding_fungibles` (0..holding_fungibles) .map(|i| { Asset { id: AssetId(GeneralIndex(i as u128).into()), - fun: Fungible(fungibles_amount * i as u128), + fun: Fungible(fungibles_amount * (i + 1) as u128), // non-zero amount } .into() }) + // add one more `Here` asset .chain(core::iter::once(Asset { id: AssetId(Here.into()), fun: Fungible(u128::MAX) })) + // add count of `holding_non_fungibles` .chain((0..holding_non_fungibles).map(|i| Asset { id: AssetId(GeneralIndex(i as u128).into()), fun: NonFungible(asset_instance_from(i)), diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/mock.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/mock.rs index 78a9e5f8a018aad85fde699412a816fb54f30391..be3af5d4a3f325feb215d597e4328a90baf68911 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/mock.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/mock.rs @@ -58,7 +58,7 @@ impl xcm_executor::traits::ConvertLocation for AccountIdConverter { } parameter_types! { - pub UniversalLocation: InteriorLocation = Junction::Parachain(101).into(); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(ByGenesis([1; 32])), Junction::Parachain(101)].into(); pub UnitWeightCost: Weight = Weight::from_parts(10, 10); pub WeightPrice: (AssetId, u128, u128) = (AssetId(Here.into()), 1_000_000, 1024); } diff --git a/polkadot/xcm/pallet-xcm/src/benchmarking.rs b/polkadot/xcm/pallet-xcm/src/benchmarking.rs index e2903d592dc18aeafc7ef15d5ccc8d3fade1bcef..081a4235b7794b72beda33895c2e5e5c9577d219 100644 --- a/polkadot/xcm/pallet-xcm/src/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm/src/benchmarking.rs @@ -16,12 +16,8 @@ use super::*; use bounded_collections::{ConstU32, WeakBoundedVec}; -use codec::Encode; use frame_benchmarking::{benchmarks, whitelisted_caller, BenchmarkError, BenchmarkResult}; -use frame_support::{ - traits::fungible::{Inspect, Mutate}, - weights::Weight, -}; +use frame_support::{assert_ok, weights::Weight}; use frame_system::RawOrigin; use sp_std::prelude::*; use xcm::{latest::prelude::*, v2}; @@ -90,11 +86,6 @@ pub trait Config: crate::Config { } benchmarks! { - where_clause { - where - T: pallet_balances::Config, - ::Balance: From + Into, - } send { let send_origin = T::SendXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; @@ -109,31 +100,12 @@ benchmarks! { let versioned_msg = VersionedXcm::from(msg); }: _>(send_origin, Box::new(versioned_dest), Box::new(versioned_msg)) - send_blob { - let send_origin = - T::SendXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - if T::SendXcmOrigin::try_origin(send_origin.clone()).is_err() { - return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) - } - let msg = Xcm::<()>(vec![ClearOrigin]); - let versioned_dest: VersionedLocation = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )? - .into(); - let versioned_msg = VersionedXcm::from(msg); - let encoded_versioned_msg = versioned_msg.encode().try_into().unwrap(); - }: _>(send_origin, Box::new(versioned_dest), encoded_versioned_msg) - teleport_assets { let (asset, destination) = T::teleportable_asset_and_dest().ok_or( BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), )?; - let transferred_amount = match &asset.fun { - Fungible(amount) => *amount, - _ => return Err(BenchmarkError::Stop("Benchmark asset not fungible")), - }.into(); - let assets: Assets = asset.into(); + let assets: Assets = asset.clone().into(); let caller: T::AccountId = whitelisted_caller(); let send_origin = RawOrigin::Signed(caller.clone()); @@ -150,13 +122,26 @@ benchmarks! { FeeReason::ChargeFees, ); - // Actual balance (e.g. `ensure_successful_delivery` could drip delivery fees, ...) - let balance = as Inspect<_>>::balance(&caller); - // Add transferred_amount to origin - as Mutate<_>>::mint_into(&caller, transferred_amount)?; - // verify initial balance - let balance = balance + transferred_amount; - assert_eq!( as Inspect<_>>::balance(&caller), balance); + match &asset.fun { + Fungible(amount) => { + // Add transferred_amount to origin + ::AssetTransactor::deposit_asset( + &Asset { fun: Fungible(*amount), id: asset.id }, + &origin_location, + None, + ).map_err(|error| { + log::error!("Fungible asset couldn't be deposited, error: {:?}", error); + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) + })?; + }, + NonFungible(instance) => { + ::AssetTransactor::deposit_asset(&asset, &origin_location, None) + .map_err(|error| { + log::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) + })?; + } + }; let recipient = [0u8; 32]; let versioned_dest: VersionedLocation = destination.into(); @@ -164,21 +149,13 @@ benchmarks! { AccountId32 { network: None, id: recipient.into() }.into(); let versioned_assets: VersionedAssets = assets.into(); }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) - verify { - // verify balance after transfer, decreased by transferred amount (+ maybe XCM delivery fees) - assert!( as Inspect<_>>::balance(&caller) <= balance - transferred_amount); - } reserve_transfer_assets { let (asset, destination) = T::reserve_transferable_asset_and_dest().ok_or( BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), )?; - let transferred_amount = match &asset.fun { - Fungible(amount) => *amount, - _ => return Err(BenchmarkError::Stop("Benchmark asset not fungible")), - }.into(); - let assets: Assets = asset.into(); + let assets: Assets = asset.clone().into(); let caller: T::AccountId = whitelisted_caller(); let send_origin = RawOrigin::Signed(caller.clone()); @@ -195,23 +172,50 @@ benchmarks! { FeeReason::ChargeFees, ); - // Actual balance (e.g. `ensure_successful_delivery` could drip delivery fees, ...) - let balance = as Inspect<_>>::balance(&caller); - // Add transferred_amount to origin - as Mutate<_>>::mint_into(&caller, transferred_amount)?; - // verify initial balance - let balance = balance + transferred_amount; - assert_eq!( as Inspect<_>>::balance(&caller), balance); + match &asset.fun { + Fungible(amount) => { + // Add transferred_amount to origin + ::AssetTransactor::deposit_asset( + &Asset { fun: Fungible(*amount), id: asset.id.clone() }, + &origin_location, + None, + ).map_err(|error| { + log::error!("Fungible asset couldn't be deposited, error: {:?}", error); + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) + })?; + }, + NonFungible(instance) => { + ::AssetTransactor::deposit_asset(&asset, &origin_location, None) + .map_err(|error| { + log::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) + })?; + } + }; let recipient = [0u8; 32]; - let versioned_dest: VersionedLocation = destination.into(); + let versioned_dest: VersionedLocation = destination.clone().into(); let versioned_beneficiary: VersionedLocation = AccountId32 { network: None, id: recipient.into() }.into(); let versioned_assets: VersionedAssets = assets.into(); }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) verify { - // verify balance after transfer, decreased by transferred amount (+ maybe XCM delivery fees) - assert!( as Inspect<_>>::balance(&caller) <= balance - transferred_amount); + match &asset.fun { + Fungible(amount) => { + assert_ok!(::AssetTransactor::withdraw_asset( + &Asset { fun: Fungible(*amount), id: asset.id }, + &destination, + None, + )); + }, + NonFungible(instance) => { + assert_ok!(::AssetTransactor::withdraw_asset( + &asset, + &destination, + None, + )); + } + }; } transfer_assets { @@ -243,19 +247,6 @@ benchmarks! { let versioned_msg = VersionedXcm::from(msg); }: _>(execute_origin, Box::new(versioned_msg), Weight::MAX) - execute_blob { - let execute_origin = - T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - let origin_location = T::ExecuteXcmOrigin::try_origin(execute_origin.clone()) - .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; - let msg = Xcm(vec![ClearOrigin]); - if !T::XcmExecuteFilter::contains(&(origin_location, msg.clone())) { - return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) - } - let versioned_msg = VersionedXcm::from(msg); - let encoded_versioned_msg = versioned_msg.encode().try_into().unwrap(); - }: _>(execute_origin, encoded_versioned_msg, Weight::MAX) - force_xcm_version { let loc = T::reachable_dest().ok_or( BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index ef255068734aae29d879e16c575a8a5cea4c5e59..af3b66121ea131bba2b8a0e694e59763cd7eb4cc 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -45,13 +45,13 @@ use sp_runtime::{ AccountIdConversion, BadOrigin, BlakeTwo256, BlockNumberProvider, Dispatchable, Hash, Saturating, Zero, }, - RuntimeDebug, + Either, RuntimeDebug, }; use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec}; use xcm::{latest::QueryResponseInfo, prelude::*}; use xcm_builder::{ - ExecuteController, ExecuteControllerWeightInfo, MaxXcmEncodedSize, QueryController, - QueryControllerWeightInfo, SendController, SendControllerWeightInfo, + ExecuteController, ExecuteControllerWeightInfo, QueryController, QueryControllerWeightInfo, + SendController, SendControllerWeightInfo, }; use xcm_executor::{ traits::{ @@ -87,8 +87,6 @@ pub trait WeightInfo { fn new_query() -> Weight; fn take_response() -> Weight; fn claim_assets() -> Weight; - fn execute_blob() -> Weight; - fn send_blob() -> Weight; } /// fallback implementation @@ -173,14 +171,6 @@ impl WeightInfo for TestWeightInfo { fn claim_assets() -> Weight { Weight::from_parts(100_000_000, 0) } - - fn execute_blob() -> Weight { - Weight::from_parts(100_000_000, 0) - } - - fn send_blob() -> Weight { - Weight::from_parts(100_000_000, 0) - } } #[frame_support::pallet] @@ -296,49 +286,76 @@ pub mod pallet { } impl ExecuteControllerWeightInfo for Pallet { - fn execute_blob() -> Weight { - T::WeightInfo::execute_blob() + fn execute() -> Weight { + T::WeightInfo::execute() } } impl ExecuteController, ::RuntimeCall> for Pallet { type WeightInfo = Self; - fn execute_blob( + fn execute( origin: OriginFor, - encoded_message: BoundedVec, + message: Box::RuntimeCall>>, max_weight: Weight, ) -> Result { - let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; - let message = - VersionedXcm::<::RuntimeCall>::decode(&mut &encoded_message[..]) - .map_err(|error| { - log::error!(target: "xcm::execute_blob", "Unable to decode XCM, error: {:?}", error); - Error::::UnableToDecode - })?; - Self::execute_base(origin_location, Box::new(message), max_weight) + log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight); + let outcome = (|| { + let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let mut hash = message.using_encoded(sp_io::hashing::blake2_256); + let message = (*message).try_into().map_err(|()| Error::::BadVersion)?; + let value = (origin_location, message); + ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered); + let (origin_location, message) = value; + Ok(T::XcmExecutor::prepare_and_execute( + origin_location, + message, + &mut hash, + max_weight, + max_weight, + )) + })() + .map_err(|e: DispatchError| { + e.with_weight(::execute()) + })?; + + Self::deposit_event(Event::Attempted { outcome: outcome.clone() }); + let weight_used = outcome.weight_used(); + outcome.ensure_complete().map_err(|error| { + log::error!(target: "xcm::pallet_xcm::execute", "XCM execution failed with error {:?}", error); + Error::::LocalExecutionIncomplete.with_weight( + weight_used.saturating_add( + ::execute(), + ), + ) + })?; + Ok(weight_used) } } impl SendControllerWeightInfo for Pallet { - fn send_blob() -> Weight { - T::WeightInfo::send_blob() + fn send() -> Weight { + T::WeightInfo::send() } } impl SendController> for Pallet { type WeightInfo = Self; - fn send_blob( + fn send( origin: OriginFor, dest: Box, - encoded_message: BoundedVec, + message: Box>, ) -> Result { let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; - let message = - VersionedXcm::<()>::decode(&mut &encoded_message[..]).map_err(|error| { - log::error!(target: "xcm::send_blob", "Unable to decode XCM, error: {:?}", error); - Error::::UnableToDecode - })?; - Self::send_base(origin_location, dest, Box::new(message)) + let interior: Junctions = + origin_location.clone().try_into().map_err(|_| Error::::InvalidOrigin)?; + let dest = Location::try_from(*dest).map_err(|()| Error::::BadVersion)?; + let message: Xcm<()> = (*message).try_into().map_err(|()| Error::::BadVersion)?; + + let message_id = Self::send_xcm(interior, dest.clone(), message.clone()) + .map_err(Error::::from)?; + let e = Event::Sent { origin: origin_location, destination: dest, message, message_id }; + Self::deposit_event(e); + Ok(message_id) } } @@ -535,21 +552,18 @@ pub mod pallet { LockNotFound, /// The unlock operation cannot succeed because there are still consumers of the lock. InUse, - /// Invalid non-concrete asset. - InvalidAssetNotConcrete, /// Invalid asset, reserve chain could not be determined for it. + #[codec(index = 21)] InvalidAssetUnknownReserve, /// Invalid asset, do not support remote asset reserves with different fees reserves. + #[codec(index = 22)] InvalidAssetUnsupportedReserve, /// Too many assets with different reserve locations have been attempted for transfer. + #[codec(index = 23)] TooManyReserves, /// Local XCM execution incomplete. + #[codec(index = 24)] LocalExecutionIncomplete, - /// Could not decode XCM. - UnableToDecode, - /// XCM encoded length is too large. - /// Returned when an XCM encoded length is larger than `MaxXcmEncodedSize`. - XcmTooLarge, } impl From for Error { @@ -565,7 +579,6 @@ pub mod pallet { impl From for Error { fn from(e: AssetTransferError) -> Self { match e { - AssetTransferError::NotConcrete => Error::::InvalidAssetNotConcrete, AssetTransferError::UnknownReserve => Error::::InvalidAssetUnknownReserve, } } @@ -887,72 +900,15 @@ pub mod pallet { } } - impl Pallet { - /// Underlying logic for both [`execute_blob`] and [`execute`]. - fn execute_base( - origin_location: Location, - message: Box::RuntimeCall>>, - max_weight: Weight, - ) -> Result { - log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight); - let outcome = (|| { - let mut hash = message.using_encoded(sp_io::hashing::blake2_256); - let message = (*message).try_into().map_err(|()| Error::::BadVersion)?; - let value = (origin_location, message); - ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered); - let (origin_location, message) = value; - Ok(T::XcmExecutor::prepare_and_execute( - origin_location, - message, - &mut hash, - max_weight, - max_weight, - )) - })() - .map_err(|e: DispatchError| e.with_weight(T::WeightInfo::execute()))?; - - Self::deposit_event(Event::Attempted { outcome: outcome.clone() }); - let weight_used = outcome.weight_used(); - outcome.ensure_complete().map_err(|error| { - log::error!(target: "xcm::pallet_xcm::execute", "XCM execution failed with error {:?}", error); - Error::::LocalExecutionIncomplete - .with_weight(weight_used.saturating_add(T::WeightInfo::execute())) - })?; - Ok(weight_used) - } - - /// Underlying logic for both [`send_blob`] and [`send`]. - fn send_base( - origin_location: Location, - dest: Box, - message: Box>, - ) -> Result { - let interior: Junctions = - origin_location.clone().try_into().map_err(|_| Error::::InvalidOrigin)?; - let dest = Location::try_from(*dest).map_err(|()| Error::::BadVersion)?; - let message: Xcm<()> = (*message).try_into().map_err(|()| Error::::BadVersion)?; - - let message_id = Self::send_xcm(interior, dest.clone(), message.clone()) - .map_err(Error::::from)?; - let e = Event::Sent { origin: origin_location, destination: dest, message, message_id }; - Self::deposit_event(e); - Ok(message_id) - } - } - #[pallet::call(weight(::WeightInfo))] impl Pallet { - /// WARNING: DEPRECATED. `send` will be removed after June 2024. Use `send_blob` instead. - #[allow(deprecated)] - #[deprecated(note = "`send` will be removed after June 2024. Use `send_blob` instead.")] #[pallet::call_index(0)] pub fn send( origin: OriginFor, dest: Box, message: Box>, ) -> DispatchResult { - let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; - Self::send_base(origin_location, dest, message)?; + >::send(origin, dest, message)?; Ok(()) } @@ -1049,13 +1005,6 @@ pub mod pallet { /// No more than `max_weight` will be used in its attempted execution. If this is less than /// the maximum amount of weight that the message could take to be executed, then no /// execution attempt will be made. - /// - /// WARNING: DEPRECATED. `execute` will be removed after June 2024. Use `execute_blob` - /// instead. - #[allow(deprecated)] - #[deprecated( - note = "`execute` will be removed after June 2024. Use `execute_blob` instead." - )] #[pallet::call_index(3)] #[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))] pub fn execute( @@ -1063,8 +1012,8 @@ pub mod pallet { message: Box::RuntimeCall>>, max_weight: Weight, ) -> DispatchResultWithPostInfo { - let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; - let weight_used = Self::execute_base(origin_location, message, max_weight)?; + let weight_used = + >::execute(origin, message, max_weight)?; Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into()) } @@ -1299,64 +1248,20 @@ pub mod pallet { ); ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); - let mut assets = assets.into_inner(); + let assets = assets.into_inner(); let fee_asset_item = fee_asset_item as usize; - let fees = assets.get(fee_asset_item as usize).ok_or(Error::::Empty)?.clone(); // Find transfer types for fee and non-fee assets. let (fees_transfer_type, assets_transfer_type) = Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?; - // local and remote XCM programs to potentially handle fees separately - let fees = if fees_transfer_type == assets_transfer_type { - // no need for custom fees instructions, fees are batched with assets - FeesHandling::Batched { fees } - } else { - // Disallow _remote reserves_ unless assets & fees have same remote reserve (covered - // by branch above). The reason for this is that we'd need to send XCMs to separate - // chains with no guarantee of delivery order on final destination; therefore we - // cannot guarantee to have fees in place on final destination chain to pay for - // assets transfer. - ensure!( - !matches!(assets_transfer_type, TransferType::RemoteReserve(_)), - Error::::InvalidAssetUnsupportedReserve - ); - let weight_limit = weight_limit.clone(); - // remove `fees` from `assets` and build separate fees transfer instructions to be - // added to assets transfers XCM programs - let fees = assets.remove(fee_asset_item); - let (local_xcm, remote_xcm) = match fees_transfer_type { - TransferType::LocalReserve => Self::local_reserve_fees_instructions( - origin.clone(), - dest.clone(), - fees, - weight_limit, - )?, - TransferType::DestinationReserve => - Self::destination_reserve_fees_instructions( - origin.clone(), - dest.clone(), - fees, - weight_limit, - )?, - TransferType::Teleport => Self::teleport_fees_instructions( - origin.clone(), - dest.clone(), - fees, - weight_limit, - )?, - TransferType::RemoteReserve(_) => - return Err(Error::::InvalidAssetUnsupportedReserve.into()), - }; - FeesHandling::Separate { local_xcm, remote_xcm } - }; - - Self::build_and_execute_xcm_transfer_type( + Self::do_transfer_assets( origin, dest, - beneficiary, + Either::Left(beneficiary), assets, assets_transfer_type, - fees, + fee_asset_item, + fees_transfer_type, weight_limit, ) } @@ -1403,45 +1308,95 @@ pub mod pallet { Ok(()) } - /// Execute an XCM from a local, signed, origin. + /// Transfer assets from the local chain to the destination chain using explicit transfer + /// types for assets and fees. /// - /// An event is deposited indicating whether the message could be executed completely - /// or only partially. + /// `assets` must have same reserve location or may be teleportable to `dest`. Caller must + /// provide the `assets_transfer_type` to be used for `assets`: + /// - `TransferType::LocalReserve`: transfer assets to sovereign account of destination + /// chain and forward a notification XCM to `dest` to mint and deposit reserve-based + /// assets to `beneficiary`. + /// - `TransferType::DestinationReserve`: burn local assets and forward a notification to + /// `dest` chain to withdraw the reserve assets from this chain's sovereign account and + /// deposit them to `beneficiary`. + /// - `TransferType::RemoteReserve(reserve)`: burn local assets, forward XCM to `reserve` + /// chain to move reserves from this chain's SA to `dest` chain's SA, and forward another + /// XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. Typically + /// the remote `reserve` is Asset Hub. + /// - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to + /// mint/teleport assets and deposit them to `beneficiary`. /// - /// No more than `max_weight` will be used in its attempted execution. If this is less than - /// the maximum amount of weight that the message could take to be executed, then no - /// execution attempt will be made. + /// On the destination chain, as well as any intermediary hops, `BuyExecution` is used to + /// buy execution using transferred `assets` identified by `remote_fees_id`. + /// Make sure enough of the specified `remote_fees_id` asset is included in the given list + /// of `assets`. `remote_fees_id` should be enough to pay for `weight_limit`. If more weight + /// is needed than `weight_limit`, then the operation will fail and the sent assets may be + /// at risk. /// - /// The message is passed in encoded. It needs to be decodable as a [`VersionedXcm`]. - #[pallet::call_index(13)] - #[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute_blob()))] - pub fn execute_blob( - origin: OriginFor, - encoded_message: BoundedVec, - max_weight: Weight, - ) -> DispatchResultWithPostInfo { - let weight_used = >::execute_blob( - origin, - encoded_message, - max_weight, - )?; - Ok(Some(weight_used.saturating_add(T::WeightInfo::execute_blob())).into()) - } - - /// Send an XCM from a local, signed, origin. + /// `remote_fees_id` may use different transfer type than rest of `assets` and can be + /// specified through `fees_transfer_type`. /// - /// The destination, `dest`, will receive this message with a `DescendOrigin` instruction - /// that makes the origin of the message be the origin on this system. + /// The caller needs to specify what should happen to the transferred assets once they reach + /// the `dest` chain. This is done through the `custom_xcm_on_dest` parameter, which + /// contains the instructions to execute on `dest` as a final step. + /// This is usually as simple as: + /// `Xcm(vec![DepositAsset { assets: Wild(AllCounted(assets.len())), beneficiary }])`, + /// but could be something more exotic like sending the `assets` even further. /// - /// The message is passed in encoded. It needs to be decodable as a [`VersionedXcm`]. - #[pallet::call_index(14)] - pub fn send_blob( + /// - `origin`: Must be capable of withdrawing the `assets` and executing XCM. + /// - `dest`: Destination context for the assets. Will typically be `[Parent, + /// Parachain(..)]` to send from parachain to parachain, or `[Parachain(..)]` to send from + /// relay to parachain, or `(parents: 2, (GlobalConsensus(..), ..))` to send from + /// parachain across a bridge to another ecosystem destination. + /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the + /// fee on the `dest` (and possibly reserve) chains. + /// - `assets_transfer_type`: The XCM `TransferType` used to transfer the `assets`. + /// - `remote_fees_id`: One of the included `assets` to be be used to pay fees. + /// - `fees_transfer_type`: The XCM `TransferType` used to transfer the `fees` assets. + /// - `custom_xcm_on_dest`: The XCM to be executed on `dest` chain as the last step of the + /// transfer, which also determines what happens to the assets on the destination chain. + /// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase. + #[pallet::call_index(13)] + #[pallet::weight(T::WeightInfo::transfer_assets())] + pub fn transfer_assets_using_type_and_then( origin: OriginFor, dest: Box, - encoded_message: BoundedVec, + assets: Box, + assets_transfer_type: Box, + remote_fees_id: Box, + fees_transfer_type: Box, + custom_xcm_on_dest: Box>, + weight_limit: WeightLimit, ) -> DispatchResult { - >::send_blob(origin, dest, encoded_message)?; - Ok(()) + let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let dest: Location = (*dest).try_into().map_err(|()| Error::::BadVersion)?; + let assets: Assets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; + let fees_id: AssetId = + (*remote_fees_id).try_into().map_err(|()| Error::::BadVersion)?; + let remote_xcm: Xcm<()> = + (*custom_xcm_on_dest).try_into().map_err(|()| Error::::BadVersion)?; + log::debug!( + target: "xcm::pallet_xcm::transfer_assets_using_type_and_then", + "origin {origin_location:?}, dest {dest:?}, assets {assets:?} through {assets_transfer_type:?}, \ + remote_fees_id {fees_id:?} through {fees_transfer_type:?}, \ + custom_xcm_on_dest {remote_xcm:?}, weight-limit {weight_limit:?}", + ); + + let assets = assets.into_inner(); + ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); + + let fee_asset_index = + assets.iter().position(|a| a.id == fees_id).ok_or(Error::::FeesNotMet)?; + Self::do_transfer_assets( + origin_location, + dest, + Either::Right(remote_xcm), + assets, + *assets_transfer_type, + fee_asset_index, + *fees_transfer_type, + weight_limit, + ) } } } @@ -1607,15 +1562,16 @@ impl Pallet { // Ensure all assets (including fees) have same reserve location. ensure!(assets_transfer_type == fees_transfer_type, Error::::TooManyReserves); - Self::build_and_execute_xcm_transfer_type( - origin, - dest, - beneficiary, + let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type( + origin.clone(), + dest.clone(), + Either::Left(beneficiary), assets, assets_transfer_type, FeesHandling::Batched { fees }, weight_limit, - ) + )?; + Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm) } fn do_teleport_assets( @@ -1648,83 +1604,158 @@ impl Pallet { } let fees = assets.get(fee_asset_item as usize).ok_or(Error::::Empty)?.clone(); - Self::build_and_execute_xcm_transfer_type( - origin_location, - dest, - beneficiary, + let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type( + origin_location.clone(), + dest.clone(), + Either::Left(beneficiary), assets, TransferType::Teleport, FeesHandling::Batched { fees }, weight_limit, - ) + )?; + Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm) } - fn build_and_execute_xcm_transfer_type( + fn do_transfer_assets( origin: Location, dest: Location, - beneficiary: Location, - assets: Vec, - transfer_type: TransferType, - fees: FeesHandling, + beneficiary: Either>, + mut assets: Vec, + assets_transfer_type: TransferType, + fee_asset_index: usize, + fees_transfer_type: TransferType, weight_limit: WeightLimit, ) -> DispatchResult { - log::debug!( - target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type", - "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \ - fees_handling {:?}, weight_limit: {:?}", - origin, dest, beneficiary, assets, transfer_type, fees, weight_limit, - ); - let (mut local_xcm, remote_xcm) = match transfer_type { - TransferType::LocalReserve => { - let (local, remote) = Self::local_reserve_transfer_programs( + // local and remote XCM programs to potentially handle fees separately + let fees = if fees_transfer_type == assets_transfer_type { + let fees = assets.get(fee_asset_index).ok_or(Error::::Empty)?.clone(); + // no need for custom fees instructions, fees are batched with assets + FeesHandling::Batched { fees } + } else { + // Disallow _remote reserves_ unless assets & fees have same remote reserve (covered + // by branch above). The reason for this is that we'd need to send XCMs to separate + // chains with no guarantee of delivery order on final destination; therefore we + // cannot guarantee to have fees in place on final destination chain to pay for + // assets transfer. + ensure!( + !matches!(assets_transfer_type, TransferType::RemoteReserve(_)), + Error::::InvalidAssetUnsupportedReserve + ); + let weight_limit = weight_limit.clone(); + // remove `fees` from `assets` and build separate fees transfer instructions to be + // added to assets transfers XCM programs + let fees = assets.remove(fee_asset_index); + let (local_xcm, remote_xcm) = match fees_transfer_type { + TransferType::LocalReserve => Self::local_reserve_fees_instructions( origin.clone(), dest.clone(), - beneficiary, - assets, fees, weight_limit, - )?; - (local, Some(remote)) - }, - TransferType::DestinationReserve => { - let (local, remote) = Self::destination_reserve_transfer_programs( + )?, + TransferType::DestinationReserve => Self::destination_reserve_fees_instructions( origin.clone(), dest.clone(), - beneficiary, - assets, fees, weight_limit, - )?; - (local, Some(remote)) - }, + )?, + TransferType::Teleport => Self::teleport_fees_instructions( + origin.clone(), + dest.clone(), + fees, + weight_limit, + )?, + TransferType::RemoteReserve(_) => + return Err(Error::::InvalidAssetUnsupportedReserve.into()), + }; + FeesHandling::Separate { local_xcm, remote_xcm } + }; + + let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type( + origin.clone(), + dest.clone(), + beneficiary, + assets, + assets_transfer_type, + fees, + weight_limit, + )?; + Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm) + } + + fn build_xcm_transfer_type( + origin: Location, + dest: Location, + beneficiary: Either>, + assets: Vec, + transfer_type: TransferType, + fees: FeesHandling, + weight_limit: WeightLimit, + ) -> Result<(Xcm<::RuntimeCall>, Option>), Error> { + log::debug!( + target: "xcm::pallet_xcm::build_xcm_transfer_type", + "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \ + fees_handling {:?}, weight_limit: {:?}", + origin, dest, beneficiary, assets, transfer_type, fees, weight_limit, + ); + match transfer_type { + TransferType::LocalReserve => Self::local_reserve_transfer_programs( + origin.clone(), + dest.clone(), + beneficiary, + assets, + fees, + weight_limit, + ) + .map(|(local, remote)| (local, Some(remote))), + TransferType::DestinationReserve => Self::destination_reserve_transfer_programs( + origin.clone(), + dest.clone(), + beneficiary, + assets, + fees, + weight_limit, + ) + .map(|(local, remote)| (local, Some(remote))), TransferType::RemoteReserve(reserve) => { let fees = match fees { FeesHandling::Batched { fees } => fees, _ => return Err(Error::::InvalidAssetUnsupportedReserve.into()), }; - let local = Self::remote_reserve_transfer_program( + Self::remote_reserve_transfer_program( origin.clone(), - reserve, - dest.clone(), + reserve.try_into().map_err(|()| Error::::BadVersion)?, beneficiary, - assets, - fees, - weight_limit, - )?; - (local, None) - }, - TransferType::Teleport => { - let (local, remote) = Self::teleport_assets_program( - origin.clone(), dest.clone(), - beneficiary, assets, fees, weight_limit, - )?; - (local, Some(remote)) + ) + .map(|local| (local, None)) }, - }; + TransferType::Teleport => Self::teleport_assets_program( + origin.clone(), + dest.clone(), + beneficiary, + assets, + fees, + weight_limit, + ) + .map(|(local, remote)| (local, Some(remote))), + } + } + + fn execute_xcm_transfer( + origin: Location, + dest: Location, + mut local_xcm: Xcm<::RuntimeCall>, + remote_xcm: Option>, + ) -> DispatchResult { + log::debug!( + target: "xcm::pallet_xcm::execute_xcm_transfer", + "origin {:?}, dest {:?}, local_xcm {:?}, remote_xcm {:?}", + origin, dest, local_xcm, remote_xcm, + ); + let weight = T::Weigher::weight(&mut local_xcm).map_err(|()| Error::::UnweighableMessage)?; let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256); @@ -1738,7 +1769,7 @@ impl Pallet { Self::deposit_event(Event::Attempted { outcome: outcome.clone() }); outcome.ensure_complete().map_err(|error| { log::error!( - target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type", + target: "xcm::pallet_xcm::execute_xcm_transfer", "XCM execution failed with error {:?}", error ); Error::::LocalExecutionIncomplete @@ -1750,7 +1781,7 @@ impl Pallet { if origin != Here.into_location() { Self::charge_fees(origin.clone(), price).map_err(|error| { log::error!( - target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type", + target: "xcm::pallet_xcm::execute_xcm_transfer", "Unable to charge fee with error {:?}", error ); Error::::FeesNotMet @@ -1825,7 +1856,7 @@ impl Pallet { fn local_reserve_transfer_programs( origin: Location, dest: Location, - beneficiary: Location, + beneficiary: Either>, assets: Vec, fees: FeesHandling, weight_limit: WeightLimit, @@ -1858,10 +1889,16 @@ impl Pallet { ]); // handle fees Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; - // deposit all remaining assets in holding to `beneficiary` location - xcm_on_dest - .inner_mut() - .push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); + + // Use custom XCM on remote chain, or just default to depositing everything to beneficiary. + let custom_remote_xcm = match beneficiary { + Either::Right(custom_xcm) => custom_xcm, + Either::Left(beneficiary) => { + // deposit all remaining assets in holding to `beneficiary` location + Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }]) + }, + }; + xcm_on_dest.0.extend(custom_remote_xcm.into_iter()); Ok((local_execute_xcm, xcm_on_dest)) } @@ -1900,7 +1937,7 @@ impl Pallet { fn destination_reserve_transfer_programs( origin: Location, dest: Location, - beneficiary: Location, + beneficiary: Either>, assets: Vec, fees: FeesHandling, weight_limit: WeightLimit, @@ -1935,10 +1972,16 @@ impl Pallet { ]); // handle fees Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; - // deposit all remaining assets in holding to `beneficiary` location - xcm_on_dest - .inner_mut() - .push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); + + // Use custom XCM on remote chain, or just default to depositing everything to beneficiary. + let custom_remote_xcm = match beneficiary { + Either::Right(custom_xcm) => custom_xcm, + Either::Left(beneficiary) => { + // deposit all remaining assets in holding to `beneficiary` location + Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }]) + }, + }; + xcm_on_dest.0.extend(custom_remote_xcm.into_iter()); Ok((local_execute_xcm, xcm_on_dest)) } @@ -1947,8 +1990,8 @@ impl Pallet { fn remote_reserve_transfer_program( origin: Location, reserve: Location, + beneficiary: Either>, dest: Location, - beneficiary: Location, assets: Vec, fees: Asset, weight_limit: WeightLimit, @@ -1973,10 +2016,17 @@ impl Pallet { // identifies `dest` as seen by `reserve` let dest = dest.reanchored(&reserve, &context).map_err(|_| Error::::CannotReanchor)?; // xcm to be executed at dest - let xcm_on_dest = Xcm(vec![ - BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }, - DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, - ]); + let mut xcm_on_dest = + Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]); + // Use custom XCM on remote chain, or just default to depositing everything to beneficiary. + let custom_xcm_on_dest = match beneficiary { + Either::Right(custom_xcm) => custom_xcm, + Either::Left(beneficiary) => { + // deposit all remaining assets in holding to `beneficiary` location + Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }]) + }, + }; + xcm_on_dest.0.extend(custom_xcm_on_dest.into_iter()); // xcm to be executed on reserve let xcm_on_reserve = Xcm(vec![ BuyExecution { fees: reserve_fees, weight_limit }, @@ -2048,7 +2098,7 @@ impl Pallet { fn teleport_assets_program( origin: Location, dest: Location, - beneficiary: Location, + beneficiary: Either>, assets: Vec, fees: FeesHandling, weight_limit: WeightLimit, @@ -2108,10 +2158,16 @@ impl Pallet { ]); // handle fees Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; - // deposit all remaining assets in holding to `beneficiary` location - xcm_on_dest - .inner_mut() - .push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); + + // Use custom XCM on remote chain, or just default to depositing everything to beneficiary. + let custom_remote_xcm = match beneficiary { + Either::Right(custom_xcm) => custom_xcm, + Either::Left(beneficiary) => { + // deposit all remaining assets in holding to `beneficiary` location + Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }]) + }, + }; + xcm_on_dest.0.extend(custom_remote_xcm.into_iter()); Ok((local_execute_xcm, xcm_on_dest)) } diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index 2cc228476ba8e18dace2fc950d7253cba61cd8a4..8e94803e84315285391c0f85e5d249330a3db31b 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -33,10 +33,11 @@ use xcm::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, Case, ChildParachainAsNative, ChildParachainConvertsVia, - ChildSystemParachainAsSuperuser, DescribeAllTerminal, FixedRateOfFungible, FixedWeightBounds, - FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, HashedDescription, IsConcrete, - MatchedConvertedConcreteId, NoChecking, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, XcmFeeManagerFromComponents, XcmFeeToAccount, + ChildSystemParachainAsSuperuser, DescribeAllTerminal, EnsureDecodableXcm, FixedRateOfFungible, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, + HashedDescription, IsConcrete, MatchedConvertedConcreteId, NoChecking, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{ traits::{Identity, JustTry}, @@ -413,7 +414,7 @@ parameter_types! { )), }; pub const AnyNetwork: Option = None; - pub UniversalLocation: InteriorLocation = Here; + pub UniversalLocation: InteriorLocation = GlobalConsensus(ByGenesis([0; 32])).into(); pub UnitWeightCost: u64 = 1_000; pub CheckingAccount: AccountId = XcmPallet::check_account(); } @@ -488,7 +489,8 @@ pub type Barrier = ( AllowSubscriptionsFrom, ); -pub type XcmRouter = (TestPaidForPara3000SendXcm, TestSendXcmErrX8, TestSendXcm); +pub type XcmRouter = + EnsureDecodableXcm<(TestPaidForPara3000SendXcm, TestSendXcmErrX8, TestSendXcm)>; pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { diff --git a/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs b/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs index 7dc05c1cc70e69f7de32d26356eaacdf52d9debf..f42e220d693203f7fa6e1391676f36a84feb9be0 100644 --- a/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs +++ b/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs @@ -22,12 +22,12 @@ use crate::{ DispatchResult, OriginFor, }; use frame_support::{ - assert_ok, + assert_err, assert_ok, traits::{tokens::fungibles::Inspect, Currency}, weights::Weight, }; use polkadot_parachain_primitives::primitives::Id as ParaId; -use sp_runtime::{traits::AccountIdConversion, DispatchError, ModuleError}; +use sp_runtime::traits::AccountIdConversion; use xcm::prelude::*; use xcm_executor::traits::ConvertLocation; @@ -112,14 +112,8 @@ fn limited_teleport_filtered_assets_disallowed() { 0, Unlimited, ); - assert_eq!( - result, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered") - })) - ); + let expected_result = Err(crate::Error::::Filtered.into()); + assert_eq!(result, expected_result); }); } @@ -365,11 +359,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_local_fee_reserve_works( /// Test `limited_teleport_assets` with local asset reserve and local fee reserve disallowed. #[test] fn teleport_assets_with_local_asset_reserve_and_local_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); local_asset_reserve_and_local_fee_reserve_call( XcmPallet::limited_teleport_assets, expected_result, @@ -527,11 +517,7 @@ fn transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_works() /// disallowed. #[test] fn reserve_transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [23, 0, 0, 0], - message: Some("TooManyReserves"), - })); + let expected_result = Err(crate::Error::::TooManyReserves.into()); destination_asset_reserve_and_local_fee_reserve_call( XcmPallet::limited_reserve_transfer_assets, expected_result, @@ -542,11 +528,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_ /// disallowed. #[test] fn teleport_assets_with_destination_asset_reserve_and_local_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); destination_asset_reserve_and_local_fee_reserve_call( XcmPallet::limited_teleport_assets, expected_result, @@ -633,11 +615,7 @@ fn remote_asset_reserve_and_local_fee_reserve_call_disallowed( /// Test `transfer_assets` with remote asset reserve and local fee reserve is disallowed. #[test] fn transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve"), - })); + let expected_result = Err(crate::Error::::InvalidAssetUnsupportedReserve.into()); remote_asset_reserve_and_local_fee_reserve_call_disallowed( XcmPallet::transfer_assets, expected_result, @@ -648,11 +626,7 @@ fn transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disallowed() /// disallowed. #[test] fn reserve_transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [23, 0, 0, 0], - message: Some("TooManyReserves"), - })); + let expected_result = Err(crate::Error::::TooManyReserves.into()); remote_asset_reserve_and_local_fee_reserve_call_disallowed( XcmPallet::limited_reserve_transfer_assets, expected_result, @@ -662,11 +636,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disal /// Test `limited_teleport_assets` with remote asset reserve and local fee reserve is disallowed. #[test] fn teleport_assets_with_remote_asset_reserve_and_local_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); remote_asset_reserve_and_local_fee_reserve_call_disallowed( XcmPallet::limited_teleport_assets, expected_result, @@ -745,7 +715,7 @@ fn local_asset_reserve_and_destination_fee_reserve_call( assert_eq!(result, expected_result); if expected_result.is_err() { // short-circuit here for tests where we expect failure - return + return; } let weight = BaseXcmWeight::get() * 3; @@ -821,11 +791,7 @@ fn transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_works() /// disallowed. #[test] fn reserve_transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [23, 0, 0, 0], - message: Some("TooManyReserves"), - })); + let expected_result = Err(crate::Error::::TooManyReserves.into()); local_asset_reserve_and_destination_fee_reserve_call( XcmPallet::limited_reserve_transfer_assets, expected_result, @@ -835,11 +801,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_ /// Test `limited_teleport_assets` with local asset reserve and destination fee reserve disallowed. #[test] fn teleport_assets_with_local_asset_reserve_and_destination_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); local_asset_reserve_and_destination_fee_reserve_call( XcmPallet::limited_teleport_assets, expected_result, @@ -993,11 +955,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_destination_fee_re /// disallowed. #[test] fn teleport_assets_with_destination_asset_reserve_and_destination_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); destination_asset_reserve_and_destination_fee_reserve_call( XcmPallet::limited_teleport_assets, expected_result, @@ -1102,11 +1060,7 @@ fn remote_asset_reserve_and_destination_fee_reserve_call_disallowed( /// Test `transfer_assets` with remote asset reserve and destination fee reserve is disallowed. #[test] fn transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve"), - })); + let expected_result = Err(crate::Error::::InvalidAssetUnsupportedReserve.into()); remote_asset_reserve_and_destination_fee_reserve_call_disallowed( XcmPallet::transfer_assets, expected_result, @@ -1117,11 +1071,7 @@ fn transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve_disallo /// disallowed. #[test] fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [23, 0, 0, 0], - message: Some("TooManyReserves"), - })); + let expected_result = Err(crate::Error::::TooManyReserves.into()); remote_asset_reserve_and_destination_fee_reserve_call_disallowed( XcmPallet::limited_reserve_transfer_assets, expected_result, @@ -1132,11 +1082,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve /// disallowed. #[test] fn teleport_assets_with_remote_asset_reserve_and_destination_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); remote_asset_reserve_and_destination_fee_reserve_call_disallowed( XcmPallet::limited_teleport_assets, expected_result, @@ -1222,11 +1168,7 @@ fn local_asset_reserve_and_remote_fee_reserve_call_disallowed( /// Test `transfer_assets` with local asset reserve and remote fee reserve is disallowed. #[test] fn transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve"), - })); + let expected_result = Err(crate::Error::::InvalidAssetUnsupportedReserve.into()); local_asset_reserve_and_remote_fee_reserve_call_disallowed( XcmPallet::transfer_assets, expected_result, @@ -1237,11 +1179,7 @@ fn transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disallowed() /// disallowed. #[test] fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [23, 0, 0, 0], - message: Some("TooManyReserves"), - })); + let expected_result = Err(crate::Error::::TooManyReserves.into()); local_asset_reserve_and_remote_fee_reserve_call_disallowed( XcmPallet::limited_reserve_transfer_assets, expected_result, @@ -1251,11 +1189,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disal /// Test `limited_teleport_assets` with local asset reserve and remote fee reserve is disallowed. #[test] fn teleport_assets_with_local_asset_reserve_and_remote_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); local_asset_reserve_and_remote_fee_reserve_call_disallowed( XcmPallet::limited_teleport_assets, expected_result, @@ -1366,11 +1300,7 @@ fn destination_asset_reserve_and_remote_fee_reserve_call_disallowed( /// Test `transfer_assets` with destination asset reserve and remote fee reserve is disallowed. #[test] fn transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve"), - })); + let expected_result = Err(crate::Error::::InvalidAssetUnsupportedReserve.into()); destination_asset_reserve_and_remote_fee_reserve_call_disallowed( XcmPallet::transfer_assets, expected_result, @@ -1381,11 +1311,7 @@ fn transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve_disallo /// disallowed. #[test] fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [23, 0, 0, 0], - message: Some("TooManyReserves"), - })); + let expected_result = Err(crate::Error::::TooManyReserves.into()); destination_asset_reserve_and_remote_fee_reserve_call_disallowed( XcmPallet::limited_reserve_transfer_assets, expected_result, @@ -1396,11 +1322,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve /// disallowed. #[test] fn teleport_assets_with_destination_asset_reserve_and_remote_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); destination_asset_reserve_and_remote_fee_reserve_call_disallowed( XcmPallet::limited_teleport_assets, expected_result, @@ -1485,7 +1407,7 @@ fn remote_asset_reserve_and_remote_fee_reserve_call( assert_eq!(result, expected_result); if expected_result.is_err() { // short-circuit here for tests where we expect failure - return + return; } assert!(matches!( @@ -1558,11 +1480,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_work /// disallowed. #[test] fn teleport_assets_with_remote_asset_reserve_and_remote_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); remote_asset_reserve_and_remote_fee_reserve_call( XcmPallet::limited_teleport_assets, expected_result, @@ -1702,11 +1620,7 @@ fn transfer_assets_with_local_asset_reserve_and_teleported_fee_works() { /// Test `limited_reserve_transfer_assets` with local asset reserve and teleported fee disallowed. #[test] fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [23, 0, 0, 0], - message: Some("TooManyReserves"), - })); + let expected_result = Err(crate::Error::::TooManyReserves.into()); local_asset_reserve_and_teleported_fee_call( XcmPallet::limited_reserve_transfer_assets, expected_result, @@ -1716,11 +1630,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_disallowe /// Test `limited_teleport_assets` with local asset reserve and teleported fee disallowed. #[test] fn teleport_assets_with_local_asset_reserve_and_teleported_fee_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); local_asset_reserve_and_teleported_fee_call( XcmPallet::limited_teleport_assets, expected_result, @@ -1802,7 +1712,7 @@ fn destination_asset_reserve_and_teleported_fee_call( assert_eq!(result, expected_result); if expected_result.is_err() { // short-circuit here for tests where we expect failure - return + return; } let weight = BaseXcmWeight::get() * 4; @@ -1891,11 +1801,7 @@ fn transfer_assets_with_destination_asset_reserve_and_teleported_fee_works() { /// disallowed. #[test] fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [23, 0, 0, 0], - message: Some("TooManyReserves"), - })); + let expected_result = Err(crate::Error::::TooManyReserves.into()); destination_asset_reserve_and_teleported_fee_call( XcmPallet::limited_reserve_transfer_assets, expected_result, @@ -1905,11 +1811,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_dis /// Test `limited_teleport_assets` with destination asset reserve and teleported fee disallowed. #[test] fn teleport_assets_with_destination_asset_reserve_and_teleported_fee_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); destination_asset_reserve_and_teleported_fee_call( XcmPallet::limited_teleport_assets, expected_result, @@ -2013,11 +1915,7 @@ fn remote_asset_reserve_and_teleported_fee_reserve_call_disallowed( /// Test `transfer_assets` with remote asset reserve and teleported fee is disallowed. #[test] fn transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve"), - })); + let expected_result = Err(crate::Error::::InvalidAssetUnsupportedReserve.into()); remote_asset_reserve_and_teleported_fee_reserve_call_disallowed( XcmPallet::transfer_assets, expected_result, @@ -2028,11 +1926,7 @@ fn transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallowed() { /// disallowed. #[test] fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [23, 0, 0, 0], - message: Some("TooManyReserves"), - })); + let expected_result = Err(crate::Error::::TooManyReserves.into()); remote_asset_reserve_and_teleported_fee_reserve_call_disallowed( XcmPallet::limited_reserve_transfer_assets, expected_result, @@ -2042,11 +1936,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallow /// Test `limited_teleport_assets` with remote asset reserve and teleported fee is disallowed. #[test] fn teleport_assets_with_remote_asset_reserve_and_teleported_fee_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); remote_asset_reserve_and_teleported_fee_reserve_call_disallowed( XcmPallet::limited_teleport_assets, expected_result, @@ -2088,14 +1978,7 @@ fn reserve_transfer_assets_with_teleportable_asset_disallowed() { fee_index as u32, Unlimited, ); - assert_eq!( - res, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered") - })) - ); + assert_err!(res, crate::Error::::Filtered); // Alice native asset is still same assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // Alice USDT balance is still same @@ -2136,14 +2019,7 @@ fn transfer_assets_with_filtered_teleported_fee_disallowed() { fee_index as u32, Unlimited, ); - assert_eq!( - result, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered") - })) - ); + assert_err!(result, crate::Error::::Filtered); }); } @@ -2350,11 +2226,7 @@ fn transfer_assets_with_teleportable_asset_and_local_fee_reserve_works() { /// Test `limited_reserve_transfer_assets` with teleportable asset and local fee reserve disallowed. #[test] fn reserve_transfer_assets_with_teleportable_asset_and_local_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); teleport_asset_using_local_fee_reserve_call( XcmPallet::limited_reserve_transfer_assets, expected_result, @@ -2364,11 +2236,7 @@ fn reserve_transfer_assets_with_teleportable_asset_and_local_fee_reserve_disallo /// Test `limited_teleport_assets` with teleportable asset and local fee reserve disallowed. #[test] fn teleport_assets_with_teleportable_asset_and_local_fee_reserve_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); teleport_asset_using_local_fee_reserve_call( XcmPallet::limited_teleport_assets, expected_result, @@ -2541,11 +2409,7 @@ fn transfer_teleported_assets_using_destination_reserve_fee_works() { /// disallowed. #[test] fn reserve_transfer_teleported_assets_using_destination_reserve_fee_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); teleported_asset_using_destination_reserve_fee_call( XcmPallet::limited_reserve_transfer_assets, expected_result, @@ -2555,11 +2419,7 @@ fn reserve_transfer_teleported_assets_using_destination_reserve_fee_disallowed() /// Test `limited_teleport_assets` with teleported asset reserve and destination fee disallowed. #[test] fn teleport_assets_using_destination_reserve_fee_disallowed() { - let expected_result = Err(DispatchError::Module(ModuleError { - index: 4, - error: [2, 0, 0, 0], - message: Some("Filtered"), - })); + let expected_result = Err(crate::Error::::Filtered.into()); teleported_asset_using_destination_reserve_fee_call( XcmPallet::limited_teleport_assets, expected_result, diff --git a/polkadot/xcm/pallet-xcm/src/tests/mod.rs b/polkadot/xcm/pallet-xcm/src/tests/mod.rs index 1147635081a41db78927f5f1765210e315a0c92a..782c8bed478e18181db64256471fed0dca56e6fd 100644 --- a/polkadot/xcm/pallet-xcm/src/tests/mod.rs +++ b/polkadot/xcm/pallet-xcm/src/tests/mod.rs @@ -20,10 +20,10 @@ pub(crate) mod assets_transfer; use crate::{ mock::*, pallet::SupportedVersion, AssetTraps, Config, CurrentMigration, Error, - LatestVersionedLocation, Pallet, Queries, QueryStatus, VersionDiscoveryQueue, - VersionMigrationStage, VersionNotifiers, VersionNotifyTargets, WeightInfo, + ExecuteControllerWeightInfo, LatestVersionedLocation, Pallet, Queries, QueryStatus, + VersionDiscoveryQueue, VersionMigrationStage, VersionNotifiers, VersionNotifyTargets, + WeightInfo, }; -use codec::Encode; use frame_support::{ assert_err_ignore_postinfo, assert_noop, assert_ok, traits::{Currency, Hooks}, @@ -305,12 +305,11 @@ fn send_works() { ]); let versioned_dest = Box::new(RelayLocation::get().into()); - let versioned_message = VersionedXcm::from(message.clone()); - let encoded_versioned_message = versioned_message.encode().try_into().unwrap(); - assert_ok!(XcmPallet::send_blob( + let versioned_message = Box::new(VersionedXcm::from(message.clone())); + assert_ok!(XcmPallet::send( RuntimeOrigin::signed(ALICE), versioned_dest, - encoded_versioned_message + versioned_message )); let sent_message = Xcm(Some(DescendOrigin(sender.clone().try_into().unwrap())) .into_iter() @@ -342,16 +341,16 @@ fn send_fails_when_xcm_router_blocks() { ]; new_test_ext_with_balances(balances).execute_with(|| { let sender: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); - let message = Xcm::<()>(vec![ + let message = Xcm(vec![ ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), buy_execution((Parent, SEND_AMOUNT)), DepositAsset { assets: AllCounted(1).into(), beneficiary: sender }, ]); assert_noop!( - XcmPallet::send_blob( + XcmPallet::send( RuntimeOrigin::signed(ALICE), Box::new(Location::ancestor(8).into()), - VersionedXcm::from(message.clone()).encode().try_into().unwrap(), + Box::new(VersionedXcm::from(message.clone())), ), crate::Error::::SendFailure ); @@ -372,16 +371,13 @@ fn execute_withdraw_to_deposit_works() { let weight = BaseXcmWeight::get() * 3; let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into(); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); - assert_ok!(XcmPallet::execute_blob( + assert_ok!(XcmPallet::execute( RuntimeOrigin::signed(ALICE), - VersionedXcm::from(Xcm::(vec![ + Box::new(VersionedXcm::from(Xcm(vec![ WithdrawAsset((Here, SEND_AMOUNT).into()), buy_execution((Here, SEND_AMOUNT)), DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, - ])) - .encode() - .try_into() - .unwrap(), + ]))), weight )); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); @@ -403,21 +399,18 @@ fn trapped_assets_can_be_claimed() { let weight = BaseXcmWeight::get() * 6; let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into(); - assert_ok!(XcmPallet::execute_blob( + assert_ok!(XcmPallet::execute( RuntimeOrigin::signed(ALICE), - VersionedXcm::from(Xcm(vec![ + Box::new(VersionedXcm::from(Xcm(vec![ WithdrawAsset((Here, SEND_AMOUNT).into()), buy_execution((Here, SEND_AMOUNT)), // Don't propagated the error into the result. - SetErrorHandler(Xcm::(vec![ClearError])), + SetErrorHandler(Xcm(vec![ClearError])), // This will make an error. Trap(0), // This would succeed, but we never get to it. DepositAsset { assets: AllCounted(1).into(), beneficiary: dest.clone() }, - ])) - .encode() - .try_into() - .unwrap(), + ]))), weight )); let source: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); @@ -444,16 +437,13 @@ fn trapped_assets_can_be_claimed() { assert_eq!(trapped, expected); let weight = BaseXcmWeight::get() * 3; - assert_ok!(XcmPallet::execute_blob( + assert_ok!(XcmPallet::execute( RuntimeOrigin::signed(ALICE), - VersionedXcm::from(Xcm::(vec![ + Box::new(VersionedXcm::from(Xcm(vec![ ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() }, buy_execution((Here, SEND_AMOUNT)), DepositAsset { assets: AllCounted(1).into(), beneficiary: dest.clone() }, - ])) - .encode() - .try_into() - .unwrap(), + ]))), weight )); @@ -463,16 +453,13 @@ fn trapped_assets_can_be_claimed() { // Can't claim twice. assert_err_ignore_postinfo!( - XcmPallet::execute_blob( + XcmPallet::execute( RuntimeOrigin::signed(ALICE), - VersionedXcm::from(Xcm::(vec![ + Box::new(VersionedXcm::from(Xcm(vec![ ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() }, buy_execution((Here, SEND_AMOUNT)), DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, - ])) - .encode() - .try_into() - .unwrap(), + ]))), weight ), Error::::LocalExecutionIncomplete @@ -489,9 +476,9 @@ fn claim_assets_works() { let trapping_program = Xcm::::builder_unsafe().withdraw_asset((Here, SEND_AMOUNT)).build(); // Even though assets are trapped, the extrinsic returns success. - assert_ok!(XcmPallet::execute_blob( + assert_ok!(XcmPallet::execute( RuntimeOrigin::signed(ALICE), - VersionedXcm::V4(trapping_program).encode().try_into().unwrap(), + Box::new(VersionedXcm::V4(trapping_program)), BaseXcmWeight::get() * 2, )); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); @@ -544,9 +531,9 @@ fn incomplete_execute_reverts_side_effects() { assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); let amount_to_send = INITIAL_BALANCE - ExistentialDeposit::get(); let assets: Assets = (Here, amount_to_send).into(); - let result = XcmPallet::execute_blob( + let result = XcmPallet::execute( RuntimeOrigin::signed(ALICE), - VersionedXcm::from(Xcm::(vec![ + Box::new(VersionedXcm::from(Xcm(vec![ // Withdraw + BuyExec + Deposit should work WithdrawAsset(assets.clone()), buy_execution(assets.inner()[0].clone()), @@ -554,10 +541,7 @@ fn incomplete_execute_reverts_side_effects() { // Withdrawing once more will fail because of InsufficientBalance, and we expect to // revert the effects of the above instructions as well WithdrawAsset(assets), - ])) - .encode() - .try_into() - .unwrap(), + ]))), weight, ); // all effects are reverted and balances unchanged for either sender or receiver @@ -569,15 +553,11 @@ fn incomplete_execute_reverts_side_effects() { Err(sp_runtime::DispatchErrorWithPostInfo { post_info: frame_support::dispatch::PostDispatchInfo { actual_weight: Some( - <::WeightInfo>::execute_blob() + weight + as ExecuteControllerWeightInfo>::execute() + weight ), pays_fee: frame_support::dispatch::Pays::Yes, }, - error: sp_runtime::DispatchError::Module(sp_runtime::ModuleError { - index: 4, - error: [24, 0, 0, 0,], - message: Some("LocalExecutionIncomplete") - }) + error: sp_runtime::DispatchError::from(Error::::LocalExecutionIncomplete) }) ); }); diff --git a/polkadot/xcm/src/lib.rs b/polkadot/xcm/src/lib.rs index e836486e86e3068b82c3f3a5905836c7704e09f7..513dfe5501ba6c6ee8c233717c86b7f24f6eeb5f 100644 --- a/polkadot/xcm/src/lib.rs +++ b/polkadot/xcm/src/lib.rs @@ -25,7 +25,7 @@ extern crate alloc; use derivative::Derivative; -use parity_scale_codec::{Decode, Encode, Error as CodecError, Input, MaxEncodedLen}; +use parity_scale_codec::{Decode, DecodeLimit, Encode, Error as CodecError, Input, MaxEncodedLen}; use scale_info::TypeInfo; pub mod v2; @@ -48,9 +48,6 @@ mod tests; /// Maximum nesting level for XCM decoding. pub const MAX_XCM_DECODE_DEPTH: u32 = 8; -/// Maximum encoded size. -/// See `decoding_respects_limit` test for more reasoning behind this value. -pub const MAX_XCM_ENCODED_SIZE: u32 = 12402; /// A version of XCM. pub type Version = u32; @@ -456,6 +453,23 @@ impl IdentifyVersion for VersionedXcm { } } +impl VersionedXcm { + /// Checks that the XCM is decodable with `MAX_XCM_DECODE_DEPTH`. Consequently, it also checks + /// all decode implementations and limits, such as MAX_ITEMS_IN_ASSETS or + /// MAX_INSTRUCTIONS_TO_DECODE. + /// + /// Note that this uses the limit of the sender - not the receiver. It is a best effort. + pub fn validate_xcm_nesting(&self) -> Result<(), ()> { + self.using_encoded(|mut enc| { + Self::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut enc).map(|_| ()) + }) + .map_err(|e| { + log::error!(target: "xcm::validate_xcm_nesting", "Decode error: {e:?} for xcm: {self:?}!"); + () + }) + } +} + impl From> for VersionedXcm { fn from(x: v2::Xcm) -> Self { VersionedXcm::V2(x) @@ -704,3 +718,77 @@ fn size_limits() { } assert!(!test_failed); } + +#[test] +fn validate_xcm_nesting_works() { + use crate::latest::{ + prelude::{GeneralIndex, ReserveAssetDeposited, SetAppendix}, + Assets, Xcm, MAX_INSTRUCTIONS_TO_DECODE, MAX_ITEMS_IN_ASSETS, + }; + + // closure generates assets of `count` + let assets = |count| { + let mut assets = Assets::new(); + for i in 0..count { + assets.push((GeneralIndex(i as u128), 100).into()); + } + assets + }; + + // closer generates `Xcm` with nested instructions of `depth` + let with_instr = |depth| { + let mut xcm = Xcm::<()>(vec![]); + for _ in 0..depth - 1 { + xcm = Xcm::<()>(vec![SetAppendix(xcm)]); + } + xcm + }; + + // `MAX_INSTRUCTIONS_TO_DECODE` check + assert!(VersionedXcm::<()>::from(Xcm(vec![ + ReserveAssetDeposited(assets(1)); + (MAX_INSTRUCTIONS_TO_DECODE - 1) as usize + ])) + .validate_xcm_nesting() + .is_ok()); + assert!(VersionedXcm::<()>::from(Xcm(vec![ + ReserveAssetDeposited(assets(1)); + MAX_INSTRUCTIONS_TO_DECODE as usize + ])) + .validate_xcm_nesting() + .is_ok()); + assert!(VersionedXcm::<()>::from(Xcm(vec![ + ReserveAssetDeposited(assets(1)); + (MAX_INSTRUCTIONS_TO_DECODE + 1) as usize + ])) + .validate_xcm_nesting() + .is_err()); + + // `MAX_XCM_DECODE_DEPTH` check + assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH - 1)) + .validate_xcm_nesting() + .is_ok()); + assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH)) + .validate_xcm_nesting() + .is_ok()); + assert!(VersionedXcm::<()>::from(with_instr(MAX_XCM_DECODE_DEPTH + 1)) + .validate_xcm_nesting() + .is_err()); + + // `MAX_ITEMS_IN_ASSETS` check + assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets( + MAX_ITEMS_IN_ASSETS + ))])) + .validate_xcm_nesting() + .is_ok()); + assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets( + MAX_ITEMS_IN_ASSETS - 1 + ))])) + .validate_xcm_nesting() + .is_ok()); + assert!(VersionedXcm::<()>::from(Xcm(vec![ReserveAssetDeposited(assets( + MAX_ITEMS_IN_ASSETS + 1 + ))])) + .validate_xcm_nesting() + .is_err()); +} diff --git a/polkadot/xcm/src/v3/junction.rs b/polkadot/xcm/src/v3/junction.rs index e9e51941b1ac0c50130ad5501c4b6f374656152a..32ce352c5c02b0a7a3dd31fa02854767cf483efa 100644 --- a/polkadot/xcm/src/v3/junction.rs +++ b/polkadot/xcm/src/v3/junction.rs @@ -26,7 +26,6 @@ use crate::{ VersionedLocation, }; use bounded_collections::{BoundedSlice, BoundedVec, ConstU32}; -use core::convert::{TryFrom, TryInto}; use parity_scale_codec::{self, Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; diff --git a/polkadot/xcm/src/v3/junctions.rs b/polkadot/xcm/src/v3/junctions.rs index 9748e81fa55f53c90c8a78419fce410ea1adb82d..7b014304fdaf7826861ad9e8ea469390bc0513b9 100644 --- a/polkadot/xcm/src/v3/junctions.rs +++ b/polkadot/xcm/src/v3/junctions.rs @@ -17,7 +17,7 @@ //! XCM `Junctions`/`InteriorMultiLocation` datatype. use super::{Junction, MultiLocation, NetworkId}; -use core::{convert::TryFrom, mem, result}; +use core::{mem, result}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/polkadot/xcm/src/v3/mod.rs b/polkadot/xcm/src/v3/mod.rs index d4e2da07a25ac9dbf4ca7177ed09f0abd26b46d3..e7c57f414eb786a50f3689a3d3706632bdc4f902 100644 --- a/polkadot/xcm/src/v3/mod.rs +++ b/polkadot/xcm/src/v3/mod.rs @@ -29,11 +29,7 @@ use super::{ use crate::DoubleEncoded; use alloc::{vec, vec::Vec}; use bounded_collections::{parameter_types, BoundedVec}; -use core::{ - convert::{TryFrom, TryInto}, - fmt::Debug, - result, -}; +use core::{fmt::Debug, result}; use derivative::Derivative; use parity_scale_codec::{ self, decode_vec_with_len, Compact, Decode, Encode, Error as CodecError, Input as CodecInput, diff --git a/polkadot/xcm/src/v3/multiasset.rs b/polkadot/xcm/src/v3/multiasset.rs index f9041ecd81bac1b383b65ea8920920ff95e14419..9a67b0e4986caf1a52eaa108e77f9394537d7553 100644 --- a/polkadot/xcm/src/v3/multiasset.rs +++ b/polkadot/xcm/src/v3/multiasset.rs @@ -42,10 +42,7 @@ use crate::{ }; use alloc::{vec, vec::Vec}; use bounded_collections::{BoundedVec, ConstU32}; -use core::{ - cmp::Ordering, - convert::{TryFrom, TryInto}, -}; +use core::cmp::Ordering; use parity_scale_codec::{self as codec, Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -825,7 +822,9 @@ impl MultiAssets { /// Prepend a `MultiLocation` to any concrete asset items, giving it a new root location. pub fn prepend_with(&mut self, prefix: &MultiLocation) -> Result<(), ()> { - self.0.iter_mut().try_for_each(|i| i.prepend_with(prefix)) + self.0.iter_mut().try_for_each(|i| i.prepend_with(prefix))?; + self.0.sort(); + Ok(()) } /// Mutate the location of the asset identifier if concrete, giving it the same location @@ -1213,8 +1212,73 @@ mod tests { vec![asset_1.clone(), asset_2.clone(), asset_3.clone()].into(); assert_eq!(assets.clone(), vec![asset_1.clone(), asset_2.clone(), asset_3.clone()].into()); + // decoding respects limits and sorting + assert!(assets + .using_encoded(|mut enc| MultiAssets::decode(&mut enc).map(|_| ())) + .is_ok()); + assert!(assets.reanchor(&dest, reanchor_context).is_ok()); - assert_eq!(assets, vec![asset_2_reanchored, asset_3_reanchored, asset_1_reanchored].into()); + assert_eq!(assets.0, vec![asset_2_reanchored, asset_3_reanchored, asset_1_reanchored]); + + // decoding respects limits and sorting + assert!(assets + .using_encoded(|mut enc| MultiAssets::decode(&mut enc).map(|_| ())) + .is_ok()); + } + + #[test] + fn prepend_preserves_sorting() { + use super::*; + use alloc::vec; + + let prefix = MultiLocation::new(0, X1(Parachain(1000))); + + let asset_1: MultiAsset = + (MultiLocation::new(0, X2(PalletInstance(50), GeneralIndex(1))), 10).into(); + let mut asset_1_prepended = asset_1.clone(); + assert!(asset_1_prepended.prepend_with(&prefix).is_ok()); + // changes interior X2->X3 + assert_eq!( + asset_1_prepended, + (MultiLocation::new(0, X3(Parachain(1000), PalletInstance(50), GeneralIndex(1))), 10) + .into() + ); + + let asset_2: MultiAsset = + (MultiLocation::new(1, X2(PalletInstance(50), GeneralIndex(1))), 10).into(); + let mut asset_2_prepended = asset_2.clone(); + assert!(asset_2_prepended.prepend_with(&prefix).is_ok()); + // changes parent + assert_eq!( + asset_2_prepended, + (MultiLocation::new(0, X2(PalletInstance(50), GeneralIndex(1))), 10).into() + ); + + let asset_3: MultiAsset = + (MultiLocation::new(2, X2(PalletInstance(50), GeneralIndex(1))), 10).into(); + let mut asset_3_prepended = asset_3.clone(); + assert!(asset_3_prepended.prepend_with(&prefix).is_ok()); + // changes parent + assert_eq!( + asset_3_prepended, + (MultiLocation::new(1, X2(PalletInstance(50), GeneralIndex(1))), 10).into() + ); + + // `From` impl does sorting. + let mut assets: MultiAssets = vec![asset_1, asset_2, asset_3].into(); + // decoding respects limits and sorting + assert!(assets + .using_encoded(|mut enc| MultiAssets::decode(&mut enc).map(|_| ())) + .is_ok()); + + // let's do `prepend_with` + assert!(assets.prepend_with(&prefix).is_ok()); + assert_eq!(assets.0, vec![asset_2_prepended, asset_1_prepended, asset_3_prepended]); + + // decoding respects limits and sorting + assert!(assets + .using_encoded(|mut enc| MultiAssets::decode(&mut enc).map(|_| ())) + .is_ok()); } #[test] diff --git a/polkadot/xcm/src/v3/multilocation.rs b/polkadot/xcm/src/v3/multilocation.rs index 18fe01ec8fa7da6eb1db44ce961098c00a2ac13b..731e277b29d8897d46406a1c7a8927461bcf889b 100644 --- a/polkadot/xcm/src/v3/multilocation.rs +++ b/polkadot/xcm/src/v3/multilocation.rs @@ -20,10 +20,7 @@ use super::{Junction, Junctions}; use crate::{ v2::MultiLocation as OldMultiLocation, v4::Location as NewMultiLocation, VersionedLocation, }; -use core::{ - convert::{TryFrom, TryInto}, - result, -}; +use core::result; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -766,7 +763,6 @@ mod tests { #[test] fn conversion_from_other_types_works() { use crate::v2; - use core::convert::TryInto; fn takes_multilocation>(_arg: Arg) {} diff --git a/polkadot/xcm/src/v4/asset.rs b/polkadot/xcm/src/v4/asset.rs index bdff0c272306aced0fbf5f2372a70d66ac37e841..6b6d200f32febca6ef5cb208bfed95d50724e58b 100644 --- a/polkadot/xcm/src/v4/asset.rs +++ b/polkadot/xcm/src/v4/asset.rs @@ -34,10 +34,7 @@ use crate::v3::{ }; use alloc::{vec, vec::Vec}; use bounded_collections::{BoundedVec, ConstU32}; -use core::{ - cmp::Ordering, - convert::{TryFrom, TryInto}, -}; +use core::cmp::Ordering; use parity_scale_codec::{self as codec, Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -723,7 +720,9 @@ impl Assets { /// Prepend a `Location` to any concrete asset items, giving it a new root location. pub fn prepend_with(&mut self, prefix: &Location) -> Result<(), ()> { - self.0.iter_mut().try_for_each(|i| i.prepend_with(prefix)) + self.0.iter_mut().try_for_each(|i| i.prepend_with(prefix))?; + self.0.sort(); + Ok(()) } /// Return a reference to an item at a specific index or `None` if it doesn't exist. @@ -1035,8 +1034,61 @@ mod tests { let mut assets: Assets = vec![asset_1.clone(), asset_2.clone(), asset_3.clone()].into(); assert_eq!(assets.clone(), vec![asset_1.clone(), asset_2.clone(), asset_3.clone()].into()); + // decoding respects limits and sorting + assert!(assets.using_encoded(|mut enc| Assets::decode(&mut enc).map(|_| ())).is_ok()); + assert!(assets.reanchor(&dest, &reanchor_context).is_ok()); - assert_eq!(assets, vec![asset_2_reanchored, asset_3_reanchored, asset_1_reanchored].into()); + assert_eq!(assets.0, vec![asset_2_reanchored, asset_3_reanchored, asset_1_reanchored]); + + // decoding respects limits and sorting + assert!(assets.using_encoded(|mut enc| Assets::decode(&mut enc).map(|_| ())).is_ok()); + } + + #[test] + fn prepend_preserves_sorting() { + use super::*; + use alloc::vec; + + let prefix = Location::new(0, [Parachain(1000)]); + + let asset_1: Asset = (Location::new(0, [PalletInstance(50), GeneralIndex(1)]), 10).into(); + let mut asset_1_prepended = asset_1.clone(); + assert!(asset_1_prepended.prepend_with(&prefix).is_ok()); + // changes interior X2->X3 + assert_eq!( + asset_1_prepended, + (Location::new(0, [Parachain(1000), PalletInstance(50), GeneralIndex(1)]), 10).into() + ); + + let asset_2: Asset = (Location::new(1, [PalletInstance(50), GeneralIndex(1)]), 10).into(); + let mut asset_2_prepended = asset_2.clone(); + assert!(asset_2_prepended.prepend_with(&prefix).is_ok()); + // changes parent + assert_eq!( + asset_2_prepended, + (Location::new(0, [PalletInstance(50), GeneralIndex(1)]), 10).into() + ); + + let asset_3: Asset = (Location::new(2, [PalletInstance(50), GeneralIndex(1)]), 10).into(); + let mut asset_3_prepended = asset_3.clone(); + assert!(asset_3_prepended.prepend_with(&prefix).is_ok()); + // changes parent + assert_eq!( + asset_3_prepended, + (Location::new(1, [PalletInstance(50), GeneralIndex(1)]), 10).into() + ); + + // `From` impl does sorting. + let mut assets: Assets = vec![asset_1, asset_2, asset_3].into(); + // decoding respects limits and sorting + assert!(assets.using_encoded(|mut enc| Assets::decode(&mut enc).map(|_| ())).is_ok()); + + // let's do `prepend_with` + assert!(assets.prepend_with(&prefix).is_ok()); + assert_eq!(assets.0, vec![asset_2_prepended, asset_1_prepended, asset_3_prepended]); + + // decoding respects limits and sorting + assert!(assets.using_encoded(|mut enc| Assets::decode(&mut enc).map(|_| ())).is_ok()); } #[test] diff --git a/polkadot/xcm/src/v4/junction.rs b/polkadot/xcm/src/v4/junction.rs index b5d10484aa021aebfc2324bf251564c6e54a111e..3ae97de5e9b83336c10e0c20e865ddbebfef8b50 100644 --- a/polkadot/xcm/src/v4/junction.rs +++ b/polkadot/xcm/src/v4/junction.rs @@ -23,7 +23,6 @@ use crate::{ VersionedLocation, }; use bounded_collections::{BoundedSlice, BoundedVec, ConstU32}; -use core::convert::TryFrom; use parity_scale_codec::{self, Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; diff --git a/polkadot/xcm/src/v4/junctions.rs b/polkadot/xcm/src/v4/junctions.rs index 48712dd74c6cdd57411409fda689ce22378b8a75..6d1af59e13dcb179399379010d037e1c5a112c3c 100644 --- a/polkadot/xcm/src/v4/junctions.rs +++ b/polkadot/xcm/src/v4/junctions.rs @@ -18,7 +18,7 @@ use super::{Junction, Location, NetworkId}; use alloc::sync::Arc; -use core::{convert::TryFrom, mem, ops::Range, result}; +use core::{mem, ops::Range, result}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/polkadot/xcm/src/v4/location.rs b/polkadot/xcm/src/v4/location.rs index 9275bfdb94923cd38de4d3690aafe70f3ab4b952..cee76b6894076cc1ba9ddf960b2ad68a0693351e 100644 --- a/polkadot/xcm/src/v4/location.rs +++ b/polkadot/xcm/src/v4/location.rs @@ -18,10 +18,7 @@ use super::{traits::Reanchorable, Junction, Junctions}; use crate::{v3::MultiLocation as OldLocation, VersionedLocation}; -use core::{ - convert::{TryFrom, TryInto}, - result, -}; +use core::result; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -723,7 +720,6 @@ mod tests { #[test] fn conversion_from_other_types_works() { use crate::v3; - use core::convert::TryInto; fn takes_location>(_arg: Arg) {} diff --git a/polkadot/xcm/src/v4/mod.rs b/polkadot/xcm/src/v4/mod.rs index 6635408282e4849861a62be955e52d2fc4dd1b87..77b6d915fcb5fa902e5168e46b38cdb677f98cbf 100644 --- a/polkadot/xcm/src/v4/mod.rs +++ b/polkadot/xcm/src/v4/mod.rs @@ -24,11 +24,7 @@ use super::v3::{ use crate::DoubleEncoded; use alloc::{vec, vec::Vec}; use bounded_collections::{parameter_types, BoundedVec}; -use core::{ - convert::{TryFrom, TryInto}, - fmt::Debug, - result, -}; +use core::{fmt::Debug, result}; use derivative::Derivative; use parity_scale_codec::{ self, decode_vec_with_len, Compact, Decode, Encode, Error as CodecError, Input as CodecInput, @@ -1488,21 +1484,7 @@ mod tests { let encoded = big_xcm.encode(); assert!(Xcm::<()>::decode(&mut &encoded[..]).is_err()); - let mut many_assets = Assets::new(); - for index in 0..MAX_ITEMS_IN_ASSETS { - many_assets.push((GeneralIndex(index as u128), 1u128).into()); - } - - let full_xcm_pass = - Xcm::<()>(vec![ - TransferAsset { assets: many_assets, beneficiary: Here.into() }; - MAX_INSTRUCTIONS_TO_DECODE as usize - ]); - let encoded = full_xcm_pass.encode(); - assert_eq!(encoded.len(), 12402); - assert!(Xcm::<()>::decode(&mut &encoded[..]).is_ok()); - - let nested_xcm_fail = Xcm::<()>(vec![ + let nested_xcm = Xcm::<()>(vec![ DepositReserveAsset { assets: All.into(), dest: Here.into(), @@ -1510,10 +1492,10 @@ mod tests { }; (MAX_INSTRUCTIONS_TO_DECODE / 2) as usize ]); - let encoded = nested_xcm_fail.encode(); + let encoded = nested_xcm.encode(); assert!(Xcm::<()>::decode(&mut &encoded[..]).is_err()); - let even_more_nested_xcm = Xcm::<()>(vec![SetAppendix(nested_xcm_fail); 64]); + let even_more_nested_xcm = Xcm::<()>(vec![SetAppendix(nested_xcm); 64]); let encoded = even_more_nested_xcm.encode(); assert_eq!(encoded.len(), 342530); // This should not decode since the limit is 100 diff --git a/polkadot/xcm/xcm-builder/src/asset_conversion.rs b/polkadot/xcm/xcm-builder/src/asset_conversion.rs index e38af149be541f3f85ed47109e979351d6569f0c..520ce87448ea4f868fbb62130a7da45353d220b6 100644 --- a/polkadot/xcm/xcm-builder/src/asset_conversion.rs +++ b/polkadot/xcm/xcm-builder/src/asset_conversion.rs @@ -107,17 +107,6 @@ impl< #[deprecated = "Use `ConvertedConcreteId` instead"] pub type ConvertedConcreteAssetId = ConvertedConcreteId; -pub struct V4V3LocationConverter; -impl MaybeEquivalence for V4V3LocationConverter { - fn convert(old: &xcm::v4::Location) -> Option { - (*old).clone().try_into().ok() - } - - fn convert_back(new: &xcm::v3::Location) -> Option { - (*new).try_into().ok() - } -} - pub struct MatchedConvertedConcreteId( PhantomData<(AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertOther)>, ); diff --git a/polkadot/xcm/xcm-builder/src/barriers.rs b/polkadot/xcm/xcm-builder/src/barriers.rs index b8923a4d5c6e88d9034536fa4dacf21c47e9108b..11e9122f9a121b1b08db7a5aec2fd423d63c98cf 100644 --- a/polkadot/xcm/xcm-builder/src/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/barriers.rs @@ -322,6 +322,29 @@ impl> Contains for IsChildSystemParachain } } +/// Matches if the given location is a system-level sibling parachain. +pub struct IsSiblingSystemParachain(PhantomData<(ParaId, SelfParaId)>); +impl + Eq, SelfParaId: Get> Contains + for IsSiblingSystemParachain +{ + fn contains(l: &Location) -> bool { + matches!( + l.unpack(), + (1, [Junction::Parachain(id)]) + if SelfParaId::get() != ParaId::from(*id) && ParaId::from(*id).is_system(), + ) + } +} + +/// Matches if the given location contains only the specified amount of parents and no interior +/// junctions. +pub struct IsParentsOnly(PhantomData); +impl> Contains for IsParentsOnly { + fn contains(t: &Location) -> bool { + t.contains_parents_only(Count::get()) + } +} + /// Allows only messages if the generic `ResponseHandler` expects them via `expecting_response`. pub struct AllowKnownQueryResponses(PhantomData); impl ShouldExecute for AllowKnownQueryResponses { @@ -376,6 +399,41 @@ impl> ShouldExecute for AllowSubscriptionsFrom { } } +/// Allows execution for the Relay Chain origin (represented as `Location::parent()`) if it is just +/// a straight `HrmpNewChannelOpenRequest`, `HrmpChannelAccepted`, or `HrmpChannelClosing` +/// instruction. +/// +/// Note: This barrier fulfills safety recommendations for the mentioned instructions - see their +/// documentation. +pub struct AllowHrmpNotificationsFromRelayChain; +impl ShouldExecute for AllowHrmpNotificationsFromRelayChain { + fn should_execute( + origin: &Location, + instructions: &mut [Instruction], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + log::trace!( + target: "xcm::barriers", + "AllowHrmpNotificationsFromRelayChain origin: {:?}, instructions: {:?}, max_weight: {:?}, properties: {:?}", + origin, instructions, _max_weight, _properties, + ); + // accept only the Relay Chain + ensure!(matches!(origin.unpack(), (1, [])), ProcessMessageError::Unsupported); + // accept only HRMP notifications and nothing else + instructions + .matcher() + .assert_remaining_insts(1)? + .match_next_inst(|inst| match inst { + HrmpNewChannelOpenRequest { .. } | + HrmpChannelAccepted { .. } | + HrmpChannelClosing { .. } => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + })?; + Ok(()) + } +} + /// Deny executing the XCM if it matches any of the Deny filter regardless of anything else. /// If it passes the Deny, and matches one of the Allow cases then it is let through. pub struct DenyThenTry(PhantomData, PhantomData) diff --git a/polkadot/xcm/xcm-builder/src/controller.rs b/polkadot/xcm/xcm-builder/src/controller.rs index 6bdde2a967de9195f62fa59544fc3c254b51796b..04b19eaa587009aa118e7c7d51ce69b159e4d948 100644 --- a/polkadot/xcm/xcm-builder/src/controller.rs +++ b/polkadot/xcm/xcm-builder/src/controller.rs @@ -21,7 +21,6 @@ use frame_support::{ dispatch::{DispatchErrorWithPostInfo, WithPostDispatchInfo}, pallet_prelude::DispatchError, - parameter_types, BoundedVec, }; use sp_std::boxed::Box; use xcm::prelude::*; @@ -42,12 +41,8 @@ impl Controller f /// Weight functions needed for [`ExecuteController`]. pub trait ExecuteControllerWeightInfo { - /// Weight for [`ExecuteController::execute_blob`] - fn execute_blob() -> Weight; -} - -parameter_types! { - pub const MaxXcmEncodedSize: u32 = xcm::MAX_XCM_ENCODED_SIZE; + /// Weight for [`ExecuteController::execute`] + fn execute() -> Weight; } /// Execute an XCM locally, for a given origin. @@ -66,19 +61,19 @@ pub trait ExecuteController { /// # Parameters /// /// - `origin`: the origin of the call. - /// - `msg`: the encoded XCM to be executed, should be decodable as a [`VersionedXcm`] + /// - `message`: the XCM program to be executed. /// - `max_weight`: the maximum weight that can be consumed by the execution. - fn execute_blob( + fn execute( origin: Origin, - message: BoundedVec, + message: Box>, max_weight: Weight, ) -> Result; } /// Weight functions needed for [`SendController`]. pub trait SendControllerWeightInfo { - /// Weight for [`SendController::send_blob`] - fn send_blob() -> Weight; + /// Weight for [`SendController::send`] + fn send() -> Weight; } /// Send an XCM from a given origin. @@ -98,11 +93,11 @@ pub trait SendController { /// /// - `origin`: the origin of the call. /// - `dest`: the destination of the message. - /// - `msg`: the encoded XCM to be sent, should be decodable as a [`VersionedXcm`] - fn send_blob( + /// - `msg`: the XCM to be sent. + fn send( origin: Origin, dest: Box, - message: BoundedVec, + message: Box>, ) -> Result; } @@ -142,35 +137,35 @@ pub trait QueryController: QueryHandler { impl ExecuteController for () { type WeightInfo = (); - fn execute_blob( + fn execute( _origin: Origin, - _message: BoundedVec, + _message: Box>, _max_weight: Weight, ) -> Result { - Err(DispatchError::Other("ExecuteController::execute_blob not implemented") + Err(DispatchError::Other("ExecuteController::execute not implemented") .with_weight(Weight::zero())) } } impl ExecuteControllerWeightInfo for () { - fn execute_blob() -> Weight { + fn execute() -> Weight { Weight::zero() } } impl SendController for () { type WeightInfo = (); - fn send_blob( + fn send( _origin: Origin, _dest: Box, - _message: BoundedVec, + _message: Box>, ) -> Result { Ok(Default::default()) } } impl SendControllerWeightInfo for () { - fn send_blob() -> Weight { + fn send() -> Weight { Weight::zero() } } diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs index 46d0ad227bfdf8e5e77188165fc259b0c1aec585..1ba38d0db836b4e73e3f7785cf0e96f079666441 100644 --- a/polkadot/xcm/xcm-builder/src/lib.rs +++ b/polkadot/xcm/xcm-builder/src/lib.rs @@ -30,20 +30,21 @@ mod asset_conversion; #[allow(deprecated)] pub use asset_conversion::ConvertedConcreteAssetId; pub use asset_conversion::{ - AsPrefixedGeneralIndex, ConvertedConcreteId, MatchedConvertedConcreteId, V4V3LocationConverter, + AsPrefixedGeneralIndex, ConvertedConcreteId, MatchedConvertedConcreteId, }; mod barriers; pub use barriers::{ - AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, - AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, IsChildSystemParachain, RespectSuspension, TakeWeightCredit, TrailingSetTopicAsId, - WithComputedOrigin, + AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + AllowUnpaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, IsChildSystemParachain, + IsParentsOnly, IsSiblingSystemParachain, RespectSuspension, TakeWeightCredit, + TrailingSetTopicAsId, WithComputedOrigin, }; mod controller; pub use controller::{ - Controller, ExecuteController, ExecuteControllerWeightInfo, MaxXcmEncodedSize, QueryController, + Controller, ExecuteController, ExecuteControllerWeightInfo, QueryController, QueryControllerWeightInfo, QueryHandler, SendController, SendControllerWeightInfo, }; @@ -81,7 +82,9 @@ pub use location_conversion::{ }; mod matches_location; -pub use matches_location::{StartsWith, StartsWithExplicitGlobalConsensus}; +pub use matches_location::{ + StartsWith, StartsWithExplicitGlobalConsensus, WithLatestLocationConverter, +}; mod matches_token; pub use matches_token::IsConcrete; @@ -117,7 +120,7 @@ mod process_xcm_message; pub use process_xcm_message::ProcessXcmMessage; mod routing; -pub use routing::{EnsureDelivery, WithTopicSource, WithUniqueTopic}; +pub use routing::{EnsureDecodableXcm, EnsureDelivery, WithTopicSource, WithUniqueTopic}; mod transactional; pub use transactional::FrameTransactionalProcessor; diff --git a/polkadot/xcm/xcm-builder/src/matches_location.rs b/polkadot/xcm/xcm-builder/src/matches_location.rs index 1664c24772909a8a287cf620da308b07158270bb..b6c2807e6b29db302e2d1182729c5acb4426b24b 100644 --- a/polkadot/xcm/xcm-builder/src/matches_location.rs +++ b/polkadot/xcm/xcm-builder/src/matches_location.rs @@ -18,6 +18,8 @@ //! `InteriorLocation` types. use frame_support::traits::{Contains, Get}; +use sp_runtime::traits::MaybeEquivalence; +use sp_std::marker::PhantomData; use xcm::latest::{InteriorLocation, Location, NetworkId}; /// An implementation of `Contains` that checks for `Location` or @@ -51,3 +53,18 @@ impl> Contains for StartsWithExplicitGlobalC matches!(location.global_consensus(), Ok(requested_network) if requested_network.eq(&T::get())) } } + +/// An adapter implementation of `MaybeEquivalence` which can convert between the latest `Location` +/// and other versions that implement `TryInto` and `TryFrom`. +pub struct WithLatestLocationConverter(PhantomData); +impl + TryFrom + Clone> MaybeEquivalence + for WithLatestLocationConverter +{ + fn convert(old: &Location) -> Option { + (*old).clone().try_into().ok() + } + + fn convert_back(new: &Target) -> Option { + new.clone().try_into().ok() + } +} diff --git a/polkadot/xcm/xcm-builder/src/process_xcm_message.rs b/polkadot/xcm/xcm-builder/src/process_xcm_message.rs index bcf91d8e68c3389377c84ea85e23ec9835006186..7760274f6e2451f1b5f695884086a31cd36f3209 100644 --- a/polkadot/xcm/xcm-builder/src/process_xcm_message.rs +++ b/polkadot/xcm/xcm-builder/src/process_xcm_message.rs @@ -102,7 +102,12 @@ impl< target: LOG_TARGET, "XCM message execution error: {error:?}", ); - (required, Err(ProcessMessageError::Unsupported)) + let error = match error { + xcm::latest::Error::ExceedsStackLimit => ProcessMessageError::StackLimitReached, + _ => ProcessMessageError::Unsupported, + }; + + (required, Err(error)) }, }; meter.consume(consumed); @@ -148,6 +153,45 @@ mod tests { } } + #[test] + fn process_message_exceeds_limits_fails() { + struct MockedExecutor; + impl ExecuteXcm<()> for MockedExecutor { + type Prepared = xcm_executor::WeighedMessage<()>; + fn prepare( + message: xcm::latest::Xcm<()>, + ) -> core::result::Result> { + Ok(xcm_executor::WeighedMessage::new(Weight::zero(), message)) + } + fn execute( + _: impl Into, + _: Self::Prepared, + _: &mut XcmHash, + _: Weight, + ) -> Outcome { + Outcome::Error { error: xcm::latest::Error::ExceedsStackLimit } + } + fn charge_fees(_location: impl Into, _fees: Assets) -> xcm::latest::Result { + unreachable!() + } + } + + type Processor = ProcessXcmMessage; + + let xcm = VersionedXcm::V4(xcm::latest::Xcm::<()>(vec![ + xcm::latest::Instruction::<()>::ClearOrigin, + ])); + assert_err!( + Processor::process_message( + &xcm.encode(), + ORIGIN, + &mut WeightMeter::new(), + &mut [0; 32] + ), + ProcessMessageError::StackLimitReached, + ); + } + #[test] fn process_message_overweight_fails() { for msg in [v3_xcm(true), v3_xcm(false), v3_xcm(false), v2_xcm(false)] { diff --git a/polkadot/xcm/xcm-builder/src/routing.rs b/polkadot/xcm/xcm-builder/src/routing.rs index 529ef80c15ff11d3c0d2629aeb4fa9506cb37a28..921b9ac5922eaf37e65151b8e3a115e010b0abcb 100644 --- a/polkadot/xcm/xcm-builder/src/routing.rs +++ b/polkadot/xcm/xcm-builder/src/routing.rs @@ -139,3 +139,37 @@ impl EnsureDelivery for Tuple { (None, None) } } + +/// A wrapper router that attempts to *encode* and *decode* passed XCM `message` to ensure that the +/// receiving side will be able to decode, at least with the same XCM version. +/// +/// This is designed to be at the top-level of any routers which do the real delivery. While other +/// routers can manipulate the `message`, we cannot access the final XCM due to the generic +/// `Inner::Ticket`. Therefore, this router aims to validate at least the passed `message`. +/// +/// NOTE: For use in mock runtimes which don't have the DMP/UMP/HRMP XCM validations. +pub struct EnsureDecodableXcm(sp_std::marker::PhantomData); +impl SendXcm for EnsureDecodableXcm { + type Ticket = Inner::Ticket; + + fn validate( + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + if let Some(msg) = message { + let versioned_xcm = VersionedXcm::<()>::from(msg.clone()); + if versioned_xcm.validate_xcm_nesting().is_err() { + log::error!( + target: "xcm::validate_xcm_nesting", + "EnsureDecodableXcm validate_xcm_nesting error for \nversioned_xcm: {versioned_xcm:?}\nbased on xcm: {msg:?}" + ); + return Err(SendError::Transport("EnsureDecodableXcm validate_xcm_nesting error")) + } + } + Inner::validate(destination, message) + } + + fn deliver(ticket: Self::Ticket) -> Result { + Inner::deliver(ticket) + } +} diff --git a/polkadot/xcm/xcm-builder/src/tests/barriers.rs b/polkadot/xcm/xcm-builder/src/tests/barriers.rs index 6516263f57a0936b45171ca262f4001b4315ee95..665b5febc61fec2d549cf707af55e4ba872d21e2 100644 --- a/polkadot/xcm/xcm-builder/src/tests/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/tests/barriers.rs @@ -315,56 +315,150 @@ fn allow_subscriptions_from_should_work() { // allow only parent AllowSubsFrom::set(vec![Location::parent()]); - let valid_xcm_1 = Xcm::(vec![SubscribeVersion { - query_id: 42, - max_response_weight: Weight::from_parts(5000, 5000), - }]); - let valid_xcm_2 = Xcm::(vec![UnsubscribeVersion]); - let invalid_xcm_1 = Xcm::(vec![ - SetAppendix(Xcm(vec![])), - SubscribeVersion { query_id: 42, max_response_weight: Weight::from_parts(5000, 5000) }, - ]); - let invalid_xcm_2 = Xcm::(vec![ - SubscribeVersion { query_id: 42, max_response_weight: Weight::from_parts(5000, 5000) }, - SetTopic([0; 32]), - ]); + // closure for (xcm, origin) testing with `AllowSubscriptionsFrom` + let assert_should_execute = |mut xcm: Vec>, origin, expected_result| { + assert_eq!( + AllowSubscriptionsFrom::>::should_execute( + &origin, + &mut xcm, + Weight::from_parts(10, 10), + &mut props(Weight::zero()), + ), + expected_result + ); + }; + + // invalid origin + assert_should_execute( + vec![SubscribeVersion { + query_id: Default::default(), + max_response_weight: Default::default(), + }], + Parachain(1).into_location(), + Err(ProcessMessageError::Unsupported), + ); + assert_should_execute( + vec![UnsubscribeVersion], + Parachain(1).into_location(), + Err(ProcessMessageError::Unsupported), + ); - let test_data = vec![ - ( - valid_xcm_1.clone(), - Parachain(1).into_location(), - // not allowed origin - Err(ProcessMessageError::Unsupported), - ), - (valid_xcm_1, Location::parent(), Ok(())), - ( - valid_xcm_2.clone(), - Parachain(1).into_location(), - // not allowed origin - Err(ProcessMessageError::Unsupported), - ), - (valid_xcm_2, Location::parent(), Ok(())), - ( - invalid_xcm_1, - Location::parent(), - // invalid XCM - Err(ProcessMessageError::BadFormat), - ), - ( - invalid_xcm_2, - Location::parent(), - // invalid XCM - Err(ProcessMessageError::BadFormat), - ), - ]; - - for (mut message, origin, expected_result) in test_data { - let r = AllowSubscriptionsFrom::>::should_execute( - &origin, - message.inner_mut(), - Weight::from_parts(10, 10), - &mut props(Weight::zero()), + // invalid XCM (unexpected instruction before) + assert_should_execute( + vec![ + SetAppendix(Xcm(vec![])), + SubscribeVersion { + query_id: Default::default(), + max_response_weight: Default::default(), + }, + ], + Location::parent(), + Err(ProcessMessageError::BadFormat), + ); + assert_should_execute( + vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion], + Location::parent(), + Err(ProcessMessageError::BadFormat), + ); + // invalid XCM (unexpected instruction after) + assert_should_execute( + vec![ + SubscribeVersion { + query_id: Default::default(), + max_response_weight: Default::default(), + }, + SetTopic([0; 32]), + ], + Location::parent(), + Err(ProcessMessageError::BadFormat), + ); + assert_should_execute( + vec![UnsubscribeVersion, SetTopic([0; 32])], + Location::parent(), + Err(ProcessMessageError::BadFormat), + ); + // invalid XCM (unexpected instruction) + assert_should_execute( + vec![SetAppendix(Xcm(vec![]))], + Location::parent(), + Err(ProcessMessageError::BadFormat), + ); + + // ok + assert_should_execute( + vec![SubscribeVersion { + query_id: Default::default(), + max_response_weight: Default::default(), + }], + Location::parent(), + Ok(()), + ); + assert_should_execute(vec![UnsubscribeVersion], Location::parent(), Ok(())); +} + +#[test] +fn allow_hrmp_notifications_from_relay_chain_should_work() { + // closure for (xcm, origin) testing with `AllowHrmpNotificationsFromRelayChain` + let assert_should_execute = |mut xcm: Vec>, origin, expected_result| { + assert_eq!( + AllowHrmpNotificationsFromRelayChain::should_execute( + &origin, + &mut xcm, + Weight::from_parts(10, 10), + &mut props(Weight::zero()), + ), + expected_result ); - assert_eq!(r, expected_result, "Failed for origin: {origin:?} and message: {message:?}"); - } + }; + + // invalid origin + assert_should_execute( + vec![HrmpChannelAccepted { recipient: Default::default() }], + Location::new(1, [Parachain(1)]), + Err(ProcessMessageError::Unsupported), + ); + + // invalid XCM (unexpected instruction before) + assert_should_execute( + vec![SetAppendix(Xcm(vec![])), HrmpChannelAccepted { recipient: Default::default() }], + Location::parent(), + Err(ProcessMessageError::BadFormat), + ); + // invalid XCM (unexpected instruction after) + assert_should_execute( + vec![HrmpChannelAccepted { recipient: Default::default() }, SetTopic([0; 32])], + Location::parent(), + Err(ProcessMessageError::BadFormat), + ); + // invalid XCM (unexpected instruction) + assert_should_execute( + vec![SetAppendix(Xcm(vec![]))], + Location::parent(), + Err(ProcessMessageError::BadFormat), + ); + + // ok + assert_should_execute( + vec![HrmpChannelAccepted { recipient: Default::default() }], + Location::parent(), + Ok(()), + ); + assert_should_execute( + vec![HrmpNewChannelOpenRequest { + max_capacity: Default::default(), + sender: Default::default(), + max_message_size: Default::default(), + }], + Location::parent(), + Ok(()), + ); + assert_should_execute( + vec![HrmpChannelClosing { + recipient: Default::default(), + sender: Default::default(), + initiator: Default::default(), + }], + Location::parent(), + Ok(()), + ); } diff --git a/polkadot/xcm/xcm-builder/src/tests/mock.rs b/polkadot/xcm/xcm-builder/src/tests/mock.rs index 3d03ab054248d907c89fe0f7a9d2fbb21896ace2..7532b97d97b3ba0a8c4ac648d68accc3cadfd6cd 100644 --- a/polkadot/xcm/xcm-builder/src/tests/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/mock.rs @@ -19,6 +19,7 @@ use crate::{ barriers::{AllowSubscriptionsFrom, RespectSuspension, TrailingSetTopicAsId}, test_utils::*, + EnsureDecodableXcm, }; pub use crate::{ AliasForeignAccountId32, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, @@ -165,8 +166,8 @@ pub fn set_exporter_override( pub fn clear_exporter_override() { EXPORTER_OVERRIDE.with(|x| x.replace(None)); } -pub struct TestMessageSender; -impl SendXcm for TestMessageSender { +pub struct TestMessageSenderImpl; +impl SendXcm for TestMessageSenderImpl { type Ticket = (Location, Xcm<()>, XcmHash); fn validate( dest: &mut Option, @@ -183,6 +184,8 @@ impl SendXcm for TestMessageSender { Ok(hash) } } +pub type TestMessageSender = EnsureDecodableXcm; + pub struct TestMessageExporter; impl ExportXcm for TestMessageExporter { type Ticket = (NetworkId, u32, InteriorLocation, InteriorLocation, Xcm<()>, XcmHash); diff --git a/polkadot/xcm/xcm-builder/src/tests/mod.rs b/polkadot/xcm/xcm-builder/src/tests/mod.rs index 63d254a1067582d3359c6a7dc1b27102bd1e45e7..16ce3d2cf8ffef68d1f218fb1082cee8dc122b4b 100644 --- a/polkadot/xcm/xcm-builder/src/tests/mod.rs +++ b/polkadot/xcm/xcm-builder/src/tests/mod.rs @@ -15,7 +15,6 @@ // along with Polkadot. If not, see . use super::{test_utils::*, *}; -use core::convert::TryInto; use frame_support::{ assert_err, traits::{ConstU32, ContainsPair, ProcessMessageError}, diff --git a/polkadot/xcm/xcm-builder/src/universal_exports.rs b/polkadot/xcm/xcm-builder/src/universal_exports.rs index 6e031cdbc270d73c49fcd7960430473a9bb9c9d5..d0e3ef3032ea1c0aeae698d1e275d4a401a7bb0d 100644 --- a/polkadot/xcm/xcm-builder/src/universal_exports.rs +++ b/polkadot/xcm/xcm-builder/src/universal_exports.rs @@ -187,7 +187,7 @@ pub fn forward_id_for(original_id: &XcmHash) -> XcmHash { /// end with the `SetTopic` instruction. /// /// In the case that the message ends with a `SetTopic(T)` (as should be the case if the top-level -/// router is `EnsureUniqueTopic`), then the forwarding message (i.e. the one carrying the +/// router is `WithUniqueTopic`), then the forwarding message (i.e. the one carrying the /// export instruction *to* the bridge in local consensus) will also end with a `SetTopic` whose /// inner is `forward_id_for(T)`. If this is not the case then the onward message will not be given /// the `SetTopic` afterword. @@ -254,7 +254,7 @@ impl; + // copied from kusama constants pub const UNITS: Balance = 1_000_000_000_000; pub const CENTS: Balance = UNITS / 30_000; @@ -137,7 +139,7 @@ impl configuration::Config for Runtime { parameter_types! { pub const KsmLocation: Location = Location::here(); pub const KusamaNetwork: NetworkId = NetworkId::Kusama; - pub UniversalLocation: InteriorLocation = Here; + pub UniversalLocation: InteriorLocation = KusamaNetwork::get().into(); pub CheckAccount: (AccountId, MintLocation) = (XcmPallet::check_account(), MintLocation::Local); } @@ -180,7 +182,7 @@ pub type TrustedTeleporters = (xcm_builder::Case,); pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; - type XcmSender = TestSendXcm; + type XcmSender = TestXcmRouter; type AssetTransactor = LocalAssetTransactor; type OriginConverter = LocalOriginConverter; type IsReserve = (); @@ -215,7 +217,7 @@ impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type UniversalLocation = UniversalLocation; type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; - type XcmRouter = TestSendXcm; + type XcmRouter = TestXcmRouter; // Anyone can execute XCM messages locally... type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; type XcmExecuteFilter = Nothing; diff --git a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs index 1d1ee40d092c6d251b200439971986b9e0d6abd7..279d7118f8cf90cd5b8666ada2c7a8fbc5d4c94a 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -#![cfg_attr(not(feature = "std"), no_std)] #![cfg(test)] use codec::Encode; diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 81b81fe6a17154c74e03ebbddf8bfd81e4f7181c..a7052328da00140faf90d5391f27d126dc953a27 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -182,6 +182,13 @@ impl PreparedMessage for WeighedMessage { } } +#[cfg(any(test, feature = "std"))] +impl WeighedMessage { + pub fn new(weight: Weight, message: Xcm) -> Self { + Self(weight, message) + } +} + impl ExecuteXcm for XcmExecutor { type Prepared = WeighedMessage; fn prepare( @@ -347,6 +354,9 @@ impl XcmExecutor { msg: Xcm<()>, reason: FeeReason, ) -> Result { + log::trace!( + target: "xcm::send", "Sending msg: {msg:?}, to destination: {dest:?}, (reason: {reason:?})" + ); let (ticket, fee) = validate_send::(dest, msg)?; self.take_fee(fee, reason)?; Config::XcmSender::deliver(ticket).map_err(Into::into) diff --git a/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs b/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs index 5da3d1da37c8117d2f0f754c4cfea1b3e638cb06..22e4a3bd61a83d0739b12c2276104b31f6ebe37e 100644 --- a/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs +++ b/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs @@ -23,14 +23,12 @@ use xcm::prelude::*; /// Errors related to determining asset transfer support. #[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] pub enum Error { - /// Invalid non-concrete asset. - NotConcrete, /// Reserve chain could not be determined for assets. UnknownReserve, } /// Specify which type of asset transfer is required for a particular `(asset, dest)` combination. -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo)] pub enum TransferType { /// should teleport `asset` to `dest` Teleport, @@ -39,7 +37,7 @@ pub enum TransferType { /// should reserve-transfer `asset` to `dest`, using `dest` as reserve DestinationReserve, /// should reserve-transfer `asset` to `dest`, using remote chain `Location` as reserve - RemoteReserve(Location), + RemoteReserve(VersionedLocation), } /// A trait for identifying asset transfer type based on `IsTeleporter` and `IsReserve` @@ -77,7 +75,7 @@ pub trait XcmAssetTransfers { Ok(TransferType::LocalReserve) } else if Self::IsReserve::contains(asset, &asset_location) { // remote location that is recognized as reserve location for asset - Ok(TransferType::RemoteReserve(asset_location)) + Ok(TransferType::RemoteReserve(asset_location.into())) } else { // remote location that is not configured either as teleporter or reserve => cannot // determine asset reserve diff --git a/polkadot/xcm/xcm-simulator/example/src/lib.rs b/polkadot/xcm/xcm-simulator/example/src/lib.rs index 56e204bf5718164385e3da924f75ae98c2fcb075..6fb9a69770ea8f74860f528bce56ecd44cc2fc9f 100644 --- a/polkadot/xcm/xcm-simulator/example/src/lib.rs +++ b/polkadot/xcm/xcm-simulator/example/src/lib.rs @@ -17,13 +17,16 @@ mod parachain; mod relay_chain; +#[cfg(test)] +mod tests; + use sp_runtime::BuildStorage; use sp_tracing; use xcm::prelude::*; use xcm_executor::traits::ConvertLocation; use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain, TestExt}; -pub const ALICE: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]); +pub const ALICE: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([1u8; 32]); pub const INITIAL_BALANCE: u128 = 1_000_000_000; decl_test_parachain! { @@ -68,27 +71,27 @@ decl_test_network! { pub fn parent_account_id() -> parachain::AccountId { let location = (Parent,); - parachain::LocationToAccountId::convert_location(&location.into()).unwrap() + parachain::location_converter::LocationConverter::convert_location(&location.into()).unwrap() } pub fn child_account_id(para: u32) -> relay_chain::AccountId { let location = (Parachain(para),); - relay_chain::LocationToAccountId::convert_location(&location.into()).unwrap() + relay_chain::location_converter::LocationConverter::convert_location(&location.into()).unwrap() } pub fn child_account_account_id(para: u32, who: sp_runtime::AccountId32) -> relay_chain::AccountId { let location = (Parachain(para), AccountId32 { network: None, id: who.into() }); - relay_chain::LocationToAccountId::convert_location(&location.into()).unwrap() + relay_chain::location_converter::LocationConverter::convert_location(&location.into()).unwrap() } pub fn sibling_account_account_id(para: u32, who: sp_runtime::AccountId32) -> parachain::AccountId { let location = (Parent, Parachain(para), AccountId32 { network: None, id: who.into() }); - parachain::LocationToAccountId::convert_location(&location.into()).unwrap() + parachain::location_converter::LocationConverter::convert_location(&location.into()).unwrap() } pub fn parent_account_account_id(who: sp_runtime::AccountId32) -> parachain::AccountId { let location = (Parent, AccountId32 { network: None, id: who.into() }); - parachain::LocationToAccountId::convert_location(&location.into()).unwrap() + parachain::location_converter::LocationConverter::convert_location(&location.into()).unwrap() } pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { @@ -137,517 +140,3 @@ pub fn relay_ext() -> sp_io::TestExternalities { pub type RelayChainPalletXcm = pallet_xcm::Pallet; pub type ParachainPalletXcm = pallet_xcm::Pallet; - -#[cfg(test)] -mod tests { - use super::*; - - use codec::Encode; - use frame_support::{assert_ok, weights::Weight}; - use xcm::latest::QueryResponseInfo; - use xcm_simulator::TestExt; - - // Helper function for forming buy execution message - fn buy_execution(fees: impl Into) -> Instruction { - BuyExecution { fees: fees.into(), weight_limit: Unlimited } - } - - #[test] - fn remote_account_ids_work() { - child_account_account_id(1, ALICE); - sibling_account_account_id(1, ALICE); - parent_account_account_id(ALICE); - } - - #[test] - fn dmp() { - MockNet::reset(); - - let remark = parachain::RuntimeCall::System( - frame_system::Call::::remark_with_event { remark: vec![1, 2, 3] }, - ); - Relay::execute_with(|| { - assert_ok!(RelayChainPalletXcm::send_xcm( - Here, - Parachain(1), - Xcm(vec![Transact { - origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), - call: remark.encode().into(), - }]), - )); - }); - - ParaA::execute_with(|| { - use parachain::{RuntimeEvent, System}; - assert!(System::events().iter().any(|r| matches!( - r.event, - RuntimeEvent::System(frame_system::Event::Remarked { .. }) - ))); - }); - } - - #[test] - fn ump() { - MockNet::reset(); - - let remark = relay_chain::RuntimeCall::System( - frame_system::Call::::remark_with_event { remark: vec![1, 2, 3] }, - ); - ParaA::execute_with(|| { - assert_ok!(ParachainPalletXcm::send_xcm( - Here, - Parent, - Xcm(vec![Transact { - origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), - call: remark.encode().into(), - }]), - )); - }); - - Relay::execute_with(|| { - use relay_chain::{RuntimeEvent, System}; - assert!(System::events().iter().any(|r| matches!( - r.event, - RuntimeEvent::System(frame_system::Event::Remarked { .. }) - ))); - }); - } - - #[test] - fn xcmp() { - MockNet::reset(); - - let remark = parachain::RuntimeCall::System( - frame_system::Call::::remark_with_event { remark: vec![1, 2, 3] }, - ); - ParaA::execute_with(|| { - assert_ok!(ParachainPalletXcm::send_xcm( - Here, - (Parent, Parachain(2)), - Xcm(vec![Transact { - origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), - call: remark.encode().into(), - }]), - )); - }); - - ParaB::execute_with(|| { - use parachain::{RuntimeEvent, System}; - assert!(System::events().iter().any(|r| matches!( - r.event, - RuntimeEvent::System(frame_system::Event::Remarked { .. }) - ))); - }); - } - - #[test] - fn reserve_transfer() { - MockNet::reset(); - - let withdraw_amount = 123; - - Relay::execute_with(|| { - assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets( - relay_chain::RuntimeOrigin::signed(ALICE), - Box::new(Parachain(1).into()), - Box::new(AccountId32 { network: None, id: ALICE.into() }.into()), - Box::new((Here, withdraw_amount).into()), - 0, - Unlimited, - )); - assert_eq!( - relay_chain::Balances::free_balance(&child_account_id(1)), - INITIAL_BALANCE + withdraw_amount - ); - }); - - ParaA::execute_with(|| { - // free execution, full amount received - assert_eq!( - pallet_balances::Pallet::::free_balance(&ALICE), - INITIAL_BALANCE + withdraw_amount - ); - }); - } - - #[test] - fn remote_locking_and_unlocking() { - MockNet::reset(); - - let locked_amount = 100; - - ParaB::execute_with(|| { - let message = Xcm(vec![LockAsset { - asset: (Here, locked_amount).into(), - unlocker: Parachain(1).into(), - }]); - assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); - }); - - Relay::execute_with(|| { - use pallet_balances::{BalanceLock, Reasons}; - assert_eq!( - relay_chain::Balances::locks(&child_account_id(2)), - vec![BalanceLock { - id: *b"py/xcmlk", - amount: locked_amount, - reasons: Reasons::All - }] - ); - }); - - ParaA::execute_with(|| { - assert_eq!( - parachain::MsgQueue::received_dmp(), - vec![Xcm(vec![NoteUnlockable { - owner: (Parent, Parachain(2)).into(), - asset: (Parent, locked_amount).into() - }])] - ); - }); - - ParaB::execute_with(|| { - // Request unlocking part of the funds on the relay chain - let message = Xcm(vec![RequestUnlock { - asset: (Parent, locked_amount - 50).into(), - locker: Parent.into(), - }]); - assert_ok!(ParachainPalletXcm::send_xcm(Here, (Parent, Parachain(1)), message)); - }); - - Relay::execute_with(|| { - use pallet_balances::{BalanceLock, Reasons}; - // Lock is reduced - assert_eq!( - relay_chain::Balances::locks(&child_account_id(2)), - vec![BalanceLock { - id: *b"py/xcmlk", - amount: locked_amount - 50, - reasons: Reasons::All - }] - ); - }); - } - - /// Scenario: - /// A parachain transfers an NFT resident on the relay chain to another parachain account. - /// - /// Asserts that the parachain accounts are updated as expected. - #[test] - fn withdraw_and_deposit_nft() { - MockNet::reset(); - - Relay::execute_with(|| { - assert_eq!(relay_chain::Uniques::owner(1, 42), Some(child_account_id(1))); - }); - - ParaA::execute_with(|| { - let message = Xcm(vec![TransferAsset { - assets: (GeneralIndex(1), 42u32).into(), - beneficiary: Parachain(2).into(), - }]); - // Send withdraw and deposit - assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message)); - }); - - Relay::execute_with(|| { - assert_eq!(relay_chain::Uniques::owner(1, 42), Some(child_account_id(2))); - }); - } - - /// Scenario: - /// The relay-chain teleports an NFT to a parachain. - /// - /// Asserts that the parachain accounts are updated as expected. - #[test] - fn teleport_nft() { - MockNet::reset(); - - Relay::execute_with(|| { - // Mint the NFT (1, 69) and give it to our "parachain#1 alias". - assert_ok!(relay_chain::Uniques::mint( - relay_chain::RuntimeOrigin::signed(ALICE), - 1, - 69, - child_account_account_id(1, ALICE), - )); - // The parachain#1 alias of Alice is what must hold it on the Relay-chain for it to be - // withdrawable by Alice on the parachain. - assert_eq!( - relay_chain::Uniques::owner(1, 69), - Some(child_account_account_id(1, ALICE)) - ); - }); - ParaA::execute_with(|| { - assert_ok!(parachain::ForeignUniques::force_create( - parachain::RuntimeOrigin::root(), - (Parent, GeneralIndex(1)).into(), - ALICE, - false, - )); - assert_eq!( - parachain::ForeignUniques::owner((Parent, GeneralIndex(1)).into(), 69u32.into()), - None, - ); - assert_eq!(parachain::Balances::reserved_balance(&ALICE), 0); - - // IRL Alice would probably just execute this locally on the Relay-chain, but we can't - // easily do that here since we only send between chains. - let message = Xcm(vec![ - WithdrawAsset((GeneralIndex(1), 69u32).into()), - InitiateTeleport { - assets: AllCounted(1).into(), - dest: Parachain(1).into(), - xcm: Xcm(vec![DepositAsset { - assets: AllCounted(1).into(), - beneficiary: (AccountId32 { id: ALICE.into(), network: None },).into(), - }]), - }, - ]); - // Send teleport - let alice = AccountId32 { id: ALICE.into(), network: None }; - assert_ok!(ParachainPalletXcm::send_xcm(alice, Parent, message)); - }); - ParaA::execute_with(|| { - assert_eq!( - parachain::ForeignUniques::owner((Parent, GeneralIndex(1)).into(), 69u32.into()), - Some(ALICE), - ); - assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000); - }); - Relay::execute_with(|| { - assert_eq!(relay_chain::Uniques::owner(1, 69), None); - }); - } - - /// Scenario: - /// The relay-chain transfers an NFT into a parachain's sovereign account, who then mints a - /// trustless-backed-derived locally. - /// - /// Asserts that the parachain accounts are updated as expected. - #[test] - fn reserve_asset_transfer_nft() { - sp_tracing::init_for_tests(); - MockNet::reset(); - - Relay::execute_with(|| { - assert_ok!(relay_chain::Uniques::force_create( - relay_chain::RuntimeOrigin::root(), - 2, - ALICE, - false - )); - assert_ok!(relay_chain::Uniques::mint( - relay_chain::RuntimeOrigin::signed(ALICE), - 2, - 69, - child_account_account_id(1, ALICE) - )); - assert_eq!( - relay_chain::Uniques::owner(2, 69), - Some(child_account_account_id(1, ALICE)) - ); - }); - ParaA::execute_with(|| { - assert_ok!(parachain::ForeignUniques::force_create( - parachain::RuntimeOrigin::root(), - (Parent, GeneralIndex(2)).into(), - ALICE, - false, - )); - assert_eq!( - parachain::ForeignUniques::owner((Parent, GeneralIndex(2)).into(), 69u32.into()), - None, - ); - assert_eq!(parachain::Balances::reserved_balance(&ALICE), 0); - - let message = Xcm(vec![ - WithdrawAsset((GeneralIndex(2), 69u32).into()), - DepositReserveAsset { - assets: AllCounted(1).into(), - dest: Parachain(1).into(), - xcm: Xcm(vec![DepositAsset { - assets: AllCounted(1).into(), - beneficiary: (AccountId32 { id: ALICE.into(), network: None },).into(), - }]), - }, - ]); - // Send transfer - let alice = AccountId32 { id: ALICE.into(), network: None }; - assert_ok!(ParachainPalletXcm::send_xcm(alice, Parent, message)); - }); - ParaA::execute_with(|| { - log::debug!(target: "xcm-executor", "Hello"); - assert_eq!( - parachain::ForeignUniques::owner((Parent, GeneralIndex(2)).into(), 69u32.into()), - Some(ALICE), - ); - assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000); - }); - - Relay::execute_with(|| { - assert_eq!(relay_chain::Uniques::owner(2, 69), Some(child_account_id(1))); - }); - } - - /// Scenario: - /// The relay-chain creates an asset class on a parachain and then Alice transfers her NFT into - /// that parachain's sovereign account, who then mints a trustless-backed-derivative locally. - /// - /// Asserts that the parachain accounts are updated as expected. - #[test] - fn reserve_asset_class_create_and_reserve_transfer() { - MockNet::reset(); - - Relay::execute_with(|| { - assert_ok!(relay_chain::Uniques::force_create( - relay_chain::RuntimeOrigin::root(), - 2, - ALICE, - false - )); - assert_ok!(relay_chain::Uniques::mint( - relay_chain::RuntimeOrigin::signed(ALICE), - 2, - 69, - child_account_account_id(1, ALICE) - )); - assert_eq!( - relay_chain::Uniques::owner(2, 69), - Some(child_account_account_id(1, ALICE)) - ); - - let message = Xcm(vec![Transact { - origin_kind: OriginKind::Xcm, - require_weight_at_most: Weight::from_parts(1_000_000_000, 1024 * 1024), - call: parachain::RuntimeCall::from( - pallet_uniques::Call::::create { - collection: (Parent, 2u64).into(), - admin: parent_account_id(), - }, - ) - .encode() - .into(), - }]); - // Send creation. - assert_ok!(RelayChainPalletXcm::send_xcm(Here, Parachain(1), message)); - }); - ParaA::execute_with(|| { - // Then transfer - let message = Xcm(vec![ - WithdrawAsset((GeneralIndex(2), 69u32).into()), - DepositReserveAsset { - assets: AllCounted(1).into(), - dest: Parachain(1).into(), - xcm: Xcm(vec![DepositAsset { - assets: AllCounted(1).into(), - beneficiary: (AccountId32 { id: ALICE.into(), network: None },).into(), - }]), - }, - ]); - let alice = AccountId32 { id: ALICE.into(), network: None }; - assert_ok!(ParachainPalletXcm::send_xcm(alice, Parent, message)); - }); - ParaA::execute_with(|| { - assert_eq!(parachain::Balances::reserved_balance(&parent_account_id()), 1000); - assert_eq!( - parachain::ForeignUniques::collection_owner((Parent, 2u64).into()), - Some(parent_account_id()) - ); - }); - } - - /// Scenario: - /// A parachain transfers funds on the relay chain to another parachain account. - /// - /// Asserts that the parachain accounts are updated as expected. - #[test] - fn withdraw_and_deposit() { - MockNet::reset(); - - let send_amount = 10; - - ParaA::execute_with(|| { - let message = Xcm(vec![ - WithdrawAsset((Here, send_amount).into()), - buy_execution((Here, send_amount)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: Parachain(2).into() }, - ]); - // Send withdraw and deposit - assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); - }); - - Relay::execute_with(|| { - assert_eq!( - relay_chain::Balances::free_balance(child_account_id(1)), - INITIAL_BALANCE - send_amount - ); - assert_eq!( - relay_chain::Balances::free_balance(child_account_id(2)), - INITIAL_BALANCE + send_amount - ); - }); - } - - /// Scenario: - /// A parachain wants to be notified that a transfer worked correctly. - /// It sends a `QueryHolding` after the deposit to get notified on success. - /// - /// Asserts that the balances are updated correctly and the expected XCM is sent. - #[test] - fn query_holding() { - MockNet::reset(); - - let send_amount = 10; - let query_id_set = 1234; - - // Send a message which fully succeeds on the relay chain - ParaA::execute_with(|| { - let message = Xcm(vec![ - WithdrawAsset((Here, send_amount).into()), - buy_execution((Here, send_amount)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: Parachain(2).into() }, - ReportHolding { - response_info: QueryResponseInfo { - destination: Parachain(1).into(), - query_id: query_id_set, - max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), - }, - assets: All.into(), - }, - ]); - // Send withdraw and deposit with query holding - assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); - }); - - // Check that transfer was executed - Relay::execute_with(|| { - // Withdraw executed - assert_eq!( - relay_chain::Balances::free_balance(child_account_id(1)), - INITIAL_BALANCE - send_amount - ); - // Deposit executed - assert_eq!( - relay_chain::Balances::free_balance(child_account_id(2)), - INITIAL_BALANCE + send_amount - ); - }); - - // Check that QueryResponse message was received - ParaA::execute_with(|| { - assert_eq!( - parachain::MsgQueue::received_dmp(), - vec![Xcm(vec![QueryResponse { - query_id: query_id_set, - response: Response::Assets(Assets::new()), - max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), - querier: Some(Here.into()), - }])], - ); - }); - } -} diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain.rs b/polkadot/xcm/xcm-simulator/example/src/parachain.rs deleted file mode 100644 index 86401d756af3b7d86c34e394ae89c677684c0fb8..0000000000000000000000000000000000000000 --- a/polkadot/xcm/xcm-simulator/example/src/parachain.rs +++ /dev/null @@ -1,470 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Parachain runtime mock. - -use codec::{Decode, Encode}; -use core::marker::PhantomData; -use frame_support::{ - construct_runtime, derive_impl, parameter_types, - traits::{ContainsPair, EnsureOrigin, EnsureOriginWithArg, Everything, EverythingBut, Nothing}, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, -}; - -use frame_system::EnsureRoot; -use sp_core::{ConstU32, H256}; -use sp_runtime::{ - traits::{Get, Hash, IdentityLookup}, - AccountId32, -}; -use sp_std::prelude::*; - -use pallet_xcm::XcmPassthrough; -use polkadot_core_primitives::BlockNumber as RelayBlockNumber; -use polkadot_parachain_primitives::primitives::{ - DmpMessageHandler, Id as ParaId, Sibling, XcmpMessageFormat, XcmpMessageHandler, -}; -use xcm::{latest::prelude::*, VersionedXcm}; -use xcm_builder::{ - Account32Hash, AccountId32Aliases, AllowUnpaidExecutionFrom, ConvertedConcreteId, - EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, FrameTransactionalProcessor, - FungibleAdapter, IsConcrete, NativeAsset, NoChecking, NonFungiblesAdapter, ParentIsPreset, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, -}; -use xcm_executor::{ - traits::{ConvertLocation, JustTry}, - Config, XcmExecutor, -}; - -pub type SovereignAccountOf = ( - SiblingParachainConvertsVia, - AccountId32Aliases, - ParentIsPreset, -); - -pub type AccountId = AccountId32; -pub type Balance = u128; - -parameter_types! { - pub const BlockHashCount: u64 = 250; -} - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl frame_system::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; -} - -parameter_types! { - pub ExistentialDeposit: Balance = 1; - pub const MaxLocks: u32 = 50; - pub const MaxReserves: u32 = 50; -} - -impl pallet_balances::Config for Runtime { - type MaxLocks = MaxLocks; - type Balance = Balance; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = (); - type MaxReserves = MaxReserves; - type ReserveIdentifier = [u8; 8]; - type RuntimeHoldReason = RuntimeHoldReason; - type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); - type MaxFreezes = ConstU32<0>; -} - -#[cfg(feature = "runtime-benchmarks")] -pub struct UniquesHelper; -#[cfg(feature = "runtime-benchmarks")] -impl pallet_uniques::BenchmarkHelper for UniquesHelper { - fn collection(i: u16) -> Location { - GeneralIndex(i as u128).into() - } - fn item(i: u16) -> AssetInstance { - AssetInstance::Index(i as u128) - } -} - -impl pallet_uniques::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type CollectionId = Location; - type ItemId = AssetInstance; - type Currency = Balances; - type CreateOrigin = ForeignCreators; - type ForceOrigin = frame_system::EnsureRoot; - type CollectionDeposit = frame_support::traits::ConstU128<1_000>; - type ItemDeposit = frame_support::traits::ConstU128<1_000>; - type MetadataDepositBase = frame_support::traits::ConstU128<1_000>; - type AttributeDepositBase = frame_support::traits::ConstU128<1_000>; - type DepositPerByte = frame_support::traits::ConstU128<1>; - type StringLimit = ConstU32<64>; - type KeyLimit = ConstU32<64>; - type ValueLimit = ConstU32<128>; - type Locker = (); - type WeightInfo = (); - #[cfg(feature = "runtime-benchmarks")] - type Helper = UniquesHelper; -} - -// `EnsureOriginWithArg` impl for `CreateOrigin` which allows only XCM origins -// which are locations containing the class location. -pub struct ForeignCreators; -impl EnsureOriginWithArg for ForeignCreators { - type Success = AccountId; - - fn try_origin( - o: RuntimeOrigin, - a: &Location, - ) -> sp_std::result::Result { - let origin_location = pallet_xcm::EnsureXcm::::try_origin(o.clone())?; - if !a.starts_with(&origin_location) { - return Err(o) - } - SovereignAccountOf::convert_location(&origin_location).ok_or(o) - } - - #[cfg(feature = "runtime-benchmarks")] - fn try_successful_origin(a: &Location) -> Result { - Ok(pallet_xcm::Origin::Xcm(a.clone()).into()) - } -} - -parameter_types! { - pub const ReservedXcmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); - pub const ReservedDmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); -} - -parameter_types! { - pub const KsmLocation: Location = Location::parent(); - pub const RelayNetwork: NetworkId = NetworkId::Kusama; - pub UniversalLocation: InteriorLocation = Parachain(MsgQueue::parachain_id().into()).into(); -} - -pub type LocationToAccountId = ( - ParentIsPreset, - SiblingParachainConvertsVia, - AccountId32Aliases, - Account32Hash<(), AccountId>, -); - -pub type XcmOriginToCallOrigin = ( - SovereignSignedViaLocation, - SignedAccountId32AsNative, - XcmPassthrough, -); - -parameter_types! { - pub const UnitWeightCost: Weight = Weight::from_parts(1, 1); - pub KsmPerSecondPerByte: (AssetId, u128, u128) = (AssetId(Parent.into()), 1, 1); - pub const MaxInstructions: u32 = 100; - pub const MaxAssetsIntoHolding: u32 = 64; - pub ForeignPrefix: Location = (Parent,).into(); -} - -pub type LocalAssetTransactor = ( - FungibleAdapter, LocationToAccountId, AccountId, ()>, - NonFungiblesAdapter< - ForeignUniques, - ConvertedConcreteId, - SovereignAccountOf, - AccountId, - NoChecking, - (), - >, -); - -pub type XcmRouter = super::ParachainXcmRouter; -pub type Barrier = AllowUnpaidExecutionFrom; - -parameter_types! { - pub NftCollectionOne: AssetFilter - = Wild(AllOf { fun: WildNonFungible, id: AssetId((Parent, GeneralIndex(1)).into()) }); - pub NftCollectionOneForRelay: (AssetFilter, Location) - = (NftCollectionOne::get(), (Parent,).into()); -} -pub type TrustedTeleporters = xcm_builder::Case; -pub type TrustedReserves = EverythingBut>; - -pub struct XcmConfig; -impl Config for XcmConfig { - type RuntimeCall = RuntimeCall; - type XcmSender = XcmRouter; - type AssetTransactor = LocalAssetTransactor; - type OriginConverter = XcmOriginToCallOrigin; - type IsReserve = (NativeAsset, TrustedReserves); - type IsTeleporter = TrustedTeleporters; - type UniversalLocation = UniversalLocation; - type Barrier = Barrier; - type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; - type ResponseHandler = (); - type AssetTrap = (); - type AssetLocker = PolkadotXcm; - type AssetExchanger = (); - type AssetClaims = (); - type SubscriptionService = (); - type PalletInstancesInfo = (); - type FeeManager = (); - type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type MessageExporter = (); - type UniversalAliases = Nothing; - type CallDispatcher = RuntimeCall; - type SafeCallFilter = Everything; - type Aliasers = Nothing; - type TransactionalProcessor = FrameTransactionalProcessor; - type HrmpNewChannelOpenRequestHandler = (); - type HrmpChannelAcceptedHandler = (); - type HrmpChannelClosingHandler = (); -} - -#[frame_support::pallet] -pub mod mock_msg_queue { - use super::*; - use frame_support::pallet_prelude::*; - - #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - type XcmExecutor: ExecuteXcm; - } - - #[pallet::call] - impl Pallet {} - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(_); - - #[pallet::storage] - #[pallet::getter(fn parachain_id)] - pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn received_dmp)] - /// A queue of received DMP messages - pub(super) type ReceivedDmp = StorageValue<_, Vec>, ValueQuery>; - - impl Get for Pallet { - fn get() -> ParaId { - Self::parachain_id() - } - } - - pub type MessageId = [u8; 32]; - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - // XCMP - /// Some XCM was executed OK. - Success(Option), - /// Some XCM failed. - Fail(Option, XcmError), - /// Bad XCM version used. - BadVersion(Option), - /// Bad XCM format used. - BadFormat(Option), - - // DMP - /// Downward message is invalid XCM. - InvalidFormat(MessageId), - /// Downward message is unsupported version of XCM. - UnsupportedVersion(MessageId), - /// Downward message executed with the given outcome. - ExecutedDownward(MessageId, Outcome), - } - - impl Pallet { - pub fn set_para_id(para_id: ParaId) { - ParachainId::::put(para_id); - } - - fn handle_xcmp_message( - sender: ParaId, - _sent_at: RelayBlockNumber, - xcm: VersionedXcm, - max_weight: Weight, - ) -> Result { - let hash = Encode::using_encoded(&xcm, T::Hashing::hash); - let mut message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); - let (result, event) = match Xcm::::try_from(xcm) { - Ok(xcm) => { - let location = (Parent, Parachain(sender.into())); - match T::XcmExecutor::prepare_and_execute( - location, - xcm, - &mut message_hash, - max_weight, - Weight::zero(), - ) { - Outcome::Error { error } => (Err(error), Event::Fail(Some(hash), error)), - Outcome::Complete { used } => (Ok(used), Event::Success(Some(hash))), - // As far as the caller is concerned, this was dispatched without error, so - // we just report the weight used. - Outcome::Incomplete { used, error } => - (Ok(used), Event::Fail(Some(hash), error)), - } - }, - Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), - }; - Self::deposit_event(event); - result - } - } - - impl XcmpMessageHandler for Pallet { - fn handle_xcmp_messages<'a, I: Iterator>( - iter: I, - max_weight: Weight, - ) -> Weight { - for (sender, sent_at, data) in iter { - let mut data_ref = data; - let _ = XcmpMessageFormat::decode(&mut data_ref) - .expect("Simulator encodes with versioned xcm format; qed"); - - let mut remaining_fragments = data_ref; - while !remaining_fragments.is_empty() { - if let Ok(xcm) = - VersionedXcm::::decode(&mut remaining_fragments) - { - let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); - } else { - debug_assert!(false, "Invalid incoming XCMP message data"); - } - } - } - max_weight - } - } - - impl DmpMessageHandler for Pallet { - fn handle_dmp_messages( - iter: impl Iterator)>, - limit: Weight, - ) -> Weight { - for (_i, (_sent_at, data)) in iter.enumerate() { - let mut id = sp_io::hashing::blake2_256(&data[..]); - let maybe_versioned = VersionedXcm::::decode(&mut &data[..]); - match maybe_versioned { - Err(_) => { - Self::deposit_event(Event::InvalidFormat(id)); - }, - Ok(versioned) => match Xcm::try_from(versioned) { - Err(()) => Self::deposit_event(Event::UnsupportedVersion(id)), - Ok(x) => { - let outcome = T::XcmExecutor::prepare_and_execute( - Parent, - x.clone(), - &mut id, - limit, - Weight::zero(), - ); - >::append(x); - Self::deposit_event(Event::ExecutedDownward(id, outcome)); - }, - }, - } - } - limit - } - } -} - -impl mock_msg_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; -} - -pub type LocalOriginToLocation = SignedToAccountId32; - -pub struct TrustedLockerCase(PhantomData); -impl> ContainsPair for TrustedLockerCase { - fn contains(origin: &Location, asset: &Asset) -> bool { - let (o, a) = T::get(); - a.matches(asset) && &o == origin - } -} - -parameter_types! { - pub RelayTokenForRelay: (Location, AssetFilter) = (Parent.into(), Wild(AllOf { id: AssetId(Parent.into()), fun: WildFungible })); -} - -pub type TrustedLockers = TrustedLockerCase; - -impl pallet_xcm::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SendXcmOrigin = EnsureXcmOrigin; - type XcmRouter = XcmRouter; - type ExecuteXcmOrigin = EnsureXcmOrigin; - type XcmExecuteFilter = Everything; - type XcmExecutor = XcmExecutor; - type XcmTeleportFilter = Nothing; - type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; - type UniversalLocation = UniversalLocation; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; - type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; - type Currency = Balances; - type CurrencyMatcher = (); - type TrustedLockers = TrustedLockers; - type SovereignAccountOf = LocationToAccountId; - type MaxLockers = ConstU32<8>; - type MaxRemoteLockConsumers = ConstU32<0>; - type RemoteLockConsumerIdentifier = (); - type WeightInfo = pallet_xcm::TestWeightInfo; - type AdminOrigin = EnsureRoot; -} - -type Block = frame_system::mocking::MockBlock; - -construct_runtime!( - pub enum Runtime - { - System: frame_system, - Balances: pallet_balances, - MsgQueue: mock_msg_queue, - PolkadotXcm: pallet_xcm, - ForeignUniques: pallet_uniques, - } -); diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain/mock_msg_queue.rs b/polkadot/xcm/xcm-simulator/example/src/parachain/mock_msg_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..17cde921f3e20cbfe4da23c68009e30afbd6fca6 --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/parachain/mock_msg_queue.rs @@ -0,0 +1,185 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +pub use pallet::*; +use polkadot_core_primitives::BlockNumber as RelayBlockNumber; +use polkadot_parachain_primitives::primitives::{ + DmpMessageHandler, Id as ParaId, XcmpMessageFormat, XcmpMessageHandler, +}; +use sp_runtime::traits::{Get, Hash}; +use xcm::{latest::prelude::*, VersionedXcm}; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type XcmExecutor: ExecuteXcm; + } + + #[pallet::call] + impl Pallet {} + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery>; + + #[pallet::storage] + /// A queue of received DMP messages + pub(super) type ReceivedDmp = StorageValue<_, Vec>, ValueQuery>; + + impl Get for Pallet { + fn get() -> ParaId { + Self::parachain_id() + } + } + + pub type MessageId = [u8; 32]; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + // XCMP + /// Some XCM was executed OK. + Success(Option), + /// Some XCM failed. + Fail(Option, XcmError), + /// Bad XCM version used. + BadVersion(Option), + /// Bad XCM format used. + BadFormat(Option), + + // DMP + /// Downward message is invalid XCM. + InvalidFormat(MessageId), + /// Downward message is unsupported version of XCM. + UnsupportedVersion(MessageId), + /// Downward message executed with the given outcome. + ExecutedDownward(MessageId, Outcome), + } + + impl Pallet { + /// Get the Parachain Id. + pub fn parachain_id() -> ParaId { + ParachainId::::get() + } + + /// Set the Parachain Id. + pub fn set_para_id(para_id: ParaId) { + ParachainId::::put(para_id); + } + + /// Get the queue of receieved DMP messages. + pub fn received_dmp() -> Vec> { + ReceivedDmp::::get() + } + + fn handle_xcmp_message( + sender: ParaId, + _sent_at: RelayBlockNumber, + xcm: VersionedXcm, + max_weight: Weight, + ) -> Result { + let hash = Encode::using_encoded(&xcm, T::Hashing::hash); + let mut message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); + let (result, event) = match Xcm::::try_from(xcm) { + Ok(xcm) => { + let location = (Parent, Parachain(sender.into())); + match T::XcmExecutor::prepare_and_execute( + location, + xcm, + &mut message_hash, + max_weight, + Weight::zero(), + ) { + Outcome::Error { error } => (Err(error), Event::Fail(Some(hash), error)), + Outcome::Complete { used } => (Ok(used), Event::Success(Some(hash))), + // As far as the caller is concerned, this was dispatched without error, so + // we just report the weight used. + Outcome::Incomplete { used, error } => + (Ok(used), Event::Fail(Some(hash), error)), + } + }, + Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), + }; + Self::deposit_event(event); + result + } + } + + impl XcmpMessageHandler for Pallet { + fn handle_xcmp_messages<'a, I: Iterator>( + iter: I, + max_weight: Weight, + ) -> Weight { + for (sender, sent_at, data) in iter { + let mut data_ref = data; + let _ = XcmpMessageFormat::decode(&mut data_ref) + .expect("Simulator encodes with versioned xcm format; qed"); + + let mut remaining_fragments = data_ref; + while !remaining_fragments.is_empty() { + if let Ok(xcm) = + VersionedXcm::::decode(&mut remaining_fragments) + { + let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); + } else { + debug_assert!(false, "Invalid incoming XCMP message data"); + } + } + } + max_weight + } + } + + impl DmpMessageHandler for Pallet { + fn handle_dmp_messages( + iter: impl Iterator)>, + limit: Weight, + ) -> Weight { + for (_i, (_sent_at, data)) in iter.enumerate() { + let mut id = sp_io::hashing::blake2_256(&data[..]); + let maybe_versioned = VersionedXcm::::decode(&mut &data[..]); + match maybe_versioned { + Err(_) => { + Self::deposit_event(Event::InvalidFormat(id)); + }, + Ok(versioned) => match Xcm::try_from(versioned) { + Err(()) => Self::deposit_event(Event::UnsupportedVersion(id)), + Ok(x) => { + let outcome = T::XcmExecutor::prepare_and_execute( + Parent, + x.clone(), + &mut id, + limit, + Weight::zero(), + ); + >::append(x); + Self::deposit_event(Event::ExecutedDownward(id, outcome)); + }, + }, + } + } + limit + } + } +} diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain/mod.rs b/polkadot/xcm/xcm-simulator/example/src/parachain/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..8021f9551658075fa672c02306ade1bd12298e4d --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/parachain/mod.rs @@ -0,0 +1,182 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Parachain runtime mock. + +mod mock_msg_queue; +mod xcm_config; +pub use xcm_config::*; + +use core::marker::PhantomData; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{ConstU128, ContainsPair, EnsureOrigin, EnsureOriginWithArg, Everything, Nothing}, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, +}; +use frame_system::EnsureRoot; +use sp_core::ConstU32; +use sp_runtime::{ + traits::{Get, IdentityLookup}, + AccountId32, +}; +use sp_std::prelude::*; +use xcm::latest::prelude::*; +use xcm_builder::{EnsureXcmOrigin, SignedToAccountId32}; +use xcm_executor::{traits::ConvertLocation, XcmExecutor}; + +pub type AccountId = AccountId32; +pub type Balance = u128; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; +} + +#[cfg(feature = "runtime-benchmarks")] +pub struct UniquesHelper; +#[cfg(feature = "runtime-benchmarks")] +impl pallet_uniques::BenchmarkHelper for UniquesHelper { + fn collection(i: u16) -> Location { + GeneralIndex(i as u128).into() + } + fn item(i: u16) -> AssetInstance { + AssetInstance::Index(i as u128) + } +} + +impl pallet_uniques::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CollectionId = Location; + type ItemId = AssetInstance; + type Currency = Balances; + type CreateOrigin = ForeignCreators; + type ForceOrigin = frame_system::EnsureRoot; + type CollectionDeposit = frame_support::traits::ConstU128<1_000>; + type ItemDeposit = frame_support::traits::ConstU128<1_000>; + type MetadataDepositBase = frame_support::traits::ConstU128<1_000>; + type AttributeDepositBase = frame_support::traits::ConstU128<1_000>; + type DepositPerByte = frame_support::traits::ConstU128<1>; + type StringLimit = ConstU32<64>; + type KeyLimit = ConstU32<64>; + type ValueLimit = ConstU32<128>; + type Locker = (); + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = UniquesHelper; +} + +// `EnsureOriginWithArg` impl for `CreateOrigin` which allows only XCM origins +// which are locations containing the class location. +pub struct ForeignCreators; +impl EnsureOriginWithArg for ForeignCreators { + type Success = AccountId; + + fn try_origin( + o: RuntimeOrigin, + a: &Location, + ) -> sp_std::result::Result { + let origin_location = pallet_xcm::EnsureXcm::::try_origin(o.clone())?; + if !a.starts_with(&origin_location) { + return Err(o); + } + xcm_config::location_converter::LocationConverter::convert_location(&origin_location) + .ok_or(o) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(a: &Location) -> Result { + Ok(pallet_xcm::Origin::Xcm(a.clone()).into()) + } +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); + pub const ReservedDmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); +} + +impl mock_msg_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} + +pub type LocalOriginToLocation = + SignedToAccountId32; + +pub struct TrustedLockerCase(PhantomData); +impl> ContainsPair for TrustedLockerCase { + fn contains(origin: &Location, asset: &Asset) -> bool { + let (o, a) = T::get(); + a.matches(asset) && &o == origin + } +} + +parameter_types! { + pub RelayTokenForRelay: (Location, AssetFilter) = (Parent.into(), Wild(AllOf { id: AssetId(Parent.into()), fun: WildFungible })); +} + +pub type TrustedLockers = TrustedLockerCase; + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Nothing; + type XcmReserveTransferFilter = Everything; + type Weigher = weigher::Weigher; + type UniversalLocation = constants::UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = TrustedLockers; + type SovereignAccountOf = location_converter::LocationConverter; + type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); + type WeightInfo = pallet_xcm::TestWeightInfo; + type AdminOrigin = EnsureRoot; +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub struct Runtime { + System: frame_system, + Balances: pallet_balances, + MsgQueue: mock_msg_queue, + PolkadotXcm: pallet_xcm, + ForeignUniques: pallet_uniques, + } +); diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/asset_transactor.rs b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/asset_transactor.rs new file mode 100644 index 0000000000000000000000000000000000000000..25cffcf8cef25e96983903f324985b10a70ab496 --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/asset_transactor.rs @@ -0,0 +1,39 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::parachain::{ + constants::KsmLocation, location_converter::LocationConverter, AccountId, Balances, + ForeignUniques, +}; +use xcm::latest::prelude::*; +use xcm_builder::{ + ConvertedConcreteId, FungibleAdapter, IsConcrete, NoChecking, NonFungiblesAdapter, +}; +use xcm_executor::traits::JustTry; + +type LocalAssetTransactor = ( + FungibleAdapter, LocationConverter, AccountId, ()>, + NonFungiblesAdapter< + ForeignUniques, + ConvertedConcreteId, + LocationConverter, + AccountId, + NoChecking, + (), + >, +); + +pub type AssetTransactor = LocalAssetTransactor; diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/barrier.rs b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/barrier.rs new file mode 100644 index 0000000000000000000000000000000000000000..1c7aa2c6d32100d5de4cd63870b438c2afc192c5 --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/barrier.rs @@ -0,0 +1,20 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use frame_support::traits::Everything; +use xcm_builder::AllowUnpaidExecutionFrom; + +pub type Barrier = AllowUnpaidExecutionFrom; diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/constants.rs b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/constants.rs new file mode 100644 index 0000000000000000000000000000000000000000..f6d0174def8f40162f8f2c75231f9b2bad7df0fd --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/constants.rs @@ -0,0 +1,30 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::parachain::MsgQueue; +use frame_support::parameter_types; +use xcm::latest::prelude::*; + +parameter_types! { + pub KsmPerSecondPerByte: (AssetId, u128, u128) = (AssetId(Parent.into()), 1, 1); + pub const MaxAssetsIntoHolding: u32 = 64; +} + +parameter_types! { + pub const KsmLocation: Location = Location::parent(); + pub const RelayNetwork: NetworkId = NetworkId::Kusama; + pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(MsgQueue::parachain_id().into())].into(); +} diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/location_converter.rs b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/location_converter.rs new file mode 100644 index 0000000000000000000000000000000000000000..5a54414dd13fbc05cdbd150ea1edf0400e992c38 --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/location_converter.rs @@ -0,0 +1,25 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::parachain::{constants::RelayNetwork, AccountId}; +use xcm_builder::{AccountId32Aliases, DescribeAllTerminal, DescribeFamily, HashedDescription}; + +type LocationToAccountId = ( + HashedDescription>, + AccountId32Aliases, +); + +pub type LocationConverter = LocationToAccountId; diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/mod.rs b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..0ba02aab9bf92ac699485fcd5803e2c658574d13 --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/mod.rs @@ -0,0 +1,63 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +pub mod asset_transactor; +pub mod barrier; +pub mod constants; +pub mod location_converter; +pub mod origin_converter; +pub mod reserve; +pub mod teleporter; +pub mod weigher; + +use crate::parachain::{MsgQueue, PolkadotXcm, RuntimeCall}; +use frame_support::traits::{Everything, Nothing}; +use xcm_builder::{EnsureDecodableXcm, FixedRateOfFungible, FrameTransactionalProcessor}; + +// Generated from `decl_test_network!` +pub type XcmRouter = EnsureDecodableXcm>; + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = asset_transactor::AssetTransactor; + type OriginConverter = origin_converter::OriginConverter; + type IsReserve = reserve::TrustedReserves; + type IsTeleporter = teleporter::TrustedTeleporters; + type UniversalLocation = constants::UniversalLocation; + type Barrier = barrier::Barrier; + type Weigher = weigher::Weigher; + type Trader = FixedRateOfFungible; + type ResponseHandler = (); + type AssetTrap = (); + type AssetLocker = PolkadotXcm; + type AssetExchanger = (); + type AssetClaims = (); + type SubscriptionService = (); + type PalletInstancesInfo = (); + type FeeManager = (); + type MaxAssetsIntoHolding = constants::MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; + type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); +} diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/origin_converter.rs b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/origin_converter.rs new file mode 100644 index 0000000000000000000000000000000000000000..5a60f0e6001453c746676024528e9a745282ba66 --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/origin_converter.rs @@ -0,0 +1,29 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::parachain::{ + constants::RelayNetwork, location_converter::LocationConverter, RuntimeOrigin, +}; +use pallet_xcm::XcmPassthrough; +use xcm_builder::{SignedAccountId32AsNative, SovereignSignedViaLocation}; + +type XcmOriginToCallOrigin = ( + SovereignSignedViaLocation, + SignedAccountId32AsNative, + XcmPassthrough, +); + +pub type OriginConverter = XcmOriginToCallOrigin; diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/reserve.rs b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/reserve.rs new file mode 100644 index 0000000000000000000000000000000000000000..8763a2f37ccd5b0a1f50bdaa18ef006a7621d145 --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/reserve.rs @@ -0,0 +1,21 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::parachain::teleporter::TrustedTeleporters; +use frame_support::traits::EverythingBut; +use xcm_builder::NativeAsset; + +pub type TrustedReserves = (NativeAsset, EverythingBut); diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/teleporter.rs b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/teleporter.rs new file mode 100644 index 0000000000000000000000000000000000000000..41cb7a5eb2de73c5327eefdb29dcbf4d36b99efa --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/teleporter.rs @@ -0,0 +1,27 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use frame_support::parameter_types; +use xcm::latest::prelude::*; + +parameter_types! { + pub NftCollectionOne: AssetFilter + = Wild(AllOf { fun: WildNonFungible, id: AssetId((Parent, GeneralIndex(1)).into()) }); + pub NftCollectionOneForRelay: (AssetFilter, Location) + = (NftCollectionOne::get(), (Parent,).into()); +} + +pub type TrustedTeleporters = xcm_builder::Case; diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/weigher.rs b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/weigher.rs new file mode 100644 index 0000000000000000000000000000000000000000..4bdc98ea3b0e6438ddb1a41b22ede6b393a76628 --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/weigher.rs @@ -0,0 +1,27 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::parachain::RuntimeCall; +use frame_support::parameter_types; +use xcm::latest::prelude::*; +use xcm_builder::FixedWeightBounds; + +parameter_types! { + pub const UnitWeightCost: Weight = Weight::from_parts(1, 1); + pub const MaxInstructions: u32 = 100; +} + +pub type Weigher = FixedWeightBounds; diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/mod.rs similarity index 53% rename from polkadot/xcm/xcm-simulator/example/src/relay_chain.rs rename to polkadot/xcm/xcm-simulator/example/src/relay_chain/mod.rs index 286d0038e187a45504e9b1092fed4b152ffe9d3d..f698eba41d445d5eb70e59873b5c64c50c4a2810 100644 --- a/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/mod.rs @@ -16,31 +16,29 @@ //! Relay chain runtime mock. +mod xcm_config; +pub use xcm_config::*; + use frame_support::{ construct_runtime, derive_impl, parameter_types, - traits::{AsEnsureOriginWithArg, Everything, Nothing, ProcessMessage, ProcessMessageError}, + traits::{ + AsEnsureOriginWithArg, ConstU128, Everything, Nothing, ProcessMessage, ProcessMessageError, + }, weights::{Weight, WeightMeter}, }; use frame_system::EnsureRoot; -use sp_core::{ConstU32, H256}; +use sp_core::ConstU32; use sp_runtime::{traits::IdentityLookup, AccountId32}; -use polkadot_parachain_primitives::primitives::Id as ParaId; use polkadot_runtime_parachains::{ configuration, inclusion::{AggregateMessageOrigin, UmpQueueId}, origin, shared, }; use xcm::latest::prelude::*; -use xcm_builder::{ - Account32Hash, AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, - ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - ConvertedConcreteId, FixedRateOfFungible, FixedWeightBounds, FrameTransactionalProcessor, - FungibleAdapter, IsConcrete, NoChecking, NonFungiblesAdapter, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, -}; -use xcm_executor::{traits::JustTry, Config, XcmExecutor}; +use xcm_builder::{IsConcrete, SignedToAccountId32}; +use xcm_executor::XcmExecutor; pub type AccountId = AccountId32; pub type Balance = u128; @@ -51,51 +49,17 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; -} - -parameter_types! { - pub ExistentialDeposit: Balance = 1; - pub const MaxLocks: u32 = 50; - pub const MaxReserves: u32 = 50; } +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Runtime { - type MaxLocks = MaxLocks; type Balance = Balance; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU128<1>; type AccountStore = System; - type WeightInfo = (); - type MaxReserves = MaxReserves; - type ReserveIdentifier = [u8; 8]; - type RuntimeHoldReason = RuntimeHoldReason; - type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); - type MaxFreezes = ConstU32<0>; } impl pallet_uniques::Config for Runtime { @@ -127,83 +91,8 @@ impl configuration::Config for Runtime { type WeightInfo = configuration::TestWeightInfo; } -parameter_types! { - pub const TokenLocation: Location = Here.into_location(); - pub RelayNetwork: NetworkId = ByGenesis([0; 32]); - pub const AnyNetwork: Option = None; - pub UniversalLocation: InteriorLocation = Here; - pub UnitWeightCost: u64 = 1_000; -} - -pub type LocationToAccountId = ( - ChildParachainConvertsVia, - AccountId32Aliases, - Account32Hash<(), AccountId>, -); - -pub type LocalAssetTransactor = ( - FungibleAdapter, LocationToAccountId, AccountId, ()>, - NonFungiblesAdapter< - Uniques, - ConvertedConcreteId, JustTry>, - LocationToAccountId, - AccountId, - NoChecking, - (), - >, -); - -type LocalOriginConverter = ( - SovereignSignedViaLocation, - ChildParachainAsNative, - SignedAccountId32AsNative, - ChildSystemParachainAsSuperuser, -); - -parameter_types! { - pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); - pub TokensPerSecondPerByte: (AssetId, u128, u128) = - (AssetId(TokenLocation::get()), 1_000_000_000_000, 1024 * 1024); - pub const MaxInstructions: u32 = 100; - pub const MaxAssetsIntoHolding: u32 = 64; -} - -pub type XcmRouter = super::RelayChainXcmRouter; -pub type Barrier = AllowUnpaidExecutionFrom; - -pub struct XcmConfig; -impl Config for XcmConfig { - type RuntimeCall = RuntimeCall; - type XcmSender = XcmRouter; - type AssetTransactor = LocalAssetTransactor; - type OriginConverter = LocalOriginConverter; - type IsReserve = (); - type IsTeleporter = (); - type UniversalLocation = UniversalLocation; - type Barrier = Barrier; - type Weigher = FixedWeightBounds; - type Trader = FixedRateOfFungible; - type ResponseHandler = (); - type AssetTrap = (); - type AssetLocker = XcmPallet; - type AssetExchanger = (); - type AssetClaims = (); - type SubscriptionService = (); - type PalletInstancesInfo = (); - type FeeManager = (); - type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type MessageExporter = (); - type UniversalAliases = Nothing; - type CallDispatcher = RuntimeCall; - type SafeCallFilter = Everything; - type Aliasers = Nothing; - type TransactionalProcessor = FrameTransactionalProcessor; - type HrmpNewChannelOpenRequestHandler = (); - type HrmpChannelAcceptedHandler = (); - type HrmpChannelClosingHandler = (); -} - -pub type LocalOriginToLocation = SignedToAccountId32; +pub type LocalOriginToLocation = + SignedToAccountId32; impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -215,16 +104,16 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; - type UniversalLocation = UniversalLocation; + type Weigher = weigher::Weigher; + type UniversalLocation = constants::UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; type Currency = Balances; - type CurrencyMatcher = IsConcrete; + type CurrencyMatcher = IsConcrete; type TrustedLockers = (); - type SovereignAccountOf = LocationToAccountId; + type SovereignAccountOf = location_converter::LocationConverter; type MaxLockers = ConstU32<8>; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/asset_transactor.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/asset_transactor.rs new file mode 100644 index 0000000000000000000000000000000000000000..c212569d48113e58fc7447cb077571b35614c46a --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/asset_transactor.rs @@ -0,0 +1,38 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::relay_chain::{ + constants::TokenLocation, location_converter::LocationConverter, AccountId, Balances, Uniques, +}; +use xcm_builder::{ + AsPrefixedGeneralIndex, ConvertedConcreteId, FungibleAdapter, IsConcrete, NoChecking, + NonFungiblesAdapter, +}; +use xcm_executor::traits::JustTry; + +type LocalAssetTransactor = ( + FungibleAdapter, LocationConverter, AccountId, ()>, + NonFungiblesAdapter< + Uniques, + ConvertedConcreteId, JustTry>, + LocationConverter, + AccountId, + NoChecking, + (), + >, +); + +pub type AssetTransactor = LocalAssetTransactor; diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/barrier.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/barrier.rs new file mode 100644 index 0000000000000000000000000000000000000000..1c7aa2c6d32100d5de4cd63870b438c2afc192c5 --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/barrier.rs @@ -0,0 +1,20 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use frame_support::traits::Everything; +use xcm_builder::AllowUnpaidExecutionFrom; + +pub type Barrier = AllowUnpaidExecutionFrom; diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/constants.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/constants.rs new file mode 100644 index 0000000000000000000000000000000000000000..f590c42990da30d112ef25b39bd022e2e376683f --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/constants.rs @@ -0,0 +1,31 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use frame_support::parameter_types; +use xcm::latest::prelude::*; + +parameter_types! { + pub TokensPerSecondPerByte: (AssetId, u128, u128) = + (AssetId(TokenLocation::get()), 1_000_000_000_000, 1024 * 1024); + pub const MaxAssetsIntoHolding: u32 = 64; +} + +parameter_types! { + pub const TokenLocation: Location = Here.into_location(); + pub RelayNetwork: NetworkId = ByGenesis([0; 32]); + pub UniversalLocation: InteriorLocation = RelayNetwork::get().into(); + pub UnitWeightCost: u64 = 1_000; +} diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/location_converter.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/location_converter.rs new file mode 100644 index 0000000000000000000000000000000000000000..0f5f4e43dc9706b84ad11e2de01101ad25559069 --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/location_converter.rs @@ -0,0 +1,25 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::relay_chain::{constants::RelayNetwork, AccountId}; +use xcm_builder::{AccountId32Aliases, DescribeAllTerminal, DescribeFamily, HashedDescription}; + +type LocationToAccountId = ( + HashedDescription>, + AccountId32Aliases, +); + +pub type LocationConverter = LocationToAccountId; diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..a7a8bae51567b3a526333fc56e14ff38c337c776 --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs @@ -0,0 +1,62 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +pub mod asset_transactor; +pub mod barrier; +pub mod constants; +pub mod location_converter; +pub mod origin_converter; +pub mod weigher; + +use crate::relay_chain::{RuntimeCall, XcmPallet}; +use frame_support::traits::{Everything, Nothing}; +use xcm_builder::{EnsureDecodableXcm, FixedRateOfFungible, FrameTransactionalProcessor}; +use xcm_executor::Config; + +// Generated from `decl_test_network!` +pub type XcmRouter = EnsureDecodableXcm; + +pub struct XcmConfig; +impl Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = asset_transactor::AssetTransactor; + type OriginConverter = origin_converter::OriginConverter; + type IsReserve = (); + type IsTeleporter = (); + type UniversalLocation = constants::UniversalLocation; + type Barrier = barrier::Barrier; + type Weigher = weigher::Weigher; + type Trader = FixedRateOfFungible; + type ResponseHandler = (); + type AssetTrap = (); + type AssetLocker = XcmPallet; + type AssetExchanger = (); + type AssetClaims = (); + type SubscriptionService = (); + type PalletInstancesInfo = (); + type FeeManager = (); + type MaxAssetsIntoHolding = constants::MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; + type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); +} diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/origin_converter.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/origin_converter.rs new file mode 100644 index 0000000000000000000000000000000000000000..3c79912a926231bf08d4605c1d5f4e9ef253262f --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/origin_converter.rs @@ -0,0 +1,34 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::relay_chain::{ + constants::RelayNetwork, location_converter::LocationConverter, RuntimeOrigin, +}; +use polkadot_parachain_primitives::primitives::Id as ParaId; +use polkadot_runtime_parachains::origin; +use xcm_builder::{ + ChildParachainAsNative, ChildSystemParachainAsSuperuser, SignedAccountId32AsNative, + SovereignSignedViaLocation, +}; + +type LocalOriginConverter = ( + SovereignSignedViaLocation, + ChildParachainAsNative, + SignedAccountId32AsNative, + ChildSystemParachainAsSuperuser, +); + +pub type OriginConverter = LocalOriginConverter; diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/weigher.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/weigher.rs new file mode 100644 index 0000000000000000000000000000000000000000..5c02565f460071d9a3100ce57845c4a60b6ebe00 --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/weigher.rs @@ -0,0 +1,27 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::relay_chain::RuntimeCall; +use frame_support::parameter_types; +use xcm::latest::prelude::*; +use xcm_builder::FixedWeightBounds; + +parameter_types! { + pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); + pub const MaxInstructions: u32 = 100; +} + +pub type Weigher = FixedWeightBounds; diff --git a/polkadot/xcm/xcm-simulator/example/src/tests.rs b/polkadot/xcm/xcm-simulator/example/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..6486a849af363af6b41bb40ff440efa5424e92eb --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/tests.rs @@ -0,0 +1,513 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::*; + +use codec::Encode; +use frame_support::{assert_ok, weights::Weight}; +use xcm::latest::QueryResponseInfo; +use xcm_simulator::TestExt; + +// Helper function for forming buy execution message +fn buy_execution(fees: impl Into) -> Instruction { + BuyExecution { fees: fees.into(), weight_limit: Unlimited } +} + +#[test] +fn remote_account_ids_work() { + child_account_account_id(1, ALICE); + sibling_account_account_id(1, ALICE); + parent_account_account_id(ALICE); +} + +#[test] +fn dmp() { + MockNet::reset(); + + let remark = parachain::RuntimeCall::System( + frame_system::Call::::remark_with_event { remark: vec![1, 2, 3] }, + ); + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::send_xcm( + Here, + Parachain(1), + Xcm(vec![Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: remark.encode().into(), + }]), + )); + }); + + ParaA::execute_with(|| { + use parachain::{RuntimeEvent, System}; + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::System(frame_system::Event::Remarked { .. }) + ))); + }); +} + +#[test] +fn ump() { + MockNet::reset(); + + let remark = relay_chain::RuntimeCall::System( + frame_system::Call::::remark_with_event { remark: vec![1, 2, 3] }, + ); + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm( + Here, + Parent, + Xcm(vec![Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: remark.encode().into(), + }]), + )); + }); + + Relay::execute_with(|| { + use relay_chain::{RuntimeEvent, System}; + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::System(frame_system::Event::Remarked { .. }) + ))); + }); +} + +#[test] +fn xcmp() { + MockNet::reset(); + + let remark = parachain::RuntimeCall::System( + frame_system::Call::::remark_with_event { remark: vec![1, 2, 3] }, + ); + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm( + Here, + (Parent, Parachain(2)), + Xcm(vec![Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts(INITIAL_BALANCE as u64, 1024 * 1024), + call: remark.encode().into(), + }]), + )); + }); + + ParaB::execute_with(|| { + use parachain::{RuntimeEvent, System}; + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::System(frame_system::Event::Remarked { .. }) + ))); + }); +} + +#[test] +fn reserve_transfer() { + MockNet::reset(); + + let withdraw_amount = 123; + + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::limited_reserve_transfer_assets( + relay_chain::RuntimeOrigin::signed(ALICE), + Box::new(Parachain(1).into()), + Box::new(AccountId32 { network: None, id: ALICE.into() }.into()), + Box::new((Here, withdraw_amount).into()), + 0, + Unlimited, + )); + assert_eq!( + relay_chain::Balances::free_balance(&child_account_id(1)), + INITIAL_BALANCE + withdraw_amount + ); + }); + + ParaA::execute_with(|| { + // free execution, full amount received + assert_eq!( + pallet_balances::Pallet::::free_balance(&ALICE), + INITIAL_BALANCE + withdraw_amount + ); + }); +} + +#[test] +fn remote_locking_and_unlocking() { + MockNet::reset(); + + let locked_amount = 100; + + ParaB::execute_with(|| { + let message = Xcm(vec![LockAsset { + asset: (Here, locked_amount).into(), + unlocker: Parachain(1).into(), + }]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + }); + + Relay::execute_with(|| { + use pallet_balances::{BalanceLock, Reasons}; + assert_eq!( + relay_chain::Balances::locks(&child_account_id(2)), + vec![BalanceLock { id: *b"py/xcmlk", amount: locked_amount, reasons: Reasons::All }] + ); + }); + + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![NoteUnlockable { + owner: (Parent, Parachain(2)).into(), + asset: (Parent, locked_amount).into() + }])] + ); + }); + + ParaB::execute_with(|| { + // Request unlocking part of the funds on the relay chain + let message = Xcm(vec![RequestUnlock { + asset: (Parent, locked_amount - 50).into(), + locker: Parent.into(), + }]); + assert_ok!(ParachainPalletXcm::send_xcm(Here, (Parent, Parachain(1)), message)); + }); + + Relay::execute_with(|| { + use pallet_balances::{BalanceLock, Reasons}; + // Lock is reduced + assert_eq!( + relay_chain::Balances::locks(&child_account_id(2)), + vec![BalanceLock { + id: *b"py/xcmlk", + amount: locked_amount - 50, + reasons: Reasons::All + }] + ); + }); +} + +/// Scenario: +/// A parachain transfers an NFT resident on the relay chain to another parachain account. +/// +/// Asserts that the parachain accounts are updated as expected. +#[test] +fn withdraw_and_deposit_nft() { + MockNet::reset(); + + Relay::execute_with(|| { + assert_eq!(relay_chain::Uniques::owner(1, 42), Some(child_account_id(1))); + }); + + ParaA::execute_with(|| { + let message = Xcm(vec![TransferAsset { + assets: (GeneralIndex(1), 42u32).into(), + beneficiary: Parachain(2).into(), + }]); + // Send withdraw and deposit + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message)); + }); + + Relay::execute_with(|| { + assert_eq!(relay_chain::Uniques::owner(1, 42), Some(child_account_id(2))); + }); +} + +/// Scenario: +/// The relay-chain teleports an NFT to a parachain. +/// +/// Asserts that the parachain accounts are updated as expected. +#[test] +fn teleport_nft() { + MockNet::reset(); + + Relay::execute_with(|| { + // Mint the NFT (1, 69) and give it to our "parachain#1 alias". + assert_ok!(relay_chain::Uniques::mint( + relay_chain::RuntimeOrigin::signed(ALICE), + 1, + 69, + child_account_account_id(1, ALICE), + )); + // The parachain#1 alias of Alice is what must hold it on the Relay-chain for it to be + // withdrawable by Alice on the parachain. + assert_eq!(relay_chain::Uniques::owner(1, 69), Some(child_account_account_id(1, ALICE))); + }); + ParaA::execute_with(|| { + assert_ok!(parachain::ForeignUniques::force_create( + parachain::RuntimeOrigin::root(), + (Parent, GeneralIndex(1)).into(), + ALICE, + false, + )); + assert_eq!( + parachain::ForeignUniques::owner((Parent, GeneralIndex(1)).into(), 69u32.into()), + None, + ); + assert_eq!(parachain::Balances::reserved_balance(&ALICE), 0); + + // IRL Alice would probably just execute this locally on the Relay-chain, but we can't + // easily do that here since we only send between chains. + let message = Xcm(vec![ + WithdrawAsset((GeneralIndex(1), 69u32).into()), + InitiateTeleport { + assets: AllCounted(1).into(), + dest: Parachain(1).into(), + xcm: Xcm(vec![DepositAsset { + assets: AllCounted(1).into(), + beneficiary: (AccountId32 { id: ALICE.into(), network: None },).into(), + }]), + }, + ]); + // Send teleport + let alice = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm(alice, Parent, message)); + }); + ParaA::execute_with(|| { + assert_eq!( + parachain::ForeignUniques::owner((Parent, GeneralIndex(1)).into(), 69u32.into()), + Some(ALICE), + ); + assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000); + }); + Relay::execute_with(|| { + assert_eq!(relay_chain::Uniques::owner(1, 69), None); + }); +} + +/// Scenario: +/// The relay-chain transfers an NFT into a parachain's sovereign account, who then mints a +/// trustless-backed-derived locally. +/// +/// Asserts that the parachain accounts are updated as expected. +#[test] +fn reserve_asset_transfer_nft() { + sp_tracing::init_for_tests(); + MockNet::reset(); + + Relay::execute_with(|| { + assert_ok!(relay_chain::Uniques::force_create( + relay_chain::RuntimeOrigin::root(), + 2, + ALICE, + false + )); + assert_ok!(relay_chain::Uniques::mint( + relay_chain::RuntimeOrigin::signed(ALICE), + 2, + 69, + child_account_account_id(1, ALICE) + )); + assert_eq!(relay_chain::Uniques::owner(2, 69), Some(child_account_account_id(1, ALICE))); + }); + ParaA::execute_with(|| { + assert_ok!(parachain::ForeignUniques::force_create( + parachain::RuntimeOrigin::root(), + (Parent, GeneralIndex(2)).into(), + ALICE, + false, + )); + assert_eq!( + parachain::ForeignUniques::owner((Parent, GeneralIndex(2)).into(), 69u32.into()), + None, + ); + assert_eq!(parachain::Balances::reserved_balance(&ALICE), 0); + + let message = Xcm(vec![ + WithdrawAsset((GeneralIndex(2), 69u32).into()), + DepositReserveAsset { + assets: AllCounted(1).into(), + dest: Parachain(1).into(), + xcm: Xcm(vec![DepositAsset { + assets: AllCounted(1).into(), + beneficiary: (AccountId32 { id: ALICE.into(), network: None },).into(), + }]), + }, + ]); + // Send transfer + let alice = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm(alice, Parent, message)); + }); + ParaA::execute_with(|| { + log::debug!(target: "xcm-executor", "Hello"); + assert_eq!( + parachain::ForeignUniques::owner((Parent, GeneralIndex(2)).into(), 69u32.into()), + Some(ALICE), + ); + assert_eq!(parachain::Balances::reserved_balance(&ALICE), 1000); + }); + + Relay::execute_with(|| { + assert_eq!(relay_chain::Uniques::owner(2, 69), Some(child_account_id(1))); + }); +} + +/// Scenario: +/// The relay-chain creates an asset class on a parachain and then Alice transfers her NFT into +/// that parachain's sovereign account, who then mints a trustless-backed-derivative locally. +/// +/// Asserts that the parachain accounts are updated as expected. +#[test] +fn reserve_asset_class_create_and_reserve_transfer() { + MockNet::reset(); + + Relay::execute_with(|| { + assert_ok!(relay_chain::Uniques::force_create( + relay_chain::RuntimeOrigin::root(), + 2, + ALICE, + false + )); + assert_ok!(relay_chain::Uniques::mint( + relay_chain::RuntimeOrigin::signed(ALICE), + 2, + 69, + child_account_account_id(1, ALICE) + )); + assert_eq!(relay_chain::Uniques::owner(2, 69), Some(child_account_account_id(1, ALICE))); + + let message = Xcm(vec![Transact { + origin_kind: OriginKind::Xcm, + require_weight_at_most: Weight::from_parts(1_000_000_000, 1024 * 1024), + call: parachain::RuntimeCall::from( + pallet_uniques::Call::::create { + collection: (Parent, 2u64).into(), + admin: parent_account_id(), + }, + ) + .encode() + .into(), + }]); + // Send creation. + assert_ok!(RelayChainPalletXcm::send_xcm(Here, Parachain(1), message)); + }); + ParaA::execute_with(|| { + // Then transfer + let message = Xcm(vec![ + WithdrawAsset((GeneralIndex(2), 69u32).into()), + DepositReserveAsset { + assets: AllCounted(1).into(), + dest: Parachain(1).into(), + xcm: Xcm(vec![DepositAsset { + assets: AllCounted(1).into(), + beneficiary: (AccountId32 { id: ALICE.into(), network: None },).into(), + }]), + }, + ]); + let alice = AccountId32 { id: ALICE.into(), network: None }; + assert_ok!(ParachainPalletXcm::send_xcm(alice, Parent, message)); + }); + ParaA::execute_with(|| { + assert_eq!(parachain::Balances::reserved_balance(&parent_account_id()), 1000); + assert_eq!( + parachain::ForeignUniques::collection_owner((Parent, 2u64).into()), + Some(parent_account_id()) + ); + }); +} + +/// Scenario: +/// A parachain transfers funds on the relay chain to another parachain account. +/// +/// Asserts that the parachain accounts are updated as expected. +#[test] +fn withdraw_and_deposit() { + MockNet::reset(); + + let send_amount = 10; + + ParaA::execute_with(|| { + let message = Xcm(vec![ + WithdrawAsset((Here, send_amount).into()), + buy_execution((Here, send_amount)), + DepositAsset { assets: AllCounted(1).into(), beneficiary: Parachain(2).into() }, + ]); + // Send withdraw and deposit + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone())); + }); + + Relay::execute_with(|| { + assert_eq!( + relay_chain::Balances::free_balance(child_account_id(1)), + INITIAL_BALANCE - send_amount + ); + assert_eq!( + relay_chain::Balances::free_balance(child_account_id(2)), + INITIAL_BALANCE + send_amount + ); + }); +} + +/// Scenario: +/// A parachain wants to be notified that a transfer worked correctly. +/// It sends a `QueryHolding` after the deposit to get notified on success. +/// +/// Asserts that the balances are updated correctly and the expected XCM is sent. +#[test] +fn query_holding() { + MockNet::reset(); + + let send_amount = 10; + let query_id_set = 1234; + + // Send a message which fully succeeds on the relay chain + ParaA::execute_with(|| { + let message = Xcm(vec![ + WithdrawAsset((Here, send_amount).into()), + buy_execution((Here, send_amount)), + DepositAsset { assets: AllCounted(1).into(), beneficiary: Parachain(2).into() }, + ReportHolding { + response_info: QueryResponseInfo { + destination: Parachain(1).into(), + query_id: query_id_set, + max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), + }, + assets: All.into(), + }, + ]); + // Send withdraw and deposit with query holding + assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, message.clone(),)); + }); + + // Check that transfer was executed + Relay::execute_with(|| { + // Withdraw executed + assert_eq!( + relay_chain::Balances::free_balance(child_account_id(1)), + INITIAL_BALANCE - send_amount + ); + // Deposit executed + assert_eq!( + relay_chain::Balances::free_balance(child_account_id(2)), + INITIAL_BALANCE + send_amount + ); + }); + + // Check that QueryResponse message was received + ParaA::execute_with(|| { + assert_eq!( + parachain::MsgQueue::received_dmp(), + vec![Xcm(vec![QueryResponse { + query_id: query_id_set, + response: Response::Assets(Assets::new()), + max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), + querier: Some(Here.into()), + }])], + ); + }); +} diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs index cadfc1e7200c075433ebf295982cc49f8a0d06b1..d8d65fbf0ce74a17b75e6bff8c9acfc02dbf928c 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs @@ -101,7 +101,7 @@ parameter_types! { parameter_types! { pub const KsmLocation: Location = Location::parent(); pub const RelayNetwork: NetworkId = NetworkId::Kusama; - pub UniversalLocation: InteriorLocation = Parachain(MsgQueue::parachain_id().into()).into(); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(MsgQueue::parachain_id().into())].into(); } pub type LocationToAccountId = ( diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs index 6790b535d169220a3def3e4780af1aa1ef83e759..47209b765d15b7dc00b82e45b9dcdd92947efed5 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs @@ -104,7 +104,7 @@ parameter_types! { pub const TokenLocation: Location = Here.into_location(); pub const ThisNetwork: NetworkId = NetworkId::ByGenesis([0; 32]); pub const AnyNetwork: Option = None; - pub const UniversalLocation: InteriorLocation = Here; + pub UniversalLocation: InteriorLocation = ThisNetwork::get().into(); } pub type SovereignAccountOf = diff --git a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml b/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml index db508e14dbaa5c443e30c6c50d9d835927055162..83f5434edddb19afefcba93a9ce7bb305909c07f 100644 --- a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml +++ b/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml @@ -20,8 +20,8 @@ chain = "rococo-local" default_command = "polkadot" [relaychain.default_resources] - limits = { memory = "4G", cpu = "2" } - requests = { memory = "2G", cpu = "1" } + limits = { memory = "4G", cpu = "3" } + requests = { memory = "4G", cpu = "3" } [[relaychain.node_groups]] name = "elastic-validator" @@ -32,11 +32,20 @@ default_command = "polkadot" [[parachains]] id = {{id}} addToGenesis = true + [parachains.default_resources] + limits = { memory = "4G", cpu = "3" } + requests = { memory = "4G", cpu = "3" } [parachains.collator] name = "some-parachain" image = "{{COL_IMAGE}}" command = "adder-collator" args = ["-lparachain::collation-generation=trace,parachain::collator-protocol=trace,parachain=debug"] + {% endfor %} +# This represents the layout of the adder collator block header. +[types.Header] +number = "u64" +parent_hash = "Hash" +post_state = "Hash" \ No newline at end of file diff --git a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl b/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl index b9c002457549788c04673f291f54599757cd67d7..d624cbaf9df6a62448db2cef637e6d29a0d419b5 100644 --- a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl +++ b/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl @@ -18,11 +18,11 @@ elastic-validator-0: js-script ./assign-core.js with "2000,1" return is 0 within elastic-validator-0: reports substrate_block_height{status="best"} is at least 20 within 600 seconds # Non elastic parachain should progress normally -some-parachain-1: count of log lines containing "Parachain velocity: 1" is at least 9 within 20 seconds +some-parachain-1: count of log lines containing "Parachain velocity: 1" is at least 5 within 20 seconds # Sanity -some-parachain-1: count of log lines containing "Parachain velocity: 2" is 0 within 20 seconds +some-parachain-1: count of log lines containing "Parachain velocity: 2" is 0 -# Parachain should progress 3 blocks per relay chain block ideally, however this measurement does -# `ceil()` on the actual velocity to account for CI overload. -some-parachain: count of log lines containing "Parachain velocity: 3" is at least 9 within 20 seconds +# Parachain should progress 3 blocks per relay chain block ideally, however CI might not be +# the most performant environment so we'd just use a lower bound of 2 blocks per RCB +elastic-validator-0: parachain 2000 block height is at least 20 within 200 seconds diff --git a/polkadot/zombienet_tests/elastic_scaling/assign-core.js b/polkadot/zombienet_tests/elastic_scaling/assign-core.js index 2e5f9d8cfa58ae26ef058f334ad782f1fbd9eb9f..add63b6d30859d2c50a38a73e33ec75ed3669433 100644 --- a/polkadot/zombienet_tests/elastic_scaling/assign-core.js +++ b/polkadot/zombienet_tests/elastic_scaling/assign-core.js @@ -1,6 +1,6 @@ async function run(nodeName, networkInfo, args) { - const { wsUri, userDefinedTypes } = networkInfo.nodesByName[nodeName]; - const api = await zombie.connect(wsUri, userDefinedTypes); + const wsUri = networkInfo.nodesByName[nodeName].wsUri; + const api = await zombie.connect(wsUri); let para = Number(args[0]); let core = Number(args[1]); @@ -33,8 +33,6 @@ async function run(nodeName, networkInfo, args) { }); }); - - return 0; } diff --git a/polkadot/zombienet_tests/functional/0010-validator-disabling.toml b/polkadot/zombienet_tests/functional/0010-validator-disabling.toml index c9d79c5f8f236918cf409fd684b7d0b8d9792d33..806f34d7f7670d9c654837f144f845352c7b41e7 100644 --- a/polkadot/zombienet_tests/functional/0010-validator-disabling.toml +++ b/polkadot/zombienet_tests/functional/0010-validator-disabling.toml @@ -21,7 +21,7 @@ requests = { memory = "2G", cpu = "1" } [[relaychain.node_groups]] name = "honest-validator" count = 3 - args = ["-lparachain=debug"] + args = ["-lparachain=debug,runtime::staking=debug"] [[relaychain.node_groups]] image = "{{MALUS_IMAGE}}" diff --git a/prdoc/pr_2226.prdoc b/prdoc/pr_2226.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f03540a50f6cc986ed529b80453a94ac4ee79acb --- /dev/null +++ b/prdoc/pr_2226.prdoc @@ -0,0 +1,28 @@ +title: Validator disabling strategy in runtime + +doc: + - audience: Node Operator + description: | + On each committed offence (no matter slashable or not) the offending validator will be + disabled for a whole era. + - audience: Runtime Dev + description: | + The disabling strategy in staking pallet is no longer hardcoded but abstracted away via + `DisablingStrategy` trait. The trait contains a single function (make_disabling_decision) which + is called for each offence. The function makes a decision if (and which) validators should be + disabled. A default implementation is provided - `UpToLimitDisablingStrategy`. It + will be used on Kusama and Polkadot. In nutshell `UpToLimitDisablingStrategy` + disables offenders up to the configured threshold. Offending validators are not disabled for + offences in previous eras. The threshold is controlled via `DISABLING_LIMIT_FACTOR` (a generic + parameter of `UpToLimitDisablingStrategy`). + +migrations: + db: [] + runtime: + - reference: pallet-staking + description: | + Renames `OffendingValidators` storage item to `DisabledValidators` and changes its type from + `Vec<(u32, bool)>` to `Vec`. + +crates: + - name: pallet-staking \ No newline at end of file diff --git a/prdoc/pr_3250.prdoc b/prdoc/pr_3250.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..77ea725073e65590715b76eada871afe95e40b5b --- /dev/null +++ b/prdoc/pr_3250.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Asset Conversion: Pool Account ID derivation with additional Pallet ID seed" + +doc: + - audience: Runtime Dev + description: | + Introduce PalletId as an additional seed parameter for pool's account id derivation. + The PR also introduces the `pallet_asset_conversion_ops` pallet with a call to migrate + a pool to the new account. Additionally `fungibles::roles::ResetTeam` and + `fungible::lifetime::Refund` traits, to facilitate the migration functionality. + +crates: + - name: pallet-asset-conversion + bump: minor diff --git a/prdoc/pr_3251.prdoc b/prdoc/pr_3251.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1f95c228f7a8c945d59cc8b2bc64759d3df18421 --- /dev/null +++ b/prdoc/pr_3251.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Asset Conversion: Pool Touch Call" + +doc: + - audience: Runtime Dev + description: | + Introduce `touch` call designed to address operational prerequisites before providing liquidity to a pool. + This function ensures that essential requirements, such as the presence of the pool's accounts, are fulfilled. + It is particularly beneficial in scenarios where a pool creator removes the pool's accounts without providing liquidity. + +crates: + - name: pallet-asset-conversion diff --git a/prdoc/pr_3455.prdoc b/prdoc/pr_3455.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c16ac2244862fc16b0205514564ce2348e205c34 --- /dev/null +++ b/prdoc/pr_3455.prdoc @@ -0,0 +1,28 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Region reserve transfers fix + +doc: + - audience: Runtime User + description: | + This PR introduces changes enabling the transfer of coretime regions via XCM. + There are two primary issues that are resolved in this PR: + 1. The mint and burn functions were not implemented for coretime regions. These operations + are essential for moving assets to and from the XCM holding register. + 2. The transfer of non-fungible assets through XCM was previously disallowed. This was due + to incorrectly benchmarking non-fungible asset transfers via XCM, which led to assigning + it a weight of Weight::Max, effectively preventing its execution. + +migrations: + db: [] + runtime: + - reference: pallet-broker + description: | + The region owner is optional. + +crates: + - name: pallet-broker + - name: pallet-xcm + - name: coretime-rococo-runtime + - name: coretime-westend-runtime diff --git a/prdoc/pr_3659.prdoc b/prdoc/pr_3659.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..393844d822d864bca595a0257f02989fb4a19ac7 --- /dev/null +++ b/prdoc/pr_3659.prdoc @@ -0,0 +1,12 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Unity Balance Conversion for Different IDs of Native Asset + +doc: + - audience: Runtime Dev + description: | + Introduce types to define 1:1 balance conversion for different relative asset ids/locations + of native asset for `ConversionToAssetBalance` trait bounds. + +crates: [ ] \ No newline at end of file diff --git a/prdoc/pr_3695.prdoc b/prdoc/pr_3695.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..cc54fb240cd021f62a42ed50561d08ec0fb369d9 --- /dev/null +++ b/prdoc/pr_3695.prdoc @@ -0,0 +1,45 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "pallet-xcm: add new extrinsic for asset transfers using explicit reserve" + +doc: + - audience: Runtime User + description: | + pallet-xcm has a new extrinsic `transfer_assets_using_type_and_then` for transferring + assets from local chain to destination chain using an explicit XCM transfer + types for transferring the assets and the fees: + - `TransferType::LocalReserve`: transfer assets to sovereign account of destination + chain and forward a notification XCM to `dest` to mint and deposit reserve-based + assets to `beneficiary`. + - `TransferType::DestinationReserve`: burn local assets and forward a notification to + `dest` chain to withdraw the reserve assets from this chain's sovereign account and + deposit them to `beneficiary`. + - `TransferType::RemoteReserve(reserve)`: burn local assets, forward XCM to `reserve` + chain to move reserves from this chain's SA to `dest` chain's SA, and forward another + XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. Typically + the remote `reserve` is Asset Hub. + - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to + mint/teleport assets and deposit them to `beneficiary`. + By default, an asset's reserve is its origin chain. But sometimes we may want to + explicitly use another chain as reserve (as long as allowed by runtime IsReserve + filter). + This is very helpful for transferring assets with multiple configured reserves + (such as Asset Hub ForeignAssets), when the transfer strictly depends on the used + reserve location. + + E.g. For transferring a bridged Foreign Assets between local parachains, Asset Hub + or the parachain that bridged the asset over must be used as the reserve location. + Same when transferring bridged assets back across the bridge, the local bridging + parachain must be used as the explicit reserve location. + + The new method takes a `custom_xcm_on_dest` parameter allowing the caller to specify + what should happen to the transferred assets once they reach + the `dest` chain. The `custom_xcm_on_dest` parameter should contains the instructions + to execute on `dest` as a final step. Usually as simple as: + `Xcm(vec![DepositAsset { assets: Wild(AllCounted(assets.len())), beneficiary }])`, + but could be something more exotic like sending the `assets` even further. + +crates: +- name: pallet-xcm + bump: minor diff --git a/prdoc/pr_3721.prdoc b/prdoc/pr_3721.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..be36103c474286cea613696e05dbba6ea5fc5782 --- /dev/null +++ b/prdoc/pr_3721.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: New call `hrmp.establish_channel_with_system` to allow parachains to establish a channel with a system parachain + +doc: + - audience: Runtime Dev + description: | + This PR adds a new call `hrmp.establish_channel_with_system` that allows a parachain origin to open a bidirectional channel with a system parachain. + +crates: +- name: polkadot-runtime-parachains + bump: minor diff --git a/prdoc/pr_3852.prdoc b/prdoc/pr_3852.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f13e1766d518a14a0a5309e75937007bf937a79d --- /dev/null +++ b/prdoc/pr_3852.prdoc @@ -0,0 +1,25 @@ +title: (Breaking change)Enforce network key presence on authorities. + +doc: + - audience: Node Operator + description: | + (Breaking change) For all authority nodes, the node binary now enforces the presence + of a network key, instead of auto-generating when it is absent. + + Before this change, all node binaries were auto-generating the node key when it was not present, + that is dangerous because other nodes in the network expects a stable identity for authorities. + + To prevent accidental generation of node key, we removed this behaviour and node binary will now throw + an error if the network key is not present and operators will receive instructions to either persist + their network key or explicitly generate a new one with the `polkadot key generate-node-key`. + + To prevent this error on restart/upgrades node operators need to make sure their network key are always + persisted, if nodes already correctly persist all directories in `--base-path` then no action is needed. + +crates: + - name: sc-cli + bump: major + - name: polkadot + bump: major + - name: subkey + bump: minor \ No newline at end of file diff --git a/prdoc/pr_3865.prdoc b/prdoc/pr_3865.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..8e39c04825b1a525850939e3030f00521e0d0065 --- /dev/null +++ b/prdoc/pr_3865.prdoc @@ -0,0 +1,11 @@ +title: "Balances: add failsafe for consumer ref underflow" + +doc: + - audience: Runtime Dev + description: | + Pallet balances now handles the case that historic accounts violate a invariant that they should have a consumer ref on `reserved > 0` balance. + This disallows such accounts from reaping and should prevent TI from getting messed up even more. + +crates: + - name: pallet-balances + bump: patch diff --git a/prdoc/pr_3889.prdoc b/prdoc/pr_3889.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b32ffcc214c055879b546c4b19d3339f37dde71b --- /dev/null +++ b/prdoc/pr_3889.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Allow privileged virtual bond into pallet Staking + +doc: + - audience: Runtime Dev + description: | + Introduces a new low level API to allow privileged virtual bond into pallet Staking. This allows other pallets + to stake funds into staking pallet while managing the fund lock and unlocking process themselves. + +crates: + - name: pallet-staking + diff --git a/prdoc/pr_4006.prdoc b/prdoc/pr_4006.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e6c339c406ac743f149ef14b3777a3214cfc10d2 --- /dev/null +++ b/prdoc/pr_4006.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Deploy pallet-parameters to rococo and fix dynamic_params name expand" + +doc: + - audience: Runtime Dev + description: | + Fix the expanded names of `dynamic_params` to not remove suffix "s". + + Also deploy the parameters pallet to the rococo-runtime. + +crates: + - name: frame-support-procedural + bump: major + - name: rococo-runtime + bump: major + - name: pallet-parameters + bump: patch diff --git a/prdoc/pr_4027.prdoc b/prdoc/pr_4027.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c85fd196a6c4a4af81e2d6882a424fb7a2d8b28d --- /dev/null +++ b/prdoc/pr_4027.prdoc @@ -0,0 +1,25 @@ +title: Add `candidates_pending_availability` Runtime API + +doc: + - audience: "Node Dev" + description: | + This new API retrieves all `CommittedCandidateReceipts` of all candidates pending availability + for a parachain at a given relay chain block number. It is required by collators that make use + of elastic scaling capability in the context of PoV recovery and block import. The old API + `candidate_pending_availability` is now deprectated and will be removed in the future. + +crates: + - name: polkadot-node-core-runtime-api + bump: minor + - name: polkadot-node-subsystem-types + bump: minor + - name: polkadot-node-subsystem-util + bump: minor + - name: polkadot-primitives + bump: minor + - name: polkadot-runtime-parachains + bump: minor + - name: cumulus-relay-chain-rpc-interface + bump: minor + - name: cumulus-relay-chain-minimal-node + bump: minor diff --git a/prdoc/pr_4037.prdoc b/prdoc/pr_4037.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7071875a7e370fdf11a0e88df1f6c0c5bb0a35a7 --- /dev/null +++ b/prdoc/pr_4037.prdoc @@ -0,0 +1,26 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Remove `xcm::v3` from `assets-common` nits" + +doc: + - audience: Runtime Dev + description: | + Remove `xcm::v3` imports from `assets-common` to make it more generic and facilitate the transition to newer XCM versions. + The implementations `AssetIdForTrustBackedAssetsConvert`, `ForeignAssetsConvertedConcreteId`, or `TrustBackedAssetsAsLocation` + used hard-coded `xcm::v3::Location`, which has been changed to use `xcm::latest::Location` by default. + Alternatively, the providing runtime can configure them according to its needs, such as with a lower XCM version. + + Example: + ```patch + - AssetIdForTrustBackedAssetsConvert, + + AssetIdForTrustBackedAssetsConvert, + ``` + + Another change is that the removed `xcm_builder::V4V3LocationConverter` can be replaced with `WithLatestLocationConverter`. + +crates: +- name: assets-common + bump: patch +- name: staging-xcm-builder + bump: patch diff --git a/prdoc/pr_4059.prdoc b/prdoc/pr_4059.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..92753328a433a65f327572c255375a9b5f8e45fd --- /dev/null +++ b/prdoc/pr_4059.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Remove redundent logging code + +doc: + - audience: Node Dev + description: | + Simplified logging code, now does slightly less work while logging. + +crates: +- name: sc-tracing + bump: minor diff --git a/prdoc/pr_4060.prdoc b/prdoc/pr_4060.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..621620a44893ea080385332f2dd7b6317f4d4d6b --- /dev/null +++ b/prdoc/pr_4060.prdoc @@ -0,0 +1,54 @@ +title: "Fix nostd build of several crates" + +doc: + - audience: Runtime Dev + description: | + Fixes feature and dependency configuration of several crate. This should allow for better no-std build capabilities. + +crates: + - name: cumulus-pallet-session-benchmarking + bump: patch + - name: asset-hub-rococo-runtime + bump: patch + - name: glutton-westend-runtime + bump: patch + - name: cumulus-primitives-parachain-inherent + bump: patch + - name: polkadot-primitives + bump: patch + - name: polkadot-runtime-parachains + bump: patch + - name: xcm-executor-integration-tests + bump: patch + - name: pallet-atomic-swap + bump: patch + - name: pallet-election-provider-support-benchmarking + bump: patch + - name: pallet-dev-mode + bump: patch + - name: pallet-example-offchain-worker + bump: patch + - name: pallet-indices + bump: patch + - name: pallet-nomination-pools + bump: patch + - name: pallet-nomination-pools-benchmarking + bump: patch + - name: pallet-offences-benchmarking + bump: patch + - name: pallet-root-offences + bump: patch + - name: pallet-session-benchmarking + bump: patch + - name: frame-system-benchmarking + bump: patch + - name: sp-consensus-babe + bump: patch + - name: sp-consensus-babe + bump: patch + - name: sp-core + bump: patch + - name: sp-session + bump: patch + - name: sp-transaction-storage-proof + bump: patch diff --git a/prdoc/pr_4075.prdoc b/prdoc/pr_4075.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..05e54073b6c7b5562b6a8880185d3e38b2526ab8 --- /dev/null +++ b/prdoc/pr_4075.prdoc @@ -0,0 +1,19 @@ +title: Adds ability to trigger tasks via unsigned transactions + +doc: + - audience: Runtime Dev + description: | + This PR updates the `validate_unsigned` hook for `frame_system` to allow valid tasks + to be submitted as unsigned transactions. It also updates the task example to be able to + submit such transactions via an off-chain worker. + + Note that `is_valid` call on a task MUST be cheap with minimal to no storage reads. + Else, it can make the blockchain vulnerable to DoS attacks. + + Further, these tasks will be executed in a random order. + +crates: + - name: frame-system + bump: patch + - name: pallet-example-tasks + bump: minor diff --git a/prdoc/pr_4089.prdoc b/prdoc/pr_4089.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..29ac736ccd082ec493b89824fab0f9519bd66c6d --- /dev/null +++ b/prdoc/pr_4089.prdoc @@ -0,0 +1,11 @@ +title: "pallet_broker: Support renewing leases expired in a previous period" + +doc: + - audience: Runtime User + description: | + Allow renewals of leases that ended before `start_sales` or in the first period after calling `start_sales`. + This ensures that everyone has a smooth experience when migrating to coretime and the timing of + `start_sales` isn't that important. + +crates: + - name: pallet-broker diff --git a/prdoc/pr_4102.prdoc b/prdoc/pr_4102.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..50c1ec23b2ac1a08c02181727b70bce49f80a0fa --- /dev/null +++ b/prdoc/pr_4102.prdoc @@ -0,0 +1,43 @@ +title: "Bridge: make some headers submissions free" + +doc: + - audience: Runtime Dev + description: | + Adds `FreeHeadersInterval` configuration constant to the `pallet_bridge_grandpa`. + Transactions that improve best known header by at least `FreeHeadersInterval` headers + are now free for the submitter. Additionally, we allow single free parachain header + update per every free relay chain header. Bridge signed extensions are adjusted + to support that new scheme. Bridge runtime APIs are extended to support that new + scheme. Bridge fees are decreased by ~98% because now they do not include cost of + finality submissions - we assume relayers will be submitting finality transactions + for free. + +crates: + - name: bridge-runtime-common + bump: major + - name: bp-bridge-hub-cumulus + bump: patch + - name: bp-bridge-hub-kusama + bump: major + - name: bp-bridge-hub-polkadot + bump: major + - name: bp-bridge-hub-rococo + bump: major + - name: bp-bridge-hub-westend + bump: major + - name: pallet-bridge-grandpa + bump: major + - name: pallet-bridge-parachains + bump: major + - name: bp-parachains + bump: major + - name: bp-runtime + bump: major + - name: relay-substrate-client + bump: major + - name: bridge-hub-rococo-runtime + bump: major + - name: bridge-hub-westend-runtime + bump: major + - name: bridge-hub-test-utils + bump: minor diff --git a/prdoc/pr_4118.prdoc b/prdoc/pr_4118.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..20f36c1b0a37cc330c23115daa59f7a6c14b3c6a --- /dev/null +++ b/prdoc/pr_4118.prdoc @@ -0,0 +1,13 @@ +title: "pallet assets: minor improvement on errors returned for some calls" + +doc: + - audience: Runtime Dev + description: | + Some calls in pallet assets have better errors. No new error is introduced, only more sensible choice are made. + - audience: Runtime User + description: | + Some calls in pallet assets have better errors. No new error is introduced, only more sensible choice are made. + +crates: + - name: pallet-assets + bump: minor diff --git a/prdoc/pr_4151.prdoc b/prdoc/pr_4151.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..70b9f5e60e14c5a0896b60651be89cc40fbe56a5 --- /dev/null +++ b/prdoc/pr_4151.prdoc @@ -0,0 +1,11 @@ +title: "[pallet-broker] Use saturating math in input validation" + +doc: + - audience: Runtime Dev + description: | + Use saturating in the pallet-broker input validation of the `drop_history` extrinsic. This + fixes a safeguard that only expired historic instantaneous pool records get dropped. + +crates: + - name: pallet-broker + bump: patch diff --git a/prdoc/pr_4156.prdoc b/prdoc/pr_4156.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..fc09a4e0df44b830bf1b394e20a36dd3c3644a48 --- /dev/null +++ b/prdoc/pr_4156.prdoc @@ -0,0 +1,13 @@ +title: "`AllowHrmpNotificationsFromRelayChain` barrier for HRMP notifications from the relaychain" + +doc: + - audience: Runtime Dev + description: | + A new barrier, `AllowHrmpNotificationsFromRelayChain`, has been added. + This barrier can be utilized to ensure that HRMP notifications originate solely from the Relay Chain. + If your runtime relies on these notifications, + you can include it in the runtime's barrier type for `xcm_executor::Config`. + +crates: +- name: staging-xcm-builder + bump: minor diff --git a/prdoc/pr_4157.prdoc b/prdoc/pr_4157.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..783eaa2dd4276fbf35dd996c06865f734433a3af --- /dev/null +++ b/prdoc/pr_4157.prdoc @@ -0,0 +1,29 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Bridge: added free headers submission support to the substrate-relay" + +doc: + - audience: Node Dev + description: | + Bridge finality and parachains relayer now supports mode, where it only submits some headers + for free. There's a setting in a runtime configuration, which introduces this "free header" + concept. Submitting such header is considered a common good deed, so it is free for relayers. + +crates: + - name: bp-bridge-hub-kusama + bump: major + - name: bp-bridge-hub-polkadot + bump: major + - name: bp-bridge-hub-rococo + bump: major + - name: bp-bridge-hub-westend + bump: major + - name: relay-substrate-client + bump: major + - name: finality-relay + bump: major + - name: substrate-relay-helper + bump: major + - name: parachains-relay + bump: major diff --git a/prdoc/pr_4168.prdoc b/prdoc/pr_4168.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9a498500f08b4f7013c3b2829d87b7e588829406 --- /dev/null +++ b/prdoc/pr_4168.prdoc @@ -0,0 +1,8 @@ +title: Stabilize chianHead RPC class to version 1 + +doc: + - audience: Node Dev + description: | + The chainHead RPC API is stabilized to version 1. + +crates: [ ] diff --git a/prdoc/pr_4169.prdoc b/prdoc/pr_4169.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..03f2f6e597e42a9e4f7f5710f86a542438a70c43 --- /dev/null +++ b/prdoc/pr_4169.prdoc @@ -0,0 +1,8 @@ +title: Stabilize transactionBroadcast RPC class to version 1 + +doc: + - audience: Node Dev + description: | + The transactionBroadcast RPC API is stabilized to version 1. + +crates: [ ] diff --git a/prdoc/pr_4171.prdoc b/prdoc/pr_4171.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..eef45ba922c52b2f636a77bf280cc7937ad2b140 --- /dev/null +++ b/prdoc/pr_4171.prdoc @@ -0,0 +1,8 @@ +title: Stabilize transactionWatch RPC class to version 1 + +doc: + - audience: Node Dev + description: | + The transactionWatch RPC API is stabilized to version 1. + +crates: [ ] diff --git a/prdoc/pr_4175.prdoc b/prdoc/pr_4175.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7fc2fb68b38e610108adc57b9b1e5eb7cec5bd31 --- /dev/null +++ b/prdoc/pr_4175.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Snowbridge: deposit extra fee to beneficiary on Asset Hub" + +doc: + - audience: Runtime Dev + description: | + Snowbridge transfers arriving on Asset Hub will deposit both asset and fees to beneficiary so the fees will not get trapped. + Another benefit is when fees left more than ED, could be used to create the beneficiary account in case it does not exist on asset hub. + +crates: + - name: snowbridge-router-primitives diff --git a/prdoc/pr_4177.prdoc b/prdoc/pr_4177.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..29d011c93516c35af2049dafdec035e4f888ffec --- /dev/null +++ b/prdoc/pr_4177.prdoc @@ -0,0 +1,12 @@ +title: "wasm-builder: Make it easier to build a WASM binary" + +doc: + - audience: [Runtime Dev, Node Dev] + description: | + Combines all the recommended calls of the `WasmBuilder` into + `build_using_defaults()` or `init_with_defaults()` if more changes are required. + Otherwise the interface doesn't change and users can still continue to use + the "old" interface. + +crates: + - name: substrate-wasm-builder diff --git a/prdoc/pr_4189.prdoc b/prdoc/pr_4189.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..74ed7c67cbdeaf17bcc18476e6e011d62e0d8583 --- /dev/null +++ b/prdoc/pr_4189.prdoc @@ -0,0 +1,16 @@ +title: "polkadot_runtime_parachains::coretime: Expose `MaxXcmTransactWeight`" + +doc: + - audience: Runtime Dev + description: | + Expose `MaxXcmTransactWeight` via the `Config` trait. This exposes the + possibility for runtime implementors to set the maximum weight required + for the calls on the coretime chain. Basically it needs to be set to + `max_weight(set_leases, reserve, notify_core_count)` where `set_leases` + etc are the calls on the coretime chain. This ensures that these XCM + transact calls send by the relay chain coretime pallet to the coretime + chain can be dispatched. + +crates: + - name: polkadot-runtime-parachains + bump: major diff --git a/prdoc/pr_4199.prdoc b/prdoc/pr_4199.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..39f08a0532b8c0318c8d71329962da65309f51cd --- /dev/null +++ b/prdoc/pr_4199.prdoc @@ -0,0 +1,29 @@ +title: "Remove XCM SafeCallFilter for chains using Weights::v3" + +doc: + - audience: Runtime User + description: | + `SafeCallFilter` was removed from Rococo and Westend relay and system chains as they + all now use Weights::v3 which already accounts for call PoV size. + This effectively removes artificial limitations on what users can `XCM::Transact` on + these chains (blockspace limitations are still upheld). + +crates: + - name: asset-hub-rococo-runtime + bump: minor + - name: asset-hub-westend-runtime + bump: minor + - name: bridge-hub-rococo-runtime + bump: minor + - name: bridge-hub-westend-runtime + bump: minor + - name: collectives-westend-runtime + bump: minor + - name: coretime-rococo-runtime + bump: minor + - name: coretime-westend-runtime + bump: minor + - name: people-rococo-runtime + bump: minor + - name: people-westend-runtime + bump: minor diff --git a/prdoc/pr_4202.prdoc b/prdoc/pr_4202.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..6469c3c7840767080b52a6e52d2eea1c7faf3559 --- /dev/null +++ b/prdoc/pr_4202.prdoc @@ -0,0 +1,16 @@ +title: "Treat XCM ExceedsStackLimit errors as transient in the MQ pallet" + +doc: + - audience: Runtime User + description: | + Fixes an issue where the MessageQueue can incorrectly assume that a message will permanently fail to process and disallow retrial of it. + +crates: + - name: frame-support + bump: major + - name: pallet-message-queue + bump: patch + - name: staging-xcm-builder + bump: patch + - name: staging-xcm-executor + bump: patch diff --git a/prdoc/pr_4208.prdoc b/prdoc/pr_4208.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..be2a1b084a5f98f5192590eb6c645c11b8f15bb4 --- /dev/null +++ b/prdoc/pr_4208.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fixed GrandpaConsensusLogReader::find_scheduled_change + +doc: + - audience: Runtime Dev + description: | + This PR fixes the issue with authorities set change digest item search + in the bridges code. The issue happens when there are multiple consensus + digest items in the same header digest. + +crates: + - name: bp-header-chain diff --git a/prdoc/pr_4211.prdoc b/prdoc/pr_4211.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..161dc8485e83ca8de8574b3573b0bb3461f10e15 --- /dev/null +++ b/prdoc/pr_4211.prdoc @@ -0,0 +1,15 @@ +title: "Re-prepare PVF artifacts only if needed" + +doc: + - audience: Node Dev + description: | + When a change in the executor environment parameters can not affect the prepared artifact, + it is preserved without recompilation and used for future executions. That mitigates + situations where every unrelated executor parameter change resulted in re-preparing every + artifact on every validator, causing a significant finality lag. + +crates: + - name: polkadot-node-core-pvf + bump: minor + - name: polkadot-primitives + bump: minor diff --git a/prdoc/pr_4213.prdoc b/prdoc/pr_4213.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ce7eb65969b06aa54adba7753362fdd5e77c8400 --- /dev/null +++ b/prdoc/pr_4213.prdoc @@ -0,0 +1,11 @@ +title: "[pallet-contracts] stabilize xcm_send and xcm_execute" + +doc: + - audience: Runtime Dev + description: | + `xcm_send` and `xcm_execute` are currently marked as unstable. This PR stabilizes them. +crates: +- name: pallet-contracts + bump: major + + diff --git a/prdoc/pr_4220.prdoc b/prdoc/pr_4220.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d5688ab325cd1ee013dc235c19ce78af46253fd6 --- /dev/null +++ b/prdoc/pr_4220.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Refactor XCM Simulator Example + +doc: + - audience: Runtime Dev + description: | + This PR refactors the XCM Simulator Example to improve developer experience when trying to read and understand the example. 3 monolithic files have been broken down into their respective components across various modules. No major logical changes were made. + +crates: [ ] diff --git a/prdoc/pr_4221.prdoc b/prdoc/pr_4221.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e4941cce892a183c8feafe5b259207dbb0e01c57 --- /dev/null +++ b/prdoc/pr_4221.prdoc @@ -0,0 +1,15 @@ +title: "pallet_broker::start_sales: Take `extra_cores` and not total cores" + +doc: + - audience: Runtime User + description: | + Change `pallet_broker::start_sales` to take `extra_cores` and not total cores. + It will calculate the total number of cores to offer based on number of + reservations plus number of leases plus `extra_cores`. Internally it will + also notify the relay chain of the required number of cores. + + Thus, starting the first sales with `pallet-broker` requires less brain power ;) + +crates: +- name: pallet-broker + bump: minor diff --git a/prdoc/pr_4229.prdoc b/prdoc/pr_4229.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..05af8e062a32b5df8284f269159df53982412531 --- /dev/null +++ b/prdoc/pr_4229.prdoc @@ -0,0 +1,10 @@ +title: "Fix Stuck Collator Funds" + +doc: + - audience: Runtime Dev + description: | + Fixes stuck collator funds by providing a migration that should have been in PR 1340. + +crates: + - name: pallet-collator-selection + bump: patch diff --git a/prdoc/pr_4252.prdoc b/prdoc/pr_4252.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..22987b46845d8382d83e4501301465643167fea4 --- /dev/null +++ b/prdoc/pr_4252.prdoc @@ -0,0 +1,15 @@ +title: "Add logic to increase pvf worker based on chain" + +doc: + - audience: Node Operator + description: | + A new logic and cli parameters were added to allow increasing the number of pvf + workers based on the chain-id. + +crates: + - name: polkadot-node-core-candidate-validation + bump: minor + - name: polkadot-cli + bump: minor + - name: polkadot-service + bump: minor diff --git a/prdoc/pr_4301.prdoc b/prdoc/pr_4301.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2ca2534243a8e1875ae2ef30b164d05a5c36f95e --- /dev/null +++ b/prdoc/pr_4301.prdoc @@ -0,0 +1,13 @@ +title: New runtime api to check if a validator has pending pages of rewards for an era. + +doc: + - audience: + - Node Dev + - Runtime User + description: | + Creates a new runtime api to check if reward for an era is pending for a validator. Era rewards are paged and this + api will return true as long as there is one or more pages of era reward which are not claimed. + +crates: +- name: pallet-staking +- name: pallet-staking-runtime-api diff --git a/prdoc/pr_4311.prdoc b/prdoc/pr_4311.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..cf32acaf00895995da73df90b7f6be1053d77ac9 --- /dev/null +++ b/prdoc/pr_4311.prdoc @@ -0,0 +1,9 @@ +title: Not allow reap stash for virtual stakers. + +doc: + - audience: Runtime Dev + description: | + Add guards to staking dispathables to prevent virtual stakers to be reaped. + +crates: +- name: pallet-staking diff --git a/prdoc/pr_4312.prdoc b/prdoc/pr_4312.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d773edbd14de113c5106485210683aedf491e9ba --- /dev/null +++ b/prdoc/pr_4312.prdoc @@ -0,0 +1,19 @@ +title: Add `Deposited`/`Withdrawn` events for `pallet-assets` + +doc: + - audience: Runtime Dev + description: | + New events were added to `pallet-assets`: `Deposited` and `Withdrawn`. Make sure + to cover those events on tests if necessary. + - audience: Runtime User + description: | + New events were added to `pallet-assets`: `Deposited` and `Withdrawn`. These indicate + a change in the balance of an account. + +crates: + - name: pallet-assets + bump: minor + - name: pallet-asset-tx-payment + bump: minor + - name: pallet-asset-conversion-tx-payment + bump: minor diff --git a/prdoc/schema_user.json b/prdoc/schema_user.json index 6b44b1b28dfb859dde52e46974a6ef1d06aa3722..1fa4b8d1202c99c1fea077854b6f5efcf58f1ae5 100644 --- a/prdoc/schema_user.json +++ b/prdoc/schema_user.json @@ -200,6 +200,11 @@ "const": "patch", "title": "Patch", "description": "A bump to the third leftmost non-zero digit of the version number." + }, + { + "const": "none", + "title": "None", + "description": "This change requires no SemVer bump (e.g. change was a test)." } ] }, diff --git a/scripts/release/build-changelogs.sh b/scripts/release/build-changelogs.sh index 84054391936294adbd13340932f2ac5c458692aa..d9a1f11d01e92aa18c860cb87021caa066c7e96d 100755 --- a/scripts/release/build-changelogs.sh +++ b/scripts/release/build-changelogs.sh @@ -2,11 +2,10 @@ export PRODUCT=polkadot export VERSION=${VERSION:-1.5.0} -export ENGINE=${ENGINE:-docker} +export ENGINE=${ENGINE:-podman} export REF1=${REF1:-'HEAD'} export REF2=${REF2} export RUSTC_STABLE=${RUSTC_STABLE:-'1.0'} -export RUSTC_NIGHTLY=${RUSTC_NIGHTLY:-'1.0'} PROJECT_ROOT=`git rev-parse --show-toplevel` echo $PROJECT_ROOT @@ -27,35 +26,43 @@ echo -e "OUTPUT: \t\t$OUTPUT" mkdir -p $OUTPUT $ENGINE run --rm -v ${PROJECT_ROOT}:/repo paritytech/prdoc load -d "prdoc/$VERSION" --json > $DATA_JSON -# ls -al $DATA_JSON cat $DATA_JSON | jq ' { "prdoc" : .}' > $CONTEXT_JSON -# ls -al $CONTEXT_JSON -# Fetch the list of valid audiences +# Fetch the list of valid audiences and their descriptions SCHEMA_URL=https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json SCHEMA=$(curl -s $SCHEMA_URL | sed 's|^//.*||') -AUDIENCE_ARRAY=$(echo -E $SCHEMA | jq -r '."$defs".audience.oneOf[] | .const') - -readarray -t audiences < <(echo "$AUDIENCE_ARRAY") -declare -p audiences - - -# Generate a changelog -echo "Generating changelog..." -tera -t "${TEMPLATE_CHANGELOG}" --env --env-key env "${CONTEXT_JSON}" > "$OUTPUT/changelog.md" -echo "Changelog ready in $OUTPUT/changelog.md" +aud_desc_array=() +while IFS= read -r line; do + audience=$(jq -r '.const' <<< "$line" ) + description=$(jq -r '.description' <<< "$line") + if [ -n "$audience" ] && [ -n "$description" ]; then + aud_desc_array+=("($audience; $description)") + fi +done < <(jq -c '."$defs".audience_id.oneOf[]' <<< "$SCHEMA") # Generate a release notes doc per audience -for audience in "${audiences[@]}"; do +for tuple in "${aud_desc_array[@]}"; do + audience=$(echo "$tuple" | cut -d ';' -f 1 | sed 's/(//') audience_id="$(tr [A-Z] [a-z] <<< "$audience")" audience_id="$(tr ' ' '_' <<< "$audience_id")" + + description=$(echo "$tuple" | cut -d ';' -f 2 | sed 's/)//') + echo "Processing audience: $audience ($audience_id)" - export TARGET_AUDIENCE=$audience + export TARGET_AUDIENCE="$audience" + export AUDIENCE_DESC="**These changes are relevant to:** $description" + tera -t "${TEMPLATE_AUDIENCE}" --env --env-key env "${CONTEXT_JSON}" > "$OUTPUT/relnote_${audience_id}.md" cat "$OUTPUT/relnote_${audience_id}.md" >> "$PROJECT_ROOT/scripts/release/templates/changelog.md" done + +# Generate a changelog containing list of the commits +echo "Generating changelog..." +tera -t "${TEMPLATE_CHANGELOG}" --env --env-key env "${CONTEXT_JSON}" > "$OUTPUT/relnote_commits.md" +echo "Changelog ready in $OUTPUT/relnote_commits.md" + # Show the files tree -s -h -c $OUTPUT/ diff --git a/scripts/release/templates/audience.md.tera b/scripts/release/templates/audience.md.tera index 0b47850e3a37026a5aa5562cd685cbca48bde6f0..237643cfa3926688ed0d09c9874e4643a218a486 100644 --- a/scripts/release/templates/audience.md.tera +++ b/scripts/release/templates/audience.md.tera @@ -1,5 +1,7 @@ ### Changelog for `{{ env.TARGET_AUDIENCE }}` +{{ env.AUDIENCE_DESC }} + {% for file in prdoc -%} {% for doc_item in file.content.doc %} {%- if doc_item.audience == env.TARGET_AUDIENCE %} diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index f4ed75b919642811ace4147076245c912a0e7d07..00eab9b75f60dcc4f5b2c8bee15e61d14f306b3c 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -66,6 +66,7 @@ frame-system-rpc-runtime-api = { path = "../../../frame/system/rpc/runtime-api", frame-try-runtime = { path = "../../../frame/try-runtime", default-features = false, optional = true } pallet-alliance = { path = "../../../frame/alliance", default-features = false } pallet-asset-conversion = { path = "../../../frame/asset-conversion", default-features = false } +pallet-asset-conversion-ops = { path = "../../../frame/asset-conversion/ops", default-features = false } pallet-asset-rate = { path = "../../../frame/asset-rate", default-features = false } pallet-assets = { path = "../../../frame/assets", default-features = false } pallet-authority-discovery = { path = "../../../frame/authority-discovery", default-features = false } @@ -166,6 +167,7 @@ std = [ "log/std", "node-primitives/std", "pallet-alliance/std", + "pallet-asset-conversion-ops/std", "pallet-asset-conversion-tx-payment/std", "pallet-asset-conversion/std", "pallet-asset-rate/std", @@ -278,6 +280,7 @@ runtime-benchmarks = [ "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-alliance/runtime-benchmarks", + "pallet-asset-conversion-ops/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", "pallet-asset-rate/runtime-benchmarks", "pallet-asset-tx-payment/runtime-benchmarks", @@ -354,6 +357,7 @@ try-runtime = [ "frame-system/try-runtime", "frame-try-runtime/try-runtime", "pallet-alliance/try-runtime", + "pallet-asset-conversion-ops/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-conversion/try-runtime", "pallet-asset-rate/try-runtime", diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 65d3ba0ed6eea3a63873af5575e258e87e1b9424..5d8016532a5d7ea80b91ba7ca3908c9ed4a4e23a 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -63,7 +63,7 @@ use frame_system::{ }; pub use node_primitives::{AccountId, Signature}; use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Moment, Nonce}; -use pallet_asset_conversion::{Ascending, Chain, WithFirstAsset}; +use pallet_asset_conversion::{AccountIdConverter, Ascending, Chain, WithFirstAsset}; use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600}; use pallet_election_provider_multi_phase::{GeometricDepositBase, SolutionAccuracyOf}; use pallet_identity::legacy::IdentityInfo; @@ -654,7 +654,6 @@ parameter_types! { pub const SlashDeferDuration: sp_staking::EraIndex = 24 * 7; // 1/4 the bonding duration. pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub const MaxNominators: u32 = 64; - pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); pub const MaxControllersInDeprecationBatch: u32 = 5900; pub OffchainRepeat: BlockNumber = 5; pub HistoryDepth: u32 = 84; @@ -690,7 +689,6 @@ impl pallet_staking::Config for Runtime { type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; type MaxExposurePageSize = ConstU32<256>; - type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainExecution; type VoterList = VoterList; @@ -703,6 +701,7 @@ impl pallet_staking::Config for Runtime { type EventListeners = NominationPools; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } impl pallet_fast_unstake::Config for Runtime { @@ -1602,6 +1601,7 @@ impl pallet_mmr::Config for Runtime { type Hashing = Keccak256; type LeafData = pallet_mmr::ParentNumberAndHash; type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest; + type BlockHashProvider = pallet_mmr::DefaultBlockHashProvider; type WeightInfo = (); } @@ -1709,8 +1709,17 @@ impl pallet_asset_conversion::Config for Runtime { type Assets = UnionOf, AccountId>; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = Chain< - WithFirstAsset>, - Ascending>, + WithFirstAsset< + Native, + AccountId, + NativeOrWithId, + AccountIdConverter, + >, + Ascending< + AccountId, + NativeOrWithId, + AccountIdConverter, + >, >; type PoolAssetId = >::AssetId; type PoolAssets = PoolAssets; @@ -1727,6 +1736,19 @@ impl pallet_asset_conversion::Config for Runtime { type BenchmarkHelper = (); } +impl pallet_asset_conversion_ops::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed<( + NativeOrWithId, + NativeOrWithId, + )>; + type AssetsRefund = ::Assets; + type PoolAssetsRefund = ::PoolAssets; + type PoolAssetsTeam = ::PoolAssets; + type DepositAsset = Balances; + type WeightInfo = pallet_asset_conversion_ops::weights::SubstrateWeight; +} + parameter_types! { pub const QueueCount: u32 = 300; pub const MaxQueueLen: u32 = 1000; @@ -2197,7 +2219,7 @@ impl EnsureOriginWithArg for DynamicParamet frame_system::ensure_root(origin.clone()).map_err(|_| origin)?; return Ok(()) }, - RuntimeParametersKey::Contract(_) => { + RuntimeParametersKey::Contracts(_) => { frame_system::ensure_root(origin.clone()).map_err(|_| origin)?; return Ok(()) }, @@ -2473,6 +2495,9 @@ mod runtime { #[runtime::pallet_index(78)] pub type PalletExampleMbms = pallet_example_mbm; + + #[runtime::pallet_index(79)] + pub type AssetConversionMigration = pallet_asset_conversion_ops; } /// The address format for describing accounts. @@ -2632,6 +2657,7 @@ mod benches { [pallet_tx_pause, TxPause] [pallet_safe_mode, SafeMode] [pallet_example_mbm, PalletExampleMbms] + [pallet_asset_conversion_ops, AssetConversionMigration] ); } @@ -2765,6 +2791,10 @@ impl_runtime_apis! { fn eras_stakers_page_count(era: sp_staking::EraIndex, account: AccountId) -> sp_staking::Page { Staking::api_eras_stakers_page_count(era, account) } + + fn pending_rewards(era: sp_staking::EraIndex, account: AccountId) -> bool { + Staking::api_pending_rewards(era, account) + } } impl sp_consensus_babe::BabeApi for Runtime { diff --git a/substrate/bin/utils/subkey/src/lib.rs b/substrate/bin/utils/subkey/src/lib.rs index 33f28ef46a5e7805b2c00b047e413f049169f6ef..0ca65cd08a6bcade0522b112a9bd327ed898badd 100644 --- a/substrate/bin/utils/subkey/src/lib.rs +++ b/substrate/bin/utils/subkey/src/lib.rs @@ -310,7 +310,7 @@ use clap::Parser; use sc_cli::{ - Error, GenerateCmd, GenerateNodeKeyCmd, InspectKeyCmd, InspectNodeKeyCmd, SignCmd, VanityCmd, + Error, GenerateCmd, GenerateKeyCmdCommon, InspectKeyCmd, InspectNodeKeyCmd, SignCmd, VanityCmd, VerifyCmd, }; @@ -324,7 +324,7 @@ use sc_cli::{ pub enum Subkey { /// Generate a random node key, write it to a file or stdout and write the /// corresponding peer-id to stderr - GenerateNodeKey(GenerateNodeKeyCmd), + GenerateNodeKey(GenerateKeyCmdCommon), /// Generate a random account Generate(GenerateCmd), diff --git a/substrate/client/cli/src/commands/generate_node_key.rs b/substrate/client/cli/src/commands/generate_node_key.rs index 43851dc1af5ccf555668e2014ba6fe57c31e858c..bdb94eec93b4a38797b54a7e7459d91dc88a3794 100644 --- a/substrate/client/cli/src/commands/generate_node_key.rs +++ b/substrate/client/cli/src/commands/generate_node_key.rs @@ -17,23 +17,19 @@ //! Implementation of the `generate-node-key` subcommand -use crate::Error; -use clap::Parser; +use crate::{build_network_key_dir_or_default, Error, NODE_KEY_ED25519_FILE}; +use clap::{Args, Parser}; use libp2p_identity::{ed25519, Keypair}; +use sc_service::BasePath; use std::{ fs, io::{self, Write}, path::PathBuf, }; -/// The `generate-node-key` command -#[derive(Debug, Parser)] -#[command( - name = "generate-node-key", - about = "Generate a random node key, write it to a file or stdout \ - and write the corresponding peer-id to stderr" -)] -pub struct GenerateNodeKeyCmd { +/// Common arguments accross all generate key commands, subkey and node. +#[derive(Debug, Args, Clone)] +pub struct GenerateKeyCmdCommon { /// Name of file to save secret key to. /// If not given, the secret key is printed to stdout. #[arg(long)] @@ -45,32 +41,111 @@ pub struct GenerateNodeKeyCmd { bin: bool, } -impl GenerateNodeKeyCmd { +/// The `generate-node-key` command +#[derive(Debug, Clone, Parser)] +#[command( + name = "generate-node-key", + about = "Generate a random node key, write it to a file or stdout \ + and write the corresponding peer-id to stderr" +)] +pub struct GenerateNodeKeyCmd { + #[clap(flatten)] + pub common: GenerateKeyCmdCommon, + /// Specify the chain specification. + /// + /// It can be any of the predefined chains like dev, local, staging, polkadot, kusama. + #[arg(long, value_name = "CHAIN_SPEC")] + pub chain: Option, + /// A directory where the key should be saved. If a key already + /// exists in the directory, it won't be overwritten. + #[arg(long, conflicts_with_all = ["file", "default_base_path"])] + base_path: Option, + + /// Save the key in the default directory. If a key already + /// exists in the directory, it won't be overwritten. + #[arg(long, conflicts_with_all = ["base_path", "file"])] + default_base_path: bool, +} + +impl GenerateKeyCmdCommon { /// Run the command pub fn run(&self) -> Result<(), Error> { - let keypair = ed25519::Keypair::generate(); + generate_key(&self.file, self.bin, None, &None, false, None) + } +} + +impl GenerateNodeKeyCmd { + /// Run the command + pub fn run(&self, chain_spec_id: &str, executable_name: &String) -> Result<(), Error> { + generate_key( + &self.common.file, + self.common.bin, + Some(chain_spec_id), + &self.base_path, + self.default_base_path, + Some(executable_name), + ) + } +} + +// Utility function for generating a key based on the provided CLI arguments +// +// `file` - Name of file to save secret key to +// `bin` +fn generate_key( + file: &Option, + bin: bool, + chain_spec_id: Option<&str>, + base_path: &Option, + default_base_path: bool, + executable_name: Option<&String>, +) -> Result<(), Error> { + let keypair = ed25519::Keypair::generate(); - let secret = keypair.secret(); + let secret = keypair.secret(); - let file_data = if self.bin { - secret.as_ref().to_owned() - } else { - array_bytes::bytes2hex("", secret).into_bytes() - }; + let file_data = if bin { + secret.as_ref().to_owned() + } else { + array_bytes::bytes2hex("", secret).into_bytes() + }; - match &self.file { - Some(file) => fs::write(file, file_data)?, - None => io::stdout().lock().write_all(&file_data)?, - } + match (file, base_path, default_base_path) { + (Some(file), None, false) => fs::write(file, file_data)?, + (None, Some(_), false) | (None, None, true) => { + let network_path = build_network_key_dir_or_default( + base_path.clone().map(BasePath::new), + chain_spec_id.unwrap_or_default(), + executable_name.ok_or(Error::Input("Executable name not provided".into()))?, + ); - eprintln!("{}", Keypair::from(keypair).public().to_peer_id()); + fs::create_dir_all(network_path.as_path())?; - Ok(()) + let key_path = network_path.join(NODE_KEY_ED25519_FILE); + if key_path.exists() { + eprintln!("Skip generation, a key already exists in {:?}", key_path); + return Err(Error::KeyAlreadyExistsInPath(key_path)); + } else { + eprintln!("Generating key in {:?}", key_path); + fs::write(key_path, file_data)? + } + }, + (None, None, false) => io::stdout().lock().write_all(&file_data)?, + (_, _, _) => { + // This should not happen, arguments are marked as mutually exclusive. + return Err(Error::Input("Mutually exclusive arguments provided".into())); + }, } + + eprintln!("{}", Keypair::from(keypair).public().to_peer_id()); + + Ok(()) } #[cfg(test)] -mod tests { +pub mod tests { + use crate::DEFAULT_NETWORK_CONFIG_PATH; + use super::*; use std::io::Read; use tempfile::Builder; @@ -80,9 +155,32 @@ mod tests { let mut file = Builder::new().prefix("keyfile").tempfile().unwrap(); let file_path = file.path().display().to_string(); let generate = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", &file_path]); - assert!(generate.run().is_ok()); + assert!(generate.run("test", &String::from("test")).is_ok()); let mut buf = String::new(); assert!(file.read_to_string(&mut buf).is_ok()); assert!(array_bytes::hex2bytes(&buf).is_ok()); } + + #[test] + fn generate_node_key_base_path() { + let base_dir = Builder::new().prefix("keyfile").tempdir().unwrap(); + let key_path = base_dir + .path() + .join("chains/test_id/") + .join(DEFAULT_NETWORK_CONFIG_PATH) + .join(NODE_KEY_ED25519_FILE); + let base_path = base_dir.path().display().to_string(); + let generate = + GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--base-path", &base_path]); + assert!(generate.run("test_id", &String::from("test")).is_ok()); + let buf = fs::read_to_string(key_path.as_path()).unwrap(); + assert!(array_bytes::hex2bytes(&buf).is_ok()); + + assert!(generate.run("test_id", &String::from("test")).is_err()); + let new_buf = fs::read_to_string(key_path).unwrap(); + assert_eq!( + array_bytes::hex2bytes(&new_buf).unwrap(), + array_bytes::hex2bytes(&buf).unwrap() + ); + } } diff --git a/substrate/client/cli/src/commands/inspect_node_key.rs b/substrate/client/cli/src/commands/inspect_node_key.rs index 6cf025a2d115066ed18277e4036a028965e3c32f..25a0a685650e3d39709e7f8e9af903d7d72070d0 100644 --- a/substrate/client/cli/src/commands/inspect_node_key.rs +++ b/substrate/client/cli/src/commands/inspect_node_key.rs @@ -79,7 +79,9 @@ impl InspectNodeKeyCmd { #[cfg(test)] mod tests { - use super::{super::GenerateNodeKeyCmd, *}; + use crate::commands::generate_node_key::GenerateNodeKeyCmd; + + use super::*; #[test] fn inspect_node_key() { @@ -87,7 +89,7 @@ mod tests { let path = path.to_str().unwrap(); let cmd = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", path]); - assert!(cmd.run().is_ok()); + assert!(cmd.run("test", &String::from("test")).is_ok()); let cmd = InspectNodeKeyCmd::parse_from(&["inspect-node-key", "--file", path]); assert!(cmd.run().is_ok()); diff --git a/substrate/client/cli/src/commands/key.rs b/substrate/client/cli/src/commands/key.rs index d49b7e4072c8eb5453b0540117dacaf23e89f912..52747b404622691cc0ef572ab91f39e3d23a3eb5 100644 --- a/substrate/client/cli/src/commands/key.rs +++ b/substrate/client/cli/src/commands/key.rs @@ -47,7 +47,10 @@ impl KeySubcommand { /// run the key subcommands pub fn run(&self, cli: &C) -> Result<(), Error> { match self { - KeySubcommand::GenerateNodeKey(cmd) => cmd.run(), + KeySubcommand::GenerateNodeKey(cmd) => { + let chain_spec = cli.load_spec(cmd.chain.as_deref().unwrap_or(""))?; + cmd.run(chain_spec.id(), &C::executable_name()) + }, KeySubcommand::Generate(cmd) => cmd.run(), KeySubcommand::Inspect(cmd) => cmd.run(), KeySubcommand::Insert(cmd) => cmd.run(cli), diff --git a/substrate/client/cli/src/commands/mod.rs b/substrate/client/cli/src/commands/mod.rs index 9d48d2bdf644fdc2ec6417148f138aafaf04f063..2d7a0dc72ff5355d2541ea098c140878649accdb 100644 --- a/substrate/client/cli/src/commands/mod.rs +++ b/substrate/client/cli/src/commands/mod.rs @@ -42,7 +42,7 @@ mod verify; pub use self::{ build_spec_cmd::BuildSpecCmd, chain_info_cmd::ChainInfoCmd, check_block_cmd::CheckBlockCmd, export_blocks_cmd::ExportBlocksCmd, export_state_cmd::ExportStateCmd, generate::GenerateCmd, - generate_node_key::GenerateNodeKeyCmd, import_blocks_cmd::ImportBlocksCmd, + generate_node_key::GenerateKeyCmdCommon, import_blocks_cmd::ImportBlocksCmd, insert_key::InsertKeyCmd, inspect_key::InspectKeyCmd, inspect_node_key::InspectNodeKeyCmd, key::KeySubcommand, purge_chain_cmd::PurgeChainCmd, revert_cmd::RevertCmd, run_cmd::RunCmd, sign::SignCmd, vanity::VanityCmd, verify::VerifyCmd, diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs index 5def9ce9b72620eb7942ac6ee68b16493f6b8053..70a4885e5eef79780bdbec5b2cb7550b28ad866a 100644 --- a/substrate/client/cli/src/config.rs +++ b/substrate/client/cli/src/config.rs @@ -428,8 +428,10 @@ pub trait CliConfiguration: Sized { /// By default this is retrieved from `NodeKeyParams` if it is available. Otherwise its /// `NodeKeyConfig::default()`. fn node_key(&self, net_config_dir: &PathBuf) -> Result { + let is_dev = self.is_dev()?; + let role = self.role(is_dev)?; self.node_key_params() - .map(|x| x.node_key(net_config_dir)) + .map(|x| x.node_key(net_config_dir, role, is_dev)) .unwrap_or_else(|| Ok(Default::default())) } @@ -463,11 +465,9 @@ pub trait CliConfiguration: Sized { let is_dev = self.is_dev()?; let chain_id = self.chain_id(is_dev)?; let chain_spec = cli.load_spec(&chain_id)?; - let base_path = self - .base_path()? - .unwrap_or_else(|| BasePath::from_project("", "", &C::executable_name())); - let config_dir = base_path.config_dir(chain_spec.id()); - let net_config_dir = config_dir.join(DEFAULT_NETWORK_CONFIG_PATH); + let base_path = base_path_or_default(self.base_path()?, &C::executable_name()); + let config_dir = build_config_dir(&base_path, chain_spec.id()); + let net_config_dir = build_net_config_dir(&config_dir); let client_id = C::client_id(); let database_cache_size = self.database_cache_size()?.unwrap_or(1024); let database = self.database()?.unwrap_or( @@ -665,3 +665,33 @@ pub fn generate_node_name() -> String { } } } + +/// Returns the value of `base_path` or the default_path if it is None +pub(crate) fn base_path_or_default( + base_path: Option, + executable_name: &String, +) -> BasePath { + base_path.unwrap_or_else(|| BasePath::from_project("", "", executable_name)) +} + +/// Returns the default path for configuration directory based on the chain_spec +pub(crate) fn build_config_dir(base_path: &BasePath, chain_spec_id: &str) -> PathBuf { + base_path.config_dir(chain_spec_id) +} + +/// Returns the default path for the network configuration inside the configuration dir +pub(crate) fn build_net_config_dir(config_dir: &PathBuf) -> PathBuf { + config_dir.join(DEFAULT_NETWORK_CONFIG_PATH) +} + +/// Returns the default path for the network directory starting from the provided base_path +/// or from the default base_path. +pub(crate) fn build_network_key_dir_or_default( + base_path: Option, + chain_spec_id: &str, + executable_name: &String, +) -> PathBuf { + let config_dir = + build_config_dir(&base_path_or_default(base_path, executable_name), chain_spec_id); + build_net_config_dir(&config_dir) +} diff --git a/substrate/client/cli/src/error.rs b/substrate/client/cli/src/error.rs index 6c0cfca4932efca53ff6f8d38e8ae4de004f8b91..90ad048009ade90b5ccd4cfc264cc85e3b311843 100644 --- a/substrate/client/cli/src/error.rs +++ b/substrate/client/cli/src/error.rs @@ -18,6 +18,8 @@ //! Initialization errors. +use std::path::PathBuf; + use sp_core::crypto; /// Result type alias for the CLI. @@ -78,6 +80,20 @@ pub enum Error { #[error(transparent)] GlobalLoggerError(#[from] sc_tracing::logging::Error), + + #[error( + "Starting an authorithy without network key in {0}. + \n This is not a safe operation because other authorities in the network may depend on your node having a stable identity. + \n Otherwise these other authorities may not being able to reach you. + \n If it is the first time running your node you could use one of the following methods: + \n 1. [Preferred] Separately generate the key with: key generate-node-key --base-path + \n 2. [Preferred] Separately generate the key with: key generate-node-key --file + \n 3. [Preferred] Separately generate the key with: key generate-node-key --default-base-path + \n 4. [Unsafe] Pass --unsafe-force-node-key-generation and make sure you remove it for subsequent node restarts" + )] + NetworkKeyNotFound(PathBuf), + #[error("A network key already exists in path {0}")] + KeyAlreadyExistsInPath(PathBuf), } impl From<&str> for Error { diff --git a/substrate/client/cli/src/params/node_key_params.rs b/substrate/client/cli/src/params/node_key_params.rs index 53f19f58e1fb0d657d387fd50a8a2346c238ead4..7058af19f1d4a5edc31a8e93d062f7f93c065f1e 100644 --- a/substrate/client/cli/src/params/node_key_params.rs +++ b/substrate/client/cli/src/params/node_key_params.rs @@ -18,15 +18,16 @@ use clap::Args; use sc_network::config::{identity::ed25519, NodeKeyConfig}; +use sc_service::Role; use sp_core::H256; use std::{path::PathBuf, str::FromStr}; -use crate::{arg_enums::NodeKeyType, error}; +use crate::{arg_enums::NodeKeyType, error, Error}; /// The file name of the node's Ed25519 secret key inside the chain-specific /// network config directory, if neither `--node-key` nor `--node-key-file` /// is specified in combination with `--node-key-type=ed25519`. -const NODE_KEY_ED25519_FILE: &str = "secret_ed25519"; +pub(crate) const NODE_KEY_ED25519_FILE: &str = "secret_ed25519"; /// Parameters used to create the `NodeKeyConfig`, which determines the keypair /// used for libp2p networking. @@ -79,22 +80,48 @@ pub struct NodeKeyParams { /// the chosen type. #[arg(long, value_name = "FILE")] pub node_key_file: Option, + + /// Forces key generation if node-key-file file does not exist. + /// + /// This is an unsafe feature for production networks, because as an active authority + /// other authorities may depend on your node having a stable identity and they might + /// not being able to reach you if your identity changes after entering the active set. + /// + /// For minimal node downtime if no custom `node-key-file` argument is provided + /// the network-key is usually persisted accross nodes restarts, + /// in the `network` folder from directory provided in `--base-path` + /// + /// Warning!! If you ever run the node with this argument, make sure + /// you remove it for the subsequent restarts. + #[arg(long)] + pub unsafe_force_node_key_generation: bool, } impl NodeKeyParams { /// Create a `NodeKeyConfig` from the given `NodeKeyParams` in the context /// of an optional network config storage directory. - pub fn node_key(&self, net_config_dir: &PathBuf) -> error::Result { + pub fn node_key( + &self, + net_config_dir: &PathBuf, + role: Role, + is_dev: bool, + ) -> error::Result { Ok(match self.node_key_type { NodeKeyType::Ed25519 => { let secret = if let Some(node_key) = self.node_key.as_ref() { parse_ed25519_secret(node_key)? } else { - sc_network::config::Secret::File( - self.node_key_file - .clone() - .unwrap_or_else(|| net_config_dir.join(NODE_KEY_ED25519_FILE)), - ) + let key_path = self + .node_key_file + .clone() + .unwrap_or_else(|| net_config_dir.join(NODE_KEY_ED25519_FILE)); + if !self.unsafe_force_node_key_generation && + role.is_authority() && !is_dev && + !key_path.exists() + { + return Err(Error::NetworkKeyNotFound(key_path)) + } + sc_network::config::Secret::File(key_path) }; NodeKeyConfig::Ed25519(secret) @@ -122,7 +149,8 @@ mod tests { use super::*; use clap::ValueEnum; use libp2p_identity::ed25519; - use std::fs; + use std::fs::{self, File}; + use tempfile::TempDir; #[test] fn test_node_key_config_input() { @@ -136,8 +164,9 @@ mod tests { node_key_type, node_key: Some(format!("{:x}", H256::from_slice(sk.as_ref()))), node_key_file: None, + unsafe_force_node_key_generation: false, }; - params.node_key(net_config_dir).and_then(|c| match c { + params.node_key(net_config_dir, Role::Authority, false).and_then(|c| match c { NodeKeyConfig::Ed25519(sc_network::config::Secret::Input(ref ski)) if node_key_type == NodeKeyType::Ed25519 && &sk[..] == ski.as_ref() => Ok(()), @@ -156,10 +185,11 @@ mod tests { node_key_type: NodeKeyType::Ed25519, node_key: None, node_key_file: Some(file), + unsafe_force_node_key_generation: false, }; let node_key = params - .node_key(&PathBuf::from("not-used")) + .node_key(&PathBuf::from("not-used"), Role::Authority, false) .expect("Creates node key config") .into_keypair() .expect("Creates node key pair"); @@ -186,29 +216,58 @@ mod tests { #[test] fn test_node_key_config_default() { - fn with_def_params(f: F) -> error::Result<()> + fn with_def_params(f: F, unsafe_force_node_key_generation: bool) -> error::Result<()> where F: Fn(NodeKeyParams) -> error::Result<()>, { NodeKeyType::value_variants().iter().try_for_each(|t| { let node_key_type = *t; - f(NodeKeyParams { node_key_type, node_key: None, node_key_file: None }) + f(NodeKeyParams { + node_key_type, + node_key: None, + node_key_file: None, + unsafe_force_node_key_generation, + }) }) } - fn some_config_dir(net_config_dir: &PathBuf) -> error::Result<()> { - with_def_params(|params| { - let dir = PathBuf::from(net_config_dir.clone()); - let typ = params.node_key_type; - params.node_key(net_config_dir).and_then(move |c| match c { - NodeKeyConfig::Ed25519(sc_network::config::Secret::File(ref f)) - if typ == NodeKeyType::Ed25519 && f == &dir.join(NODE_KEY_ED25519_FILE) => - Ok(()), - _ => Err(error::Error::Input("Unexpected node key config".into())), - }) - }) + fn some_config_dir( + net_config_dir: &PathBuf, + unsafe_force_node_key_generation: bool, + role: Role, + is_dev: bool, + ) -> error::Result<()> { + with_def_params( + |params| { + let dir = PathBuf::from(net_config_dir.clone()); + let typ = params.node_key_type; + let role = role.clone(); + params.node_key(net_config_dir, role, is_dev).and_then(move |c| match c { + NodeKeyConfig::Ed25519(sc_network::config::Secret::File(ref f)) + if typ == NodeKeyType::Ed25519 && + f == &dir.join(NODE_KEY_ED25519_FILE) => + Ok(()), + _ => Err(error::Error::Input("Unexpected node key config".into())), + }) + }, + unsafe_force_node_key_generation, + ) } - assert!(some_config_dir(&PathBuf::from_str("x").unwrap()).is_ok()); + assert!(some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Full, false).is_ok()); + assert!( + some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, true).is_ok() + ); + assert!( + some_config_dir(&PathBuf::from_str("x").unwrap(), true, Role::Authority, false).is_ok() + ); + assert!(matches!( + some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, false), + Err(Error::NetworkKeyNotFound(_)) + )); + + let tempdir = TempDir::new().unwrap(); + let _file = File::create(tempdir.path().join(NODE_KEY_ED25519_FILE)).unwrap(); + assert!(some_config_dir(&tempdir.path().into(), false, Role::Authority, false).is_ok()); } } diff --git a/substrate/client/consensus/aura/src/lib.rs b/substrate/client/consensus/aura/src/lib.rs index e220aaac508df50c1c6d440cfe50ee3b4015d64a..2d6264a48929502c2791180c1fbfb305bf9181cc 100644 --- a/substrate/client/consensus/aura/src/lib.rs +++ b/substrate/client/consensus/aura/src/lib.rs @@ -579,15 +579,15 @@ mod tests { type Error = sp_blockchain::Error; struct DummyFactory(Arc); - struct DummyProposer(u64, Arc); + struct DummyProposer(Arc); impl Environment for DummyFactory { type Proposer = DummyProposer; type CreateProposer = futures::future::Ready>; type Error = Error; - fn init(&mut self, parent_header: &::Header) -> Self::CreateProposer { - futures::future::ready(Ok(DummyProposer(parent_header.number + 1, self.0.clone()))) + fn init(&mut self, _: &::Header) -> Self::CreateProposer { + futures::future::ready(Ok(DummyProposer(self.0.clone()))) } } @@ -604,9 +604,9 @@ mod tests { _: Duration, _: Option, ) -> Self::Proposal { - let r = BlockBuilderBuilder::new(&*self.1) - .on_parent_block(self.1.chain_info().best_hash) - .fetch_parent_block_number(&*self.1) + let r = BlockBuilderBuilder::new(&*self.0) + .on_parent_block(self.0.chain_info().best_hash) + .fetch_parent_block_number(&*self.0) .unwrap() .with_inherent_digests(digests) .build() diff --git a/substrate/client/consensus/beefy/src/justification.rs b/substrate/client/consensus/beefy/src/justification.rs index 7f1b9e5237c3970eb8cba4936debd18c77117288..886368c9d7cb096c0178819e1bad23f06c82f3fb 100644 --- a/substrate/client/consensus/beefy/src/justification.rs +++ b/substrate/client/consensus/beefy/src/justification.rs @@ -16,12 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::keystore::BeefyKeystore; -use codec::{DecodeAll, Encode}; +use codec::DecodeAll; use sp_consensus::Error as ConsensusError; use sp_consensus_beefy::{ ecdsa_crypto::{AuthorityId, Signature}, - ValidatorSet, ValidatorSetId, VersionedFinalityProof, + BeefySignatureHasher, KnownSignature, ValidatorSet, ValidatorSetId, VersionedFinalityProof, }; use sp_runtime::traits::{Block as BlockT, NumberFor}; @@ -45,46 +44,31 @@ pub(crate) fn decode_and_verify_finality_proof( ) -> Result, (ConsensusError, u32)> { let proof = >::decode_all(&mut &*encoded) .map_err(|_| (ConsensusError::InvalidJustification, 0))?; - verify_with_validator_set::(target_number, validator_set, &proof).map(|_| proof) + verify_with_validator_set::(target_number, validator_set, &proof)?; + Ok(proof) } /// Verify the Beefy finality proof against the validator set at the block it was generated. -pub(crate) fn verify_with_validator_set( +pub(crate) fn verify_with_validator_set<'a, Block: BlockT>( target_number: NumberFor, - validator_set: &ValidatorSet, - proof: &BeefyVersionedFinalityProof, -) -> Result<(), (ConsensusError, u32)> { - let mut signatures_checked = 0u32; + validator_set: &'a ValidatorSet, + proof: &'a BeefyVersionedFinalityProof, +) -> Result>, (ConsensusError, u32)> { match proof { VersionedFinalityProof::V1(signed_commitment) => { - if signed_commitment.signatures.len() != validator_set.len() || - signed_commitment.commitment.validator_set_id != validator_set.id() || - signed_commitment.commitment.block_number != target_number - { - return Err((ConsensusError::InvalidJustification, 0)) - } - - // Arrangement of signatures in the commitment should be in the same order - // as validators for that set. - let message = signed_commitment.commitment.encode(); - let valid_signatures = validator_set - .validators() - .into_iter() - .zip(signed_commitment.signatures.iter()) - .filter(|(id, signature)| { - signature - .as_ref() - .map(|sig| { - signatures_checked += 1; - BeefyKeystore::verify(*id, sig, &message[..]) - }) - .unwrap_or(false) - }) - .count(); - if valid_signatures >= crate::round::threshold(validator_set.len()) { - Ok(()) + let signatories = signed_commitment + .verify_signatures::<_, BeefySignatureHasher>(target_number, validator_set) + .map_err(|checked_signatures| { + (ConsensusError::InvalidJustification, checked_signatures) + })?; + + if signatories.len() >= crate::round::threshold(validator_set.len()) { + Ok(signatories) } else { - Err((ConsensusError::InvalidJustification, signatures_checked)) + Err(( + ConsensusError::InvalidJustification, + signed_commitment.signature_count() as u32, + )) } }, } @@ -92,6 +76,7 @@ pub(crate) fn verify_with_validator_set( #[cfg(test)] pub(crate) mod tests { + use codec::Encode; use sp_consensus_beefy::{ known_payloads, test_utils::Keyring, Commitment, Payload, SignedCommitment, VersionedFinalityProof, diff --git a/substrate/client/consensus/grandpa/rpc/src/lib.rs b/substrate/client/consensus/grandpa/rpc/src/lib.rs index 0557eab93e2956b56956f50edea118c9c30cbd76..68de068c3058377a6be9c8907581498d3466aaba 100644 --- a/substrate/client/consensus/grandpa/rpc/src/lib.rs +++ b/substrate/client/consensus/grandpa/rpc/src/lib.rs @@ -125,7 +125,7 @@ where #[cfg(test)] mod tests { use super::*; - use std::{collections::HashSet, convert::TryInto, sync::Arc}; + use std::{collections::HashSet, sync::Arc}; use jsonrpsee::{core::EmptyServerParams as EmptyParams, types::SubscriptionId, RpcModule}; use parity_scale_codec::{Decode, Encode}; diff --git a/substrate/client/consensus/grandpa/src/communication/gossip.rs b/substrate/client/consensus/grandpa/src/communication/gossip.rs index 88821faf0aba1a67ea65fe0e2a553e89eac40f47..c7fe5a46a5eb6db29501effde313cdc6c08894d9 100644 --- a/substrate/client/consensus/grandpa/src/communication/gossip.rs +++ b/substrate/client/consensus/grandpa/src/communication/gossip.rs @@ -810,7 +810,7 @@ impl Inner { self.live_topics.push(round, set_id); self.peers.reshuffle(); - self.multicast_neighbor_packet(false) + self.multicast_neighbor_packet() } /// Note that a voter set with given ID has started. Does nothing if the last @@ -847,10 +847,7 @@ impl Inner { self.live_topics.push(Round(1), set_id); self.authorities = authorities; - // when transitioning to a new set we also want to send neighbor packets to light clients, - // this is so that they know who to ask justifications from in order to finalize the last - // block in the previous set. - self.multicast_neighbor_packet(true) + self.multicast_neighbor_packet() } /// Note that we've imported a commit finalizing a given block. Does nothing if the last @@ -869,7 +866,7 @@ impl Inner { return None } - self.multicast_neighbor_packet(false) + self.multicast_neighbor_packet() } fn consider_vote(&self, round: Round, set_id: SetId) -> Consider { @@ -1183,7 +1180,7 @@ impl Inner { (neighbor_topics, action, catch_up, report) } - fn multicast_neighbor_packet(&self, force_light: bool) -> MaybeMessage { + fn multicast_neighbor_packet(&self) -> MaybeMessage { self.local_view.as_ref().map(|local_view| { let packet = NeighborPacket { round: local_view.round, @@ -1191,22 +1188,7 @@ impl Inner { commit_finalized_height: *local_view.last_commit_height().unwrap_or(&Zero::zero()), }; - let peers = self - .peers - .inner - .iter() - .filter_map(|(id, info)| { - // light clients don't participate in the full GRANDPA voter protocol - // and therefore don't need to be informed about all view updates unless - // we explicitly require it (e.g. when transitioning to a new set) - if info.roles.is_light() && !force_light { - None - } else { - Some(id) - } - }) - .cloned() - .collect(); + let peers = self.peers.inner.iter().map(|(id, _)| id).cloned().collect(); (peers, packet) }) @@ -2602,7 +2584,7 @@ mod tests { } #[test] - fn sends_neighbor_packets_to_non_light_peers_when_starting_a_new_round() { + fn sends_neighbor_packets_to_all_peers_when_starting_a_new_round() { let (val, _) = GossipValidator::::new(config(), voter_set_state(), None, None); // initialize the validator to a stable set id @@ -2617,10 +2599,10 @@ mod tests { val.inner.write().peers.new_peer(light_peer, ObservedRole::Light); val.note_round(Round(2), |peers, message| { - assert_eq!(peers.len(), 2); + assert_eq!(peers.len(), 3); assert!(peers.contains(&authority_peer)); assert!(peers.contains(&full_peer)); - assert!(!peers.contains(&light_peer)); + assert!(peers.contains(&light_peer)); assert!(matches!(message, NeighborPacket { set_id: SetId(1), round: Round(2), .. })); }); } diff --git a/substrate/client/consensus/grandpa/src/communication/tests.rs b/substrate/client/consensus/grandpa/src/communication/tests.rs index 40d901b2fec68449a29e6f4c2cf6ea03fa504a9e..bc3023fc0281d150864cb3bb21214c3648a73c42 100644 --- a/substrate/client/consensus/grandpa/src/communication/tests.rs +++ b/substrate/client/consensus/grandpa/src/communication/tests.rs @@ -51,10 +51,8 @@ use std::{ #[derive(Debug)] pub(crate) enum Event { - EventStream(TracingUnboundedSender), WriteNotification(PeerId, Vec), Report(PeerId, ReputationChange), - Announce(Hash), } #[derive(Clone)] @@ -146,15 +144,13 @@ impl NetworkEventStream for TestNetwork { &self, _name: &'static str, ) -> Pin + Send>> { - let (tx, rx) = tracing_unbounded("test", 100_000); - let _ = self.sender.unbounded_send(Event::EventStream(tx)); - Box::pin(rx) + futures::stream::pending().boxed() } } impl NetworkBlock> for TestNetwork { - fn announce_block(&self, hash: Hash, _data: Option>) { - let _ = self.sender.unbounded_send(Event::Announce(hash)); + fn announce_block(&self, _: Hash, _data: Option>) { + unimplemented!(); } fn new_best_block_imported(&self, _hash: Hash, _number: NumberFor) { diff --git a/substrate/client/consensus/grandpa/src/environment.rs b/substrate/client/consensus/grandpa/src/environment.rs index d3e2beb84e79c101f7618421c16f7797123f55fa..31df038044a4904e49f8447c4ab05c4cd36a5142 100644 --- a/substrate/client/consensus/grandpa/src/environment.rs +++ b/substrate/client/consensus/grandpa/src/environment.rs @@ -18,7 +18,6 @@ use std::{ collections::{BTreeMap, HashMap}, - iter::FromIterator, marker::PhantomData, pin::Pin, sync::Arc, diff --git a/substrate/client/consensus/manual-seal/src/lib.rs b/substrate/client/consensus/manual-seal/src/lib.rs index c3d360f071974ab38c6f1d112c23d6705f937bd3..8fc7e7ecab2f45cf8359c2f449dde3b480bb3ad3 100644 --- a/substrate/client/consensus/manual-seal/src/lib.rs +++ b/substrate/client/consensus/manual-seal/src/lib.rs @@ -86,7 +86,7 @@ where BasicQueue::new(ManualSealVerifier, block_import, None, spawner, registry) } -/// Params required to start the instant sealing authorship task. +/// Params required to start the manual sealing authorship task. pub struct ManualSealParams, TP, SC, CS, CIDP, P> { /// Block import instance. pub block_import: BI, @@ -114,7 +114,7 @@ pub struct ManualSealParams, TP, SC, C pub create_inherent_data_providers: CIDP, } -/// Params required to start the manual sealing authorship task. +/// Params required to start the instant sealing authorship task. pub struct InstantSealParams, TP, SC, CIDP, P> { /// Block import instance for well. importing blocks. pub block_import: BI, diff --git a/substrate/client/mixnet/Cargo.toml b/substrate/client/mixnet/Cargo.toml index 65b81bda4b08de83101a01880d1aaac1a358e1ad..e605a06c9d9c7e886a9bb388bd9ab70254d193f5 100644 --- a/substrate/client/mixnet/Cargo.toml +++ b/substrate/client/mixnet/Cargo.toml @@ -4,7 +4,7 @@ name = "sc-mixnet" version = "0.4.0" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" authors = ["Parity Technologies "] -edition = "2021" +edition.workspace = true homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" diff --git a/substrate/client/network/src/protocol/notifications/upgrade/collec.rs b/substrate/client/network/src/protocol/notifications/upgrade/collec.rs index 791821b3f75dab135a34f7478b4c1c808566890e..33c090ae50e9d87b8976f78fc61e751b474b49ec 100644 --- a/substrate/client/network/src/protocol/notifications/upgrade/collec.rs +++ b/substrate/client/network/src/protocol/notifications/upgrade/collec.rs @@ -19,7 +19,6 @@ use futures::prelude::*; use libp2p::core::upgrade::{InboundUpgrade, ProtocolName, UpgradeInfo}; use std::{ - iter::FromIterator, pin::Pin, task::{Context, Poll}, vec, diff --git a/substrate/client/network/test/src/lib.rs b/substrate/client/network/test/src/lib.rs index 1dfe7d4454e9949f9ba9be08998363d77b5993a6..48a4b3d6e6e16a4281c6e504d1c0d9511d2eb677 100644 --- a/substrate/client/network/test/src/lib.rs +++ b/substrate/client/network/test/src/lib.rs @@ -393,13 +393,14 @@ where futures::executor::block_on(self.block_import.import_block(import_block)) .expect("block_import failed"); - if announce_block { - self.sync_service.announce_block(hash, None); - } hashes.push(hash); at = hash; } + if announce_block { + self.sync_service.announce_block(at, None); + } + if inform_sync_about_new_best_block { self.sync_service.new_best_block_imported( at, diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index e2612d914542ecd74758fadaa9ac3dc4b01bc7c1..f2fc7bee6e203d963cb30dd531563a67f4241feb 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -42,6 +42,7 @@ array-bytes = "6.1" log = { workspace = true, default-features = true } futures-util = { version = "0.3.30", default-features = false } rand = "0.8.5" +schnellru = "0.2.1" [dev-dependencies] jsonrpsee = { version = "0.22", features = ["server", "ws-client"] } diff --git a/substrate/client/rpc-spec-v2/src/chain_head/api.rs b/substrate/client/rpc-spec-v2/src/chain_head/api.rs index 3851adac2644d09b824383dba2e80d6877eff01a..23cb0bbf54585383e797a452d9747cac36649ad1 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/api.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/api.rs @@ -37,15 +37,15 @@ pub trait ChainHeadApi { /// /// This method is unstable and subject to change in the future. #[subscription( - name = "chainHead_unstable_follow" => "chainHead_unstable_followEvent", - unsubscribe = "chainHead_unstable_unfollow", + name = "chainHead_v1_follow" => "chainHead_v1_followEvent", + unsubscribe = "chainHead_v1_unfollow", item = FollowEvent, )] fn chain_head_unstable_follow(&self, with_runtime: bool); /// Retrieves the body (list of transactions) of a pinned block. /// - /// This method should be seen as a complement to `chainHead_unstable_follow`, + /// This method should be seen as a complement to `chainHead_v1_follow`, /// allowing the JSON-RPC client to retrieve more information about a block /// that has been reported. /// @@ -54,7 +54,7 @@ pub trait ChainHeadApi { /// # Unstable /// /// This method is unstable and subject to change in the future. - #[method(name = "chainHead_unstable_body", raw_method)] + #[method(name = "chainHead_v1_body", raw_method)] async fn chain_head_unstable_body( &self, follow_subscription: String, @@ -63,7 +63,7 @@ pub trait ChainHeadApi { /// Retrieves the header of a pinned block. /// - /// This method should be seen as a complement to `chainHead_unstable_follow`, + /// This method should be seen as a complement to `chainHead_v1_follow`, /// allowing the JSON-RPC client to retrieve more information about a block /// that has been reported. /// @@ -73,7 +73,7 @@ pub trait ChainHeadApi { /// # Unstable /// /// This method is unstable and subject to change in the future. - #[method(name = "chainHead_unstable_header", raw_method)] + #[method(name = "chainHead_v1_header", raw_method)] async fn chain_head_unstable_header( &self, follow_subscription: String, @@ -85,7 +85,7 @@ pub trait ChainHeadApi { /// # Unstable /// /// This method is unstable and subject to change in the future. - #[method(name = "chainHead_unstable_storage", raw_method)] + #[method(name = "chainHead_v1_storage", raw_method)] async fn chain_head_unstable_storage( &self, follow_subscription: String, @@ -99,7 +99,7 @@ pub trait ChainHeadApi { /// # Unstable /// /// This method is unstable and subject to change in the future. - #[method(name = "chainHead_unstable_call", raw_method)] + #[method(name = "chainHead_v1_call", raw_method)] async fn chain_head_unstable_call( &self, follow_subscription: String, @@ -118,7 +118,7 @@ pub trait ChainHeadApi { /// # Unstable /// /// This method is unstable and subject to change in the future. - #[method(name = "chainHead_unstable_unpin", raw_method)] + #[method(name = "chainHead_v1_unpin", raw_method)] async fn chain_head_unstable_unpin( &self, follow_subscription: String, @@ -131,21 +131,21 @@ pub trait ChainHeadApi { /// # Unstable /// /// This method is unstable and subject to change in the future. - #[method(name = "chainHead_unstable_continue", raw_method)] + #[method(name = "chainHead_v1_continue", raw_method)] async fn chain_head_unstable_continue( &self, follow_subscription: String, operation_id: String, ) -> Result<(), Error>; - /// Stops an operation started with chainHead_unstable_body, chainHead_unstable_call, or - /// chainHead_unstable_storage. If the operation was still in progress, this interrupts it. If + /// Stops an operation started with chainHead_v1_body, chainHead_v1_call, or + /// chainHead_v1_storage. If the operation was still in progress, this interrupts it. If /// the operation was already finished, this call has no effect. /// /// # Unstable /// /// This method is unstable and subject to change in the future. - #[method(name = "chainHead_unstable_stopOperation", raw_method)] + #[method(name = "chainHead_v1_stopOperation", raw_method)] async fn chain_head_unstable_stop_operation( &self, follow_subscription: String, diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs index 86d9a726d7bec371c157014adac0dfb1a926fb7e..6779180a414661eac923afca9a3917b340bc5487 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -75,7 +75,7 @@ pub struct ChainHeadConfig { /// Maximum pinned blocks across all connections. /// This number is large enough to consider immediate blocks. /// Note: This should never exceed the `PINNING_CACHE_SIZE` from client/db. -const MAX_PINNED_BLOCKS: usize = 512; +pub(crate) const MAX_PINNED_BLOCKS: usize = 512; /// Any block of any subscription should not be pinned more than /// this constant. When a subscription contains a block older than this, diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs index 0d87a45c07e295784bef4c946ef05d3641234ce4..a753896b24c238f949a8f4698ea7b4c63e39746f 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs @@ -19,7 +19,7 @@ //! Implementation of the `chainHead_follow` method. use crate::chain_head::{ - chain_head::LOG_TARGET, + chain_head::{LOG_TARGET, MAX_PINNED_BLOCKS}, event::{ BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock, RuntimeEvent, RuntimeVersionEvent, @@ -37,6 +37,7 @@ use sc_client_api::{ Backend, BlockBackend, BlockImportNotification, BlockchainEvents, FinalityNotification, }; use sc_rpc::utils::to_sub_message; +use schnellru::{ByLength, LruMap}; use sp_api::CallApiAt; use sp_blockchain::{ Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata, Info, @@ -68,7 +69,9 @@ pub struct ChainHeadFollower, Block: BlockT, Client> { /// Subscription ID. sub_id: String, /// The best reported block by this subscription. - best_block_cache: Option, + current_best_block: Option, + /// LRU cache of pruned blocks. + pruned_blocks: LruMap, /// Stop all subscriptions if the distance between the leaves and the current finalized /// block is larger than this value. max_lagging_distance: usize, @@ -90,7 +93,10 @@ impl, Block: BlockT, Client> ChainHeadFollower, - ) -> Result<(Vec>, HashSet), SubscriptionManagementError> - { + ) -> Result>, SubscriptionManagementError> { let init = self.get_init_blocks_with_forks(startup_point.finalized_hash)?; // The initialized event is the first one sent. let initial_blocks = init.finalized_block_descendants; let finalized_block_hashes = init.finalized_block_hashes; + // These are the pruned blocks that we should not report again. + for pruned in init.pruned_forks { + self.pruned_blocks.insert(pruned, ()); + } let finalized_block_hash = startup_point.finalized_hash; let finalized_block_runtime = self.generate_runtime_event(finalized_block_hash, None); @@ -345,11 +353,11 @@ where let best_block_hash = startup_point.best_hash; if best_block_hash != finalized_block_hash { let best_block = FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash }); - self.best_block_cache = Some(best_block_hash); + self.current_best_block = Some(best_block_hash); finalized_block_descendants.push(best_block); }; - Ok((finalized_block_descendants, init.pruned_forks)) + Ok(finalized_block_descendants) } /// Generate the "NewBlock" event and potentially the "BestBlockChanged" event for the @@ -377,19 +385,19 @@ where let best_block_event = FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash: block_hash }); - match self.best_block_cache { + match self.current_best_block { Some(block_cache) => { // The RPC layer has not reported this block as best before. // Note: This handles the race with the finalized branch. if block_cache != block_hash { - self.best_block_cache = Some(block_hash); + self.current_best_block = Some(block_hash); vec![new_block, best_block_event] } else { vec![new_block] } }, None => { - self.best_block_cache = Some(block_hash); + self.current_best_block = Some(block_hash); vec![new_block, best_block_event] }, } @@ -458,7 +466,7 @@ where // When the node falls out of sync and then syncs up to the tip of the chain, it can // happen that we skip notifications. Then it is better to terminate the connection // instead of trying to send notifications for all missed blocks. - if let Some(best_block_hash) = self.best_block_cache { + if let Some(best_block_hash) = self.current_best_block { let ancestor = sp_blockchain::lowest_common_ancestor( &*self.client, *hash, @@ -481,13 +489,10 @@ where } /// Get all pruned block hashes from the provided stale heads. - /// - /// The result does not include hashes from `to_ignore`. fn get_pruned_hashes( - &self, + &mut self, stale_heads: &[Block::Hash], last_finalized: Block::Hash, - to_ignore: &mut HashSet, ) -> Result, SubscriptionManagementError> { let blockchain = self.backend.blockchain(); let mut pruned = Vec::new(); @@ -497,11 +502,13 @@ where // Collect only blocks that are not part of the canonical chain. pruned.extend(tree_route.enacted().iter().filter_map(|block| { - if !to_ignore.remove(&block.hash) { - Some(block.hash) - } else { - None + if self.pruned_blocks.get(&block.hash).is_some() { + // The block was already reported as pruned. + return None } + + self.pruned_blocks.insert(block.hash, ()); + Some(block.hash) })) } @@ -515,7 +522,6 @@ where fn handle_finalized_blocks( &mut self, notification: FinalityNotification, - to_ignore: &mut HashSet, startup_point: &StartupPoint, ) -> Result>, SubscriptionManagementError> { let last_finalized = notification.hash; @@ -536,25 +542,32 @@ where // Report all pruned blocks from the notification that are not // part of the fork we need to ignore. let pruned_block_hashes = - self.get_pruned_hashes(¬ification.stale_heads, last_finalized, to_ignore)?; + self.get_pruned_hashes(¬ification.stale_heads, last_finalized)?; let finalized_event = FollowEvent::Finalized(Finalized { finalized_block_hashes, pruned_block_hashes: pruned_block_hashes.clone(), }); - match self.best_block_cache { - Some(block_cache) => { - // If the best block wasn't pruned, we are done here. - if !pruned_block_hashes.iter().any(|hash| *hash == block_cache) { - events.push(finalized_event); - return Ok(events) - } - - // The best block is reported as pruned. Therefore, we need to signal a new - // best block event before submitting the finalized event. + if let Some(current_best_block) = self.current_best_block { + // The best reported block is in the pruned list. Report a new best block. + let is_in_pruned_list = + pruned_block_hashes.iter().any(|hash| *hash == current_best_block); + // The block is not the last finalized block. + // + // It can be either: + // - a descendant of the last finalized block + // - a block on a fork that will be pruned in the future. + // + // In those cases, we emit a new best block. + let is_not_last_finalized = current_best_block != last_finalized; + + if is_in_pruned_list || is_not_last_finalized { + // We need to generate a best block event. let best_block_hash = self.client.info().best_hash; - if best_block_hash == block_cache { + + // Defensive check against state missmatch. + if best_block_hash == current_best_block { // The client doest not have any new information about the best block. // The information from `.info()` is updated from the DB as the last // step of the finalization and it should be up to date. @@ -564,23 +577,18 @@ where "[follow][id={:?}] Client does not contain different best block", self.sub_id, ); - events.push(finalized_event); - Ok(events) } else { // The RPC needs to also submit a new best block changed before the // finalized event. - self.best_block_cache = Some(best_block_hash); - let best_block_event = - FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash }); - events.extend([best_block_event, finalized_event]); - Ok(events) + self.current_best_block = Some(best_block_hash); + events + .push(FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash })); } - }, - None => { - events.push(finalized_event); - Ok(events) - }, + } } + + events.push(finalized_event); + Ok(events) } /// Submit the events from the provided stream to the RPC client @@ -589,7 +597,6 @@ where &mut self, startup_point: &StartupPoint, mut stream: EventStream, - mut to_ignore: HashSet, sink: SubscriptionSink, rx_stop: oneshot::Receiver<()>, ) -> Result<(), SubscriptionManagementError> @@ -612,7 +619,7 @@ where NotificationType::NewBlock(notification) => self.handle_import_blocks(notification, &startup_point), NotificationType::Finalized(notification) => - self.handle_finalized_blocks(notification, &mut to_ignore, &startup_point), + self.handle_finalized_blocks(notification, &startup_point), NotificationType::MethodResponse(notification) => Ok(vec![notification]), }; @@ -682,7 +689,7 @@ where .map(|response| NotificationType::MethodResponse(response)); let startup_point = StartupPoint::from(self.client.info()); - let (initial_events, pruned_forks) = match self.generate_init_events(&startup_point) { + let initial_events = match self.generate_init_events(&startup_point) { Ok(blocks) => blocks, Err(err) => { debug!( @@ -702,7 +709,6 @@ where let merged = tokio_stream::StreamExt::merge(merged, stream_responses); let stream = stream::once(futures::future::ready(initial)).chain(merged); - self.submit_events(&startup_point, stream.boxed(), pruned_forks, sink, sub_data.rx_stop) - .await + self.submit_events(&startup_point, stream.boxed(), sink, sub_data.rx_stop).await } } diff --git a/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs b/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs index 0e5ccb91d39a61a76a850119590699d563d88df8..3495d9e54490c0214c75359843ce0bbf42aa5465 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs @@ -186,7 +186,7 @@ impl OperationState { /// Stops the operation if `waitingForContinue` event was emitted for the associated /// operation ID. /// - /// Returns nothing in accordance with `chainHead_unstable_stopOperation`. + /// Returns nothing in accordance with `chainHead_v1_stopOperation`. pub fn stop_operation(&self) { // `waitingForContinue` not generated. if !self.shared_state.requested_continue.load(std::sync::atomic::Ordering::Acquire) { diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs index c2bff7c50d5ebd951596f15e9e7423a77b889c0b..4bab2194e082acf65e47bd0a3917ee23c2a81884 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -156,7 +156,7 @@ async fn setup_api() -> ( ) .into_rpc(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); let sub_id = sub.subscription_id(); let sub_id = serde_json::to_string(&sub_id).unwrap(); @@ -187,6 +187,62 @@ async fn setup_api() -> ( (client, api, sub, sub_id, block) } +async fn import_block( + mut client: Arc>, + parent_hash: ::Hash, + parent_number: u64, +) -> Block { + let block = BlockBuilderBuilder::new(&*client) + .on_parent_block(parent_hash) + .with_parent_block_number(parent_number) + .build() + .unwrap() + .build() + .unwrap() + .block; + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + block +} + +async fn import_best_block_with_tx( + mut client: Arc>, + parent_hash: ::Hash, + parent_number: u64, + tx: Transfer, +) -> Block { + let mut block_builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(parent_hash) + .with_parent_block_number(parent_number) + .build() + .unwrap(); + block_builder.push_transfer(tx).unwrap(); + let block = block_builder.build().unwrap().block; + client.import_as_best(BlockOrigin::Own, block.clone()).await.unwrap(); + block +} + +/// Check the subscription produces a new block and a best block event. +/// +/// The macro is used instead of a fn to preserve the lines of code in case of panics. +macro_rules! check_new_and_best_block_events { + ($sub:expr, $block_hash:expr, $parent_hash:expr) => { + let event: FollowEvent = get_next_event($sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", $block_hash), + parent_block_hash: format!("{:?}", $parent_hash), + new_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + let event: FollowEvent = get_next_event($sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", $block_hash), + }); + assert_eq!(event, expected); + }; +} + #[tokio::test] async fn follow_subscription_produces_blocks() { let builder = TestClientBuilder::new(); @@ -210,7 +266,7 @@ async fn follow_subscription_produces_blocks() { .into_rpc(); let finalized_hash = client.info().finalized_hash; - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); // Initialized must always be reported first. let event: FollowEvent = get_next_event(&mut sub).await; @@ -281,7 +337,7 @@ async fn follow_with_runtime() { .into_rpc(); let finalized_hash = client.info().finalized_hash; - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); // Initialized must always be reported first. let event: FollowEvent = get_next_event(&mut sub).await; @@ -392,14 +448,14 @@ async fn get_header() { // Invalid subscription ID must produce no results. let res: Option = api - .call("chainHead_unstable_header", ["invalid_sub_id", &invalid_hash]) + .call("chainHead_v1_header", ["invalid_sub_id", &invalid_hash]) .await .unwrap(); assert!(res.is_none()); // Valid subscription with invalid block hash will error. let err = api - .call::<_, serde_json::Value>("chainHead_unstable_header", [&sub_id, &invalid_hash]) + .call::<_, serde_json::Value>("chainHead_v1_header", [&sub_id, &invalid_hash]) .await .unwrap_err(); assert_matches!(err, @@ -407,7 +463,7 @@ async fn get_header() { ); // Obtain the valid header. - let res: String = api.call("chainHead_unstable_header", [&sub_id, &block_hash]).await.unwrap(); + let res: String = api.call("chainHead_v1_header", [&sub_id, &block_hash]).await.unwrap(); let bytes = array_bytes::hex2bytes(&res).unwrap(); let header: Header = Decode::decode(&mut &bytes[..]).unwrap(); assert_eq!(header, block.header); @@ -420,15 +476,13 @@ async fn get_body() { let invalid_hash = hex_string(&INVALID_HASH); // Subscription ID is invalid. - let response: MethodResponse = api - .call("chainHead_unstable_body", ["invalid_sub_id", &invalid_hash]) - .await - .unwrap(); + let response: MethodResponse = + api.call("chainHead_v1_body", ["invalid_sub_id", &invalid_hash]).await.unwrap(); assert_matches!(response, MethodResponse::LimitReached); // Block hash is invalid. let err = api - .call::<_, serde_json::Value>("chainHead_unstable_body", [&sub_id, &invalid_hash]) + .call::<_, serde_json::Value>("chainHead_v1_body", [&sub_id, &invalid_hash]) .await .unwrap_err(); assert_matches!(err, @@ -437,7 +491,7 @@ async fn get_body() { // Valid call. let response: MethodResponse = - api.call("chainHead_unstable_body", [&sub_id, &block_hash]).await.unwrap(); + api.call("chainHead_v1_body", [&sub_id, &block_hash]).await.unwrap(); let operation_id = match response { MethodResponse::Started(started) => started.operation_id, MethodResponse::LimitReached => panic!("Expected started response"), @@ -478,7 +532,7 @@ async fn get_body() { // Valid call to a block with extrinsics. let response: MethodResponse = - api.call("chainHead_unstable_body", [&sub_id, &block_hash]).await.unwrap(); + api.call("chainHead_v1_body", [&sub_id, &block_hash]).await.unwrap(); let operation_id = match response { MethodResponse::Started(started) => started.operation_id, MethodResponse::LimitReached => panic!("Expected started response"), @@ -500,10 +554,7 @@ async fn call_runtime() { // Subscription ID is invalid. let response: MethodResponse = api - .call( - "chainHead_unstable_call", - ["invalid_sub_id", &block_hash, "BabeApi_current_epoch", "0x00"], - ) + .call("chainHead_v1_call", ["invalid_sub_id", &block_hash, "BabeApi_current_epoch", "0x00"]) .await .unwrap(); assert_matches!(response, MethodResponse::LimitReached); @@ -511,7 +562,7 @@ async fn call_runtime() { // Block hash is invalid. let err = api .call::<_, serde_json::Value>( - "chainHead_unstable_call", + "chainHead_v1_call", [&sub_id, &invalid_hash, "BabeApi_current_epoch", "0x00"], ) .await @@ -523,7 +574,7 @@ async fn call_runtime() { // Pass an invalid parameters that cannot be decode. let err = api .call::<_, serde_json::Value>( - "chainHead_unstable_call", + "chainHead_v1_call", // 0x0 is invalid. [&sub_id, &block_hash, "BabeApi_current_epoch", "0x0"], ) @@ -539,7 +590,7 @@ async fn call_runtime() { let call_parameters = hex_string(&alice_id.encode()); let response: MethodResponse = api .call( - "chainHead_unstable_call", + "chainHead_v1_call", [&sub_id, &block_hash, "AccountNonceApi_account_nonce", &call_parameters], ) .await @@ -558,7 +609,7 @@ async fn call_runtime() { // The `current_epoch` takes no parameters and not draining the input buffer // will cause the execution to fail. let response: MethodResponse = api - .call("chainHead_unstable_call", [&sub_id, &block_hash, "BabeApi_current_epoch", "0x00"]) + .call("chainHead_v1_call", [&sub_id, &block_hash, "BabeApi_current_epoch", "0x00"]) .await .unwrap(); let operation_id = match response { @@ -595,7 +646,7 @@ async fn call_runtime_without_flag() { ) .into_rpc(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); let sub_id = sub.subscription_id(); let sub_id = serde_json::to_string(&sub_id).unwrap(); @@ -629,7 +680,7 @@ async fn call_runtime_without_flag() { let call_parameters = hex_string(&alice_id.encode()); let err = api .call::<_, serde_json::Value>( - "chainHead_unstable_call", + "chainHead_v1_call", [&sub_id, &block_hash, "AccountNonceApi_account_nonce", &call_parameters], ) .await @@ -650,7 +701,7 @@ async fn get_storage_hash() { // Subscription ID is invalid. let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ "invalid_sub_id", &invalid_hash, @@ -664,7 +715,7 @@ async fn get_storage_hash() { // Block hash is invalid. let err = api .call::<_, serde_json::Value>( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &invalid_hash, @@ -680,7 +731,7 @@ async fn get_storage_hash() { // Valid call without storage at the key. let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -723,7 +774,7 @@ async fn get_storage_hash() { // Valid call with storage at the key. let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -756,7 +807,7 @@ async fn get_storage_hash() { // Valid call with storage at the key. let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &genesis_hash, @@ -813,7 +864,7 @@ async fn get_storage_multi_query_iter() { // Valid call with storage at the key. let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -864,7 +915,7 @@ async fn get_storage_multi_query_iter() { let expected_value = hex_string(&CHILD_VALUE); let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &genesis_hash, @@ -918,7 +969,7 @@ async fn get_storage_value() { // Subscription ID is invalid. let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ "invalid_sub_id", &invalid_hash, @@ -932,7 +983,7 @@ async fn get_storage_value() { // Block hash is invalid. let err = api .call::<_, serde_json::Value>( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &invalid_hash, @@ -948,7 +999,7 @@ async fn get_storage_value() { // Valid call without storage at the key. let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -991,7 +1042,7 @@ async fn get_storage_value() { // Valid call with storage at the key. let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -1023,7 +1074,7 @@ async fn get_storage_value() { let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &genesis_hash, @@ -1065,7 +1116,7 @@ async fn get_storage_non_queryable_key() { let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -1090,7 +1141,7 @@ async fn get_storage_non_queryable_key() { let prefixed_key = hex_string(&prefixed_key); let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -1115,7 +1166,7 @@ async fn get_storage_non_queryable_key() { let prefixed_key = hex_string(&prefixed_key); let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -1141,7 +1192,7 @@ async fn get_storage_non_queryable_key() { let prefixed_key = hex_string(&prefixed_key); let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -1171,9 +1222,9 @@ async fn unique_operation_ids() { // Ensure that operation IDs are unique for multiple method calls. for _ in 0..5 { - // Valid `chainHead_unstable_body` call. + // Valid `chainHead_v1_body` call. let response: MethodResponse = - api.call("chainHead_unstable_body", [&sub_id, &block_hash]).await.unwrap(); + api.call("chainHead_v1_body", [&sub_id, &block_hash]).await.unwrap(); let operation_id = match response { MethodResponse::Started(started) => started.operation_id, MethodResponse::LimitReached => panic!("Expected started response"), @@ -1185,11 +1236,11 @@ async fn unique_operation_ids() { // Ensure uniqueness. assert!(op_ids.insert(operation_id)); - // Valid `chainHead_unstable_storage` call. + // Valid `chainHead_v1_storage` call. let key = hex_string(&KEY); let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -1210,12 +1261,12 @@ async fn unique_operation_ids() { // Ensure uniqueness. assert!(op_ids.insert(operation_id)); - // Valid `chainHead_unstable_call` call. + // Valid `chainHead_v1_call` call. let alice_id = AccountKeyring::Alice.to_account_id(); let call_parameters = hex_string(&alice_id.encode()); let response: MethodResponse = api .call( - "chainHead_unstable_call", + "chainHead_v1_call", [&sub_id, &block_hash, "AccountNonceApi_account_nonce", &call_parameters], ) .await @@ -1257,12 +1308,11 @@ async fn separate_operation_ids_for_subscriptions() { .into_rpc(); // Create two separate subscriptions. - let mut sub_first = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut sub_first = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); let sub_id_first = sub_first.subscription_id(); let sub_id_first = serde_json::to_string(&sub_id_first).unwrap(); - let mut sub_second = - api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut sub_second = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); let sub_id_second = sub_second.subscription_id(); let sub_id_second = serde_json::to_string(&sub_id_second).unwrap(); @@ -1306,17 +1356,15 @@ async fn separate_operation_ids_for_subscriptions() { // Each `chainHead_follow` subscription receives a separate operation ID. let response: MethodResponse = - api.call("chainHead_unstable_body", [&sub_id_first, &block_hash]).await.unwrap(); + api.call("chainHead_v1_body", [&sub_id_first, &block_hash]).await.unwrap(); let operation_id: String = match response { MethodResponse::Started(started) => started.operation_id, MethodResponse::LimitReached => panic!("Expected started response"), }; assert_eq!(operation_id, "0"); - let response: MethodResponse = api - .call("chainHead_unstable_body", [&sub_id_second, &block_hash]) - .await - .unwrap(); + let response: MethodResponse = + api.call("chainHead_v1_body", [&sub_id_second, &block_hash]).await.unwrap(); let operation_id_second: String = match response { MethodResponse::Started(started) => started.operation_id, MethodResponse::LimitReached => panic!("Expected started response"), @@ -1393,7 +1441,7 @@ async fn follow_generates_initial_blocks() { let block_2_f_hash = block_2_f.header.hash(); client.import(BlockOrigin::Own, block_2_f.clone()).await.unwrap(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); // Initialized must always be reported first. let event: FollowEvent = get_next_event(&mut sub).await; @@ -1505,7 +1553,7 @@ async fn follow_exceeding_pinned_blocks() { ) .into_rpc(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); let block = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().genesis_hash) @@ -1584,7 +1632,7 @@ async fn follow_with_unpin() { ) .into_rpc(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); let sub_id = sub.subscription_id(); let sub_id = serde_json::to_string(&sub_id).unwrap(); @@ -1616,17 +1664,14 @@ async fn follow_with_unpin() { // Unpin an invalid subscription ID must return Ok(()). let invalid_hash = hex_string(&INVALID_HASH); let _res: () = api - .call("chainHead_unstable_unpin", rpc_params!["invalid_sub_id", &invalid_hash]) + .call("chainHead_v1_unpin", rpc_params!["invalid_sub_id", &invalid_hash]) .await .unwrap(); // Valid subscription with invalid block hash. let invalid_hash = hex_string(&INVALID_HASH); let err = api - .call::<_, serde_json::Value>( - "chainHead_unstable_unpin", - rpc_params![&sub_id, &invalid_hash], - ) + .call::<_, serde_json::Value>("chainHead_v1_unpin", rpc_params![&sub_id, &invalid_hash]) .await .unwrap_err(); assert_matches!(err, @@ -1634,10 +1679,7 @@ async fn follow_with_unpin() { ); // To not exceed the number of pinned blocks, we need to unpin before the next import. - let _res: () = api - .call("chainHead_unstable_unpin", rpc_params![&sub_id, &block_hash]) - .await - .unwrap(); + let _res: () = api.call("chainHead_v1_unpin", rpc_params![&sub_id, &block_hash]).await.unwrap(); // Block tree: // finalized_block -> block -> block2 @@ -1698,7 +1740,7 @@ async fn unpin_duplicate_hashes() { ) .into_rpc(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); let sub_id = sub.subscription_id(); let sub_id = serde_json::to_string(&sub_id).unwrap(); @@ -1730,7 +1772,7 @@ async fn unpin_duplicate_hashes() { // Try to unpin duplicate hashes. let err = api .call::<_, serde_json::Value>( - "chainHead_unstable_unpin", + "chainHead_v1_unpin", rpc_params![&sub_id, vec![&block_hash, &block_hash]], ) .await @@ -1765,7 +1807,7 @@ async fn unpin_duplicate_hashes() { // Try to unpin duplicate hashes. let err = api .call::<_, serde_json::Value>( - "chainHead_unstable_unpin", + "chainHead_v1_unpin", rpc_params![&sub_id, vec![&block_hash, &block_hash_2, &block_hash]], ) .await @@ -1776,7 +1818,7 @@ async fn unpin_duplicate_hashes() { // Can unpin blocks. let _res: () = api - .call("chainHead_unstable_unpin", rpc_params![&sub_id, vec![&block_hash, &block_hash_2]]) + .call("chainHead_v1_unpin", rpc_params![&sub_id, vec![&block_hash, &block_hash_2]]) .await .unwrap(); } @@ -1803,7 +1845,7 @@ async fn follow_with_multiple_unpin_hashes() { ) .into_rpc(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); let sub_id = sub.subscription_id(); let sub_id = serde_json::to_string(&sub_id).unwrap(); @@ -1874,16 +1916,13 @@ async fn follow_with_multiple_unpin_hashes() { // Unpin an invalid subscription ID must return Ok(()). let invalid_hash = hex_string(&INVALID_HASH); let _res: () = api - .call("chainHead_unstable_unpin", rpc_params!["invalid_sub_id", &invalid_hash]) + .call("chainHead_v1_unpin", rpc_params!["invalid_sub_id", &invalid_hash]) .await .unwrap(); // Valid subscription with invalid block hash. let err = api - .call::<_, serde_json::Value>( - "chainHead_unstable_unpin", - rpc_params![&sub_id, &invalid_hash], - ) + .call::<_, serde_json::Value>("chainHead_v1_unpin", rpc_params![&sub_id, &invalid_hash]) .await .unwrap_err(); assert_matches!(err, @@ -1891,14 +1930,14 @@ async fn follow_with_multiple_unpin_hashes() { ); let _res: () = api - .call("chainHead_unstable_unpin", rpc_params![&sub_id, &block_1_hash]) + .call("chainHead_v1_unpin", rpc_params![&sub_id, &block_1_hash]) .await .unwrap(); // One block hash is invalid. Block 1 is already unpinned. let err = api .call::<_, serde_json::Value>( - "chainHead_unstable_unpin", + "chainHead_v1_unpin", rpc_params![&sub_id, vec![&block_1_hash, &block_2_hash, &block_3_hash]], ) .await @@ -1909,16 +1948,13 @@ async fn follow_with_multiple_unpin_hashes() { // Unpin multiple blocks. let _res: () = api - .call("chainHead_unstable_unpin", rpc_params![&sub_id, vec![&block_2_hash, &block_3_hash]]) + .call("chainHead_v1_unpin", rpc_params![&sub_id, vec![&block_2_hash, &block_3_hash]]) .await .unwrap(); // Check block 2 and 3 are unpinned. let err = api - .call::<_, serde_json::Value>( - "chainHead_unstable_unpin", - rpc_params![&sub_id, &block_2_hash], - ) + .call::<_, serde_json::Value>("chainHead_v1_unpin", rpc_params![&sub_id, &block_2_hash]) .await .unwrap_err(); assert_matches!(err, @@ -1926,10 +1962,7 @@ async fn follow_with_multiple_unpin_hashes() { ); let err = api - .call::<_, serde_json::Value>( - "chainHead_unstable_unpin", - rpc_params![&sub_id, &block_3_hash], - ) + .call::<_, serde_json::Value>("chainHead_v1_unpin", rpc_params![&sub_id, &block_3_hash]) .await .unwrap_err(); assert_matches!(err, @@ -1960,7 +1993,7 @@ async fn follow_prune_best_block() { .into_rpc(); let finalized_hash = client.info().finalized_hash; - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); // Initialized must always be reported first. let event: FollowEvent = get_next_event(&mut sub).await; @@ -2122,7 +2155,7 @@ async fn follow_prune_best_block() { let sub_id = sub.subscription_id(); let sub_id = serde_json::to_string(&sub_id).unwrap(); let hash = format!("{:?}", block_2_hash); - let _res: () = api.call("chainHead_unstable_unpin", rpc_params![&sub_id, &hash]).await.unwrap(); + let _res: () = api.call("chainHead_v1_unpin", rpc_params![&sub_id, &hash]).await.unwrap(); } #[tokio::test] @@ -2226,7 +2259,7 @@ async fn follow_forks_pruned_block() { // Block 2_f and 3_f are not pruned, pruning happens at height (N - 1). client.finalize_block(block_3_hash, None).unwrap(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); // Initialized must always be reported first. let event: FollowEvent = get_next_event(&mut sub).await; @@ -2388,7 +2421,7 @@ async fn follow_report_multiple_pruned_block() { let block_3_f = block_builder.build().unwrap().block; let block_3_f_hash = block_3_f.hash(); client.import(BlockOrigin::Own, block_3_f.clone()).await.unwrap(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); // Initialized must always be reported first. let event: FollowEvent = get_next_event(&mut sub).await; @@ -2574,7 +2607,7 @@ async fn pin_block_references() { } } - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); let sub_id = sub.subscription_id(); let sub_id = serde_json::to_string(&sub_id).unwrap(); @@ -2613,10 +2646,7 @@ async fn pin_block_references() { wait_pinned_references(&backend, &hash, 1).await; // To not exceed the number of pinned blocks, we need to unpin before the next import. - let _res: () = api - .call("chainHead_unstable_unpin", rpc_params![&sub_id, &block_hash]) - .await - .unwrap(); + let _res: () = api.call("chainHead_v1_unpin", rpc_params![&sub_id, &block_hash]).await.unwrap(); // Make sure unpin clears out the reference. let refs = backend.pin_refs(&hash).unwrap(); @@ -2709,7 +2739,7 @@ async fn follow_finalized_before_new_block() { let block_1_hash = block_1.header.hash(); client.import(BlockOrigin::Own, block_1.clone()).await.unwrap(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); // Trigger the `FinalizedNotification` for block 1 before the `BlockImportNotification`, and // expect for the `chainHead` to generate `NewBlock`, `BestBlock` and `Finalized` events. @@ -2814,7 +2844,7 @@ async fn ensure_operation_limits_works() { ) .into_rpc(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); let sub_id = sub.subscription_id(); let sub_id = serde_json::to_string(&sub_id).unwrap(); @@ -2853,7 +2883,7 @@ async fn ensure_operation_limits_works() { ]; let response: MethodResponse = api - .call("chainHead_unstable_storage", rpc_params![&sub_id, &block_hash, items]) + .call("chainHead_v1_storage", rpc_params![&sub_id, &block_hash, items]) .await .unwrap(); let operation_id = match response { @@ -2876,7 +2906,7 @@ async fn ensure_operation_limits_works() { let call_parameters = hex_string(&alice_id.encode()); let response: MethodResponse = api .call( - "chainHead_unstable_call", + "chainHead_v1_call", [&sub_id, &block_hash, "AccountNonceApi_account_nonce", &call_parameters], ) .await @@ -2921,7 +2951,7 @@ async fn check_continue_operation() { ) .into_rpc(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); let sub_id = sub.subscription_id(); let sub_id = serde_json::to_string(&sub_id).unwrap(); @@ -2958,17 +2988,17 @@ async fn check_continue_operation() { // Invalid subscription ID must produce no results. let _res: () = api - .call("chainHead_unstable_continue", ["invalid_sub_id", &invalid_hash]) + .call("chainHead_v1_continue", ["invalid_sub_id", &invalid_hash]) .await .unwrap(); // Invalid operation ID must produce no results. - let _res: () = api.call("chainHead_unstable_continue", [&sub_id, &invalid_hash]).await.unwrap(); + let _res: () = api.call("chainHead_v1_continue", [&sub_id, &invalid_hash]).await.unwrap(); // Valid call with storage at the key. let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -3004,7 +3034,7 @@ async fn check_continue_operation() { std::time::Duration::from_secs(DOES_NOT_PRODUCE_EVENTS_SECONDS), ) .await; - let _res: () = api.call("chainHead_unstable_continue", [&sub_id, &operation_id]).await.unwrap(); + let _res: () = api.call("chainHead_v1_continue", [&sub_id, &operation_id]).await.unwrap(); assert_matches!( get_next_event::>(&mut sub).await, FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id && @@ -3023,7 +3053,7 @@ async fn check_continue_operation() { std::time::Duration::from_secs(DOES_NOT_PRODUCE_EVENTS_SECONDS), ) .await; - let _res: () = api.call("chainHead_unstable_continue", [&sub_id, &operation_id]).await.unwrap(); + let _res: () = api.call("chainHead_v1_continue", [&sub_id, &operation_id]).await.unwrap(); assert_matches!( get_next_event::>(&mut sub).await, FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id && @@ -3043,7 +3073,7 @@ async fn check_continue_operation() { std::time::Duration::from_secs(DOES_NOT_PRODUCE_EVENTS_SECONDS), ) .await; - let _res: () = api.call("chainHead_unstable_continue", [&sub_id, &operation_id]).await.unwrap(); + let _res: () = api.call("chainHead_v1_continue", [&sub_id, &operation_id]).await.unwrap(); assert_matches!( get_next_event::>(&mut sub).await, FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id && @@ -3062,7 +3092,7 @@ async fn check_continue_operation() { std::time::Duration::from_secs(DOES_NOT_PRODUCE_EVENTS_SECONDS), ) .await; - let _res: () = api.call("chainHead_unstable_continue", [&sub_id, &operation_id]).await.unwrap(); + let _res: () = api.call("chainHead_v1_continue", [&sub_id, &operation_id]).await.unwrap(); assert_matches!( get_next_event::>(&mut sub).await, FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id && @@ -3106,7 +3136,7 @@ async fn stop_storage_operation() { ) .into_rpc(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); let sub_id = sub.subscription_id(); let sub_id = serde_json::to_string(&sub_id).unwrap(); @@ -3140,20 +3170,17 @@ async fn stop_storage_operation() { // Invalid subscription ID must produce no results. let _res: () = api - .call("chainHead_unstable_stopOperation", ["invalid_sub_id", &invalid_hash]) + .call("chainHead_v1_stopOperation", ["invalid_sub_id", &invalid_hash]) .await .unwrap(); // Invalid operation ID must produce no results. - let _res: () = api - .call("chainHead_unstable_stopOperation", [&sub_id, &invalid_hash]) - .await - .unwrap(); + let _res: () = api.call("chainHead_v1_stopOperation", [&sub_id, &invalid_hash]).await.unwrap(); // Valid call with storage at the key. let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -3185,10 +3212,7 @@ async fn stop_storage_operation() { ); // Stop the operation. - let _res: () = api - .call("chainHead_unstable_stopOperation", [&sub_id, &operation_id]) - .await - .unwrap(); + let _res: () = api.call("chainHead_v1_stopOperation", [&sub_id, &operation_id]).await.unwrap(); does_not_produce_event::>( &mut sub, @@ -3216,7 +3240,7 @@ async fn storage_closest_merkle_value() { // Valid call with storage at the keys. let response: MethodResponse = api .call( - "chainHead_unstable_storage", + "chainHead_v1_storage", rpc_params![ &sub_id, &block_hash, @@ -3410,7 +3434,7 @@ async fn chain_head_stop_all_subscriptions() { ) .into_rpc(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); // Ensure the imported block is propagated and pinned for this subscription. assert_matches!( @@ -3444,8 +3468,7 @@ async fn chain_head_stop_all_subscriptions() { ); } - let mut second_sub = - api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut second_sub = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); // Lagging detected, the stop event is delivered immediately. assert_matches!( get_next_event::>(&mut second_sub).await, @@ -3456,14 +3479,14 @@ async fn chain_head_stop_all_subscriptions() { assert_matches!(get_next_event::>(&mut sub).await, FollowEvent::Stop); // Other subscriptions cannot be started until the suspension period is over. - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); // Should receive the stop event immediately. assert_matches!(get_next_event::>(&mut sub).await, FollowEvent::Stop); // For the next subscription, lagging distance must be smaller. client.finalize_block(parent_hash, None).unwrap(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); assert_matches!( get_next_event::>(&mut sub).await, FollowEvent::Initialized(_) @@ -3625,12 +3648,12 @@ async fn chain_head_limit_reached() { ) .into_rpc(); - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); // Initialized must always be reported first. let _event: FollowEvent = get_next_event(&mut sub).await; - let error = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap_err(); + let error = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap_err(); assert!(error .to_string() .contains("Maximum number of chainHead_follow has been reached")); @@ -3640,7 +3663,164 @@ async fn chain_head_limit_reached() { // Ensure the `chainHead_unfollow` is propagated to the server. tokio::time::sleep(std::time::Duration::from_secs(5)).await; - let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap(); + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [true]).await.unwrap(); // Initialized must always be reported first. let _event: FollowEvent = get_next_event(&mut sub).await; } + +#[tokio::test] +async fn follow_unique_pruned_blocks() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + ChainHeadConfig { + global_max_pinned_blocks: MAX_PINNED_BLOCKS, + subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), + subscription_max_ongoing_operations: MAX_OPERATIONS, + operation_max_storage_items: MAX_PAGINATION_LIMIT, + max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, + max_lagging_distance: MAX_LAGGING_DISTANCE, + }, + ) + .into_rpc(); + + let finalized_hash = client.info().finalized_hash; + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); + + // Initialized must always be reported first. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Initialized(Initialized { + finalized_block_hashes: vec![format!("{:?}", finalized_hash)], + finalized_block_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + // Block tree: + // + // finalized -> block 1 -> block 2 -> block 3 + // + // -> block 2 -> block 4 -> block 5 + // + // -> block 1 -> block 2_f -> block 6 + // ^^^ finalized + // -> block 7 + // ^^^ finalized + // -> block 8 + // ^^^ finalized + // The chainHead will see block 5 as the best block. However, the + // client will finalize the block 6, which is on another fork. + // + // When the block 6 is finalized, block 2 block 3 block 4 and block 5 are placed on an invalid + // fork. However, pruning of blocks happens on level N - 1. + // Therefore, no pruned blocks are reported yet. + // + // When the block 7 is finalized, block 3 is detected as stale. At this step, block 2 and 3 + // are reported as pruned. + // + // When the block 8 is finalized, block 5 block 4 and block 2 are detected as stale. However, + // only blocks 5 and 4 are reported as pruned. This is because the block 2 was previously + // reported. + + // Initial setup steps: + let block_1_hash = + import_block(client.clone(), client.chain_info().genesis_hash, 0).await.hash(); + let block_2_f_hash = import_block(client.clone(), block_1_hash, 1).await.hash(); + let block_6_hash = import_block(client.clone(), block_2_f_hash, 2).await.hash(); + // Import block 2 as best on the fork. + let mut tx_alice_ferdie = Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 41, + nonce: 0, + }; + let block_2_hash = + import_best_block_with_tx(client.clone(), block_1_hash, 1, tx_alice_ferdie.clone()) + .await + .hash(); + + let block_3_hash = import_block(client.clone(), block_2_hash, 2).await.hash(); + // Fork block 4. + tx_alice_ferdie.nonce = 1; + let block_4_hash = import_best_block_with_tx(client.clone(), block_2_hash, 2, tx_alice_ferdie) + .await + .hash(); + let block_5_hash = import_block(client.clone(), block_4_hash, 3).await.hash(); + + // Check expected events generated by the setup. + { + // Check block 1 -> block 2f -> block 6. + check_new_and_best_block_events!(&mut sub, block_1_hash, finalized_hash); + check_new_and_best_block_events!(&mut sub, block_2_f_hash, block_1_hash); + check_new_and_best_block_events!(&mut sub, block_6_hash, block_2_f_hash); + + // Check (block 1 ->) block 2 -> block 3. + check_new_and_best_block_events!(&mut sub, block_2_hash, block_1_hash); + check_new_and_best_block_events!(&mut sub, block_3_hash, block_2_hash); + + // Check (block 1 -> block 2 ->) block 4 -> block 5. + check_new_and_best_block_events!(&mut sub, block_4_hash, block_2_hash); + check_new_and_best_block_events!(&mut sub, block_5_hash, block_4_hash); + } + + // Finalize the block 6 from the fork. + client.finalize_block(block_6_hash, None).unwrap(); + + // Expect to report the best block changed before the finalized event. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_6_hash), + }); + assert_eq!(event, expected); + + // Block 2 must be reported as pruned, even if it was the previous best. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Finalized(Finalized { + finalized_block_hashes: vec![ + format!("{:?}", block_1_hash), + format!("{:?}", block_2_f_hash), + format!("{:?}", block_6_hash), + ], + pruned_block_hashes: vec![], + }); + assert_eq!(event, expected); + + // Pruned hash can be unpinned. + let sub_id = sub.subscription_id(); + let sub_id = serde_json::to_string(&sub_id).unwrap(); + let hash = format!("{:?}", block_2_hash); + let _res: () = api.call("chainHead_v1_unpin", rpc_params![&sub_id, &hash]).await.unwrap(); + + // Import block 7 and check it. + let block_7_hash = import_block(client.clone(), block_6_hash, 3).await.hash(); + check_new_and_best_block_events!(&mut sub, block_7_hash, block_6_hash); + + // Finalize the block 7. + client.finalize_block(block_7_hash, None).unwrap(); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Finalized(Finalized { + finalized_block_hashes: vec![format!("{:?}", block_7_hash)], + pruned_block_hashes: vec![format!("{:?}", block_2_hash), format!("{:?}", block_3_hash)], + }); + assert_eq!(event, expected); + + // Check block 8. + let block_8_hash = import_block(client.clone(), block_7_hash, 4).await.hash(); + check_new_and_best_block_events!(&mut sub, block_8_hash, block_7_hash); + + // Finalize the block 8. + client.finalize_block(block_8_hash, None).unwrap(); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Finalized(Finalized { + finalized_block_hashes: vec![format!("{:?}", block_8_hash)], + pruned_block_hashes: vec![format!("{:?}", block_4_hash), format!("{:?}", block_5_hash)], + }); + assert_eq!(event, expected); +} diff --git a/substrate/client/rpc-spec-v2/src/transaction/api.rs b/substrate/client/rpc-spec-v2/src/transaction/api.rs index 33af9c9533388a1e4d5832a390c8eb96da756905..ed358922d53ed0d087a2693b55a9b4f51635c1c0 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/api.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/api.rs @@ -33,8 +33,8 @@ pub trait TransactionApi { /// /// This method is unstable and subject to change in the future. #[subscription( - name = "transactionWatch_unstable_submitAndWatch" => "transactionWatch_unstable_watchEvent", - unsubscribe = "transactionWatch_unstable_unwatch", + name = "transactionWatch_v1_submitAndWatch" => "transactionWatch_v1_watchEvent", + unsubscribe = "transactionWatch_v1_unwatch", item = TransactionEvent, )] fn submit_and_watch(&self, bytes: Bytes); @@ -47,14 +47,15 @@ pub trait TransactionBroadcastApi { /// # Unstable /// /// This method is unstable and subject to change in the future. - #[method(name = "transaction_unstable_broadcast")] - fn broadcast(&self, bytes: Bytes) -> RpcResult>; + + #[method(name = "transaction_v1_broadcast", raw_method)] + async fn broadcast(&self, bytes: Bytes) -> RpcResult>; /// Broadcast an extrinsic to the chain. /// /// # Unstable /// /// This method is unstable and subject to change in the future. - #[method(name = "transaction_unstable_stop")] - fn stop_broadcast(&self, operation_id: String) -> Result<(), ErrorBroadcast>; + #[method(name = "transaction_v1_stop", raw_method)] + async fn stop_broadcast(&self, operation_id: String) -> Result<(), ErrorBroadcast>; } diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests/setup.rs b/substrate/client/rpc-spec-v2/src/transaction/tests/setup.rs index 4a15657a7f69e290e0d1cccdf7fc3082671a9ede..570174a3db6434fec278225fbb6b390cd3a5f07a 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/tests/setup.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/tests/setup.rs @@ -67,6 +67,7 @@ fn maintained_pool( pub fn setup_api( options: Options, + max_tx_per_connection: usize, ) -> ( Arc, Arc, @@ -85,9 +86,13 @@ pub fn setup_api( let (task_executor, executor_recv) = TaskExecutorBroadcast::new(); - let tx_api = - RpcTransactionBroadcast::new(client_mock.clone(), pool.clone(), Arc::new(task_executor)) - .into_rpc(); + let tx_api = RpcTransactionBroadcast::new( + client_mock.clone(), + pool.clone(), + Arc::new(task_executor), + max_tx_per_connection, + ) + .into_rpc(); (api, pool, client_mock, tx_api, executor_recv, pool_state) } diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_broadcast_tests.rs b/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_broadcast_tests.rs index 77a28968aedf8cfeefc7964ca29b972a856d618e..efb3bd94ddbfd2df8af96a49eb4d704e5f86af26 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_broadcast_tests.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_broadcast_tests.rs @@ -26,6 +26,8 @@ use std::sync::Arc; use substrate_test_runtime_client::AccountKeyring::*; use substrate_test_runtime_transaction_pool::uxt; +const MAX_TX_PER_CONNECTION: usize = 4; + // Test helpers. use crate::transaction::tests::{ middleware_pool::{MiddlewarePoolEvent, TxStatusTypeTest}, @@ -35,7 +37,7 @@ use crate::transaction::tests::{ #[tokio::test] async fn tx_broadcast_enters_pool() { let (api, pool, client_mock, tx_api, mut exec_middleware, mut pool_middleware) = - setup_api(Default::default()); + setup_api(Default::default(), MAX_TX_PER_CONNECTION); // Start at block 1. let block_1_header = api.push_block(1, vec![], true); @@ -44,12 +46,12 @@ async fn tx_broadcast_enters_pool() { let xt = hex_string(&uxt.encode()); let operation_id: String = - tx_api.call("transaction_unstable_broadcast", rpc_params![&xt]).await.unwrap(); + tx_api.call("transaction_v1_broadcast", rpc_params![&xt]).await.unwrap(); - // Announce block 1 to `transaction_unstable_broadcast`. + // Announce block 1 to `transaction_v1_broadcast`. client_mock.trigger_import_stream(block_1_header).await; - // Ensure the tx propagated from `transaction_unstable_broadcast` to the transaction pool. + // Ensure the tx propagated from `transaction_v1_broadcast` to the transaction pool. let event = get_next_event!(&mut pool_middleware); assert_eq!( event, @@ -82,10 +84,7 @@ async fn tx_broadcast_enters_pool() { // The future broadcast awaits for the finalized status to be reached. // Force the future to exit by calling stop. - let _: () = tx_api - .call("transaction_unstable_stop", rpc_params![&operation_id]) - .await - .unwrap(); + let _: () = tx_api.call("transaction_v1_stop", rpc_params![&operation_id]).await.unwrap(); // Ensure the broadcast future finishes. let _ = get_next_event!(&mut exec_middleware.recv); @@ -94,11 +93,12 @@ async fn tx_broadcast_enters_pool() { #[tokio::test] async fn tx_broadcast_invalid_tx() { - let (_, pool, _, tx_api, mut exec_middleware, _) = setup_api(Default::default()); + let (_, pool, _, tx_api, exec_middleware, _) = + setup_api(Default::default(), MAX_TX_PER_CONNECTION); // Invalid parameters. let err = tx_api - .call::<_, serde_json::Value>("transaction_unstable_broadcast", [1u8]) + .call::<_, serde_json::Value>("transaction_v1_broadcast", [1u8]) .await .unwrap_err(); assert_matches!(err, @@ -110,21 +110,18 @@ async fn tx_broadcast_invalid_tx() { // Invalid transaction that cannot be decoded. The broadcast silently exits. let xt = "0xdeadbeef"; let operation_id: String = - tx_api.call("transaction_unstable_broadcast", rpc_params![&xt]).await.unwrap(); + tx_api.call("transaction_v1_broadcast", rpc_params![&xt]).await.unwrap(); assert_eq!(0, pool.status().ready); - // Await the broadcast future to exit. - // Without this we'd be subject to races, where we try to call the stop before the tx is - // dropped. - let _ = get_next_event!(&mut exec_middleware.recv); + // The broadcast future should never be spawned when the tx decoding fails. assert_eq!(0, exec_middleware.num_tasks()); - // The broadcast future was dropped, and the operation is no longer active. + // The operation ID is no longer active. // When the operation is not active, either from the tx being finalized or a // terminal error; the stop method should return an error. let err = tx_api - .call::<_, serde_json::Value>("transaction_unstable_stop", rpc_params![&operation_id]) + .call::<_, serde_json::Value>("transaction_v1_stop", rpc_params![&operation_id]) .await .unwrap_err(); assert_matches!(err, @@ -134,11 +131,11 @@ async fn tx_broadcast_invalid_tx() { #[tokio::test] async fn tx_stop_with_invalid_operation_id() { - let (_, _, _, tx_api, _, _) = setup_api(Default::default()); + let (_, _, _, tx_api, _, _) = setup_api(Default::default(), MAX_TX_PER_CONNECTION); // Make an invalid stop call. let err = tx_api - .call::<_, serde_json::Value>("transaction_unstable_stop", ["invalid_operation_id"]) + .call::<_, serde_json::Value>("transaction_v1_stop", ["invalid_operation_id"]) .await .unwrap_err(); assert_matches!(err, @@ -149,7 +146,7 @@ async fn tx_stop_with_invalid_operation_id() { #[tokio::test] async fn tx_broadcast_resubmits_future_nonce_tx() { let (api, pool, client_mock, tx_api, mut exec_middleware, mut pool_middleware) = - setup_api(Default::default()); + setup_api(Default::default(), MAX_TX_PER_CONNECTION); // Start at block 1. let block_1_header = api.push_block(1, vec![], true); @@ -161,15 +158,13 @@ async fn tx_broadcast_resubmits_future_nonce_tx() { let future_uxt = uxt(Alice, ALICE_NONCE + 1); let future_xt = hex_string(&future_uxt.encode()); - let future_operation_id: String = tx_api - .call("transaction_unstable_broadcast", rpc_params![&future_xt]) - .await - .unwrap(); + let future_operation_id: String = + tx_api.call("transaction_v1_broadcast", rpc_params![&future_xt]).await.unwrap(); - // Announce block 1 to `transaction_unstable_broadcast`. + // Announce block 1 to `transaction_v1_broadcast`. client_mock.trigger_import_stream(block_1_header).await; - // Ensure the tx propagated from `transaction_unstable_broadcast` to the transaction pool. + // Ensure the tx propagated from `transaction_v1_broadcast` to the transaction pool. let event = get_next_event!(&mut pool_middleware); assert_eq!( event, @@ -188,13 +183,11 @@ async fn tx_broadcast_resubmits_future_nonce_tx() { let block_2_header = api.push_block(2, vec![], true); let block_2 = block_2_header.hash(); - let operation_id: String = tx_api - .call("transaction_unstable_broadcast", rpc_params![¤t_xt]) - .await - .unwrap(); + let operation_id: String = + tx_api.call("transaction_v1_broadcast", rpc_params![¤t_xt]).await.unwrap(); assert_ne!(future_operation_id, operation_id); - // Announce block 2 to `transaction_unstable_broadcast`. + // Announce block 2 to `transaction_v1_broadcast`. client_mock.trigger_import_stream(block_2_header).await; // Collect the events of both transactions. @@ -240,7 +233,7 @@ async fn tx_broadcast_resubmits_future_nonce_tx() { #[tokio::test] async fn tx_broadcast_stop_after_broadcast_finishes() { let (api, pool, client_mock, tx_api, mut exec_middleware, mut pool_middleware) = - setup_api(Default::default()); + setup_api(Default::default(), MAX_TX_PER_CONNECTION); // Start at block 1. let block_1_header = api.push_block(1, vec![], true); @@ -249,12 +242,12 @@ async fn tx_broadcast_stop_after_broadcast_finishes() { let xt = hex_string(&uxt.encode()); let operation_id: String = - tx_api.call("transaction_unstable_broadcast", rpc_params![&xt]).await.unwrap(); + tx_api.call("transaction_v1_broadcast", rpc_params![&xt]).await.unwrap(); - // Announce block 1 to `transaction_unstable_broadcast`. + // Announce block 1 to `transaction_v1_broadcast`. client_mock.trigger_import_stream(block_1_header).await; - // Ensure the tx propagated from `transaction_unstable_broadcast` to the transaction + // Ensure the tx propagated from `transaction_v1_broadcast` to the transaction // pool.inner_pool. let event = get_next_event!(&mut pool_middleware); assert_eq!( @@ -303,7 +296,7 @@ async fn tx_broadcast_stop_after_broadcast_finishes() { // The operation ID is no longer valid, check that the broadcast future // cleared out the inner state of the operation. let err = tx_api - .call::<_, serde_json::Value>("transaction_unstable_stop", rpc_params![&operation_id]) + .call::<_, serde_json::Value>("transaction_v1_stop", rpc_params![&operation_id]) .await .unwrap_err(); assert_matches!(err, @@ -323,19 +316,19 @@ async fn tx_broadcast_resubmits_invalid_tx() { }; let (api, pool, client_mock, tx_api, mut exec_middleware, mut pool_middleware) = - setup_api(options); + setup_api(options, MAX_TX_PER_CONNECTION); let uxt = uxt(Alice, ALICE_NONCE); let xt = hex_string(&uxt.encode()); let _operation_id: String = - tx_api.call("transaction_unstable_broadcast", rpc_params![&xt]).await.unwrap(); + tx_api.call("transaction_v1_broadcast", rpc_params![&xt]).await.unwrap(); let block_1_header = api.push_block(1, vec![], true); let block_1 = block_1_header.hash(); - // Announce block 1 to `transaction_unstable_broadcast`. + // Announce block 1 to `transaction_v1_broadcast`. client_mock.trigger_import_stream(block_1_header).await; - // Ensure the tx propagated from `transaction_unstable_broadcast` to the transaction pool. + // Ensure the tx propagated from `transaction_v1_broadcast` to the transaction pool. let event = get_next_event!(&mut pool_middleware); assert_eq!( event, @@ -355,7 +348,7 @@ async fn tx_broadcast_resubmits_invalid_tx() { pool.inner_pool.maintain(event).await; assert_eq!(1, pool.inner_pool.status().ready); - // Ensure the `transaction_unstable_broadcast` is aware of the invalid transaction. + // Ensure the `transaction_v1_broadcast` is aware of the invalid transaction. let event = get_next_event!(&mut pool_middleware); // Because we have received an `Invalid` status, we try to broadcast the transaction with the // next announced block. @@ -388,7 +381,7 @@ async fn tx_broadcast_resubmits_invalid_tx() { pool.inner_pool.maintain(event).await; assert_eq!(0, pool.inner_pool.status().ready); - // Announce block to `transaction_unstable_broadcast`. + // Announce block to `transaction_v1_broadcast`. client_mock.trigger_import_stream(block_3_header).await; let event = get_next_event!(&mut pool_middleware); @@ -442,7 +435,8 @@ async fn tx_broadcast_resubmits_dropped_tx() { ban_time: std::time::Duration::ZERO, }; - let (api, pool, client_mock, tx_api, _, mut pool_middleware) = setup_api(options); + let (api, pool, client_mock, tx_api, _, mut pool_middleware) = + setup_api(options, MAX_TX_PER_CONNECTION); let current_uxt = uxt(Alice, ALICE_NONCE); let current_xt = hex_string(¤t_uxt.encode()); @@ -455,12 +449,10 @@ async fn tx_broadcast_resubmits_dropped_tx() { // are immediately dropped. api.set_priority(¤t_uxt, 10); - let current_operation_id: String = tx_api - .call("transaction_unstable_broadcast", rpc_params![¤t_xt]) - .await - .unwrap(); + let current_operation_id: String = + tx_api.call("transaction_v1_broadcast", rpc_params![¤t_xt]).await.unwrap(); - // Announce block 1 to `transaction_unstable_broadcast`. + // Announce block 1 to `transaction_v1_broadcast`. let block_1_header = api.push_block(1, vec![], true); let event = ChainEvent::Finalized { hash: block_1_header.hash(), tree_route: Arc::from(vec![]) }; @@ -479,10 +471,8 @@ async fn tx_broadcast_resubmits_dropped_tx() { // The future tx has priority 2, smaller than the current 10. api.set_priority(&future_uxt, 2); - let future_operation_id: String = tx_api - .call("transaction_unstable_broadcast", rpc_params![&future_xt]) - .await - .unwrap(); + let future_operation_id: String = + tx_api.call("transaction_v1_broadcast", rpc_params![&future_xt]).await.unwrap(); assert_ne!(current_operation_id, future_operation_id); let block_2_header = api.push_block(2, vec![], true); @@ -521,3 +511,50 @@ async fn tx_broadcast_resubmits_dropped_tx() { // The dropped transaction was resubmitted. assert_eq!(events.get(&future_xt).unwrap(), &vec![TxStatusTypeTest::Ready]); } + +#[tokio::test] +async fn tx_broadcast_limit_reached() { + // One operation per connection. + let (api, _pool, client_mock, tx_api, mut exec_middleware, mut pool_middleware) = + setup_api(Default::default(), 1); + + // Start at block 1. + let block_1_header = api.push_block(1, vec![], true); + let uxt = uxt(Alice, ALICE_NONCE); + let xt = hex_string(&uxt.encode()); + + let operation_id: String = + tx_api.call("transaction_v1_broadcast", rpc_params![&xt]).await.unwrap(); + + // Announce block 1 to `transaction_v1_broadcast`. + client_mock.trigger_import_stream(block_1_header).await; + + // Ensure the tx propagated from `transaction_v1_broadcast` to the transaction pool. + let event = get_next_event!(&mut pool_middleware); + assert_eq!( + event, + MiddlewarePoolEvent::TransactionStatus { + transaction: xt.clone(), + status: TxStatusTypeTest::Ready + } + ); + assert_eq!(1, exec_middleware.num_tasks()); + + let operation_id_limit_reached: Option = + tx_api.call("transaction_v1_broadcast", rpc_params![&xt]).await.unwrap(); + assert!(operation_id_limit_reached.is_none(), "No operation ID => tx was rejected"); + + // We still have in flight one operation. + assert_eq!(1, exec_middleware.num_tasks()); + + // Force the future to exit by calling stop. + let _: () = tx_api.call("transaction_v1_stop", rpc_params![&operation_id]).await.unwrap(); + + // Ensure the broadcast future finishes. + let _ = get_next_event!(&mut exec_middleware.recv); + assert_eq!(0, exec_middleware.num_tasks()); + + // Can resubmit again now. + let _operation_id: String = + tx_api.call("transaction_v1_broadcast", rpc_params![&xt]).await.unwrap(); +} diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_tests.rs b/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_tests.rs index c83bc948c437c99e767be09d4f351718140fcb89..7ce85b9feafe3ed2840321aee6c614962789707f 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_tests.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_tests.rs @@ -38,7 +38,7 @@ async fn tx_invalid_bytes() { // This should not rely on the tx pool state. let mut sub = tx_api - .subscribe_unbounded("transactionWatch_unstable_submitAndWatch", rpc_params![&"0xdeadbeef"]) + .subscribe_unbounded("transactionWatch_v1_submitAndWatch", rpc_params![&"0xdeadbeef"]) .await .unwrap(); @@ -56,7 +56,7 @@ async fn tx_in_finalized() { let xt = hex_string(&uxt.encode()); let mut sub = tx_api - .subscribe_unbounded("transactionWatch_unstable_submitAndWatch", rpc_params![&xt]) + .subscribe_unbounded("transactionWatch_v1_submitAndWatch", rpc_params![&xt]) .await .unwrap(); @@ -95,7 +95,7 @@ async fn tx_with_pruned_best_block() { let xt = hex_string(&uxt.encode()); let mut sub = tx_api - .subscribe_unbounded("transactionWatch_unstable_submitAndWatch", rpc_params![&xt]) + .subscribe_unbounded("transactionWatch_v1_submitAndWatch", rpc_params![&xt]) .await .unwrap(); diff --git a/substrate/client/rpc-spec-v2/src/transaction/transaction.rs b/substrate/client/rpc-spec-v2/src/transaction/transaction.rs index 6a7c69b8f7d1e765d0ce64ff590e191e97935d30..723440d1b11101b71bcc1602e4da3d15ac0e931e 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/transaction.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/transaction.rs @@ -26,6 +26,7 @@ use crate::{ }, SubscriptionTaskExecutor, }; + use codec::Decode; use futures::{StreamExt, TryFutureExt}; use jsonrpsee::{core::async_trait, PendingSubscriptionSink}; diff --git a/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs b/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs index 6eaf50d6b2e2822d766f4f131f4bd6f1ba04d5cd..68c19010e31c5653509c380f6be69c072e6fe9bb 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs @@ -18,11 +18,17 @@ //! API implementation for broadcasting transactions. -use crate::{transaction::api::TransactionBroadcastApiServer, SubscriptionTaskExecutor}; +use crate::{ + common::connections::RpcConnections, transaction::api::TransactionBroadcastApiServer, + SubscriptionTaskExecutor, +}; use codec::Decode; use futures::{FutureExt, Stream, StreamExt}; use futures_util::stream::AbortHandle; -use jsonrpsee::core::{async_trait, RpcResult}; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + ConnectionDetails, +}; use parking_lot::RwLock; use rand::{distributions::Alphanumeric, Rng}; use sc_client_api::BlockchainEvents; @@ -37,7 +43,7 @@ use std::{collections::HashMap, sync::Arc}; use super::error::ErrorBroadcast; /// An API for transaction RPC calls. -pub struct TransactionBroadcast { +pub struct TransactionBroadcast { /// Substrate client. client: Arc, /// Transactions pool. @@ -45,19 +51,34 @@ pub struct TransactionBroadcast { /// Executor to spawn subscriptions. executor: SubscriptionTaskExecutor, /// The broadcast operation IDs. - broadcast_ids: Arc>>, + broadcast_ids: Arc>>>, + /// Keep track of how many concurrent operations are active for each connection. + rpc_connections: RpcConnections, } /// The state of a broadcast operation. -struct BroadcastState { +struct BroadcastState { /// Handle to abort the running future that broadcasts the transaction. handle: AbortHandle, + /// Associated tx hash. + tx_hash: ::Hash, } -impl TransactionBroadcast { +impl TransactionBroadcast { /// Creates a new [`TransactionBroadcast`]. - pub fn new(client: Arc, pool: Arc, executor: SubscriptionTaskExecutor) -> Self { - TransactionBroadcast { client, pool, executor, broadcast_ids: Default::default() } + pub fn new( + client: Arc, + pool: Arc, + executor: SubscriptionTaskExecutor, + max_transactions_per_connection: usize, + ) -> Self { + TransactionBroadcast { + client, + pool, + executor, + broadcast_ids: Default::default(), + rpc_connections: RpcConnections::new(max_transactions_per_connection), + } } /// Generate an unique operation ID for the `transaction_broadcast` RPC method. @@ -100,23 +121,46 @@ where ::Hash: Unpin, Client: HeaderBackend + BlockchainEvents + Send + Sync + 'static, { - fn broadcast(&self, bytes: Bytes) -> RpcResult> { + async fn broadcast( + &self, + connection_details: ConnectionDetails, + bytes: Bytes, + ) -> RpcResult> { let pool = self.pool.clone(); // The unique ID of this operation. let id = self.generate_unique_id(); - let mut best_block_import_stream = + // Ensure that the connection has not reached the maximum number of active operations. + let Some(reserved_connection) = self.rpc_connections.reserve_space(connection_details.id()) + else { + return Ok(None) + }; + let Some(reserved_identifier) = reserved_connection.register(id.clone()) else { + // This can only happen if the generated operation ID is not unique. + return Ok(None) + }; + + // The JSON-RPC server might check whether the transaction is valid before broadcasting it. + // If it does so and if the transaction is invalid, the server should silently do nothing + // and the JSON-RPC client is not informed of the problem. Invalid transactions should still + // count towards the limit to the number of simultaneously broadcasted transactions. + let Ok(decoded_extrinsic) = TransactionFor::::decode(&mut &bytes[..]) else { + return Ok(Some(id)); + }; + // Save the tx hash to remove it later. + let tx_hash = pool.hash_of(&decoded_extrinsic); + + // The compiler can no longer deduce the type of the stream and complains + // about `one type is more general than the other`. + let mut best_block_import_stream: std::pin::Pin< + Box::Hash> + Send>, + > = Box::pin(self.client.import_notification_stream().filter_map( |notification| async move { notification.is_new_best.then_some(notification.hash) }, )); let broadcast_transaction_fut = async move { - // There is nothing we could do with an extrinsic of invalid format. - let Ok(decoded_extrinsic) = TransactionFor::::decode(&mut &bytes[..]) else { - return; - }; - // Flag to determine if the we should broadcast the transaction again. let mut is_done = false; @@ -169,17 +213,29 @@ where let (fut, handle) = futures::future::abortable(broadcast_transaction_fut); let broadcast_ids = self.broadcast_ids.clone(); let drop_id = id.clone(); + let pool = self.pool.clone(); // The future expected by the executor must be `Future` instead of // `Future>`. - let fut = fut.map(move |_| { + let fut = fut.map(move |result| { + // Connection space is cleaned when this object is dropped. + drop(reserved_identifier); + // Remove the entry from the broadcast IDs map. - broadcast_ids.write().remove(&drop_id); + let Some(broadcast_state) = broadcast_ids.write().remove(&drop_id) else { return }; + + // The broadcast was not stopped. + if result.is_ok() { + return + } + + // Best effort pool removal (tx can already be finalized). + pool.remove_invalid(&[broadcast_state.tx_hash]); }); // Keep track of this entry and the abortable handle. { let mut broadcast_ids = self.broadcast_ids.write(); - broadcast_ids.insert(id.clone(), BroadcastState { handle }); + broadcast_ids.insert(id.clone(), BroadcastState { handle, tx_hash }); } sc_rpc::utils::spawn_subscription_task(&self.executor, fut); @@ -187,7 +243,16 @@ where Ok(Some(id)) } - fn stop_broadcast(&self, operation_id: String) -> Result<(), ErrorBroadcast> { + async fn stop_broadcast( + &self, + connection_details: ConnectionDetails, + operation_id: String, + ) -> Result<(), ErrorBroadcast> { + // The operation ID must correlate to the same connection ID. + if !self.rpc_connections.contains_identifier(connection_details.id(), &operation_id) { + return Err(ErrorBroadcast::InvalidOperationID) + } + let mut broadcast_ids = self.broadcast_ids.write(); let Some(broadcast_state) = broadcast_ids.remove(&operation_id) else { diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index 830f9884719dcf1fd1b024906f830b18a154a323..d0d7cba3862409c2df0d71ea9c6758dd83105340 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -644,10 +644,13 @@ where (chain, state, child_state) }; + const MAX_TRANSACTION_PER_CONNECTION: usize = 16; + let transaction_broadcast_rpc_v2 = sc_rpc_spec_v2::transaction::TransactionBroadcast::new( client.clone(), transaction_pool.clone(), task_executor.clone(), + MAX_TRANSACTION_PER_CONNECTION, ) .into_rpc(); diff --git a/substrate/client/tracing/src/logging/event_format.rs b/substrate/client/tracing/src/logging/event_format.rs index 235d66cadc78a7a25358f24a40ffcfc1cb18e8ea..8d875427841e94c933ed0a704050409c7e23552a 100644 --- a/substrate/client/tracing/src/logging/event_format.rs +++ b/substrate/client/tracing/src/logging/event_format.rs @@ -23,10 +23,8 @@ use std::fmt::{self, Write}; use tracing::{Event, Level, Subscriber}; use tracing_log::NormalizeEvent; use tracing_subscriber::{ - field::RecordFields, fmt::{format, time::FormatTime, FmtContext, FormatEvent, FormatFields}, - layer::Context, - registry::{LookupSpan, SpanRef}, + registry::LookupSpan, }; /// A pre-configured event formatter. @@ -54,7 +52,7 @@ where // https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/format/mod.rs#L449 pub(crate) fn format_event_custom<'b, 'w, S, N>( &self, - ctx: CustomFmtContext<'b, S, N>, + ctx: &FmtContext<'b, S, N>, writer: format::Writer<'w>, event: &Event, ) -> fmt::Result @@ -68,7 +66,7 @@ where time::write(&self.timer, &mut format::Writer::new(&mut writer), self.enable_color)?; if self.display_level { - let fmt_level = { FmtLevel::new(meta.level(), self.enable_color) }; + let fmt_level = FmtLevel::new(meta.level(), self.enable_color); write!(writer, "{} ", fmt_level)?; } @@ -137,12 +135,12 @@ where { let mut out = String::new(); let buf_writer = format::Writer::new(&mut out); - self.format_event_custom(CustomFmtContext::FmtContext(ctx), buf_writer, event)?; + self.format_event_custom(ctx, buf_writer, event)?; writer.write_str(&out)?; print!("{}", out); Ok(()) } else { - self.format_event_custom(CustomFmtContext::FmtContext(ctx), writer, event) + self.format_event_custom(ctx, writer, event) } } } @@ -261,48 +259,6 @@ mod time { } } -// NOTE: `FmtContext`'s fields are private. This enum allows us to make a `format_event` function -// that works with `FmtContext` or `Context` with `FormatFields` -#[allow(dead_code)] -pub(crate) enum CustomFmtContext<'a, S, N> { - FmtContext(&'a FmtContext<'a, S, N>), - ContextWithFormatFields(&'a Context<'a, S>, &'a N), -} - -impl<'a, S, N> FormatFields<'a> for CustomFmtContext<'a, S, N> -where - S: Subscriber + for<'lookup> LookupSpan<'lookup>, - N: for<'writer> FormatFields<'writer> + 'static, -{ - fn format_fields(&self, writer: format::Writer<'_>, fields: R) -> fmt::Result { - match self { - CustomFmtContext::FmtContext(fmt_ctx) => fmt_ctx.format_fields(writer, fields), - CustomFmtContext::ContextWithFormatFields(_ctx, fmt_fields) => - fmt_fields.format_fields(writer, fields), - } - } -} - -// NOTE: the following code has been duplicated from tracing-subscriber -// -// https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/fmt_layer.rs#L788 -impl<'a, S, N> CustomFmtContext<'a, S, N> -where - S: Subscriber + for<'lookup> LookupSpan<'lookup>, - N: for<'writer> FormatFields<'writer> + 'static, -{ - #[inline] - pub fn lookup_current(&self) -> Option> - where - S: for<'lookup> LookupSpan<'lookup>, - { - match self { - CustomFmtContext::FmtContext(fmt_ctx) => fmt_ctx.lookup_current(), - CustomFmtContext::ContextWithFormatFields(ctx, _) => ctx.lookup_current(), - } - } -} - /// A writer which (optionally) strips out terminal control codes from the logs. /// /// This is used by [`EventFormat`] to sanitize the log messages. diff --git a/substrate/frame/Cargo.toml b/substrate/frame/Cargo.toml index 919d6d17ce8b128c03a28d9bf502919aba833176..ef8d8758f3df12d6f88b58069420f1535379c6b7 100644 --- a/substrate/frame/Cargo.toml +++ b/substrate/frame/Cargo.toml @@ -2,11 +2,11 @@ name = "polkadot-sdk-frame" version = "0.1.0" authors = ["Parity Technologies "] -edition = "2021" +edition.workspace = true license = "Apache-2.0" homepage = "paritytech.github.io" repository.workspace = true -description = "The single package to get you started with building frame pallets and runtimes" +description = "Experimental: The single package to get you started with building frame pallets and runtimes" publish = false [lints] diff --git a/substrate/frame/alliance/src/benchmarking.rs b/substrate/frame/alliance/src/benchmarking.rs index 710c32a848dd152b4ae4cf02c1be87e295ae7da4..09e2045555b65b857614f3c14be669b1c6ffdd3c 100644 --- a/substrate/frame/alliance/src/benchmarking.rs +++ b/substrate/frame/alliance/src/benchmarking.rs @@ -19,11 +19,7 @@ #![cfg(feature = "runtime-benchmarks")] -use core::{ - cmp, - convert::{TryFrom, TryInto}, - mem::size_of, -}; +use core::{cmp, mem::size_of}; use sp_runtime::traits::{Bounded, Hash, StaticLookup}; use frame_benchmarking::{account, v2::*, BenchmarkError}; diff --git a/substrate/frame/alliance/src/lib.rs b/substrate/frame/alliance/src/lib.rs index 1f06241e9c83b088137547ec47372aa8e84142d7..ed771c7226ea9d9c43a94b906722f5379e7c0174 100644 --- a/substrate/frame/alliance/src/lib.rs +++ b/substrate/frame/alliance/src/lib.rs @@ -101,7 +101,7 @@ use sp_runtime::{ traits::{Dispatchable, Saturating, StaticLookup, Zero}, DispatchError, RuntimeDebug, }; -use sp_std::{convert::TryInto, prelude::*}; +use sp_std::prelude::*; use frame_support::{ dispatch::{DispatchResult, DispatchResultWithPostInfo, GetDispatchInfo, PostDispatchInfo}, diff --git a/substrate/frame/alliance/src/mock.rs b/substrate/frame/alliance/src/mock.rs index b183e412bed779291ef2bf3d94c0790295420e02..7116e69efa17ec88134699570b2235b8b0973d7c 100644 --- a/substrate/frame/alliance/src/mock.rs +++ b/substrate/frame/alliance/src/mock.rs @@ -17,7 +17,6 @@ //! Test utilities -use core::convert::{TryFrom, TryInto}; pub use sp_core::H256; use sp_runtime::traits::Hash; pub use sp_runtime::{ diff --git a/substrate/frame/alliance/src/types.rs b/substrate/frame/alliance/src/types.rs index 784993b2bc1334a26bd7430661e3481d7b6041d8..149030b52c674a8007ea19f77ec1356fb44a49b3 100644 --- a/substrate/frame/alliance/src/types.rs +++ b/substrate/frame/alliance/src/types.rs @@ -19,7 +19,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{traits::ConstU32, BoundedVec}; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; -use sp_std::{convert::TryInto, prelude::*}; +use sp_std::prelude::*; /// A Multihash instance that only supports the basic functionality and no hashing. #[derive( diff --git a/substrate/frame/asset-conversion/Cargo.toml b/substrate/frame/asset-conversion/Cargo.toml index 1a8e8eea4849ad76f5185ccb6a76745db07d4513..cf50d7b22af9b11a82011fe8e23c78ccef1112fa 100644 --- a/substrate/frame/asset-conversion/Cargo.toml +++ b/substrate/frame/asset-conversion/Cargo.toml @@ -17,6 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +log = { version = "0.4.20", default-features = false } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } @@ -40,6 +41,7 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", + "log/std", "pallet-assets/std", "pallet-balances/std", "primitive-types/std", diff --git a/substrate/frame/asset-conversion/ops/Cargo.toml b/substrate/frame/asset-conversion/ops/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..e421e904a3a17933ebeefc3affea7e5d9cc5d702 --- /dev/null +++ b/substrate/frame/asset-conversion/ops/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "pallet-asset-conversion-ops" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage = "https://substrate.io" +repository.workspace = true +description = "FRAME asset conversion pallet's operations suite" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +log = { version = "0.4.20", default-features = false } +frame-support = { path = "../../support", default-features = false } +frame-system = { path = "../../system", default-features = false } +frame-benchmarking = { path = "../../benchmarking", default-features = false, optional = true } +pallet-asset-conversion = { path = "..", default-features = false } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } +sp-core = { path = "../../../primitives/core", default-features = false } +sp-io = { path = "../../../primitives/io", default-features = false } +sp-std = { path = "../../../primitives/std", default-features = false } +sp-runtime = { path = "../../../primitives/runtime", default-features = false } +sp-arithmetic = { path = "../../../primitives/arithmetic", default-features = false } + +[dev-dependencies] +pallet-balances = { path = "../../balances" } +pallet-assets = { path = "../../assets" } +primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "num-traits", "scale-info"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-asset-conversion/std", + "pallet-assets/std", + "pallet-balances/std", + "primitive-types/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-asset-conversion/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-asset-conversion/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/asset-conversion/ops/src/benchmarking.rs b/substrate/frame/asset-conversion/ops/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..a7370f38bc4b0472c4baea94bffc0d26b3169efe --- /dev/null +++ b/substrate/frame/asset-conversion/ops/src/benchmarking.rs @@ -0,0 +1,167 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Asset Conversion Ops pallet benchmarking. + +use super::*; +use crate::Pallet as AssetConversionOps; +use frame_benchmarking::{v2::*, whitelisted_caller}; +use frame_support::{ + assert_ok, + traits::fungibles::{Create, Inspect, Mutate}, +}; +use frame_system::RawOrigin as SystemOrigin; +use pallet_asset_conversion::{BenchmarkHelper, Pallet as AssetConversion}; +use sp_core::Get; +use sp_runtime::traits::One; +use sp_std::prelude::*; + +/// Provides a pair of amounts expected to serve as sufficient initial liquidity for a pool. +fn valid_liquidity_amount(ed1: T::Balance, ed2: T::Balance) -> (T::Balance, T::Balance) +where + T::Assets: Inspect, +{ + let l = + ed1.max(ed2) + T::MintMinLiquidity::get() + T::MintMinLiquidity::get() + T::Balance::one(); + (l, l) +} + +/// Create the `asset` and mint the `amount` for the `caller`. +fn create_asset(caller: &T::AccountId, asset: &T::AssetKind, amount: T::Balance) +where + T::Assets: Create + Mutate, +{ + if !T::Assets::asset_exists(asset.clone()) { + assert_ok!(T::Assets::create(asset.clone(), caller.clone(), true, T::Balance::one())); + } + assert_ok!(T::Assets::mint_into( + asset.clone(), + &caller, + amount + T::Assets::minimum_balance(asset.clone()) + )); +} + +/// Create the designated fee asset for pool creation. +fn create_fee_asset(caller: &T::AccountId) +where + T::Assets: Create + Mutate, +{ + let fee_asset = T::PoolSetupFeeAsset::get(); + if !T::Assets::asset_exists(fee_asset.clone()) { + assert_ok!(T::Assets::create(fee_asset.clone(), caller.clone(), true, T::Balance::one())); + } + assert_ok!(T::Assets::mint_into( + fee_asset.clone(), + &caller, + T::Assets::minimum_balance(fee_asset) + )); +} + +/// Mint the fee asset for the `caller` sufficient to cover the fee for creating a new pool. +fn mint_setup_fee_asset( + caller: &T::AccountId, + asset1: &T::AssetKind, + asset2: &T::AssetKind, + lp_token: &T::PoolAssetId, +) where + T::Assets: Create + Mutate, +{ + assert_ok!(T::Assets::mint_into( + T::PoolSetupFeeAsset::get(), + &caller, + T::PoolSetupFee::get() + + T::Assets::deposit_required(asset1.clone()) + + T::Assets::deposit_required(asset2.clone()) + + T::PoolAssets::deposit_required(lp_token.clone()) + )); +} + +/// Creates a pool for a given asset pair. +/// +/// This action mints the necessary amounts of the given assets for the `caller` to provide initial +/// liquidity. It returns the LP token ID along with a pair of amounts sufficient for the pool's +/// initial liquidity. +fn create_asset_and_pool( + caller: &T::AccountId, + asset1: &T::AssetKind, + asset2: &T::AssetKind, +) -> (T::PoolAssetId, T::Balance, T::Balance) +where + T::Assets: Create + Mutate, +{ + let (liquidity1, liquidity2) = valid_liquidity_amount::( + T::Assets::minimum_balance(asset1.clone()), + T::Assets::minimum_balance(asset2.clone()), + ); + create_asset::(caller, asset1, liquidity1); + create_asset::(caller, asset2, liquidity2); + let lp_token = AssetConversion::::get_next_pool_asset_id(); + + mint_setup_fee_asset::(caller, asset1, asset2, &lp_token); + + assert_ok!(AssetConversion::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + Box::new(asset1.clone()), + Box::new(asset2.clone()) + )); + + (lp_token, liquidity1, liquidity2) +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +#[benchmarks(where T::Assets: Create + Mutate, T::PoolAssetId: Into,)] +mod benchmarks { + use super::*; + + #[benchmark] + fn migrate_to_new_account() { + let caller: T::AccountId = whitelisted_caller(); + let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1); + + create_fee_asset::(&caller); + let (_, liquidity1, liquidity2) = create_asset_and_pool::(&caller, &asset1, &asset2); + + assert_ok!(AssetConversion::::add_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + Box::new(asset1.clone()), + Box::new(asset2.clone()), + liquidity1, + liquidity2, + T::Balance::one(), + T::Balance::zero(), + caller.clone(), + )); + + #[extrinsic_call] + _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone())); + + let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap(); + let (prior_account, new_account) = AssetConversionOps::::addresses(&pool_id).unwrap(); + assert_last_event::( + Event::MigratedToNewAccount { pool_id, new_account, prior_account }.into(), + ); + } + + impl_benchmark_test_suite!(AssetConversionOps, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/substrate/frame/asset-conversion/ops/src/lib.rs b/substrate/frame/asset-conversion/ops/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..6cc7bfa2e9965abb63fb816e0ffecd6b77b5df8a --- /dev/null +++ b/substrate/frame/asset-conversion/ops/src/lib.rs @@ -0,0 +1,331 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Asset Conversion Operations Suite. +//! +//! This pallet provides operational functionalities for the Asset Conversion pallet, +//! allowing you to perform various migration and one-time-use operations. These operations +//! are designed to facilitate updates and changes to the Asset Conversion pallet without +//! breaking its API. +//! +//! ## Overview +//! +//! This suite allows you to perform the following operations: +//! - Perform migration to update account ID derivation methods for existing pools. The migration +//! operation ensures that the required accounts are created, existing account deposits are +//! transferred, and liquidity is moved to the new accounts. + +#![deny(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +pub mod weights; +pub use pallet::*; +pub use weights::WeightInfo; + +use frame_support::traits::{ + fungible::{Inspect as FungibleInspect, Mutate as FungibleMutate}, + fungibles::{roles::ResetTeam, Inspect, Mutate, Refund}, + tokens::{Fortitude, Precision, Preservation}, + AccountTouch, +}; +use pallet_asset_conversion::{PoolLocator, Pools}; +use sp_runtime::traits::{TryConvert, Zero}; +use sp_std::boxed::Box; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: + pallet_asset_conversion::Config< + PoolId = ( + ::AssetKind, + ::AssetKind, + ), + > + frame_system::Config + { + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Type previously used to derive the account ID for a pool. Indicates that the pool's + /// liquidity assets are located at this account before the migration. + type PriorAccountIdConverter: for<'a> TryConvert< + &'a (Self::AssetKind, Self::AssetKind), + Self::AccountId, + >; + + /// Retrieves information about an existing deposit for a given account ID and asset from + /// the [`pallet_asset_conversion::Config::Assets`] registry and can initiate the refund. + type AssetsRefund: Refund< + Self::AccountId, + AssetId = Self::AssetKind, + Balance = >::Balance, + >; + + /// Retrieves information about an existing deposit for a given account ID and asset from + /// the [`pallet_asset_conversion::Config::PoolAssets`] registry and can initiate the + /// refund. + type PoolAssetsRefund: Refund< + Self::AccountId, + AssetId = Self::PoolAssetId, + Balance = >::Balance, + >; + + /// Means to reset the team for assets from the + /// [`pallet_asset_conversion::Config::PoolAssets`] registry. + type PoolAssetsTeam: ResetTeam; + + /// Registry of an asset used as an account deposit for the + /// [`pallet_asset_conversion::Config::Assets`] and + /// [`pallet_asset_conversion::Config::PoolAssets`] registries. + type DepositAsset: FungibleMutate; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + // Pallet's events. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Indicates that a pool has been migrated to the new account ID. + MigratedToNewAccount { + /// Pool's ID. + pool_id: T::PoolId, + /// Pool's prior account ID. + prior_account: T::AccountId, + /// Pool's new account ID. + new_account: T::AccountId, + }, + } + + #[pallet::error] + pub enum Error { + /// Provided asset pair is not supported for pool. + InvalidAssetPair, + /// The pool doesn't exist. + PoolNotFound, + /// Pool's balance cannot be zero. + ZeroBalance, + /// Indicates a partial transfer of balance to the new account during a migration. + PartialTransfer, + } + + /// Pallet's callable functions. + #[pallet::call] + impl Pallet { + /// Migrates an existing pool to a new account ID derivation method for a given asset pair. + /// If the migration is successful, transaction fees are refunded to the caller. + /// + /// Must be signed. + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::migrate_to_new_account())] + pub fn migrate_to_new_account( + origin: OriginFor, + asset1: Box, + asset2: Box, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + + let pool_id = T::PoolLocator::pool_id(&asset1, &asset2) + .map_err(|_| Error::::InvalidAssetPair)?; + let info = Pools::::get(&pool_id).ok_or(Error::::PoolNotFound)?; + + let (prior_account, new_account) = + Self::addresses(&pool_id).ok_or(Error::::InvalidAssetPair)?; + + let (asset1, asset2) = pool_id.clone(); + + // Assets that must be transferred to the new account id. + let balance1 = T::Assets::total_balance(asset1.clone(), &prior_account); + let balance2 = T::Assets::total_balance(asset2.clone(), &prior_account); + let lp_balance = T::PoolAssets::total_balance(info.lp_token.clone(), &prior_account); + + ensure!(!balance1.is_zero(), Error::::ZeroBalance); + ensure!(!balance2.is_zero(), Error::::ZeroBalance); + ensure!(!lp_balance.is_zero(), Error::::ZeroBalance); + + // Check if a deposit needs to be placed for the new account. If so, mint the + // required deposit amount to the depositor's account to ensure the deposit can be + // provided. Once the deposit from the prior account is returned, the minted assets will + // be burned. Touching the new account is necessary because it's not possible to + // transfer assets to the new account if it's required. Additionally, the deposit cannot + // be refunded from the prior account until its balance is zero. + + let deposit_asset_ed = T::DepositAsset::minimum_balance(); + + if let Some((depositor, deposit)) = + T::AssetsRefund::deposit_held(asset1.clone(), prior_account.clone()) + { + T::DepositAsset::mint_into(&depositor, deposit + deposit_asset_ed)?; + T::Assets::touch(asset1.clone(), &new_account, &depositor)?; + } + + if let Some((depositor, deposit)) = + T::AssetsRefund::deposit_held(asset2.clone(), prior_account.clone()) + { + T::DepositAsset::mint_into(&depositor, deposit + deposit_asset_ed)?; + T::Assets::touch(asset2.clone(), &new_account, &depositor)?; + } + + if let Some((depositor, deposit)) = + T::PoolAssetsRefund::deposit_held(info.lp_token.clone(), prior_account.clone()) + { + T::DepositAsset::mint_into(&depositor, deposit + deposit_asset_ed)?; + T::PoolAssets::touch(info.lp_token.clone(), &new_account, &depositor)?; + } + + // Transfer all pool related assets to the new account. + + ensure!( + balance1 == + T::Assets::transfer( + asset1.clone(), + &prior_account, + &new_account, + balance1, + Preservation::Expendable, + )?, + Error::::PartialTransfer + ); + + ensure!( + balance2 == + T::Assets::transfer( + asset2.clone(), + &prior_account, + &new_account, + balance2, + Preservation::Expendable, + )?, + Error::::PartialTransfer + ); + + ensure!( + lp_balance == + T::PoolAssets::transfer( + info.lp_token.clone(), + &prior_account, + &new_account, + lp_balance, + Preservation::Expendable, + )?, + Error::::PartialTransfer + ); + + // Refund deposits from prior accounts and burn previously minted assets. + + if let Some((depositor, deposit)) = + T::AssetsRefund::deposit_held(asset1.clone(), prior_account.clone()) + { + T::AssetsRefund::refund(asset1.clone(), prior_account.clone())?; + T::DepositAsset::burn_from( + &depositor, + deposit + deposit_asset_ed, + Precision::Exact, + Fortitude::Force, + )?; + } + + if let Some((depositor, deposit)) = + T::AssetsRefund::deposit_held(asset2.clone(), prior_account.clone()) + { + T::AssetsRefund::refund(asset2.clone(), prior_account.clone())?; + T::DepositAsset::burn_from( + &depositor, + deposit + deposit_asset_ed, + Precision::Exact, + Fortitude::Force, + )?; + } + + if let Some((depositor, deposit)) = + T::PoolAssetsRefund::deposit_held(info.lp_token.clone(), prior_account.clone()) + { + T::PoolAssetsRefund::refund(info.lp_token.clone(), prior_account.clone())?; + T::DepositAsset::burn_from( + &depositor, + deposit + deposit_asset_ed, + Precision::Exact, + Fortitude::Force, + )?; + } + + T::PoolAssetsTeam::reset_team( + info.lp_token, + new_account.clone(), + new_account.clone(), + new_account.clone(), + new_account.clone(), + )?; + + Self::deposit_event(Event::MigratedToNewAccount { + pool_id, + prior_account, + new_account, + }); + + Ok(Pays::No.into()) + } + } + + impl Pallet { + /// Returns the prior and new account IDs for a given pool ID. The prior account ID comes + /// first in the tuple. + #[cfg(not(any(test, feature = "runtime-benchmarks")))] + fn addresses(pool_id: &T::PoolId) -> Option<(T::AccountId, T::AccountId)> { + match ( + T::PriorAccountIdConverter::try_convert(pool_id), + T::PoolLocator::address(pool_id), + ) { + (Ok(a), Ok(b)) if a != b => Some((a, b)), + _ => None, + } + } + + /// Returns the prior and new account IDs for a given pool ID. The prior account ID comes + /// first in the tuple. + /// + /// This function is intended for use only in test and benchmark environments. The prior + /// account ID represents the new account ID from [`Config::PoolLocator`], allowing the use + /// of the main pallet's calls to set up a pool with liquidity placed in that account and + /// migrate it to another account, which in this case is the result of + /// [`Config::PriorAccountIdConverter`]. + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub(crate) fn addresses(pool_id: &T::PoolId) -> Option<(T::AccountId, T::AccountId)> { + match ( + T::PoolLocator::address(pool_id), + T::PriorAccountIdConverter::try_convert(pool_id), + ) { + (Ok(a), Ok(b)) if a != b => Some((a, b)), + _ => None, + } + } + } +} diff --git a/substrate/frame/asset-conversion/ops/src/mock.rs b/substrate/frame/asset-conversion/ops/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..9454b3a9ad448af4b47d9fd974e6ec8ad5ed4e59 --- /dev/null +++ b/substrate/frame/asset-conversion/ops/src/mock.rs @@ -0,0 +1,147 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test environment for Asset Conversion Ops pallet. + +use super::*; +use crate as pallet_asset_conversion_ops; +use core::default::Default; +use frame_support::{ + construct_runtime, derive_impl, + instances::{Instance1, Instance2}, + ord_parameter_types, parameter_types, + traits::{ + tokens::{ + fungible::{NativeFromLeft, NativeOrWithId, UnionOf}, + imbalance::ResolveAssetTo, + }, + AsEnsureOriginWithArg, ConstU32, ConstU64, + }, + PalletId, +}; +use frame_system::{EnsureSigned, EnsureSignedBy}; +use pallet_asset_conversion::{self, AccountIdConverter, AccountIdConverterNoSeed, Ascending}; +use sp_arithmetic::Permill; +use sp_runtime::{traits::AccountIdConversion, BuildStorage}; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Assets: pallet_assets::, + PoolAssets: pallet_assets::, + AssetConversion: pallet_asset_conversion, + AssetConversionOps: pallet_asset_conversion_ops, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type ReserveIdentifier = [u8; 8]; + type AccountStore = System; +} + +#[derive_impl(pallet_assets::config_preludes::TestDefaultConfig)] +impl pallet_assets::Config for Test { + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type Freezer = (); +} + +#[derive_impl(pallet_assets::config_preludes::TestDefaultConfig)] +impl pallet_assets::Config for Test { + type Currency = Balances; + type CreateOrigin = + AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type Freezer = (); +} + +parameter_types! { + pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); + pub const Native: NativeOrWithId = NativeOrWithId::Native; + pub storage LiquidityWithdrawalFee: Permill = Permill::from_percent(0); +} + +ord_parameter_types! { + pub const AssetConversionOrigin: u64 = AccountIdConversion::::into_account_truncating(&AssetConversionPalletId::get()); +} + +pub type NativeAndAssets = UnionOf, u64>; +pub type PoolIdToAccountId = + AccountIdConverter, NativeOrWithId)>; +pub type AscendingLocator = Ascending, PoolIdToAccountId>; + +impl pallet_asset_conversion::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = ::Balance; + type HigherPrecisionBalance = sp_core::U256; + type AssetKind = NativeOrWithId; + type Assets = NativeAndAssets; + type PoolId = (Self::AssetKind, Self::AssetKind); + type PoolLocator = AscendingLocator; + type PoolAssetId = u32; + type PoolAssets = PoolAssets; + type PoolSetupFee = ConstU64<100>; + type PoolSetupFeeAsset = Native; + type PoolSetupFeeTarget = ResolveAssetTo; + type PalletId = AssetConversionPalletId; + type WeightInfo = (); + type LPFee = ConstU32<3>; + type LiquidityWithdrawalFee = LiquidityWithdrawalFee; + type MaxSwapPathLength = ConstU32<4>; + type MintMinLiquidity = ConstU64<100>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +pub type OldPoolIdToAccountId = + AccountIdConverterNoSeed<(NativeOrWithId, NativeOrWithId)>; + +impl pallet_asset_conversion_ops::Config for Test { + type RuntimeEvent = RuntimeEvent; + type PriorAccountIdConverter = OldPoolIdToAccountId; + type AssetsRefund = NativeAndAssets; + type PoolAssetsRefund = PoolAssets; + type PoolAssetsTeam = PoolAssets; + type DepositAsset = Balances; + type WeightInfo = (); +} + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(1, 10000), (2, 20000), (3, 30000), (4, 40000)], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/substrate/frame/asset-conversion/ops/src/tests.rs b/substrate/frame/asset-conversion/ops/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..84bbe6336747316967935737ed22f0794d21cdda --- /dev/null +++ b/substrate/frame/asset-conversion/ops/src/tests.rs @@ -0,0 +1,308 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Asset Conversion Ops pallet tests. + +use crate::{mock::*, *}; +use frame_support::{ + assert_noop, assert_ok, + traits::{ + fungible::{Inspect as FungibleInspect, NativeOrWithId}, + fungibles::{Create, Inspect}, + Incrementable, + }, +}; + +#[test] +fn migrate_pool_account_id_with_native() { + new_test_ext().execute_with(|| { + type PoolLocator = ::PoolLocator; + let user = 1; + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let pool_id = PoolLocator::pool_id(&token_1, &token_2).unwrap(); + let lp_token = + ::PoolAssetId::initial_value().unwrap(); + + // setup pool and provide some liquidity. + assert_ok!(NativeAndAssets::create(token_2.clone(), user, false, 1)); + + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + + let ed = Balances::minimum_balance(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 * 2 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + 10000, + 10, + 10000, + 10, + user, + )); + + // assert user's balance. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &user), 10000 + ed); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &user), 1000 - 10); + assert_eq!(PoolAssets::balance(lp_token, &user), 216); + + // record total issuances before migration. + let total_issuance_token1 = NativeAndAssets::total_issuance(token_1.clone()); + let total_issuance_token2 = NativeAndAssets::total_issuance(token_2.clone()); + let total_issuance_lp_token = PoolAssets::total_issuance(lp_token); + + let pool_account = PoolLocator::address(&pool_id).unwrap(); + let (prior_pool_account, new_pool_account) = + AssetConversionOps::addresses(&pool_id).unwrap(); + assert_eq!(pool_account, prior_pool_account); + + // assert pool's balances before migration. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &prior_pool_account), 10000); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &prior_pool_account), 10); + assert_eq!(PoolAssets::balance(lp_token, &prior_pool_account), 100); + + // migrate. + assert_ok!(AssetConversionOps::migrate_to_new_account( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + )); + + // assert user's balance has not changed. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &user), 10000 + ed); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &user), 1000 - 10); + assert_eq!(PoolAssets::balance(lp_token, &user), 216); + + // assert pool's balance on new account id is same as on prior account id. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &new_pool_account), 10000); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &new_pool_account), 10); + assert_eq!(PoolAssets::balance(lp_token, &new_pool_account), 100); + + // assert pool's balance on prior account id is zero. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &prior_pool_account), 0); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &prior_pool_account), 0); + assert_eq!(PoolAssets::balance(lp_token, &prior_pool_account), 0); + + // assert total issuance has not changed. + assert_eq!(total_issuance_token1, NativeAndAssets::total_issuance(token_1)); + assert_eq!(total_issuance_token2, NativeAndAssets::total_issuance(token_2)); + assert_eq!(total_issuance_lp_token, PoolAssets::total_issuance(lp_token)); + }); +} + +#[test] +fn migrate_pool_account_id_with_insufficient_assets() { + new_test_ext().execute_with(|| { + type PoolLocator = ::PoolLocator; + let user = 1; + let token_1 = NativeOrWithId::WithId(1); + let token_2 = NativeOrWithId::WithId(2); + let pool_id = PoolLocator::pool_id(&token_1, &token_2).unwrap(); + let lp_token = + ::PoolAssetId::initial_value().unwrap(); + + // setup pool and provide some liquidity. + assert_ok!(NativeAndAssets::create(token_1.clone(), user, false, 1)); + assert_ok!(NativeAndAssets::create(token_2.clone(), user, false, 1)); + + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 1, user, 20000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + 10000, + 10, + 10000, + 10, + user, + )); + + // assert user's balance. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &user), 10000); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &user), 1000 - 10); + assert_eq!(PoolAssets::balance(lp_token, &user), 216); + + // record total issuances before migration. + let total_issuance_token1 = NativeAndAssets::total_issuance(token_1.clone()); + let total_issuance_token2 = NativeAndAssets::total_issuance(token_2.clone()); + let total_issuance_lp_token = PoolAssets::total_issuance(lp_token); + + let pool_account = PoolLocator::address(&pool_id).unwrap(); + let (prior_pool_account, new_pool_account) = + AssetConversionOps::addresses(&pool_id).unwrap(); + assert_eq!(pool_account, prior_pool_account); + + // assert pool's balances before migration. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &prior_pool_account), 10000); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &prior_pool_account), 10); + assert_eq!(PoolAssets::balance(lp_token, &prior_pool_account), 100); + + // migrate. + assert_ok!(AssetConversionOps::migrate_to_new_account( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + )); + + // assert user's balance has not changed. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &user), 10000); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &user), 1000 - 10); + assert_eq!(PoolAssets::balance(lp_token, &user), 216); + + // assert pool's balance on new account id is same as on prior account id. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &new_pool_account), 10000); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &new_pool_account), 10); + assert_eq!(PoolAssets::balance(lp_token, &new_pool_account), 100); + + // assert pool's balance on prior account id is zero. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &prior_pool_account), 0); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &prior_pool_account), 0); + assert_eq!(PoolAssets::balance(lp_token, &prior_pool_account), 0); + + // assert total issuance has not changed. + assert_eq!(total_issuance_token1, NativeAndAssets::total_issuance(token_1)); + assert_eq!(total_issuance_token2, NativeAndAssets::total_issuance(token_2)); + assert_eq!(total_issuance_lp_token, PoolAssets::total_issuance(lp_token)); + }); +} + +#[test] +fn migrate_pool_account_id_with_sufficient_assets() { + new_test_ext().execute_with(|| { + type PoolLocator = ::PoolLocator; + let user = 1; + let token_1 = NativeOrWithId::WithId(1); + let token_2 = NativeOrWithId::WithId(2); + let pool_id = PoolLocator::pool_id(&token_1, &token_2).unwrap(); + let lp_token = + ::PoolAssetId::initial_value().unwrap(); + + // setup pool and provide some liquidity. + assert_ok!(NativeAndAssets::create(token_1.clone(), user, true, 1)); + assert_ok!(NativeAndAssets::create(token_2.clone(), user, true, 1)); + + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 1, user, 20000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + 10000, + 10, + 10000, + 10, + user, + )); + + // assert user's balance. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &user), 10000); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &user), 1000 - 10); + assert_eq!(PoolAssets::balance(lp_token, &user), 216); + + // record total issuances before migration. + let total_issuance_token1 = NativeAndAssets::total_issuance(token_1.clone()); + let total_issuance_token2 = NativeAndAssets::total_issuance(token_2.clone()); + let total_issuance_lp_token = PoolAssets::total_issuance(lp_token); + + let pool_account = PoolLocator::address(&pool_id).unwrap(); + let (prior_pool_account, new_pool_account) = + AssetConversionOps::addresses(&pool_id).unwrap(); + assert_eq!(pool_account, prior_pool_account); + + // assert pool's balances before migration. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &prior_pool_account), 10000); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &prior_pool_account), 10); + assert_eq!(PoolAssets::balance(lp_token, &prior_pool_account), 100); + + // migrate. + assert_ok!(AssetConversionOps::migrate_to_new_account( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + )); + + // assert user's balance has not changed. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &user), 10000); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &user), 1000 - 10); + assert_eq!(PoolAssets::balance(lp_token, &user), 216); + + // assert pool's balance on new account id is same as on prior account id. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &new_pool_account), 10000); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &new_pool_account), 10); + assert_eq!(PoolAssets::balance(lp_token, &new_pool_account), 100); + + // assert pool's balance on prior account id is zero. + assert_eq!(NativeAndAssets::balance(token_1.clone(), &prior_pool_account), 0); + assert_eq!(NativeAndAssets::balance(token_2.clone(), &prior_pool_account), 0); + assert_eq!(PoolAssets::balance(lp_token, &prior_pool_account), 0); + + // assert total issuance has not changed. + assert_eq!(total_issuance_token1, NativeAndAssets::total_issuance(token_1)); + assert_eq!(total_issuance_token2, NativeAndAssets::total_issuance(token_2)); + assert_eq!(total_issuance_lp_token, PoolAssets::total_issuance(lp_token)); + }); +} + +#[test] +fn migrate_empty_pool_account_id() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + + // setup pool and provide some liquidity. + assert_ok!(NativeAndAssets::create(token_2.clone(), user, false, 1)); + + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + + // migrate. + assert_noop!( + AssetConversionOps::migrate_to_new_account( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + ), + Error::::ZeroBalance + ); + }); +} diff --git a/substrate/frame/asset-conversion/ops/src/weights.rs b/substrate/frame/asset-conversion/ops/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..9e7379c50156e4f7bcb8a4a68881e6250c3600fb --- /dev/null +++ b/substrate/frame/asset-conversion/ops/src/weights.rs @@ -0,0 +1,104 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_asset_conversion_ops` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-04-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/production/substrate-node +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_conversion_ops +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/asset-conversion-ops/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_asset_conversion_ops`. +pub trait WeightInfo { + fn migrate_to_new_account() -> Weight; +} + +/// Weights for `pallet_asset_conversion_ops` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:4 w:4) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:2 w:2) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:2 w:2) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn migrate_to_new_account() -> Weight { + // Proof Size summary in bytes: + // Measured: `1762` + // Estimated: `11426` + // Minimum execution time: 223_850_000 picoseconds. + Weight::from_parts(231_676_000, 11426) + .saturating_add(T::DbWeight::get().reads(12_u64)) + .saturating_add(T::DbWeight::get().writes(11_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:4 w:4) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:2 w:2) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:2 w:2) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn migrate_to_new_account() -> Weight { + // Proof Size summary in bytes: + // Measured: `1762` + // Estimated: `11426` + // Minimum execution time: 223_850_000 picoseconds. + Weight::from_parts(231_676_000, 11426) + .saturating_add(RocksDbWeight::get().reads(12_u64)) + .saturating_add(RocksDbWeight::get().writes(11_u64)) + } +} diff --git a/substrate/frame/asset-conversion/src/benchmarking.rs b/substrate/frame/asset-conversion/src/benchmarking.rs index f0e02c802ad8e5a923cb9801c33c5539e9bd58a6..c5f68476b1d0a620425719877843b9a276a9f08e 100644 --- a/substrate/frame/asset-conversion/src/benchmarking.rs +++ b/substrate/frame/asset-conversion/src/benchmarking.rs @@ -24,7 +24,7 @@ use frame_support::{ assert_ok, traits::{ fungible::NativeOrWithId, - fungibles::{Create, Inspect, Mutate}, + fungibles::{Create, Inspect, Mutate, Refund}, }, }; use frame_system::RawOrigin as SystemOrigin; @@ -75,12 +75,21 @@ where } /// Create the `asset` and mint the `amount` for the `caller`. -fn create_asset(caller: &T::AccountId, asset: &T::AssetKind, amount: T::Balance) -where +fn create_asset( + caller: &T::AccountId, + asset: &T::AssetKind, + amount: T::Balance, + is_sufficient: bool, +) where T::Assets: Create + Mutate, { if !T::Assets::asset_exists(asset.clone()) { - assert_ok!(T::Assets::create(asset.clone(), caller.clone(), true, T::Balance::one())); + assert_ok!(T::Assets::create( + asset.clone(), + caller.clone(), + is_sufficient, + T::Balance::one() + )); } assert_ok!(T::Assets::mint_into( asset.clone(), @@ -141,8 +150,8 @@ where T::Assets::minimum_balance(asset1.clone()), T::Assets::minimum_balance(asset2.clone()), ); - create_asset::(caller, asset1, liquidity1); - create_asset::(caller, asset2, liquidity2); + create_asset::(caller, asset1, liquidity1, true); + create_asset::(caller, asset2, liquidity2, true); let lp_token = AssetConversion::::get_next_pool_asset_id(); mint_setup_fee_asset::(caller, asset1, asset2, &lp_token); @@ -172,8 +181,8 @@ mod benchmarks { fn create_pool() { let caller: T::AccountId = whitelisted_caller(); let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1); - create_asset::(&caller, &asset1, T::Assets::minimum_balance(asset1.clone())); - create_asset::(&caller, &asset2, T::Assets::minimum_balance(asset2.clone())); + create_asset::(&caller, &asset1, T::Assets::minimum_balance(asset1.clone()), true); + create_asset::(&caller, &asset2, T::Assets::minimum_balance(asset2.clone()), true); let lp_token = AssetConversion::::get_next_pool_asset_id(); create_fee_asset::(&caller); @@ -358,5 +367,47 @@ mod benchmarks { assert_eq!(actual_balance, init_caller_balance + T::Balance::one()); } + #[benchmark] + fn touch(n: Linear<0, 3>) { + let caller: T::AccountId = whitelisted_caller(); + let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1); + let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap(); + let pool_account = T::PoolLocator::address(&pool_id).unwrap(); + + create_fee_asset::(&caller); + create_asset::(&caller, &asset1, ::Balance::one(), false); + create_asset::(&caller, &asset2, ::Balance::one(), false); + let lp_token = AssetConversion::::get_next_pool_asset_id(); + mint_setup_fee_asset::(&caller, &asset1, &asset2, &lp_token); + + assert_ok!(AssetConversion::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + Box::new(asset1.clone()), + Box::new(asset2.clone()) + )); + + if n > 0 && + ::Assets::deposit_held(asset1.clone(), pool_account.clone()).is_some() + { + let _ = ::Assets::refund(asset1.clone(), pool_account.clone()); + } + if n > 1 && + ::Assets::deposit_held(asset2.clone(), pool_account.clone()).is_some() + { + let _ = ::Assets::refund(asset2.clone(), pool_account.clone()); + } + if n > 2 && + ::PoolAssets::deposit_held(lp_token.clone(), pool_account.clone()) + .is_some() + { + let _ = ::PoolAssets::refund(lp_token, pool_account); + } + + #[extrinsic_call] + _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone())); + + assert_last_event::(Event::Touched { pool_id, who: caller }.into()); + } + impl_benchmark_test_suite!(AssetConversion, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/substrate/frame/asset-conversion/src/lib.rs b/substrate/frame/asset-conversion/src/lib.rs index 62107211445c6de9b108c7d005c342b928cd3b2f..62acb693efb1c1e36bf22ad0530649f796a0d5a7 100644 --- a/substrate/frame/asset-conversion/src/lib.rs +++ b/substrate/frame/asset-conversion/src/lib.rs @@ -98,7 +98,10 @@ use sp_std::{boxed::Box, collections::btree_set::BTreeSet, vec::Vec}; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{ + pallet_prelude::{DispatchResult, *}, + traits::fungibles::Refund, + }; use frame_system::pallet_prelude::*; use sp_arithmetic::{traits::Unsigned, Permill}; @@ -130,7 +133,8 @@ pub mod pallet { type Assets: Inspect + Mutate + AccountTouch - + Balanced; + + Balanced + + Refund; /// Liquidity pool identifier. type PoolId: Parameter + MaxEncodedLen + Ord; @@ -149,7 +153,8 @@ pub mod pallet { type PoolAssets: Inspect + Create + Mutate - + AccountTouch; + + AccountTouch + + Refund; /// A % the liquidity providers will take of every swap. Represents 10ths of a percent. #[pallet::constant] @@ -281,6 +286,13 @@ pub mod pallet { /// E.g. (A, amount_in) -> (Dot, amount_out) -> (B, amount_out) path: BalancePath, }, + /// Pool has been touched in order to fulfill operational requirements. + Touched { + /// The ID of the pool. + pool_id: T::PoolId, + /// The account initiating the touch. + who: T::AccountId, + }, } #[pallet::error] @@ -391,7 +403,9 @@ pub mod pallet { NextPoolAssetId::::set(Some(next_lp_token_id)); T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?; - T::PoolAssets::touch(lp_token.clone(), &pool_account, &sender)?; + if T::PoolAssets::should_touch(lp_token.clone(), &pool_account) { + T::PoolAssets::touch(lp_token.clone(), &pool_account, &sender)? + }; let pool_info = PoolInfo { lp_token: lp_token.clone() }; Pools::::insert(pool_id.clone(), pool_info); @@ -663,6 +677,49 @@ pub mod pallet { )?; Ok(()) } + + /// Touch an existing pool to fulfill prerequisites before providing liquidity, such as + /// ensuring that the pool's accounts are in place. It is typically useful when a pool + /// creator removes the pool's accounts and does not provide a liquidity. This action may + /// involve holding assets from the caller as a deposit for creating the pool's accounts. + /// + /// The origin must be Signed. + /// + /// - `asset1`: The asset ID of an existing pool with a pair (asset1, asset2). + /// - `asset2`: The asset ID of an existing pool with a pair (asset1, asset2). + /// + /// Emits `Touched` event when successful. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::touch(3))] + pub fn touch( + origin: OriginFor, + asset1: Box, + asset2: Box, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let pool_id = T::PoolLocator::pool_id(&asset1, &asset2) + .map_err(|_| Error::::InvalidAssetPair)?; + let pool = Pools::::get(&pool_id).ok_or(Error::::PoolNotFound)?; + let pool_account = + T::PoolLocator::address(&pool_id).map_err(|_| Error::::InvalidAssetPair)?; + + let mut refunds_number: u32 = 0; + if T::Assets::should_touch(*asset1.clone(), &pool_account) { + T::Assets::touch(*asset1, &pool_account, &who)?; + refunds_number += 1; + } + if T::Assets::should_touch(*asset2.clone(), &pool_account) { + T::Assets::touch(*asset2, &pool_account, &who)?; + refunds_number += 1; + } + if T::PoolAssets::should_touch(pool.lp_token.clone(), &pool_account) { + T::PoolAssets::touch(pool.lp_token, &pool_account, &who)?; + refunds_number += 1; + } + Self::deposit_event(Event::Touched { pool_id, who }); + Ok(Some(T::WeightInfo::touch(refunds_number)).into()) + } } impl Pallet { diff --git a/substrate/frame/asset-conversion/src/mock.rs b/substrate/frame/asset-conversion/src/mock.rs index 4591b87c1867862e87a16e86c0d96a060bd1b7ff..477866e0051bc43207d1fb728380e5f84258964f 100644 --- a/substrate/frame/asset-conversion/src/mock.rs +++ b/substrate/frame/asset-conversion/src/mock.rs @@ -137,8 +137,11 @@ ord_parameter_types! { } pub type NativeAndAssets = UnionOf, u128>; -pub type AscendingLocator = Ascending>; -pub type WithFirstAssetLocator = WithFirstAsset>; +pub type PoolIdToAccountId = + AccountIdConverter, NativeOrWithId)>; +pub type AscendingLocator = Ascending, PoolIdToAccountId>; +pub type WithFirstAssetLocator = + WithFirstAsset, PoolIdToAccountId>; impl Config for Test { type RuntimeEvent = RuntimeEvent; diff --git a/substrate/frame/asset-conversion/src/types.rs b/substrate/frame/asset-conversion/src/types.rs index 5ee81c2012de226baf6aafbda0e2ec9a380cc1e2..27c0e8e68805ea7716ed7371aeed532be906fa47 100644 --- a/substrate/frame/asset-conversion/src/types.rs +++ b/substrate/frame/asset-conversion/src/types.rs @@ -19,6 +19,7 @@ use super::*; use codec::{Decode, Encode, MaxEncodedLen}; use core::marker::PhantomData; use scale_info::TypeInfo; +use sp_runtime::traits::TryConvert; /// Represents a swap path with associated asset amounts indicating how much of the asset needs to /// be deposited to get the following asset's amount withdrawn (this is inclusive of fees). @@ -68,49 +69,59 @@ pub trait PoolLocator { /// /// The `PoolId` is represented as a tuple of `AssetKind`s with `FirstAsset` always positioned as /// the first element. -pub struct WithFirstAsset( - PhantomData<(FirstAsset, AccountId, AssetKind)>, +pub struct WithFirstAsset( + PhantomData<(FirstAsset, AccountId, AssetKind, AccountIdConverter)>, ); -impl PoolLocator - for WithFirstAsset +impl + PoolLocator + for WithFirstAsset where AssetKind: Eq + Clone + Encode, AccountId: Decode, FirstAsset: Get, + AccountIdConverter: for<'a> TryConvert<&'a (AssetKind, AssetKind), AccountId>, { fn pool_id(asset1: &AssetKind, asset2: &AssetKind) -> Result<(AssetKind, AssetKind), ()> { + if asset1 == asset2 { + return Err(()); + } let first = FirstAsset::get(); - match true { - _ if asset1 == asset2 => Err(()), - _ if first == *asset1 => Ok((first, asset2.clone())), - _ if first == *asset2 => Ok((first, asset1.clone())), - _ => Err(()), + if first == *asset1 { + Ok((first, asset2.clone())) + } else if first == *asset2 { + Ok((first, asset1.clone())) + } else { + Err(()) } } fn address(id: &(AssetKind, AssetKind)) -> Result { - let encoded = sp_io::hashing::blake2_256(&Encode::encode(id)[..]); - Decode::decode(&mut TrailingZeroInput::new(encoded.as_ref())).map_err(|_| ()) + AccountIdConverter::try_convert(id).map_err(|_| ()) } } /// Pool locator where the `PoolId` is a tuple of `AssetKind`s arranged in ascending order. -pub struct Ascending(PhantomData<(AccountId, AssetKind)>); -impl PoolLocator - for Ascending +pub struct Ascending( + PhantomData<(AccountId, AssetKind, AccountIdConverter)>, +); +impl + PoolLocator + for Ascending where AssetKind: Ord + Clone + Encode, AccountId: Decode, + AccountIdConverter: for<'a> TryConvert<&'a (AssetKind, AssetKind), AccountId>, { fn pool_id(asset1: &AssetKind, asset2: &AssetKind) -> Result<(AssetKind, AssetKind), ()> { - match true { - _ if asset1 > asset2 => Ok((asset2.clone(), asset1.clone())), - _ if asset1 < asset2 => Ok((asset1.clone(), asset2.clone())), - _ => Err(()), + if asset1 > asset2 { + Ok((asset2.clone(), asset1.clone())) + } else if asset1 < asset2 { + Ok((asset1.clone(), asset2.clone())) + } else { + Err(()) } } fn address(id: &(AssetKind, AssetKind)) -> Result { - let encoded = sp_io::hashing::blake2_256(&Encode::encode(id)[..]); - Decode::decode(&mut TrailingZeroInput::new(encoded.as_ref())).map_err(|_| ()) + AccountIdConverter::try_convert(id).map_err(|_| ()) } } @@ -131,3 +142,30 @@ where First::address(id).or(Second::address(id)) } } + +/// `PoolId` to `AccountId` conversion. +pub struct AccountIdConverter(PhantomData<(Seed, PoolId)>); +impl TryConvert<&PoolId, AccountId> for AccountIdConverter +where + PoolId: Encode, + AccountId: Decode, + Seed: Get, +{ + fn try_convert(id: &PoolId) -> Result { + sp_io::hashing::blake2_256(&Encode::encode(&(Seed::get(), id))[..]) + .using_encoded(|e| Decode::decode(&mut TrailingZeroInput::new(e)).map_err(|_| id)) + } +} + +/// `PoolId` to `AccountId` conversion without an addition arguments to the seed. +pub struct AccountIdConverterNoSeed(PhantomData); +impl TryConvert<&PoolId, AccountId> for AccountIdConverterNoSeed +where + PoolId: Encode, + AccountId: Decode, +{ + fn try_convert(id: &PoolId) -> Result { + sp_io::hashing::blake2_256(&Encode::encode(id)[..]) + .using_encoded(|e| Decode::decode(&mut TrailingZeroInput::new(e)).map_err(|_| id)) + } +} diff --git a/substrate/frame/asset-conversion/src/weights.rs b/substrate/frame/asset-conversion/src/weights.rs index 212f56073f942bc4964ea7af2a5dde80fed1a4a5..9aea19dbf57c6adf2936d4141987d198cd944fb7 100644 --- a/substrate/frame/asset-conversion/src/weights.rs +++ b/substrate/frame/asset-conversion/src/weights.rs @@ -18,27 +18,25 @@ //! Autogenerated weights for `pallet_asset_conversion` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-03-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-p5qp1txx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// target/production/substrate-node // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_asset_conversion -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --output=./substrate/frame/asset-conversion/src/weights.rs +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_conversion +// --chain=dev // --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/asset-conversion/src/weights.rs // --template=./substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -56,6 +54,7 @@ pub trait WeightInfo { fn remove_liquidity() -> Weight; fn swap_exact_tokens_for_tokens(n: u32, ) -> Weight; fn swap_tokens_for_exact_tokens(n: u32, ) -> Weight; + fn touch(n: u32, ) -> Weight; } /// Weights for `pallet_asset_conversion` using the Substrate node and recommended hardware. @@ -63,7 +62,7 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// Storage: `AssetConversion::Pools` (r:1 w:1) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) + /// Storage: `System::Account` (r:2 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Assets::Asset` (r:2 w:0) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) @@ -77,9 +76,9 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `910` // Estimated: `6360` - // Minimum execution time: 86_709_000 picoseconds. - Weight::from_parts(88_841_000, 6360) - .saturating_add(T::DbWeight::get().reads(7_u64)) + // Minimum execution time: 95_080_000 picoseconds. + Weight::from_parts(97_241_000, 6360) + .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `AssetConversion::Pools` (r:1 w:0) @@ -98,8 +97,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1507` // Estimated: `11426` - // Minimum execution time: 148_672_000 picoseconds. - Weight::from_parts(151_824_000, 11426) + // Minimum execution time: 147_652_000 picoseconds. + Weight::from_parts(153_331_000, 11426) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().writes(10_u64)) } @@ -117,8 +116,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `11426` - // Minimum execution time: 130_743_000 picoseconds. - Weight::from_parts(132_793_000, 11426) + // Minimum execution time: 130_738_000 picoseconds. + Weight::from_parts(134_350_000, 11426) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(8_u64)) } @@ -131,10 +130,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `89 + n * (419 ±0)` // Estimated: `990 + n * (5218 ±0)` - // Minimum execution time: 81_173_000 picoseconds. - Weight::from_parts(82_574_000, 990) - // Standard Error: 335_929 - .saturating_add(Weight::from_parts(11_607_291, 0).saturating_mul(n.into())) + // Minimum execution time: 79_681_000 picoseconds. + Weight::from_parts(81_461_000, 990) + // Standard Error: 320_959 + .saturating_add(Weight::from_parts(11_223_703, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into())) @@ -148,21 +147,45 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `89 + n * (419 ±0)` // Estimated: `990 + n * (5218 ±0)` - // Minimum execution time: 80_562_000 picoseconds. - Weight::from_parts(82_501_000, 990) - // Standard Error: 329_460 - .saturating_add(Weight::from_parts(11_295_339, 0).saturating_mul(n.into())) + // Minimum execution time: 78_988_000 picoseconds. + Weight::from_parts(81_025_000, 990) + // Standard Error: 320_021 + .saturating_add(Weight::from_parts(11_040_712, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into())) } + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:2 w:2) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 3]`. + fn touch(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1571` + // Estimated: `6360` + // Minimum execution time: 45_757_000 picoseconds. + Weight::from_parts(48_502_032, 6360) + // Standard Error: 62_850 + .saturating_add(Weight::from_parts(19_450_978, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + } } // For backwards compatibility and tests. impl WeightInfo for () { /// Storage: `AssetConversion::Pools` (r:1 w:1) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) + /// Storage: `System::Account` (r:2 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Assets::Asset` (r:2 w:0) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) @@ -176,9 +199,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `910` // Estimated: `6360` - // Minimum execution time: 86_709_000 picoseconds. - Weight::from_parts(88_841_000, 6360) - .saturating_add(RocksDbWeight::get().reads(7_u64)) + // Minimum execution time: 95_080_000 picoseconds. + Weight::from_parts(97_241_000, 6360) + .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `AssetConversion::Pools` (r:1 w:0) @@ -197,8 +220,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1507` // Estimated: `11426` - // Minimum execution time: 148_672_000 picoseconds. - Weight::from_parts(151_824_000, 11426) + // Minimum execution time: 147_652_000 picoseconds. + Weight::from_parts(153_331_000, 11426) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().writes(10_u64)) } @@ -216,8 +239,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `11426` - // Minimum execution time: 130_743_000 picoseconds. - Weight::from_parts(132_793_000, 11426) + // Minimum execution time: 130_738_000 picoseconds. + Weight::from_parts(134_350_000, 11426) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(8_u64)) } @@ -230,10 +253,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `89 + n * (419 ±0)` // Estimated: `990 + n * (5218 ±0)` - // Minimum execution time: 81_173_000 picoseconds. - Weight::from_parts(82_574_000, 990) - // Standard Error: 335_929 - .saturating_add(Weight::from_parts(11_607_291, 0).saturating_mul(n.into())) + // Minimum execution time: 79_681_000 picoseconds. + Weight::from_parts(81_461_000, 990) + // Standard Error: 320_959 + .saturating_add(Weight::from_parts(11_223_703, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into())) @@ -247,12 +270,36 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `89 + n * (419 ±0)` // Estimated: `990 + n * (5218 ±0)` - // Minimum execution time: 80_562_000 picoseconds. - Weight::from_parts(82_501_000, 990) - // Standard Error: 329_460 - .saturating_add(Weight::from_parts(11_295_339, 0).saturating_mul(n.into())) + // Minimum execution time: 78_988_000 picoseconds. + Weight::from_parts(81_025_000, 990) + // Standard Error: 320_021 + .saturating_add(Weight::from_parts(11_040_712, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into())) } + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:2 w:2) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 3]`. + fn touch(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1571` + // Estimated: `6360` + // Minimum execution time: 45_757_000 picoseconds. + Weight::from_parts(48_502_032, 6360) + // Standard Error: 62_850 + .saturating_add(Weight::from_parts(19_450_978, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(n.into()))) + } } diff --git a/substrate/frame/assets/Cargo.toml b/substrate/frame/assets/Cargo.toml index 3b95750c14c8c9c78ad86442085580810e447f5a..ed6df77e15232d7249fdcdb5da41a673ef420994 100644 --- a/substrate/frame/assets/Cargo.toml +++ b/substrate/frame/assets/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-assets" -version = "29.0.0" +version = "29.1.0" authors.workspace = true edition.workspace = true license = "Apache-2.0" diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index 8791aaa736b350292cfce58c4d4067f3e8af101b..9309d010117576177f6bb055e732c8caf2f13bcb 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -368,11 +368,14 @@ impl, I: 'static> Pallet { Ok(()) } - /// Returns a `DepositFrom` of an account only if balance is zero. + /// Refunds the `DepositFrom` of an account only if its balance is zero. + /// + /// If the `maybe_check_caller` parameter is specified, it must match the account that provided + /// the deposit or must be the admin of the asset. pub(super) fn do_refund_other( id: T::AssetId, who: &T::AccountId, - caller: &T::AccountId, + maybe_check_caller: Option, ) -> DispatchResult { let mut account = Account::::get(&id, &who).ok_or(Error::::NoDeposit)?; let (depositor, deposit) = @@ -380,7 +383,9 @@ impl, I: 'static> Pallet { let mut details = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); ensure!(!account.status.is_frozen(), Error::::Frozen); - ensure!(caller == &depositor || caller == &details.admin, Error::::NoPermission); + if let Some(caller) = maybe_check_caller { + ensure!(caller == depositor || caller == details.admin, Error::::NoPermission); + } ensure!(account.balance.is_zero(), Error::::WouldBurn); T::Currency::unreserve(&depositor, deposit); @@ -491,7 +496,7 @@ impl, I: 'static> Pallet { let d = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!( d.status == AssetStatus::Live || d.status == AssetStatus::Frozen, - Error::::AssetNotLive + Error::::IncorrectStatus ); let actual = Self::decrease_balance(id.clone(), target, amount, f, |actual, details| { @@ -1013,4 +1018,28 @@ impl, I: 'static> Pallet { }) .collect::>() } + + /// Reset the team for the asset with the given `id`. + /// + /// ### Parameters + /// - `id`: The identifier of the asset for which the team is being reset. + /// - `owner`: The new `owner` account for the asset. + /// - `admin`: The new `admin` account for the asset. + /// - `issuer`: The new `issuer` account for the asset. + /// - `freezer`: The new `freezer` account for the asset. + pub(crate) fn do_reset_team( + id: T::AssetId, + owner: T::AccountId, + admin: T::AccountId, + issuer: T::AccountId, + freezer: T::AccountId, + ) -> DispatchResult { + let mut d = Asset::::get(&id).ok_or(Error::::Unknown)?; + d.owner = owner; + d.admin = admin; + d.issuer = issuer; + d.freezer = freezer; + Asset::::insert(&id, d); + Ok(()) + } } diff --git a/substrate/frame/assets/src/impl_fungibles.rs b/substrate/frame/assets/src/impl_fungibles.rs index 123abeba8283fce8300521a80acead12c1616933..30122f6d788ff868b6f077f3e0c4e7cd3b26915f 100644 --- a/substrate/frame/assets/src/impl_fungibles.rs +++ b/substrate/frame/assets/src/impl_fungibles.rs @@ -118,6 +118,22 @@ impl, I: 'static> fungibles::Balanced<::AccountI { type OnDropCredit = fungibles::DecreaseIssuance; type OnDropDebt = fungibles::IncreaseIssuance; + + fn done_deposit( + asset_id: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) { + Self::deposit_event(Event::Deposited { asset_id, who: who.clone(), amount }) + } + + fn done_withdraw( + asset_id: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) { + Self::deposit_event(Event::Withdrawn { asset_id, who: who.clone(), amount }) + } } impl, I: 'static> fungibles::Unbalanced for Pallet { @@ -308,3 +324,35 @@ impl, I: 'static> fungibles::InspectEnumerable for Pa Asset::::iter_keys() } } + +impl, I: 'static> fungibles::roles::ResetTeam for Pallet { + fn reset_team( + id: T::AssetId, + owner: T::AccountId, + admin: T::AccountId, + issuer: T::AccountId, + freezer: T::AccountId, + ) -> DispatchResult { + Self::do_reset_team(id, owner, admin, issuer, freezer) + } +} + +impl, I: 'static> fungibles::Refund for Pallet { + type AssetId = T::AssetId; + type Balance = DepositBalanceOf; + fn deposit_held(id: Self::AssetId, who: T::AccountId) -> Option<(T::AccountId, Self::Balance)> { + use ExistenceReason::*; + match Account::::get(&id, &who).ok_or(Error::::NoDeposit).ok()?.reason { + DepositHeld(b) => Some((who, b)), + DepositFrom(d, b) => Some((d, b)), + _ => None, + } + } + fn refund(id: Self::AssetId, who: T::AccountId) -> DispatchResult { + match Self::deposit_held(id.clone(), who.clone()) { + Some((d, _)) if d == who => Self::do_refund(id, who, false), + Some(..) => Self::do_refund_other(id, &who, None), + None => Err(Error::::NoDeposit.into()), + } + } +} diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index 6891f04dfb51aae356a708112a7bba284e894be2..d5214922555890ac96a931458b01522da1b52779 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -571,6 +571,10 @@ pub mod pallet { Touched { asset_id: T::AssetId, who: T::AccountId, depositor: T::AccountId }, /// Some account `who` was blocked. Blocked { asset_id: T::AssetId, who: T::AccountId }, + /// Some assets were deposited (e.g. for transaction fees). + Deposited { asset_id: T::AssetId, who: T::AccountId, amount: T::Balance }, + /// Some assets were withdrawn from the account (e.g. for transaction fees). + Withdrawn { asset_id: T::AssetId, who: T::AccountId, amount: T::Balance }, } #[pallet::error] @@ -987,7 +991,7 @@ pub mod pallet { let d = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!( d.status == AssetStatus::Live || d.status == AssetStatus::Frozen, - Error::::AssetNotLive + Error::::IncorrectStatus ); ensure!(origin == d.freezer, Error::::NoPermission); let who = T::Lookup::lookup(who)?; @@ -1024,7 +1028,7 @@ pub mod pallet { let details = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!( details.status == AssetStatus::Live || details.status == AssetStatus::Frozen, - Error::::AssetNotLive + Error::::IncorrectStatus ); ensure!(origin == details.admin, Error::::NoPermission); let who = T::Lookup::lookup(who)?; @@ -1113,7 +1117,7 @@ pub mod pallet { Asset::::try_mutate(id.clone(), |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(details.status == AssetStatus::Live, Error::::LiveAsset); + ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); ensure!(origin == details.owner, Error::::NoPermission); if details.owner == owner { return Ok(()) @@ -1644,7 +1648,7 @@ pub mod pallet { let origin = ensure_signed(origin)?; let who = T::Lookup::lookup(who)?; let id: T::AssetId = id.into(); - Self::do_refund_other(id, &who, &origin) + Self::do_refund_other(id, &who, Some(origin)) } /// Disallow further unprivileged transfers of an asset `id` to and from an account `who`. @@ -1669,7 +1673,7 @@ pub mod pallet { let d = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!( d.status == AssetStatus::Live || d.status == AssetStatus::Frozen, - Error::::AssetNotLive + Error::::IncorrectStatus ); ensure!(origin == d.freezer, Error::::NoPermission); let who = T::Lookup::lookup(who)?; @@ -1697,7 +1701,9 @@ pub mod pallet { fn should_touch(asset: T::AssetId, who: &T::AccountId) -> bool { match Asset::::get(&asset) { + // refer to the [`Self::new_account`] function for more details. Some(info) if info.is_sufficient => false, + Some(_) if frame_system::Pallet::::can_accrue_consumers(who, 2) => false, Some(_) => !Account::::contains_key(asset, who), _ => true, } diff --git a/substrate/frame/assets/src/tests/sets.rs b/substrate/frame/assets/src/tests/sets.rs index f85a736c08320c22df3ec3ad9d0aeaa74cc1706c..4d75b8aeab2c51841b751705b3ffaf7a321e5b08 100644 --- a/substrate/frame/assets/src/tests/sets.rs +++ b/substrate/frame/assets/src/tests/sets.rs @@ -90,6 +90,12 @@ fn deposit_from_set_types_works() { assert_eq!(First::::balance((), &account2), 50); assert_eq!(First::::total_issuance(()), 100); + System::assert_has_event(RuntimeEvent::Assets(crate::Event::Deposited { + asset_id: asset1, + who: account2, + amount: 50, + })); + assert_eq!(imb.peek(), 50); let (imb1, imb2) = imb.split(30); @@ -336,6 +342,12 @@ fn withdraw_from_set_types_works() { assert_eq!(First::::balance((), &account2), 50); assert_eq!(First::::total_issuance(()), 200); + System::assert_has_event(RuntimeEvent::Assets(crate::Event::Withdrawn { + asset_id: asset1, + who: account2, + amount: 50, + })); + assert_eq!(imb.peek(), 50); drop(imb); assert_eq!(First::::total_issuance(()), 150); diff --git a/substrate/frame/atomic-swap/src/lib.rs b/substrate/frame/atomic-swap/src/lib.rs index 609903e67e3e56f7016a20772a996a41705839d7..dc0300dc1a5c95d2654dfa5a210e6efab26125f6 100644 --- a/substrate/frame/atomic-swap/src/lib.rs +++ b/substrate/frame/atomic-swap/src/lib.rs @@ -58,6 +58,7 @@ use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::RuntimeDebug; +use sp_std::vec::Vec; /// Pending atomic swap operation. #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index ec54275278eb625d9648b9fd744a3f7aab0a646a..395a86e6528807ff21ee2667d1446016b6826a1e 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -144,7 +144,6 @@ parameter_types! { pub const BondingDuration: EraIndex = 3; pub const SlashDeferDuration: EraIndex = 0; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(16); pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); } @@ -174,7 +173,6 @@ impl pallet_staking::Config for Test { type UnixTime = pallet_timestamp::Pallet; type EraPayout = pallet_staking::ConvertCurve; type MaxExposurePageSize = ConstU32<64>; - type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; @@ -187,6 +185,7 @@ impl pallet_staking::Config for Test { type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } impl pallet_offences::Config for Test { diff --git a/substrate/frame/balances/Cargo.toml b/substrate/frame/balances/Cargo.toml index 28eabdaf5062b4c7250bb5a03dc64debb2f37a8c..1cc9ac5d8fd25c318351ede5e934d1ef93d89db6 100644 --- a/substrate/frame/balances/Cargo.toml +++ b/substrate/frame/balances/Cargo.toml @@ -28,6 +28,7 @@ docify = "0.2.8" [dev-dependencies] pallet-transaction-payment = { path = "../transaction-payment" } +frame-support = { path = "../support", features = ["experimental"] } sp-core = { path = "../../primitives/core" } sp-io = { path = "../../primitives/io" } paste = "1.0.12" diff --git a/substrate/frame/balances/src/lib.rs b/substrate/frame/balances/src/lib.rs index 88dd88ff8ded4f125f0054338cc25c707e6c56e6..8d904d3d21b8acbf8469b8213d1220c277d3dd11 100644 --- a/substrate/frame/balances/src/lib.rs +++ b/substrate/frame/balances/src/lib.rs @@ -980,6 +980,13 @@ pub mod pallet { if !did_consume && does_consume { frame_system::Pallet::::inc_consumers(who)?; } + if does_consume && frame_system::Pallet::::consumers(who) == 0 { + // NOTE: This is a failsafe and should not happen for normal accounts. A normal + // account should have gotten a consumer ref in `!did_consume && does_consume` + // at some point. + log::error!(target: LOG_TARGET, "Defensively bumping a consumer ref."); + frame_system::Pallet::::inc_consumers(who)?; + } if did_provide && !does_provide { // This could reap the account so must go last. frame_system::Pallet::::dec_providers(who).map_err(|r| { diff --git a/substrate/frame/balances/src/tests/general_tests.rs b/substrate/frame/balances/src/tests/general_tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..0f3e015d0a89242dd321285b84a134bd907702ec --- /dev/null +++ b/substrate/frame/balances/src/tests/general_tests.rs @@ -0,0 +1,111 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(test)] + +use crate::{ + system::AccountInfo, + tests::{ensure_ti_valid, Balances, ExtBuilder, System, Test, TestId, UseSystem}, + AccountData, ExtraFlags, TotalIssuance, +}; +use frame_support::{ + assert_noop, assert_ok, hypothetically, + traits::{ + fungible::{Mutate, MutateHold}, + tokens::Precision, + }, +}; +use sp_runtime::DispatchError; + +/// There are some accounts that have one consumer ref too few. These accounts are at risk of losing +/// their held (reserved) balance. They do not just lose it - it is also not accounted for in the +/// Total Issuance. Here we test the case that the account does not reap in such a case, but gets +/// one consumer ref for its reserved balance. +#[test] +fn regression_historic_acc_does_not_evaporate_reserve() { + ExtBuilder::default().build_and_execute_with(|| { + UseSystem::set(true); + let (alice, bob) = (0, 1); + // Alice is in a bad state with consumer == 0 && reserved > 0: + Balances::set_balance(&alice, 100); + TotalIssuance::::put(100); + ensure_ti_valid(); + + assert_ok!(Balances::hold(&TestId::Foo, &alice, 10)); + // This is the issue of the account: + System::dec_consumers(&alice); + + assert_eq!( + System::account(&alice), + AccountInfo { + data: AccountData { + free: 90, + reserved: 10, + frozen: 0, + flags: ExtraFlags(1u128 << 127), + }, + nonce: 0, + consumers: 0, // should be 1 on a good acc + providers: 1, + sufficients: 0, + } + ); + + ensure_ti_valid(); + + // Reaping the account is prevented by the new logic: + assert_noop!( + Balances::transfer_allow_death(Some(alice).into(), bob, 90), + DispatchError::ConsumerRemaining + ); + assert_noop!( + Balances::transfer_all(Some(alice).into(), bob, false), + DispatchError::ConsumerRemaining + ); + + // normal transfers still work: + hypothetically!({ + assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40)); + // Alice got back her consumer ref: + assert_eq!(System::consumers(&alice), 1); + ensure_ti_valid(); + }); + hypothetically!({ + assert_ok!(Balances::transfer_all(Some(alice).into(), bob, true)); + // Alice got back her consumer ref: + assert_eq!(System::consumers(&alice), 1); + ensure_ti_valid(); + }); + + // un-reserving all does not add a consumer ref: + hypothetically!({ + assert_ok!(Balances::release(&TestId::Foo, &alice, 10, Precision::Exact)); + assert_eq!(System::consumers(&alice), 0); + assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40)); + assert_eq!(System::consumers(&alice), 0); + ensure_ti_valid(); + }); + // un-reserving some does add a consumer ref: + hypothetically!({ + assert_ok!(Balances::release(&TestId::Foo, &alice, 5, Precision::Exact)); + assert_eq!(System::consumers(&alice), 1); + assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40)); + assert_eq!(System::consumers(&alice), 1); + ensure_ti_valid(); + }); + }); +} diff --git a/substrate/frame/balances/src/tests/mod.rs b/substrate/frame/balances/src/tests/mod.rs index 234fe6eaf2c3298183d55d646a8af6bad14153c1..0abf2251290fee6881f0346aaab7df37ff2f0efb 100644 --- a/substrate/frame/balances/src/tests/mod.rs +++ b/substrate/frame/balances/src/tests/mod.rs @@ -19,7 +19,7 @@ #![cfg(test)] -use crate::{self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet}; +use crate::{self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet, TotalIssuance}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ assert_err, assert_noop, assert_ok, assert_storage_noop, derive_impl, @@ -47,6 +47,7 @@ mod currency_tests; mod dispatchable_tests; mod fungible_conformance_tests; mod fungible_tests; +mod general_tests; mod reentrancy_tests; type Block = frame_system::mocking::MockBlock; @@ -278,6 +279,23 @@ pub fn info_from_weight(w: Weight) -> DispatchInfo { DispatchInfo { weight: w, ..Default::default() } } +/// Check that the total-issuance matches the sum of all accounts' total balances. +pub fn ensure_ti_valid() { + let mut sum = 0; + + for acc in frame_system::Account::::iter_keys() { + if UseSystem::get() { + let data = frame_system::Pallet::::account(acc); + sum += data.data.total(); + } else { + let data = crate::Account::::get(acc); + sum += data.total(); + } + } + + assert_eq!(TotalIssuance::::get(), sum, "Total Issuance wrong"); +} + #[test] fn weights_sane() { let info = crate::Call::::transfer_allow_death { dest: 10, value: 4 }.get_dispatch_info(); diff --git a/substrate/frame/balances/src/types.rs b/substrate/frame/balances/src/types.rs index 69d33bb023f397ba7b2464004224a233c4b913a9..3e36a83575c892812d687e5882784b023754fe44 100644 --- a/substrate/frame/balances/src/types.rs +++ b/substrate/frame/balances/src/types.rs @@ -111,7 +111,7 @@ pub struct AccountData { const IS_NEW_LOGIC: u128 = 0x80000000_00000000_00000000_00000000u128; #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct ExtraFlags(u128); +pub struct ExtraFlags(pub(crate) u128); impl Default for ExtraFlags { fn default() -> Self { Self(IS_NEW_LOGIC) diff --git a/substrate/frame/beefy-mmr/src/mock.rs b/substrate/frame/beefy-mmr/src/mock.rs index 9d1ece7a1d8ecf433f6d244e55fb1f6e6211e77e..d59c219d3e71eae1c38975fc8bec137cc27b9076 100644 --- a/substrate/frame/beefy-mmr/src/mock.rs +++ b/substrate/frame/beefy-mmr/src/mock.rs @@ -90,6 +90,8 @@ impl pallet_mmr::Config for Test { type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest; + type BlockHashProvider = pallet_mmr::DefaultBlockHashProvider; + type WeightInfo = (); } diff --git a/substrate/frame/beefy/src/mock.rs b/substrate/frame/beefy/src/mock.rs index 1c55adc8de4b7d87535367e1f934c9db171b4902..0b87de6bf5d79a3884d96f6f7cf79fc96ab3568b 100644 --- a/substrate/frame/beefy/src/mock.rs +++ b/substrate/frame/beefy/src/mock.rs @@ -158,7 +158,6 @@ parameter_types! { pub const SessionsPerEra: SessionIndex = 3; pub const BondingDuration: EraIndex = 3; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); pub static ElectionsBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default().build(); } @@ -188,7 +187,6 @@ impl pallet_staking::Config for Test { type UnixTime = pallet_timestamp::Pallet; type EraPayout = pallet_staking::ConvertCurve; type MaxExposurePageSize = ConstU32<64>; - type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; @@ -201,6 +199,7 @@ impl pallet_staking::Config for Test { type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } impl pallet_offences::Config for Test { diff --git a/substrate/frame/broker/Cargo.toml b/substrate/frame/broker/Cargo.toml index 969f13e269de4208a220150d688a21ffcc0191f1..ce8d41530451f978873a15a9a2e5538976ae706e 100644 --- a/substrate/frame/broker/Cargo.toml +++ b/substrate/frame/broker/Cargo.toml @@ -15,6 +15,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +log = { workspace = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } bitvec = { version = "1.0.0", default-features = false } @@ -29,6 +30,7 @@ frame-system = { path = "../system", default-features = false } [dev-dependencies] sp-io = { path = "../../primitives/io" } +pretty_assertions = "1.3.0" [features] default = ["std"] @@ -39,6 +41,7 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", + "log/std", "scale-info/std", "sp-api/std", "sp-arithmetic/std", diff --git a/substrate/frame/broker/src/benchmarking.rs b/substrate/frame/broker/src/benchmarking.rs index 98ac074ca91778cfd2538cc0e89f90ca79a62ad5..7533e3dc68c41fa54d39b7bb234ecaafe4e56fab 100644 --- a/substrate/frame/broker/src/benchmarking.rs +++ b/substrate/frame/broker/src/benchmarking.rs @@ -189,11 +189,15 @@ mod benches { let config = new_config_record::(); Configuration::::put(config.clone()); + let mut extra_cores = n; + // Assume Reservations to be filled for worst case - setup_reservations::(T::MaxReservedCores::get()); + setup_reservations::(extra_cores.min(T::MaxReservedCores::get())); + extra_cores = extra_cores.saturating_sub(T::MaxReservedCores::get()); // Assume Leases to be filled for worst case - setup_leases::(T::MaxLeasedCores::get(), 1, 10); + setup_leases::(extra_cores.min(T::MaxLeasedCores::get()), 1, 10); + extra_cores = extra_cores.saturating_sub(T::MaxLeasedCores::get()); let latest_region_begin = Broker::::latest_timeslice_ready_to_commit(&config); @@ -203,7 +207,7 @@ mod benches { T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] - _(origin as T::RuntimeOrigin, initial_price, n.try_into().unwrap()); + _(origin as T::RuntimeOrigin, initial_price, extra_cores.try_into().unwrap()); assert!(SaleInfo::::get().is_some()); assert_last_event::( @@ -313,8 +317,8 @@ mod benches { assert_last_event::( Event::Transferred { region_id: region, - old_owner: caller, - owner: recipient, + old_owner: Some(caller), + owner: Some(recipient), duration: 3u32.into(), } .into(), diff --git a/substrate/frame/broker/src/dispatchable_impls.rs b/substrate/frame/broker/src/dispatchable_impls.rs index ef20bc8fb80b19eaddef8fa6011dac17ffa22609..45a0a514c307adaeb8be65f514e6c46251210d06 100644 --- a/substrate/frame/broker/src/dispatchable_impls.rs +++ b/substrate/frame/broker/src/dispatchable_impls.rs @@ -70,8 +70,16 @@ impl Pallet { Ok(()) } - pub(crate) fn do_start_sales(price: BalanceOf, core_count: CoreIndex) -> DispatchResult { + pub(crate) fn do_start_sales(price: BalanceOf, extra_cores: CoreIndex) -> DispatchResult { let config = Configuration::::get().ok_or(Error::::Uninitialized)?; + + // Determine the core count + let core_count = Leases::::decode_len().unwrap_or(0) as CoreIndex + + Reservations::::decode_len().unwrap_or(0) as CoreIndex + + extra_cores; + + Self::do_request_core_count(core_count)?; + let commit_timeslice = Self::latest_timeslice_ready_to_commit(&config); let status = StatusRecord { core_count, @@ -81,7 +89,8 @@ impl Pallet { last_timeslice: Self::current_timeslice(), }; let now = frame_system::Pallet::::block_number(); - let new_sale = SaleInfoRecord { + // Imaginary old sale for bootstrapping the first actual sale: + let old_sale = SaleInfoRecord { sale_start: now, leadin_length: Zero::zero(), price, @@ -94,7 +103,7 @@ impl Pallet { cores_sold: 0, }; Self::deposit_event(Event::::SalesStarted { price, core_count }); - Self::rotate_sale(new_sale, &config, &status); + Self::rotate_sale(old_sale, &config, &status); Status::::put(&status); Ok(()) } @@ -119,7 +128,8 @@ impl Pallet { sale.sellout_price = Some(price); } SaleInfo::::put(&sale); - let id = Self::issue(core, sale.region_begin, sale.region_end, who.clone(), Some(price)); + let id = + Self::issue(core, sale.region_begin, sale.region_end, Some(who.clone()), Some(price)); let duration = sale.region_end.saturating_sub(sale.region_begin); Self::deposit_event(Event::Purchased { who, region_id: id, price, duration }); Ok(id) @@ -177,11 +187,11 @@ impl Pallet { let mut region = Regions::::get(®ion_id).ok_or(Error::::UnknownRegion)?; if let Some(check_owner) = maybe_check_owner { - ensure!(check_owner == region.owner, Error::::NotOwner); + ensure!(Some(check_owner) == region.owner, Error::::NotOwner); } let old_owner = region.owner; - region.owner = new_owner; + region.owner = Some(new_owner); Regions::::insert(®ion_id, ®ion); let duration = region.end.saturating_sub(region_id.begin); Self::deposit_event(Event::Transferred { @@ -202,7 +212,7 @@ impl Pallet { let mut region = Regions::::get(®ion_id).ok_or(Error::::UnknownRegion)?; if let Some(check_owner) = maybe_check_owner { - ensure!(check_owner == region.owner, Error::::NotOwner); + ensure!(Some(check_owner) == region.owner, Error::::NotOwner); } let pivot = region_id.begin.saturating_add(pivot_offset); ensure!(pivot < region.end, Error::::PivotTooLate); @@ -226,7 +236,7 @@ impl Pallet { let region = Regions::::get(®ion_id).ok_or(Error::::UnknownRegion)?; if let Some(check_owner) = maybe_check_owner { - ensure!(check_owner == region.owner, Error::::NotOwner); + ensure!(Some(check_owner) == region.owner, Error::::NotOwner); } ensure!((pivot & !region_id.mask).is_void(), Error::::ExteriorPivot); @@ -418,7 +428,10 @@ impl Pallet { pub(crate) fn do_drop_history(when: Timeslice) -> DispatchResult { let config = Configuration::::get().ok_or(Error::::Uninitialized)?; let status = Status::::get().ok_or(Error::::Uninitialized)?; - ensure!(status.last_timeslice > when + config.contribution_timeout, Error::::StillValid); + ensure!( + status.last_timeslice > when.saturating_add(config.contribution_timeout), + Error::::StillValid + ); let record = InstaPoolHistory::::take(when).ok_or(Error::::NoHistory)?; if let Some(payout) = record.maybe_payout { let _ = Self::charge(&Self::account_id(), payout); diff --git a/substrate/frame/broker/src/lib.rs b/substrate/frame/broker/src/lib.rs index a39576b09013f7fef9893ff87ae093eae467af5d..d59c4c9c6b24e4e576580715378a32b48f61eb91 100644 --- a/substrate/frame/broker/src/lib.rs +++ b/substrate/frame/broker/src/lib.rs @@ -36,6 +36,7 @@ mod tick_impls; mod types; mod utility_impls; +pub mod migration; pub mod runtime_api; pub mod weights; @@ -46,6 +47,9 @@ pub use core_mask::*; pub use coretime_interface::*; pub use types::*; +/// The log target for this pallet. +const LOG_TARGET: &str = "runtime::broker"; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -61,7 +65,10 @@ pub mod pallet { use sp_runtime::traits::{Convert, ConvertBack}; use sp_std::vec::Vec; + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] @@ -216,9 +223,9 @@ pub mod pallet { /// The duration of the Region. duration: Timeslice, /// The old owner of the Region. - old_owner: T::AccountId, + old_owner: Option, /// The new owner of the Region. - owner: T::AccountId, + owner: Option, }, /// A Region has been split into two non-overlapping Regions. Partitioned { @@ -552,16 +559,22 @@ pub mod pallet { /// /// - `origin`: Must be Root or pass `AdminOrigin`. /// - `initial_price`: The price of Bulk Coretime in the first sale. - /// - `core_count`: The number of cores which can be allocated. + /// - `extra_cores`: Number of extra cores that should be requested on top of the cores + /// required for `Reservations` and `Leases`. + /// + /// This will call [`Self::request_core_count`] internally to set the correct core count on + /// the relay chain. #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::start_sales((*core_count).into()))] + #[pallet::weight(T::WeightInfo::start_sales( + T::MaxLeasedCores::get() + T::MaxReservedCores::get() + *extra_cores as u32 + ))] pub fn start_sales( origin: OriginFor, initial_price: BalanceOf, - core_count: CoreIndex, + extra_cores: CoreIndex, ) -> DispatchResultWithPostInfo { T::AdminOrigin::ensure_origin_or_root(origin)?; - Self::do_start_sales(initial_price, core_count)?; + Self::do_start_sales(initial_price, extra_cores)?; Ok(Pays::No.into()) } diff --git a/substrate/frame/broker/src/migration.rs b/substrate/frame/broker/src/migration.rs new file mode 100644 index 0000000000000000000000000000000000000000..95aa28250a628e8352f6e4852a5588cbdad2a5e8 --- /dev/null +++ b/substrate/frame/broker/src/migration.rs @@ -0,0 +1,87 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate::types::RegionRecord; +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use frame_support::traits::{Get, UncheckedOnRuntimeUpgrade}; +use sp_runtime::Saturating; + +#[cfg(feature = "try-runtime")] +use frame_support::ensure; +#[cfg(feature = "try-runtime")] +use sp_std::vec::Vec; + +mod v1 { + use super::*; + + /// V0 region record. + #[derive(Encode, Decode)] + struct RegionRecordV0 { + /// The end of the Region. + pub end: Timeslice, + /// The owner of the Region. + pub owner: AccountId, + /// The amount paid to Polkadot for this Region, or `None` if renewal is not allowed. + pub paid: Option, + } + + pub struct MigrateToV1Impl(PhantomData); + + impl UncheckedOnRuntimeUpgrade for MigrateToV1Impl { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + let mut count: u64 = 0; + + >::translate::>, _>(|_, v0| { + count.saturating_inc(); + Some(RegionRecord { end: v0.end, owner: Some(v0.owner), paid: v0.paid }) + }); + + log::info!( + target: LOG_TARGET, + "Storage migration v1 for pallet-broker finished.", + ); + + // calculate and return migration weights + T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1) + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + Ok((Regions::::iter_keys().count() as u32).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + let old_count = u32::decode(&mut &state[..]).expect("Known good"); + let new_count = Regions::::iter_values().count() as u32; + + ensure!(old_count == new_count, "Regions count should not change"); + Ok(()) + } + } +} + +/// Migrate the pallet storage from `0` to `1`. +pub type MigrateV0ToV1 = frame_support::migrations::VersionedMigration< + 0, + 1, + v1::MigrateToV1Impl, + Pallet, + ::DbWeight, +>; diff --git a/substrate/frame/broker/src/mock.rs b/substrate/frame/broker/src/mock.rs index c7205058c9720b15838de3684a33303a45cb8c68..6219b4eff1b457294c9f3656e4be145c3639e2e0 100644 --- a/substrate/frame/broker/src/mock.rs +++ b/substrate/frame/broker/src/mock.rs @@ -29,7 +29,7 @@ use frame_support::{ }; use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_arithmetic::Perbill; -use sp_core::{ConstU32, ConstU64}; +use sp_core::{ConstU32, ConstU64, Get}; use sp_runtime::{ traits::{BlockNumberProvider, Identity}, BuildStorage, Saturating, @@ -210,6 +210,15 @@ pub fn advance_to(b: u64) { } } +pub fn advance_sale_period() { + let sale = SaleInfo::::get().unwrap(); + + let target_block_number = + sale.region_begin as u64 * <::TimeslicePeriod as Get>::get(); + + advance_to(target_block_number) +} + pub fn pot() -> u64 { balance(Broker::account_id()) } diff --git a/substrate/frame/broker/src/nonfungible_impl.rs b/substrate/frame/broker/src/nonfungible_impl.rs index b2e88bf09a0e9d59e90648c0e202872969b4f391..80dcc175df539bbc9861fdaa30adfdab1bb40172 100644 --- a/substrate/frame/broker/src/nonfungible_impl.rs +++ b/substrate/frame/broker/src/nonfungible_impl.rs @@ -25,12 +25,13 @@ use sp_std::vec::Vec; impl Inspect for Pallet { type ItemId = u128; - fn owner(index: &Self::ItemId) -> Option { - Regions::::get(RegionId::from(*index)).map(|r| r.owner) + fn owner(item: &Self::ItemId) -> Option { + let record = Regions::::get(RegionId::from(*item))?; + record.owner } - fn attribute(index: &Self::ItemId, key: &[u8]) -> Option> { - let id = RegionId::from(*index); + fn attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + let id = RegionId::from(*item); let item = Regions::::get(id)?; match key { b"begin" => Some(id.begin.encode()), @@ -46,11 +47,49 @@ impl Inspect for Pallet { } impl Transfer for Pallet { - fn transfer(index: &Self::ItemId, dest: &T::AccountId) -> DispatchResult { - Self::do_transfer((*index).into(), None, dest.clone()).map_err(Into::into) + fn transfer(item: &Self::ItemId, dest: &T::AccountId) -> DispatchResult { + Self::do_transfer((*item).into(), None, dest.clone()).map_err(Into::into) } } -// We don't allow any of the mutate operations, so the default implementation is used, which will -// return `TokenError::Unsupported` in case any of the operations is called. -impl Mutate for Pallet {} +/// We don't really support burning and minting. +/// +/// We only need this to allow the region to be reserve transferable. +/// +/// For reserve transfers that are not 'local', the asset must first be withdrawn to the holding +/// register and then deposited into the designated account. This process necessitates that the +/// asset is capable of being 'burned' and 'minted'. +/// +/// Since each region is associated with specific record data, we will not actually burn the asset. +/// If we did, we wouldn't know what record to assign to the newly minted region. Therefore, instead +/// of burning, we set the asset's owner to `None`. In essence, 'burning' a region involves setting +/// its owner to `None`, whereas 'minting' the region assigns its owner to an actual account. This +/// way we never lose track of the associated record data. +impl Mutate for Pallet { + /// Deposit a region into an account. + fn mint_into(item: &Self::ItemId, who: &T::AccountId) -> DispatchResult { + let region_id: RegionId = (*item).into(); + let record = Regions::::get(®ion_id).ok_or(Error::::UnknownRegion)?; + + // 'Minting' can only occur if the asset has previously been burned (i.e. moved to the + // holding register) + ensure!(record.owner.is_none(), Error::::NotAllowed); + Self::issue(region_id.core, region_id.begin, record.end, Some(who.clone()), record.paid); + + Ok(()) + } + + /// Withdraw a region from account. + fn burn(item: &Self::ItemId, maybe_check_owner: Option<&T::AccountId>) -> DispatchResult { + let region_id: RegionId = (*item).into(); + let mut record = Regions::::get(®ion_id).ok_or(Error::::UnknownRegion)?; + if let Some(owner) = maybe_check_owner { + ensure!(Some(owner.clone()) == record.owner, Error::::NotOwner); + } + + record.owner = None; + Regions::::insert(region_id, record); + + Ok(()) + } +} diff --git a/substrate/frame/broker/src/tests.rs b/substrate/frame/broker/src/tests.rs index 0aacd7ef3696c600ed9546d66fc84fb851553ee6..f929f0d50dcfaf3d8e7e77d07e260d54c75b1a27 100644 --- a/substrate/frame/broker/src/tests.rs +++ b/substrate/frame/broker/src/tests.rs @@ -24,6 +24,7 @@ use frame_support::{ BoundedVec, }; use frame_system::RawOrigin::Root; +use pretty_assertions::assert_eq; use sp_runtime::{traits::Get, TokenError}; use CoreAssignment::*; use CoretimeTraceItem::*; @@ -145,6 +146,7 @@ fn drop_history_works() { advance_to(16); assert_eq!(InstaPoolHistory::::iter().count(), 6); advance_to(17); + assert_noop!(Broker::do_drop_history(u32::MAX), Error::::StillValid); assert_noop!(Broker::do_drop_history(region.begin), Error::::StillValid); advance_to(18); assert_eq!(InstaPoolHistory::::iter().count(), 6); @@ -198,14 +200,35 @@ fn transfer_works() { } #[test] -fn mutate_operations_unsupported_for_regions() { - TestExt::new().execute_with(|| { +fn mutate_operations_work() { + TestExt::new().endow(1, 1000).execute_with(|| { let region_id = RegionId { begin: 0, core: 0, mask: CoreMask::complete() }; assert_noop!( >::mint_into(®ion_id.into(), &2), - TokenError::Unsupported + Error::::UnknownRegion + ); + + assert_ok!(Broker::do_start_sales(100, 1)); + advance_to(2); + let region_id = Broker::do_purchase(1, u64::max_value()).unwrap(); + assert_noop!( + >::mint_into(®ion_id.into(), &2), + Error::::NotAllowed ); - assert_noop!(>::burn(®ion_id.into(), None), TokenError::Unsupported); + + assert_noop!( + >::burn(®ion_id.into(), Some(&2)), + Error::::NotOwner + ); + // 'withdraw' the region from user 1: + assert_ok!(>::burn(®ion_id.into(), Some(&1))); + assert_eq!(Regions::::get(region_id).unwrap().owner, None); + + // `mint_into` works after burning: + assert_ok!(>::mint_into(®ion_id.into(), &2)); + assert_eq!(Regions::::get(region_id).unwrap().owner, Some(2)); + + // Unsupported operations: assert_noop!( >::set_attribute(®ion_id.into(), &[], &[]), TokenError::Unsupported @@ -283,7 +306,7 @@ fn nft_metadata_works() { assert_eq!(attribute::(region, b"begin"), 4); assert_eq!(attribute::(region, b"length"), 3); assert_eq!(attribute::(region, b"end"), 7); - assert_eq!(attribute::(region, b"owner"), 1); + assert_eq!(attribute::>(region, b"owner"), Some(1)); assert_eq!(attribute::(region, b"part"), 0xfffff_fffff_fffff_fffff.into()); assert_eq!(attribute::(region, b"core"), 0); assert_eq!(attribute::>(region, b"paid"), Some(100)); @@ -295,7 +318,7 @@ fn nft_metadata_works() { assert_eq!(attribute::(region, b"begin"), 6); assert_eq!(attribute::(region, b"length"), 1); assert_eq!(attribute::(region, b"end"), 7); - assert_eq!(attribute::(region, b"owner"), 42); + assert_eq!(attribute::>(region, b"owner"), Some(42)); assert_eq!(attribute::(region, b"part"), 0x00000_fffff_fffff_00000.into()); assert_eq!(attribute::(region, b"core"), 0); assert_eq!(attribute::>(region, b"paid"), None); @@ -306,7 +329,7 @@ fn nft_metadata_works() { fn migration_works() { TestExt::new().endow(1, 1000).execute_with(|| { assert_ok!(Broker::do_set_lease(1000, 8)); - assert_ok!(Broker::do_start_sales(100, 2)); + assert_ok!(Broker::do_start_sales(100, 1)); // Sale is for regions from TS4..7 // Not ending in this sale period. @@ -362,7 +385,7 @@ fn instapool_payouts_work() { TestExt::new().endow(1, 1000).execute_with(|| { let item = ScheduleItem { assignment: Pool, mask: CoreMask::complete() }; assert_ok!(Broker::do_reserve(Schedule::truncate_from(vec![item]))); - assert_ok!(Broker::do_start_sales(100, 3)); + assert_ok!(Broker::do_start_sales(100, 2)); advance_to(2); let region = Broker::do_purchase(1, u64::max_value()).unwrap(); assert_ok!(Broker::do_pool(region, None, 2, Final)); @@ -388,7 +411,7 @@ fn instapool_partial_core_payouts_work() { TestExt::new().endow(1, 1000).execute_with(|| { let item = ScheduleItem { assignment: Pool, mask: CoreMask::complete() }; assert_ok!(Broker::do_reserve(Schedule::truncate_from(vec![item]))); - assert_ok!(Broker::do_start_sales(100, 2)); + assert_ok!(Broker::do_start_sales(100, 1)); advance_to(2); let region = Broker::do_purchase(1, u64::max_value()).unwrap(); let (region1, region2) = @@ -454,7 +477,7 @@ fn initialize_with_system_paras_works() { ScheduleItem { assignment: Task(4u32), mask: 0x00000_00000_00000_fffff.into() }, ]; assert_ok!(Broker::do_reserve(Schedule::truncate_from(items))); - assert_ok!(Broker::do_start_sales(100, 2)); + assert_ok!(Broker::do_start_sales(100, 0)); advance_to(10); assert_eq!( CoretimeTrace::get(), @@ -487,7 +510,7 @@ fn initialize_with_leased_slots_works() { TestExt::new().execute_with(|| { assert_ok!(Broker::do_set_lease(1000, 6)); assert_ok!(Broker::do_set_lease(1001, 7)); - assert_ok!(Broker::do_start_sales(100, 2)); + assert_ok!(Broker::do_start_sales(100, 0)); advance_to(18); let end_hint = None; assert_eq!( @@ -891,6 +914,161 @@ fn short_leases_are_cleaned() { }); } +#[test] +fn leases_can_be_renewed() { + TestExt::new().endow(1, 1000).execute_with(|| { + // Timeslice period is 2. + // + // Sale 1 starts at block 7, Sale 2 starts at 13. + + // Set lease to expire in sale 1 and start sales. + assert_ok!(Broker::do_set_lease(2001, 9)); + assert_eq!(Leases::::get().len(), 1); + // Start the sales with only one core for this lease. + assert_ok!(Broker::do_start_sales(100, 0)); + + // Advance to sale period 1, we should get an AllowedRenewal for task 2001 for the next + // sale. + advance_sale_period(); + assert_eq!( + AllowedRenewals::::get(AllowedRenewalId { core: 0, when: 10 }), + Some(AllowedRenewalRecord { + price: 100, + completion: CompletionStatus::Complete( + vec![ScheduleItem { mask: CoreMask::complete(), assignment: Task(2001) }] + .try_into() + .unwrap() + ) + }) + ); + // And the lease has been removed from storage. + assert_eq!(Leases::::get().len(), 0); + + // Advance to sale period 2, where we can renew. + advance_sale_period(); + assert_ok!(Broker::do_renew(1, 0)); + // We renew for the base price of the previous sale period. + assert_eq!(balance(1), 900); + + // We just renewed for this period. + advance_sale_period(); + // Now we are off core and the core is pooled. + advance_sale_period(); + // Check the trace agrees. + assert_eq!( + CoretimeTrace::get(), + vec![ + // Period 0 gets no assign core, but leases are on-core. + // Period 1: + ( + 6, + AssignCore { + core: 0, + begin: 8, + assignment: vec![(CoreAssignment::Task(2001), 57600)], + end_hint: None, + }, + ), + // Period 2 - expiring at the end of this period, so we called renew. + ( + 12, + AssignCore { + core: 0, + begin: 14, + assignment: vec![(CoreAssignment::Task(2001), 57600)], + end_hint: None, + }, + ), + // Period 3 - we get assigned a core because we called renew in period 2. + ( + 18, + AssignCore { + core: 0, + begin: 20, + assignment: vec![(CoreAssignment::Task(2001), 57600)], + end_hint: None, + }, + ), + // Period 4 - we don't get a core as we didn't call renew again. + // This core is recycled into the pool. + ( + 24, + AssignCore { + core: 0, + begin: 26, + assignment: vec![(CoreAssignment::Pool, 57600)], + end_hint: None, + }, + ), + ] + ); + }); +} + +// We understand that this does not work as intended for leases that expire within `region_length` +// timeslices after calling `start_sales`. +#[test] +fn short_leases_cannot_be_renewed() { + TestExt::new().endow(1, 1000).execute_with(|| { + // Timeslice period is 2. + // + // Sale 1 starts at block 7, Sale 2 starts at 13. + + // Set lease to expire in sale period 0 and start sales. + assert_ok!(Broker::do_set_lease(2001, 3)); + assert_eq!(Leases::::get().len(), 1); + // Start the sales with one core for this lease. + assert_ok!(Broker::do_start_sales(100, 0)); + + // The lease is removed. + assert_eq!(Leases::::get().len(), 0); + + // We should have got an entry in AllowedRenewals, but we don't because rotate_sale + // schedules leases a period in advance. This renewal should be in the period after next + // because while bootstrapping our way into the sale periods, we give everything a lease for + // period 1, so they can renew for period 2. So we have a core until the end of period 1, + // but we are not marked as able to renew because we expired before sale period 1 starts. + // + // This should be fixed. + assert_eq!(AllowedRenewals::::get(AllowedRenewalId { core: 0, when: 10 }), None); + // And the lease has been removed from storage. + assert_eq!(Leases::::get().len(), 0); + + // Advance to sale period 2, where we now cannot renew. + advance_to(13); + assert_noop!(Broker::do_renew(1, 0), Error::::NotAllowed); + + // Check the trace. + assert_eq!( + CoretimeTrace::get(), + vec![ + // Period 0 gets no assign core, but leases are on-core. + // Period 1 we get assigned a core due to the way the sales are bootstrapped. + ( + 6, + AssignCore { + core: 0, + begin: 8, + assignment: vec![(CoreAssignment::Task(2001), 57600)], + end_hint: None, + }, + ), + // Period 2 - we don't get a core as we couldn't renew. + // This core is recycled into the pool. + ( + 12, + AssignCore { + core: 0, + begin: 14, + assignment: vec![(CoreAssignment::Pool, 57600)], + end_hint: None, + }, + ), + ] + ); + }); +} + #[test] fn leases_are_limited() { TestExt::new().execute_with(|| { @@ -1092,3 +1270,161 @@ fn config_works() { assert_noop!(Broker::configure(Root.into(), cfg), Error::::InvalidConfig); }); } + +/// Ensure that a lease that ended before `start_sales` was called can be renewed. +#[test] +fn renewal_works_leases_ended_before_start_sales() { + TestExt::new().endow(1, 1000).execute_with(|| { + let config = Configuration::::get().unwrap(); + + // This lease is ended before `start_stales` was called. + assert_ok!(Broker::do_set_lease(1, 1)); + + // Go to some block to ensure that the lease of task 1 already ended. + advance_to(5); + + // This lease will end three sale periods in. + assert_ok!(Broker::do_set_lease( + 2, + Broker::latest_timeslice_ready_to_commit(&config) + config.region_length * 3 + )); + + // This intializes the first sale and the period 0. + assert_ok!(Broker::do_start_sales(100, 0)); + assert_noop!(Broker::do_renew(1, 1), Error::::Unavailable); + assert_noop!(Broker::do_renew(1, 0), Error::::Unavailable); + + // Lease for task 1 should have been dropped. + assert!(Leases::::get().iter().any(|l| l.task == 2)); + + // This intializes the second and the period 1. + advance_sale_period(); + + // Now we can finally renew the core 0 of task 1. + let new_core = Broker::do_renew(1, 0).unwrap(); + // Renewing the active lease doesn't work. + assert_noop!(Broker::do_renew(1, 1), Error::::SoldOut); + assert_eq!(balance(1), 900); + + // This intializes the third sale and the period 2. + advance_sale_period(); + let new_core = Broker::do_renew(1, new_core).unwrap(); + + // Renewing the active lease doesn't work. + assert_noop!(Broker::do_renew(1, 0), Error::::SoldOut); + assert_eq!(balance(1), 800); + + // All leases should have ended + assert!(Leases::::get().is_empty()); + + // This intializes the fourth sale and the period 3. + advance_sale_period(); + + // Renew again + assert_eq!(0, Broker::do_renew(1, new_core).unwrap()); + // Renew the task 2. + assert_eq!(1, Broker::do_renew(1, 0).unwrap()); + assert_eq!(balance(1), 600); + + // This intializes the fifth sale and the period 4. + advance_sale_period(); + + assert_eq!( + CoretimeTrace::get(), + vec![ + ( + 10, + AssignCore { + core: 0, + begin: 12, + assignment: vec![(Task(1), 57600)], + end_hint: None + } + ), + ( + 10, + AssignCore { + core: 1, + begin: 12, + assignment: vec![(Task(2), 57600)], + end_hint: None + } + ), + ( + 16, + AssignCore { + core: 0, + begin: 18, + assignment: vec![(Task(2), 57600)], + end_hint: None + } + ), + ( + 16, + AssignCore { + core: 1, + begin: 18, + assignment: vec![(Task(1), 57600)], + end_hint: None + } + ), + ( + 22, + AssignCore { + core: 0, + begin: 24, + assignment: vec![(Task(2), 57600)], + end_hint: None, + }, + ), + ( + 22, + AssignCore { + core: 1, + begin: 24, + assignment: vec![(Task(1), 57600)], + end_hint: None, + }, + ), + ( + 28, + AssignCore { + core: 0, + begin: 30, + assignment: vec![(Task(1), 57600)], + end_hint: None, + }, + ), + ( + 28, + AssignCore { + core: 1, + begin: 30, + assignment: vec![(Task(2), 57600)], + end_hint: None, + }, + ), + ] + ); + }); +} + +#[test] +fn start_sales_sets_correct_core_count() { + TestExt::new().endow(1, 1000).execute_with(|| { + advance_to(1); + + Broker::do_set_lease(1, 100).unwrap(); + Broker::do_set_lease(2, 100).unwrap(); + Broker::do_set_lease(3, 100).unwrap(); + Broker::do_reserve(Schedule::truncate_from(vec![ScheduleItem { + assignment: Pool, + mask: CoreMask::complete(), + }])) + .unwrap(); + + Broker::do_start_sales(5, 5).unwrap(); + + System::assert_has_event(Event::::CoreCountRequested { core_count: 9 }.into()); + }) +} diff --git a/substrate/frame/broker/src/tick_impls.rs b/substrate/frame/broker/src/tick_impls.rs index 388370bce4d4b8045122c02ad758b62b4476fd15..04e9a65bf8f67b11ef66edf7268eda74e336e65d 100644 --- a/substrate/frame/broker/src/tick_impls.rs +++ b/substrate/frame/broker/src/tick_impls.rs @@ -216,11 +216,10 @@ impl Pallet { let assignment = CoreAssignment::Task(task); let schedule = BoundedVec::truncate_from(vec![ScheduleItem { mask, assignment }]); Workplan::::insert((region_begin, first_core), &schedule); - // Separate these to avoid missed expired leases hanging around forever. - let expired = until < region_end; - let expiring = until >= region_begin && expired; - if expiring { - // last time for this one - make it renewable. + // Will the lease expire at the end of the period? + let expire = until < region_end; + if expire { + // last time for this one - make it renewable in the next sale. let renewal_id = AllowedRenewalId { core: first_core, when: region_end }; let record = AllowedRenewalRecord { price, completion: Complete(schedule) }; AllowedRenewals::::insert(renewal_id, &record); @@ -232,8 +231,10 @@ impl Pallet { }); Self::deposit_event(Event::LeaseEnding { when: region_end, task }); } + first_core.saturating_inc(); - !expired + + !expire }); Leases::::put(&leases); diff --git a/substrate/frame/broker/src/types.rs b/substrate/frame/broker/src/types.rs index e8119d29ef5d38e25060514d624713a6eedd11a8..f2cae9a41ad480b1e4608a4d556b10d166282c1d 100644 --- a/substrate/frame/broker/src/types.rs +++ b/substrate/frame/broker/src/types.rs @@ -84,7 +84,7 @@ pub struct RegionRecord { /// The end of the Region. pub end: Timeslice, /// The owner of the Region. - pub owner: AccountId, + pub owner: Option, /// The amount paid to Polkadot for this Region, or `None` if renewal is not allowed. pub paid: Option, } diff --git a/substrate/frame/broker/src/utility_impls.rs b/substrate/frame/broker/src/utility_impls.rs index 3dba5be5b398b8f30156179e7dd1b203fff5e311..4163817a8b584bd949210aff383398ff50b95d58 100644 --- a/substrate/frame/broker/src/utility_impls.rs +++ b/substrate/frame/broker/src/utility_impls.rs @@ -72,11 +72,11 @@ impl Pallet { Ok(()) } - pub(crate) fn issue( + pub fn issue( core: CoreIndex, begin: Timeslice, end: Timeslice, - owner: T::AccountId, + owner: Option, paid: Option>, ) -> RegionId { let id = RegionId { begin, core, mask: CoreMask::complete() }; @@ -94,7 +94,7 @@ impl Pallet { let region = Regions::::get(®ion_id).ok_or(Error::::UnknownRegion)?; if let Some(check_owner) = maybe_check_owner { - ensure!(check_owner == region.owner, Error::::NotOwner); + ensure!(Some(check_owner) == region.owner, Error::::NotOwner); } Regions::::remove(®ion_id); diff --git a/substrate/frame/contracts/fixtures/build.rs b/substrate/frame/contracts/fixtures/build.rs index 19aff37c1601430011966768d54a475ef0bbc1d2..baaeaf0342032f43821f19c5526d3a8850e3a035 100644 --- a/substrate/frame/contracts/fixtures/build.rs +++ b/substrate/frame/contracts/fixtures/build.rs @@ -163,7 +163,7 @@ fn invoke_cargo_fmt<'a>( ) -> Result<()> { // If rustfmt is not installed, skip the check. if !Command::new("rustup") - .args(["nightly-2024-01-22", "run", "rustfmt", "--version"]) + .args(["nightly-2024-04-10", "run", "rustfmt", "--version"]) .output() .map_or(false, |o| o.status.success()) { @@ -171,7 +171,7 @@ fn invoke_cargo_fmt<'a>( } let fmt_res = Command::new("rustup") - .args(["nightly-2024-01-22", "run", "rustfmt", "--check", "--config-path"]) + .args(["nightly-2024-04-10", "run", "rustfmt", "--check", "--config-path"]) .arg(config_path) .args(files) .output() @@ -186,7 +186,7 @@ fn invoke_cargo_fmt<'a>( eprintln!("{}\n{}", stdout, stderr); eprintln!( "Fixtures files are not formatted.\n - Please run `rustup nightly-2024-01-22 run rustfmt --config-path {} {}/*.rs`", + Please run `rustup nightly-2024-04-10 run rustfmt --config-path {} {}/*.rs`", config_path.display(), contract_dir.display() ); diff --git a/substrate/frame/contracts/mock-network/src/lib.rs b/substrate/frame/contracts/mock-network/src/lib.rs index 8a17a3f2fa781703f1393f2d69ca955384a080a2..20ded0f4a0b8475d2cd203ca4acd8164dd9792eb 100644 --- a/substrate/frame/contracts/mock-network/src/lib.rs +++ b/substrate/frame/contracts/mock-network/src/lib.rs @@ -23,6 +23,7 @@ pub mod relay_chain; mod tests; use crate::primitives::{AccountId, UNITS}; +pub use pallet_contracts::test_utils::{ALICE, BOB}; use sp_runtime::BuildStorage; use xcm::latest::prelude::*; use xcm_executor::traits::ConvertLocation; @@ -31,8 +32,6 @@ use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chai // Accounts pub const ADMIN: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]); -pub const ALICE: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([1u8; 32]); -pub const BOB: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([2u8; 32]); // Balances pub const INITIAL_BALANCE: u128 = 1_000_000_000 * UNITS; diff --git a/substrate/frame/contracts/mock-network/src/parachain.rs b/substrate/frame/contracts/mock-network/src/parachain.rs index d4ad47581d16740df732459b3faee2d0b02306b4..843efab1502e7304991682d1f13180ee28288fe2 100644 --- a/substrate/frame/contracts/mock-network/src/parachain.rs +++ b/substrate/frame/contracts/mock-network/src/parachain.rs @@ -144,7 +144,7 @@ parameter_types! { pub const KsmLocation: Location = Location::parent(); pub const TokenLocation: Location = Here.into_location(); pub const RelayNetwork: NetworkId = ByGenesis([0; 32]); - pub UniversalLocation: InteriorLocation = Parachain(MsgQueue::parachain_id().into()).into(); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(MsgQueue::parachain_id().into())].into(); } pub type XcmOriginToCallOrigin = ( diff --git a/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs b/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs index 3c06131dd6088a2b044b68cfbf3b4a918eb37e3e..20fdd9a243d19582e403a52d8d1babbb3474de9a 100644 --- a/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs +++ b/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs @@ -14,88 +14,28 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use super::{Balances, Runtime, RuntimeCall, RuntimeEvent}; -use crate::{ - parachain, - parachain::RuntimeHoldReason, - primitives::{Balance, CENTS}, -}; -use frame_support::{ - parameter_types, - traits::{ConstBool, ConstU32, Contains, Randomness}, - weights::Weight, -}; -use frame_system::{pallet_prelude::BlockNumberFor, EnsureSigned}; -use pallet_xcm::BalanceOf; -use sp_runtime::{traits::Convert, Perbill}; - -pub const fn deposit(items: u32, bytes: u32) -> Balance { - items as Balance * 1 * CENTS + (bytes as Balance) * 1 * CENTS -} +use super::{Balances, Runtime, RuntimeCall, RuntimeEvent, RuntimeHoldReason}; +use frame_support::{derive_impl, parameter_types, traits::Contains}; parameter_types! { - pub const DepositPerItem: Balance = deposit(1, 0); - pub const DepositPerByte: Balance = deposit(0, 1); - pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); pub Schedule: pallet_contracts::Schedule = Default::default(); - pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); - pub const MaxDelegateDependencies: u32 = 32; -} - -pub struct DummyRandomness(sp_std::marker::PhantomData); - -impl Randomness> for DummyRandomness { - fn random(_subject: &[u8]) -> (T::Hash, BlockNumberFor) { - (Default::default(), Default::default()) - } -} - -impl Convert> for Runtime { - fn convert(w: Weight) -> BalanceOf { - w.ref_time().into() - } -} - -#[derive(Clone, Default)] -pub struct Filters; - -impl Contains for Filters { - fn contains(call: &RuntimeCall) -> bool { - match call { - parachain::RuntimeCall::Contracts(_) => true, - _ => false, - } - } } +#[derive_impl(pallet_contracts::config_preludes::TestDefaultConfig)] impl pallet_contracts::Config for Runtime { type AddressGenerator = pallet_contracts::DefaultAddressGenerator; - type CallFilter = Filters; type CallStack = [pallet_contracts::Frame; 5]; - type ChainExtension = (); - type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Currency = Balances; - type DefaultDepositLimit = DefaultDepositLimit; - type DepositPerByte = DepositPerByte; - type DepositPerItem = DepositPerItem; - type MaxCodeLen = ConstU32<{ 123 * 1024 }>; - type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; - type MaxDelegateDependencies = MaxDelegateDependencies; - type MaxStorageKeyLen = ConstU32<128>; - type Migrations = (); - type Randomness = DummyRandomness; - type RuntimeCall = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type RuntimeHoldReason = RuntimeHoldReason; type Schedule = Schedule; type Time = super::Timestamp; - type UnsafeUnstableInterface = ConstBool; - type UploadOrigin = EnsureSigned; - type InstantiateOrigin = EnsureSigned; - type WeightInfo = (); - type WeightPrice = Self; - type Debug = (); - type Environment = (); - type ApiVersion = (); + type CallFilter = CallFilter; type Xcm = pallet_xcm::Pallet; } + +/// In this mock, we only allow other contract calls via XCM. +pub struct CallFilter; +impl Contains for CallFilter { + fn contains(call: &RuntimeCall) -> bool { + matches!(call, RuntimeCall::Contracts(pallet_contracts::Call::call { .. })) + } +} diff --git a/substrate/frame/contracts/mock-network/src/relay_chain.rs b/substrate/frame/contracts/mock-network/src/relay_chain.rs index 470304ed357ec5594a2a66f795f838bc8be7740b..d5e0ec9c83fa73cc88f567f573f0d2172af23a7a 100644 --- a/substrate/frame/contracts/mock-network/src/relay_chain.rs +++ b/substrate/frame/contracts/mock-network/src/relay_chain.rs @@ -107,7 +107,7 @@ impl configuration::Config for Runtime { parameter_types! { pub RelayNetwork: NetworkId = ByGenesis([0; 32]); pub const TokenLocation: Location = Here.into_location(); - pub UniversalLocation: InteriorLocation = Here; + pub UniversalLocation: InteriorLocation = RelayNetwork::get().into(); pub UnitWeightCost: u64 = 1_000; } diff --git a/substrate/frame/contracts/mock-network/src/tests.rs b/substrate/frame/contracts/mock-network/src/tests.rs index 39aa9bebc0f5943627ae40a109dab357ae19dd07..e7d1f6279aa3dcfa7c1c313c6ee64303dca3e8f0 100644 --- a/substrate/frame/contracts/mock-network/src/tests.rs +++ b/substrate/frame/contracts/mock-network/src/tests.rs @@ -23,44 +23,33 @@ use crate::{ }; use codec::{Decode, Encode}; use frame_support::{ - pallet_prelude::Weight, + assert_err, traits::{fungibles::Mutate, Currency}, }; -use pallet_balances::{BalanceLock, Reasons}; -use pallet_contracts::{Code, CollectEvents, DebugInfo, Determinism}; +use pallet_contracts::{test_utils::builder::*, Code}; use pallet_contracts_fixtures::compile_module; use pallet_contracts_uapi::ReturnErrorCode; use xcm::{v4::prelude::*, VersionedLocation, VersionedXcm}; use xcm_simulator::TestExt; -type ParachainContracts = pallet_contracts::Pallet; - macro_rules! assert_return_code { ( $x:expr , $y:expr $(,)? ) => {{ assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); }}; } +fn bare_call(dest: sp_runtime::AccountId32) -> BareCallBuilder { + BareCallBuilder::::bare_call(ALICE, dest) +} + /// Instantiate the tests contract, and fund it with some balance and assets. fn instantiate_test_contract(name: &str) -> AccountId { let (wasm, _) = compile_module::(name).unwrap(); // Instantiate contract. let contract_addr = ParaA::execute_with(|| { - ParachainContracts::bare_instantiate( - ALICE, - 0, - Weight::MAX, - None, - Code::Upload(wasm), - vec![], - vec![], - DebugInfo::UnsafeDebug, - CollectEvents::Skip, - ) - .result - .unwrap() - .account_id + BareInstantiateBuilder::::bare_instantiate(ALICE, Code::Upload(wasm)) + .build_and_unwrap_account_id() }); // Funds contract account with some balance and assets. @@ -85,27 +74,18 @@ fn test_xcm_execute() { // Execute XCM instructions through the contract. ParaA::execute_with(|| { let amount: u128 = 10 * CENTS; + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: BOB.clone().into() }; // The XCM used to transfer funds to Bob. - let message: Xcm<()> = Xcm(vec![ - WithdrawAsset(vec![(Here, amount).into()].into()), - DepositAsset { - assets: All.into(), - beneficiary: AccountId32 { network: None, id: BOB.clone().into() }.into(), - }, - ]); - - let result = ParachainContracts::bare_call( - ALICE, - contract_addr.clone(), - 0, - Weight::MAX, - None, - VersionedXcm::V4(message).encode().encode(), - DebugInfo::UnsafeDebug, - CollectEvents::UnsafeCollect, - Determinism::Enforced, - ); + let message: Xcm<()> = Xcm::builder_unsafe() + .withdraw_asset(assets.clone()) + .deposit_asset(assets, beneficiary) + .build(); + + let result = bare_call(contract_addr.clone()) + .data(VersionedXcm::V4(message).encode()) + .build(); assert_eq!(result.gas_consumed, result.gas_required); assert_return_code!(&result.result.unwrap(), ReturnErrorCode::Success); @@ -127,29 +107,22 @@ fn test_xcm_execute_incomplete() { // Execute XCM instructions through the contract. ParaA::execute_with(|| { + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: BOB.clone().into() }; + // The XCM used to transfer funds to Bob. - let message: Xcm<()> = Xcm(vec![ - WithdrawAsset(vec![(Here, amount).into()].into()), + let message: Xcm<()> = Xcm::builder_unsafe() + .withdraw_asset(assets.clone()) // This will fail as the contract does not have enough balance to complete both // withdrawals. - WithdrawAsset(vec![(Here, INITIAL_BALANCE).into()].into()), - DepositAsset { - assets: All.into(), - beneficiary: AccountId32 { network: None, id: BOB.clone().into() }.into(), - }, - ]); - - let result = ParachainContracts::bare_call( - ALICE, - contract_addr.clone(), - 0, - Weight::MAX, - None, - VersionedXcm::V4(message).encode().encode(), - DebugInfo::UnsafeDebug, - CollectEvents::UnsafeCollect, - Determinism::Enforced, - ); + .withdraw_asset((Here, INITIAL_BALANCE)) + .buy_execution(assets.clone(), Unlimited) + .deposit_asset(assets, beneficiary) + .build(); + + let result = bare_call(contract_addr.clone()) + .data(VersionedXcm::V4(message).encode()) + .build(); assert_eq!(result.gas_consumed, result.gas_required); assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); @@ -159,6 +132,26 @@ fn test_xcm_execute_incomplete() { }); } +#[test] +fn test_xcm_execute_filtered_call() { + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + + ParaA::execute_with(|| { + // `remark` should be rejected, as it is not allowed by our CallFilter. + let call = parachain::RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); + let message: Xcm = Xcm::builder_unsafe() + .transact(OriginKind::Native, Weight::MAX, call.encode()) + .build(); + let result = bare_call(contract_addr.clone()) + .data(VersionedXcm::V4(message).encode()) + .build() + .result; + assert_err!(result, frame_system::Error::::CallFiltered); + }); +} + #[test] fn test_xcm_execute_reentrant_call() { MockNet::reset(); @@ -175,28 +168,16 @@ fn test_xcm_execute_reentrant_call() { }); // The XCM used to transfer funds to Bob. - let message: Xcm = Xcm(vec![ - Transact { - origin_kind: OriginKind::Native, - require_weight_at_most: 1_000_000_000.into(), - call: transact_call.encode().into(), - }, - ExpectTransactStatus(MaybeErrorCode::Success), - ]); - - let result = ParachainContracts::bare_call( - ALICE, - contract_addr.clone(), - 0, - Weight::MAX, - None, - VersionedXcm::V4(message).encode().encode(), - DebugInfo::UnsafeDebug, - CollectEvents::UnsafeCollect, - Determinism::Enforced, - ); + let message: Xcm = Xcm::builder_unsafe() + .transact(OriginKind::Native, 1_000_000_000, transact_call.encode()) + .expect_transact_status(MaybeErrorCode::Success) + .build(); - assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); + let result = bare_call(contract_addr.clone()) + .data(VersionedXcm::V4(message).encode()) + .build_and_unwrap_result(); + + assert_return_code!(&result, ReturnErrorCode::XcmExecutionFailed); // Funds should not change hands as the XCM transact failed. assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); @@ -207,40 +188,36 @@ fn test_xcm_execute_reentrant_call() { fn test_xcm_send() { MockNet::reset(); let contract_addr = instantiate_test_contract("xcm_send"); + let amount = 1_000 * CENTS; let fee = parachain::estimate_message_fee(4); // Accounts for the `DescendOrigin` instruction added by `send_xcm` - // Send XCM instructions through the contract, to lock some funds on the relay chain. + // Send XCM instructions through the contract, to transfer some funds from the contract + // derivative account to Alice on the relay chain. ParaA::execute_with(|| { - let dest = Location::from(Parent); - let dest = VersionedLocation::V4(dest); - - let message: Xcm<()> = Xcm(vec![ - WithdrawAsset((Here, fee).into()), - BuyExecution { fees: (Here, fee).into(), weight_limit: WeightLimit::Unlimited }, - LockAsset { asset: (Here, 5 * CENTS).into(), unlocker: (Parachain(1)).into() }, - ]); - let message = VersionedXcm::V4(message); - let exec = ParachainContracts::bare_call( - ALICE, - contract_addr.clone(), - 0, - Weight::MAX, - None, - (dest, message.encode()).encode(), - DebugInfo::UnsafeDebug, - CollectEvents::UnsafeCollect, - Determinism::Enforced, - ); + let dest = VersionedLocation::V4(Parent.into()); + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: ALICE.clone().into() }; + + let message: Xcm<()> = Xcm::builder() + .withdraw_asset(assets.clone()) + .buy_execution((Here, fee), Unlimited) + .deposit_asset(assets, beneficiary) + .build(); + + let result = bare_call(contract_addr.clone()) + .data((dest, VersionedXcm::V4(message)).encode()) + .build_and_unwrap_result(); - let mut data = &exec.result.unwrap().data[..]; + let mut data = &result.data[..]; XcmHash::decode(&mut data).expect("Failed to decode xcm_send message_id"); }); Relay::execute_with(|| { - // Check if the funds are locked on the relay chain. + let derived_contract_addr = ¶chain_account_sovereign_account_id(1, contract_addr); assert_eq!( - relay_chain::Balances::locks(¶chain_account_sovereign_account_id(1, contract_addr)), - vec![BalanceLock { id: *b"py/xcmlk", amount: 5 * CENTS, reasons: Reasons::All }] + INITIAL_BALANCE - amount, + relay_chain::Balances::free_balance(derived_contract_addr) ); + assert_eq!(INITIAL_BALANCE + amount - fee, relay_chain::Balances::free_balance(ALICE)); }); } diff --git a/substrate/frame/contracts/src/benchmarking/mod.rs b/substrate/frame/contracts/src/benchmarking/mod.rs index 676fd320a17299b2eb2471d485afcd2775168175..952ef180be21860b4a13a26fdf07bf03251f00a2 100644 --- a/substrate/frame/contracts/src/benchmarking/mod.rs +++ b/substrate/frame/contracts/src/benchmarking/mod.rs @@ -623,10 +623,13 @@ mod benchmarks { #[benchmark(pov_mode = Measured)] fn seal_caller(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::getter("seal0", "seal_caller", r)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] @@ -659,10 +662,13 @@ mod benchmarks { for acc in accounts.iter() { >::insert(acc, info.clone()); } + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] @@ -703,19 +709,25 @@ mod benchmarks { for acc in accounts.iter() { >::insert(acc, info.clone()); } + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] fn seal_own_code_hash(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::getter("seal0", "seal_own_code_hash", r)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] @@ -732,10 +744,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] @@ -754,73 +769,97 @@ mod benchmarks { let mut setup = CallSetup::::new(code); setup.set_origin(Origin::Root); call_builder!(func, setup: setup); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] fn seal_address(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::getter("seal0", "seal_address", r)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] fn seal_gas_left(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::getter("seal1", "gas_left", r)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] fn seal_balance(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::getter("seal0", "seal_balance", r)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] fn seal_value_transferred(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::getter("seal0", "seal_value_transferred", r)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] fn seal_minimum_balance(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::getter("seal0", "seal_minimum_balance", r)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] fn seal_block_number(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::getter("seal0", "seal_block_number", r)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] fn seal_now(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::getter("seal0", "seal_now", r)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] @@ -851,10 +890,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] @@ -880,10 +922,13 @@ mod benchmarks { }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] @@ -944,10 +989,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } #[benchmark(pov_mode = Measured)] @@ -970,10 +1018,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // The same argument as for `seal_return` is true here. @@ -1108,10 +1159,13 @@ mod benchmarks { }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // Overhead of calling the function without any topic. @@ -1140,10 +1194,13 @@ mod benchmarks { }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // Benchmark the overhead that topics generate. @@ -1177,10 +1234,13 @@ mod benchmarks { }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // Benchmark debug_message call with zero input data. @@ -1210,10 +1270,13 @@ mod benchmarks { let mut setup = CallSetup::::new(code); setup.enable_debug_message(); call_builder!(func, setup: setup); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -1260,10 +1323,13 @@ mod benchmarks { let mut setup = CallSetup::::new(code); setup.enable_debug_message(); call_builder!(func, setup: setup); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); assert_eq!(setup.debug_message().unwrap().len() as u32, i); Ok(()) } @@ -1324,10 +1390,13 @@ mod benchmarks { ) .map_err(|_| "Failed to write to storage during setup.")?; } + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -1366,10 +1435,13 @@ mod benchmarks { false, ) .map_err(|_| "Failed to write to storage during setup.")?; + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -1409,10 +1481,13 @@ mod benchmarks { false, ) .map_err(|_| "Failed to write to storage during setup.")?; + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -1463,10 +1538,13 @@ mod benchmarks { .map_err(|_| "Failed to write to storage during setup.")?; } >::insert(&instance.account_id, info); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -1504,10 +1582,12 @@ mod benchmarks { ) .map_err(|_| "Failed to write to storage during setup.")?; + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -1564,10 +1644,13 @@ mod benchmarks { .map_err(|_| "Failed to write to storage during setup.")?; } >::insert(&instance.account_id, info); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -1613,10 +1696,13 @@ mod benchmarks { ) .map_err(|_| "Failed to write to storage during setup.")?; >::insert(&instance.account_id, info); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -1667,10 +1753,13 @@ mod benchmarks { .map_err(|_| "Failed to write to storage during setup.")?; } >::insert(&instance.account_id, info); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -1708,10 +1797,13 @@ mod benchmarks { ) .map_err(|_| "Failed to write to storage during setup.")?; >::insert(&instance.account_id, info); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -1767,10 +1859,13 @@ mod benchmarks { .map_err(|_| "Failed to write to storage during setup.")?; } >::insert(&instance.account_id, info); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -1816,10 +1911,13 @@ mod benchmarks { ) .map_err(|_| "Failed to write to storage during setup.")?; >::insert(&instance.account_id, info); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -1867,10 +1965,12 @@ mod benchmarks { assert_eq!(T::Currency::total_balance(account), 0u32.into()); } + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); for account in &accounts { assert_eq!(T::Currency::total_balance(account), value); @@ -1947,10 +2047,13 @@ mod benchmarks { let mut setup = CallSetup::::new(code); setup.set_storage_deposit_limit(BalanceOf::::from(u32::MAX.into())); call_builder!(func, setup: setup); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -2002,10 +2105,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -2057,10 +2163,13 @@ mod benchmarks { let mut setup = CallSetup::::new(code); setup.set_data(vec![42; c as usize]); call_builder!(func, setup: setup); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -2165,10 +2274,13 @@ mod benchmarks { return Err("Expected that contract does not exist at this point.".into()); } } + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); for addr in &addresses { ContractInfoOf::::get(&addr).ok_or("Contract should have been instantiated")?; } @@ -2240,10 +2352,13 @@ mod benchmarks { let mut setup = CallSetup::::new(code); setup.set_balance(value + (Pallet::::min_balance() * 2u32.into())); call_builder!(func, setup: setup); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -2251,80 +2366,104 @@ mod benchmarks { #[benchmark(pov_mode = Measured)] fn seal_hash_sha2_256(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::hasher("seal_hash_sha2_256", r, 0)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // `n`: Input to hash in bytes #[benchmark(pov_mode = Measured)] fn seal_hash_sha2_256_per_byte(n: Linear<0, { code::max_pages::() * 64 * 1024 }>) { call_builder!(func, WasmModule::hasher("seal_hash_sha2_256", 1, n)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // Only the overhead of calling the function itself with minimal arguments. #[benchmark(pov_mode = Measured)] fn seal_hash_keccak_256(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::hasher("seal_hash_keccak_256", r, 0)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // `n`: Input to hash in bytes #[benchmark(pov_mode = Measured)] fn seal_hash_keccak_256_per_byte(n: Linear<0, { code::max_pages::() * 64 * 1024 }>) { call_builder!(func, WasmModule::hasher("seal_hash_keccak_256", 1, n)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // Only the overhead of calling the function itself with minimal arguments. #[benchmark(pov_mode = Measured)] fn seal_hash_blake2_256(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::hasher("seal_hash_blake2_256", r, 0)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // `n`: Input to hash in bytes #[benchmark(pov_mode = Measured)] fn seal_hash_blake2_256_per_byte(n: Linear<0, { code::max_pages::() * 64 * 1024 }>) { call_builder!(func, WasmModule::hasher("seal_hash_blake2_256", 1, n)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // Only the overhead of calling the function itself with minimal arguments. #[benchmark(pov_mode = Measured)] fn seal_hash_blake2_128(r: Linear<0, API_BENCHMARK_RUNS>) { call_builder!(func, WasmModule::hasher("seal_hash_blake2_128", r, 0)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // `n`: Input to hash in bytes #[benchmark(pov_mode = Measured)] fn seal_hash_blake2_128_per_byte(n: Linear<0, { code::max_pages::() * 64 * 1024 }>) { call_builder!(func, WasmModule::hasher("seal_hash_blake2_128", 1, n)); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // `n`: Message input length to verify in bytes. @@ -2368,10 +2507,13 @@ mod benchmarks { }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -2425,10 +2567,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -2475,10 +2620,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -2515,10 +2663,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -2556,10 +2707,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -2598,10 +2752,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -2656,10 +2813,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -2713,10 +2873,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); Ok(()) } @@ -2734,10 +2897,13 @@ mod benchmarks { ..Default::default() }); call_builder!(func, code); + + let res; #[block] { - func.call(); + res = func.call(); } + assert_eq!(res.did_revert(), false); } // We load `i64` values from random linear memory locations and store the loaded diff --git a/substrate/frame/contracts/src/lib.rs b/substrate/frame/contracts/src/lib.rs index 73c70a7704e202845a6ce4edc361527b6c125fbe..0045d72141c90a33a0bd34d90a0136b459ca2352 100644 --- a/substrate/frame/contracts/src/lib.rs +++ b/substrate/frame/contracts/src/lib.rs @@ -101,6 +101,7 @@ mod wasm; pub mod chain_extension; pub mod debug; pub mod migration; +pub mod test_utils; pub mod weights; #[cfg(test)] @@ -222,14 +223,14 @@ pub struct Environment { pub struct ApiVersion(u16); impl Default for ApiVersion { fn default() -> Self { - Self(2) + Self(3) } } #[test] fn api_version_is_up_to_date() { assert_eq!( - 109, + 111, crate::wasm::STABLE_API_COUNT, "Stable API count has changed. Bump the returned value of ApiVersion::default() and update the test." ); @@ -250,7 +251,7 @@ pub mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); - #[pallet::config] + #[pallet::config(with_default)] pub trait Config: frame_system::Config { /// The time implementation used to supply timestamps to contracts through `seal_now`. type Time: Time; @@ -263,22 +264,30 @@ pub mod pallet { /// be instantiated from existing codes that use this deprecated functionality. It will /// be removed eventually. Hence for new `pallet-contracts` deployments it is okay /// to supply a dummy implementation for this type (because it is never used). + #[pallet::no_default_bounds] type Randomness: Randomness>; /// The fungible in which fees are paid and contract balances are held. + #[pallet::no_default] type Currency: Inspect + Mutate + MutateHold; /// The overarching event type. + #[pallet::no_default_bounds] type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The overarching call type. + #[pallet::no_default_bounds] type RuntimeCall: Dispatchable + GetDispatchInfo + codec::Decode + IsType<::RuntimeCall>; + /// Overarching hold reason. + #[pallet::no_default_bounds] + type RuntimeHoldReason: From; + /// Filter that is applied to calls dispatched by contracts. /// /// Use this filter to control which dispatchables are callable by contracts. @@ -298,13 +307,12 @@ pub mod pallet { /// Therefore please make sure to be restrictive about which dispatchables are allowed /// in order to not introduce a new DoS vector like memory allocation patterns that can /// be exploited to drive the runtime into a panic. - /// - /// This filter does not apply to XCM transact calls. To impose restrictions on XCM transact - /// calls, you must configure them separately within the XCM pallet itself. + #[pallet::no_default_bounds] type CallFilter: Contains<::RuntimeCall>; /// Used to answer contracts' queries regarding the current weight price. This is **not** /// used to calculate the actual fee and is only for informational purposes. + #[pallet::no_default_bounds] type WeightPrice: Convert>; /// Describes the weights of the dispatchables of this module and is also used to @@ -312,10 +320,12 @@ pub mod pallet { type WeightInfo: WeightInfo; /// Type that allows the runtime authors to add new host functions for a contract to call. + #[pallet::no_default_bounds] type ChainExtension: chain_extension::ChainExtension + Default; /// Cost schedule and limits. #[pallet::constant] + #[pallet::no_default] type Schedule: Get>; /// The type of the call stack determines the maximum nesting depth of contract calls. @@ -326,6 +336,7 @@ pub mod pallet { /// /// This setting along with [`MaxCodeLen`](#associatedtype.MaxCodeLen) directly affects /// memory usage of your runtime. + #[pallet::no_default] type CallStack: Array>; /// The amount of balance a caller has to pay for each byte of storage. @@ -334,10 +345,12 @@ pub mod pallet { /// /// Changing this value for an existing chain might need a storage migration. #[pallet::constant] + #[pallet::no_default_bounds] type DepositPerByte: Get>; /// Fallback value to limit the storage deposit if it's not being set by the caller. #[pallet::constant] + #[pallet::no_default_bounds] type DefaultDepositLimit: Get>; /// The amount of balance a caller has to pay for each storage item. @@ -346,6 +359,7 @@ pub mod pallet { /// /// Changing this value for an existing chain might need a storage migration. #[pallet::constant] + #[pallet::no_default_bounds] type DepositPerItem: Get>; /// The percentage of the storage deposit that should be held for using a code hash. @@ -356,6 +370,7 @@ pub mod pallet { type CodeHashLockupDepositPercent: Get; /// The address generator used to generate the addresses of contracts. + #[pallet::no_default_bounds] type AddressGenerator: AddressGenerator; /// The maximum length of a contract code in bytes. @@ -395,6 +410,7 @@ pub mod pallet { /// /// By default, it is safe to set this to `EnsureSigned`, allowing anyone to upload contract /// code. + #[pallet::no_default_bounds] type UploadOrigin: EnsureOrigin; /// Origin allowed to instantiate code. @@ -407,11 +423,9 @@ pub mod pallet { /// /// By default, it is safe to set this to `EnsureSigned`, allowing anyone to instantiate /// contract code. + #[pallet::no_default_bounds] type InstantiateOrigin: EnsureOrigin; - /// Overarching hold reason. - type RuntimeHoldReason: From; - /// The sequence of migration steps that will be applied during a migration. /// /// # Examples @@ -435,6 +449,7 @@ pub mod pallet { /// For most production chains, it's recommended to use the `()` implementation of this /// trait. This implementation offers additional logging when the log target /// "runtime::contracts" is set to trace. + #[pallet::no_default_bounds] type Debug: Debugger; /// Type that bundles together all the runtime configurable interface types. @@ -442,16 +457,19 @@ pub mod pallet { /// This is not a real config. We just mention the type here as constant so that /// its type appears in the metadata. Only valid value is `()`. #[pallet::constant] + #[pallet::no_default_bounds] type Environment: Get>; /// The version of the HostFn APIs that are available in the runtime. /// /// Only valid value is `()`. #[pallet::constant] + #[pallet::no_default_bounds] type ApiVersion: Get; /// A type that exposes XCM APIs, allowing contracts to interact with other parachains, and /// execute XCM programs. + #[pallet::no_default_bounds] type Xcm: xcm_builder::Controller< OriginFor, ::RuntimeCall, @@ -459,6 +477,95 @@ pub mod pallet { >; } + /// Container for different types that implement [`DefaultConfig`]` of this pallet. + pub mod config_preludes { + use super::*; + use frame_support::{ + derive_impl, + traits::{ConstBool, ConstU32}, + }; + use frame_system::EnsureSigned; + use sp_core::parameter_types; + + type AccountId = sp_runtime::AccountId32; + type Balance = u64; + const UNITS: Balance = 10_000_000_000; + const CENTS: Balance = UNITS / 100; + + const fn deposit(items: u32, bytes: u32) -> Balance { + items as Balance * 1 * CENTS + (bytes as Balance) * 1 * CENTS + } + + parameter_types! { + pub const DepositPerItem: Balance = deposit(1, 0); + pub const DepositPerByte: Balance = deposit(0, 1); + pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); + pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); + pub const MaxDelegateDependencies: u32 = 32; + } + + /// A type providing default configurations for this pallet in testing environment. + pub struct TestDefaultConfig; + + impl Randomness for TestDefaultConfig { + fn random(_subject: &[u8]) -> (Output, BlockNumber) { + unimplemented!("No default `random` implementation in `TestDefaultConfig`, provide a custom `T::Randomness` type.") + } + } + + impl Time for TestDefaultConfig { + type Moment = u64; + fn now() -> Self::Moment { + unimplemented!("No default `now` implementation in `TestDefaultConfig` provide a custom `T::Time` type.") + } + } + + impl> Convert for TestDefaultConfig { + fn convert(w: Weight) -> T { + w.ref_time().into() + } + } + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig, no_aggregated_types)] + impl frame_system::DefaultConfig for TestDefaultConfig {} + + #[frame_support::register_default_impl(TestDefaultConfig)] + impl DefaultConfig for TestDefaultConfig { + #[inject_runtime_type] + type RuntimeEvent = (); + + #[inject_runtime_type] + type RuntimeHoldReason = (); + + #[inject_runtime_type] + type RuntimeCall = (); + + type AddressGenerator = DefaultAddressGenerator; + type CallFilter = (); + type ChainExtension = (); + type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; + type DefaultDepositLimit = DefaultDepositLimit; + type DepositPerByte = DepositPerByte; + type DepositPerItem = DepositPerItem; + type MaxCodeLen = ConstU32<{ 123 * 1024 }>; + type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; + type MaxDelegateDependencies = MaxDelegateDependencies; + type MaxStorageKeyLen = ConstU32<128>; + type Migrations = (); + type Time = Self; + type Randomness = Self; + type UnsafeUnstableInterface = ConstBool; + type UploadOrigin = EnsureSigned; + type InstantiateOrigin = EnsureSigned; + type WeightInfo = (); + type WeightPrice = Self; + type Debug = (); + type Environment = (); + type ApiVersion = (); + type Xcm = (); + } + } + #[pallet::hooks] impl Hooks> for Pallet { fn on_idle(_block: BlockNumberFor, limit: Weight) -> Weight { diff --git a/substrate/frame/contracts/src/test_utils.rs b/substrate/frame/contracts/src/test_utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..564b2d2e3bd237ae57263a3fe7ea1ddda95df408 --- /dev/null +++ b/substrate/frame/contracts/src/test_utils.rs @@ -0,0 +1,30 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Shared utilities for testing contracts. +//! This is not part of the tests module because it is made public for other crates to use. +#![cfg(feature = "std")] +use frame_support::weights::Weight; +pub use sp_runtime::AccountId32; + +pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); +pub const BOB: AccountId32 = AccountId32::new([2u8; 32]); +pub const CHARLIE: AccountId32 = AccountId32::new([3u8; 32]); +pub const DJANGO: AccountId32 = AccountId32::new([4u8; 32]); + +pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); +pub mod builder; diff --git a/substrate/frame/contracts/src/test_utils/builder.rs b/substrate/frame/contracts/src/test_utils/builder.rs new file mode 100644 index 0000000000000000000000000000000000000000..94540eca5b4bf2c02a6b58e45857e112f1f18c05 --- /dev/null +++ b/substrate/frame/contracts/src/test_utils/builder.rs @@ -0,0 +1,220 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::GAS_LIMIT; +use crate::{ + AccountIdLookupOf, AccountIdOf, BalanceOf, Code, CodeHash, CollectEvents, Config, + ContractExecResult, ContractInstantiateResult, DebugInfo, Determinism, EventRecordOf, + ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, +}; +use codec::{Encode, HasCompact}; +use core::fmt::Debug; +use frame_support::pallet_prelude::DispatchResultWithPostInfo; +use paste::paste; +use scale_info::TypeInfo; + +/// Helper macro to generate a builder for contract API calls. +macro_rules! builder { + // Entry point to generate a builder for the given method. + ( + $method:ident($($field:ident: $type:ty,)*) -> $result:ty; + $($extra:item)* + ) => { + paste!{ + builder!([< $method:camel Builder >], $method($($field: $type,)* ) -> $result; $($extra)*); + } + }; + // Generate the builder struct and its methods. + ( + $name:ident, + $method:ident($($field:ident: $type:ty,)*) -> $result:ty; + $($extra:item)* + ) => { + #[doc = concat!("A builder to construct a ", stringify!($method), " call")] + pub struct $name { + $($field: $type,)* + } + + #[allow(dead_code)] + impl $name + where + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + { + $( + #[doc = concat!("Set the ", stringify!($field))] + pub fn $field(mut self, value: $type) -> Self { + self.$field = value; + self + } + )* + + #[doc = concat!("Build the ", stringify!($method), " call")] + pub fn build(self) -> $result { + Pallet::::$method( + $(self.$field,)* + ) + } + + $($extra)* + } + } +} + +builder!( + instantiate_with_code( + origin: OriginFor, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: Option< as codec::HasCompact>::Type>, + code: Vec, + data: Vec, + salt: Vec, + ) -> DispatchResultWithPostInfo; + + /// Create an [`InstantiateWithCodeBuilder`] with default values. + pub fn instantiate_with_code(origin: OriginFor, code: Vec) -> Self { + Self { + origin: origin, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: None, + code, + data: vec![], + salt: vec![], + } + } +); + +builder!( + instantiate( + origin: OriginFor, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: Option< as codec::HasCompact>::Type>, + code_hash: CodeHash, + data: Vec, + salt: Vec, + ) -> DispatchResultWithPostInfo; + + /// Create an [`InstantiateBuilder`] with default values. + pub fn instantiate(origin: OriginFor, code_hash: CodeHash) -> Self { + Self { + origin, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: None, + code_hash, + data: vec![], + salt: vec![], + } + } +); + +builder!( + bare_instantiate( + origin: AccountIdOf, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: Option>, + code: Code>, + data: Vec, + salt: Vec, + debug: DebugInfo, + collect_events: CollectEvents, + ) -> ContractInstantiateResult, BalanceOf, EventRecordOf>; + + /// Build the instantiate call and unwrap the result. + pub fn build_and_unwrap_result(self) -> InstantiateReturnValue> { + self.build().result.unwrap() + } + + /// Build the instantiate call and unwrap the account id. + pub fn build_and_unwrap_account_id(self) -> AccountIdOf { + self.build().result.unwrap().account_id + } + + pub fn bare_instantiate(origin: AccountIdOf, code: Code>) -> Self { + Self { + origin, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: None, + code, + data: vec![], + salt: vec![], + debug: DebugInfo::Skip, + collect_events: CollectEvents::Skip, + } + } +); + +builder!( + call( + origin: OriginFor, + dest: AccountIdLookupOf, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: Option< as codec::HasCompact>::Type>, + data: Vec, + ) -> DispatchResultWithPostInfo; + + /// Create a [`CallBuilder`] with default values. + pub fn call(origin: OriginFor, dest: AccountIdLookupOf) -> Self { + CallBuilder { + origin, + dest, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: None, + data: vec![], + } + } +); + +builder!( + bare_call( + origin: AccountIdOf, + dest: AccountIdOf, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: Option>, + data: Vec, + debug: DebugInfo, + collect_events: CollectEvents, + determinism: Determinism, + ) -> ContractExecResult, EventRecordOf>; + + /// Build the call and unwrap the result. + pub fn build_and_unwrap_result(self) -> ExecReturnValue { + self.build().result.unwrap() + } + + /// Create a [`BareCallBuilder`] with default values. + pub fn bare_call(origin: AccountIdOf, dest: AccountIdOf) -> Self { + Self { + origin, + dest, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: None, + data: vec![], + debug: DebugInfo::Skip, + collect_events: CollectEvents::Skip, + determinism: Determinism::Enforced, + } + } +); diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index 0b83358a7f56a134a938076aab5f20b5915f0826..8fe845fcf0f83e8658e7638ea2536bee2794d73f 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -15,7 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod builder; mod pallet_dummy; mod test_debug; @@ -98,7 +97,6 @@ macro_rules! assert_refcount { } pub mod test_utils { - use super::{Contracts, DepositPerByte, DepositPerItem, Hash, SysConfig, Test}; use crate::{ exec::AccountIdOf, BalanceOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, @@ -166,6 +164,38 @@ pub mod test_utils { } } +mod builder { + use super::Test; + use crate::{ + test_utils::{builder::*, AccountId32, ALICE}, + tests::RuntimeOrigin, + AccountIdLookupOf, Code, CodeHash, + }; + + pub fn bare_instantiate(code: Code>) -> BareInstantiateBuilder { + BareInstantiateBuilder::::bare_instantiate(ALICE, code) + } + + pub fn bare_call(dest: AccountId32) -> BareCallBuilder { + BareCallBuilder::::bare_call(ALICE, dest) + } + + pub fn instantiate_with_code(code: Vec) -> InstantiateWithCodeBuilder { + InstantiateWithCodeBuilder::::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + code, + ) + } + + pub fn instantiate(code_hash: CodeHash) -> InstantiateBuilder { + InstantiateBuilder::::instantiate(RuntimeOrigin::signed(ALICE), code_hash) + } + + pub fn call(dest: AccountIdLookupOf) -> CallBuilder { + CallBuilder::::call(RuntimeOrigin::signed(ALICE), dest) + } +} + impl Test { pub fn set_unstable_interface(unstable_interface: bool) { UNSTABLE_INTERFACE.with(|v| *v.borrow_mut() = unstable_interface); @@ -334,34 +364,24 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { + type Block = Block; type AccountId = AccountId32; type Lookup = IdentityLookup; - type Block = Block; type AccountData = pallet_balances::AccountData; } + impl pallet_insecure_randomness_collective_flip::Config for Test {} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Test { - type MaxLocks = (); - type MaxReserves = (); - type ReserveIdentifier = [u8; 8]; - type Balance = u64; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; + type ReserveIdentifier = [u8; 8]; type AccountStore = System; - type WeightInfo = (); - type FreezeIdentifier = (); - type MaxFreezes = (); - type RuntimeHoldReason = RuntimeHoldReason; - type RuntimeFreezeReason = RuntimeFreezeReason; } -impl pallet_timestamp::Config for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = ConstU64<1>; - type WeightInfo = (); -} +#[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)] +impl pallet_timestamp::Config for Test {} + impl pallet_utility::Config for Test { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; @@ -467,16 +487,13 @@ parameter_types! { pub static UnstableInterface: bool = true; } +#[derive_impl(crate::config_preludes::TestDefaultConfig)] impl Config for Test { type Time = Timestamp; type Randomness = Randomness; type Currency = Balances; - type RuntimeEvent = RuntimeEvent; - type RuntimeCall = RuntimeCall; type CallFilter = TestFilter; type CallStack = [Frame; 5]; - type WeightPrice = Self; - type WeightInfo = (); type ChainExtension = (TestExtension, DisabledExtension, RevertingExtension, TempStorageExtension); type Schedule = MySchedule; @@ -484,20 +501,13 @@ impl Config for Test { type DepositPerItem = DepositPerItem; type DefaultDepositLimit = DefaultDepositLimit; type AddressGenerator = DefaultAddressGenerator; - type MaxCodeLen = ConstU32<{ 123 * 1024 }>; - type MaxStorageKeyLen = ConstU32<128>; type UnsafeUnstableInterface = UnstableInterface; type UploadOrigin = EnsureAccount; type InstantiateOrigin = EnsureAccount; - type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; - type RuntimeHoldReason = RuntimeHoldReason; type Migrations = crate::migration::codegen::BenchMigrations; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type MaxDelegateDependencies = MaxDelegateDependencies; type Debug = TestDebug; - type Environment = (); - type ApiVersion = (); - type Xcm = (); } pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); @@ -2459,14 +2469,7 @@ fn failed_deposit_charge_should_roll_back_call() { transfer_proxy_call, ); - >::call( - RuntimeOrigin::signed(ALICE), - addr_caller.clone(), - 0, - GAS_LIMIT, - None, - data.encode(), - ) + builder::call(addr_caller).data(data.encode()).build() }) }; diff --git a/substrate/frame/contracts/src/tests/builder.rs b/substrate/frame/contracts/src/tests/builder.rs deleted file mode 100644 index 08d12503a290ad30a08c44daf73647e278b9a398..0000000000000000000000000000000000000000 --- a/substrate/frame/contracts/src/tests/builder.rs +++ /dev/null @@ -1,219 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::{AccountId32, Test, ALICE, GAS_LIMIT}; -use crate::{ - tests::RuntimeOrigin, AccountIdLookupOf, AccountIdOf, BalanceOf, Code, CodeHash, CollectEvents, - ContractExecResult, ContractInstantiateResult, DebugInfo, Determinism, EventRecordOf, - ExecReturnValue, OriginFor, Pallet, Weight, -}; -use codec::Compact; -use frame_support::pallet_prelude::DispatchResultWithPostInfo; -use paste::paste; - -/// Helper macro to generate a builder for contract API calls. -macro_rules! builder { - // Entry point to generate a builder for the given method. - ( - $method:ident($($field:ident: $type:ty,)*) -> $result:ty - ) => { - paste!{ - builder!([< $method:camel Builder >], $method($($field: $type,)* ) -> $result); - } - }; - // Generate the builder struct and its methods. - ( - $name:ident, - $method:ident( - $($field:ident: $type:ty,)* - ) -> $result:ty - ) => { - #[doc = concat!("A builder to construct a ", stringify!($method), " call")] - pub struct $name { - $($field: $type,)* - } - - #[allow(dead_code)] - impl $name - { - $( - #[doc = concat!("Set the ", stringify!($field))] - pub fn $field(mut self, value: $type) -> Self { - self.$field = value; - self - } - )* - - #[doc = concat!("Build the ", stringify!($method), " call")] - pub fn build(self) -> $result { - Pallet::::$method( - $(self.$field,)* - ) - } - } - } -} - -builder!( - instantiate_with_code( - origin: OriginFor, - value: BalanceOf, - gas_limit: Weight, - storage_deposit_limit: Option>>, - code: Vec, - data: Vec, - salt: Vec, - ) -> DispatchResultWithPostInfo -); - -builder!( - instantiate( - origin: OriginFor, - value: BalanceOf, - gas_limit: Weight, - storage_deposit_limit: Option>>, - code_hash: CodeHash, - data: Vec, - salt: Vec, - ) -> DispatchResultWithPostInfo -); - -builder!( - bare_instantiate( - origin: AccountIdOf, - value: BalanceOf, - gas_limit: Weight, - storage_deposit_limit: Option>, - code: Code>, - data: Vec, - salt: Vec, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractInstantiateResult, BalanceOf, EventRecordOf> -); - -builder!( - call( - origin: OriginFor, - dest: AccountIdLookupOf, - value: BalanceOf, - gas_limit: Weight, - storage_deposit_limit: Option>>, - data: Vec, - ) -> DispatchResultWithPostInfo -); - -builder!( - bare_call( - origin: AccountIdOf, - dest: AccountIdOf, - value: BalanceOf, - gas_limit: Weight, - storage_deposit_limit: Option>, - data: Vec, - debug: DebugInfo, - collect_events: CollectEvents, - determinism: Determinism, - ) -> ContractExecResult, EventRecordOf> -); - -/// Create a [`BareInstantiateBuilder`] with default values. -pub fn bare_instantiate(code: Code>) -> BareInstantiateBuilder { - BareInstantiateBuilder { - origin: ALICE, - value: 0, - gas_limit: GAS_LIMIT, - storage_deposit_limit: None, - code, - data: vec![], - salt: vec![], - debug: DebugInfo::Skip, - collect_events: CollectEvents::Skip, - } -} - -impl BareInstantiateBuilder { - /// Build the instantiate call and unwrap the result. - pub fn build_and_unwrap_result(self) -> crate::InstantiateReturnValue> { - self.build().result.unwrap() - } - - /// Build the instantiate call and unwrap the account id. - pub fn build_and_unwrap_account_id(self) -> AccountIdOf { - self.build().result.unwrap().account_id - } -} - -/// Create a [`BareCallBuilder`] with default values. -pub fn bare_call(dest: AccountId32) -> BareCallBuilder { - BareCallBuilder { - origin: ALICE, - dest, - value: 0, - gas_limit: GAS_LIMIT, - storage_deposit_limit: None, - data: vec![], - debug: DebugInfo::Skip, - collect_events: CollectEvents::Skip, - determinism: Determinism::Enforced, - } -} - -impl BareCallBuilder { - /// Build the call and unwrap the result. - pub fn build_and_unwrap_result(self) -> ExecReturnValue { - self.build().result.unwrap() - } -} - -/// Create an [`InstantiateWithCodeBuilder`] with default values. -pub fn instantiate_with_code(code: Vec) -> InstantiateWithCodeBuilder { - InstantiateWithCodeBuilder { - origin: RuntimeOrigin::signed(ALICE), - value: 0, - gas_limit: GAS_LIMIT, - storage_deposit_limit: None, - code, - data: vec![], - salt: vec![], - } -} - -/// Create an [`InstantiateBuilder`] with default values. -pub fn instantiate(code_hash: CodeHash) -> InstantiateBuilder { - InstantiateBuilder { - origin: RuntimeOrigin::signed(ALICE), - value: 0, - gas_limit: GAS_LIMIT, - storage_deposit_limit: None, - code_hash, - data: vec![], - salt: vec![], - } -} - -/// Create a [`CallBuilder`] with default values. -pub fn call(dest: AccountIdLookupOf) -> CallBuilder { - CallBuilder { - origin: RuntimeOrigin::signed(ALICE), - dest, - value: 0, - gas_limit: GAS_LIMIT, - storage_deposit_limit: None, - data: vec![], - } -} diff --git a/substrate/frame/contracts/src/wasm/runtime.rs b/substrate/frame/contracts/src/wasm/runtime.rs index 28a08ab0224ddf4603c881b50c9359d4a6f54d46..52ceda99edb7edcb4fe7b15a4afe9b2782f0a595 100644 --- a/substrate/frame/contracts/src/wasm/runtime.rs +++ b/substrate/frame/contracts/src/wasm/runtime.rs @@ -25,8 +25,12 @@ use crate::{ }; use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; use frame_support::{ - dispatch::DispatchInfo, ensure, pallet_prelude::DispatchResultWithPostInfo, parameter_types, - traits::Get, weights::Weight, + dispatch::DispatchInfo, + ensure, + pallet_prelude::{DispatchResult, DispatchResultWithPostInfo}, + parameter_types, + traits::Get, + weights::Weight, }; use pallet_contracts_proc_macro::define_env; use pallet_contracts_uapi::{CallFlags, ReturnFlags}; @@ -37,6 +41,9 @@ use sp_runtime::{ }; use sp_std::{fmt, prelude::*}; use wasmi::{core::HostError, errors::LinkerError, Linker, Memory, Store}; +use xcm::VersionedXcm; + +type CallOf = ::RuntimeCall; /// The maximum nesting depth a contract can use when encoding types. const MAX_DECODE_NESTING: u32 = 256; @@ -371,6 +378,29 @@ fn already_charged(_: u32) -> Option { None } +/// Ensure that the XCM program is executable, by checking that it does not contain any [`Transact`] +/// instruction with a call that is not allowed by the CallFilter. +fn ensure_executable(message: &VersionedXcm>) -> DispatchResult { + use frame_support::traits::Contains; + use xcm::prelude::{Transact, Xcm}; + + let mut message: Xcm> = + message.clone().try_into().map_err(|_| Error::::XCMDecodeFailed)?; + + message.iter_mut().try_for_each(|inst| -> DispatchResult { + let Transact { ref mut call, .. } = inst else { return Ok(()) }; + let call = call.ensure_decoded().map_err(|_| Error::::XCMDecodeFailed)?; + + if !::CallFilter::contains(call) { + return Err(frame_system::Error::::CallFiltered.into()) + } + + Ok(()) + })?; + + Ok(()) +} + /// Can only be used for one call. pub struct Runtime<'a, E: Ext + 'a> { ext: &'a mut E, @@ -2074,7 +2104,6 @@ pub mod env { /// Execute an XCM program locally, using the contract's address as the origin. /// See [`pallet_contracts_uapi::HostFn::execute_xcm`]. - #[unstable] fn xcm_execute( ctx: _, memory: _, @@ -2082,13 +2111,16 @@ pub mod env { msg_len: u32, ) -> Result { use frame_support::dispatch::DispatchInfo; + use xcm::VersionedXcm; use xcm_builder::{ExecuteController, ExecuteControllerWeightInfo}; ctx.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; - let message = ctx.read_sandbox_memory_as_unbounded(memory, msg_ptr, msg_len)?; + let message: VersionedXcm> = + ctx.read_sandbox_memory_as_unbounded(memory, msg_ptr, msg_len)?; + ensure_executable::(&message)?; let execute_weight = - <::Xcm as ExecuteController<_, _>>::WeightInfo::execute_blob(); + <::Xcm as ExecuteController<_, _>>::WeightInfo::execute(); let weight = ctx.ext.gas_meter().gas_left().max(execute_weight); let dispatch_info = DispatchInfo { weight, ..Default::default() }; @@ -2097,9 +2129,9 @@ pub mod env { RuntimeCosts::CallXcmExecute, |ctx| { let origin = crate::RawOrigin::Signed(ctx.ext.address().clone()).into(); - let weight_used = <::Xcm>::execute_blob( + let weight_used = <::Xcm>::execute( origin, - message, + Box::new(message), weight.saturating_sub(execute_weight), )?; @@ -2110,7 +2142,6 @@ pub mod env { /// Send an XCM program from the contract to the specified destination. /// See [`pallet_contracts_uapi::HostFn::send_xcm`]. - #[unstable] fn xcm_send( ctx: _, memory: _, @@ -2119,18 +2150,19 @@ pub mod env { msg_len: u32, output_ptr: u32, ) -> Result { - use xcm::VersionedLocation; + use xcm::{VersionedLocation, VersionedXcm}; use xcm_builder::{SendController, SendControllerWeightInfo}; ctx.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; let dest: VersionedLocation = ctx.read_sandbox_memory_as(memory, dest_ptr)?; - let message = ctx.read_sandbox_memory_as_unbounded(memory, msg_ptr, msg_len)?; - let weight = <::Xcm as SendController<_>>::WeightInfo::send_blob(); + let message: VersionedXcm<()> = + ctx.read_sandbox_memory_as_unbounded(memory, msg_ptr, msg_len)?; + let weight = <::Xcm as SendController<_>>::WeightInfo::send(); ctx.charge_gas(RuntimeCosts::CallRuntime(weight))?; let origin = crate::RawOrigin::Signed(ctx.ext.address().clone()).into(); - match <::Xcm>::send_blob(origin, dest.into(), message) { + match <::Xcm>::send(origin, dest.into(), message.into()) { Ok(message_id) => { ctx.write_sandbox_memory(memory, output_ptr, &message_id.encode())?; Ok(ReturnErrorCode::Success) diff --git a/substrate/frame/contracts/src/weights.rs b/substrate/frame/contracts/src/weights.rs index ca7f58cf5b0cada995a268cc241b36a0599cb709..b95b1d1a9a2e5ae47ca0d9fe1715031f47e43986 100644 --- a/substrate/frame/contracts/src/weights.rs +++ b/substrate/frame/contracts/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for `pallet_contracts` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-04-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` @@ -143,8 +143,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `142` // Estimated: `1627` - // Minimum execution time: 2_047_000 picoseconds. - Weight::from_parts(2_116_000, 1627) + // Minimum execution time: 2_149_000 picoseconds. + Weight::from_parts(2_274_000, 1627) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -154,10 +154,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `452 + k * (69 ±0)` // Estimated: `442 + k * (70 ±0)` - // Minimum execution time: 12_474_000 picoseconds. - Weight::from_parts(12_767_000, 442) - // Standard Error: 1_081 - .saturating_add(Weight::from_parts(1_187_278, 0).saturating_mul(k.into())) + // Minimum execution time: 12_863_000 picoseconds. + Weight::from_parts(13_188_000, 442) + // Standard Error: 1_053 + .saturating_add(Weight::from_parts(1_105_325, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -171,10 +171,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `211 + c * (1 ±0)` // Estimated: `6149 + c * (1 ±0)` - // Minimum execution time: 8_307_000 picoseconds. - Weight::from_parts(8_939_322, 6149) + // Minimum execution time: 8_432_000 picoseconds. + Weight::from_parts(9_203_290, 6149) // Standard Error: 1 - .saturating_add(Weight::from_parts(1_190, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(1_186, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) @@ -187,8 +187,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `510` // Estimated: `6450` - // Minimum execution time: 16_915_000 picoseconds. - Weight::from_parts(17_638_000, 6450) + // Minimum execution time: 17_177_000 picoseconds. + Weight::from_parts(17_663_000, 6450) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -201,10 +201,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `171 + k * (1 ±0)` // Estimated: `3635 + k * (1 ±0)` - // Minimum execution time: 3_607_000 picoseconds. - Weight::from_parts(1_979_323, 3635) - // Standard Error: 1_018 - .saturating_add(Weight::from_parts(1_196_162, 0).saturating_mul(k.into())) + // Minimum execution time: 3_636_000 picoseconds. + Weight::from_parts(3_774_000, 3635) + // Standard Error: 542 + .saturating_add(Weight::from_parts(1_260_058, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -225,10 +225,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `328 + c * (1 ±0)` // Estimated: `6266 + c * (1 ±0)` - // Minimum execution time: 21_056_000 picoseconds. - Weight::from_parts(21_633_895, 6266) + // Minimum execution time: 21_585_000 picoseconds. + Weight::from_parts(22_069_944, 6266) // Standard Error: 1 - .saturating_add(Weight::from_parts(390, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(404, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) @@ -239,8 +239,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `440` // Estimated: `6380` - // Minimum execution time: 12_860_000 picoseconds. - Weight::from_parts(13_525_000, 6380) + // Minimum execution time: 13_283_000 picoseconds. + Weight::from_parts(14_015_000, 6380) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -254,8 +254,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `352` // Estimated: `6292` - // Minimum execution time: 46_926_000 picoseconds. - Weight::from_parts(47_828_000, 6292) + // Minimum execution time: 48_022_000 picoseconds. + Weight::from_parts(49_627_000, 6292) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -267,8 +267,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `594` // Estimated: `6534` - // Minimum execution time: 55_081_000 picoseconds. - Weight::from_parts(56_899_000, 6534) + // Minimum execution time: 58_374_000 picoseconds. + Weight::from_parts(59_615_000, 6534) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -278,8 +278,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `409` // Estimated: `6349` - // Minimum execution time: 12_595_000 picoseconds. - Weight::from_parts(13_059_000, 6349) + // Minimum execution time: 12_559_000 picoseconds. + Weight::from_parts(12_947_000, 6349) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -290,7 +290,7 @@ impl WeightInfo for SubstrateWeight { // Measured: `142` // Estimated: `1627` // Minimum execution time: 2_480_000 picoseconds. - Weight::from_parts(2_663_000, 1627) + Weight::from_parts(2_680_000, 1627) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -302,8 +302,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `166` // Estimated: `3631` - // Minimum execution time: 12_115_000 picoseconds. - Weight::from_parts(12_506_000, 3631) + // Minimum execution time: 12_625_000 picoseconds. + Weight::from_parts(13_094_000, 3631) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -313,8 +313,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `142` // Estimated: `3607` - // Minimum execution time: 4_757_000 picoseconds. - Weight::from_parts(5_082_000, 3607) + // Minimum execution time: 4_836_000 picoseconds. + Weight::from_parts(5_182_000, 3607) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) @@ -325,8 +325,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `167` // Estimated: `3632` - // Minimum execution time: 6_017_000 picoseconds. - Weight::from_parts(6_421_000, 3632) + // Minimum execution time: 6_319_000 picoseconds. + Weight::from_parts(6_582_000, 3632) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) @@ -337,8 +337,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `142` // Estimated: `3607` - // Minimum execution time: 6_238_000 picoseconds. - Weight::from_parts(6_587_000, 3607) + // Minimum execution time: 6_532_000 picoseconds. + Weight::from_parts(6_909_000, 3607) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -363,10 +363,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `804 + c * (1 ±0)` // Estimated: `9217 + c * (1 ±0)` - // Minimum execution time: 288_968_000 picoseconds. - Weight::from_parts(267_291_922, 9217) - // Standard Error: 78 - .saturating_add(Weight::from_parts(34_879, 0).saturating_mul(c.into())) + // Minimum execution time: 305_778_000 picoseconds. + Weight::from_parts(282_321_249, 9217) + // Standard Error: 72 + .saturating_add(Weight::from_parts(33_456, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) @@ -398,14 +398,14 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `326` // Estimated: `8740` - // Minimum execution time: 3_948_426_000 picoseconds. - Weight::from_parts(440_017_623, 8740) - // Standard Error: 555 - .saturating_add(Weight::from_parts(71_483, 0).saturating_mul(c.into())) - // Standard Error: 66 - .saturating_add(Weight::from_parts(1_831, 0).saturating_mul(i.into())) - // Standard Error: 66 - .saturating_add(Weight::from_parts(1_694, 0).saturating_mul(s.into())) + // Minimum execution time: 3_810_809_000 picoseconds. + Weight::from_parts(739_511_598, 8740) + // Standard Error: 140 + .saturating_add(Weight::from_parts(67_574, 0).saturating_mul(c.into())) + // Standard Error: 16 + .saturating_add(Weight::from_parts(1_488, 0).saturating_mul(i.into())) + // Standard Error: 16 + .saturating_add(Weight::from_parts(1_537, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().writes(10_u64)) } @@ -435,12 +435,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `563` // Estimated: `8982` - // Minimum execution time: 2_011_037_000 picoseconds. - Weight::from_parts(2_047_025_000, 8982) - // Standard Error: 28 - .saturating_add(Weight::from_parts(968, 0).saturating_mul(i.into())) - // Standard Error: 28 - .saturating_add(Weight::from_parts(780, 0).saturating_mul(s.into())) + // Minimum execution time: 1_986_789_000 picoseconds. + Weight::from_parts(2_017_466_000, 8982) + // Standard Error: 26 + .saturating_add(Weight::from_parts(827, 0).saturating_mul(i.into())) + // Standard Error: 26 + .saturating_add(Weight::from_parts(781, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -464,8 +464,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `829` // Estimated: `9244` - // Minimum execution time: 202_190_000 picoseconds. - Weight::from_parts(209_378_000, 9244) + // Minimum execution time: 210_724_000 picoseconds. + Weight::from_parts(218_608_000, 9244) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -486,10 +486,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `145` // Estimated: `6085` - // Minimum execution time: 271_161_000 picoseconds. - Weight::from_parts(279_218_977, 6085) - // Standard Error: 80 - .saturating_add(Weight::from_parts(33_973, 0).saturating_mul(c.into())) + // Minimum execution time: 271_259_000 picoseconds. + Weight::from_parts(298_852_854, 6085) + // Standard Error: 65 + .saturating_add(Weight::from_parts(33_547, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -510,10 +510,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `145` // Estimated: `6085` - // Minimum execution time: 273_684_000 picoseconds. - Weight::from_parts(284_348_722, 6085) - // Standard Error: 79 - .saturating_add(Weight::from_parts(34_205, 0).saturating_mul(c.into())) + // Minimum execution time: 278_167_000 picoseconds. + Weight::from_parts(311_888_941, 6085) + // Standard Error: 58 + .saturating_add(Weight::from_parts(33_595, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -531,8 +531,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `315` // Estimated: `3780` - // Minimum execution time: 45_150_000 picoseconds. - Weight::from_parts(46_780_000, 3780) + // Minimum execution time: 47_403_000 picoseconds. + Weight::from_parts(48_707_000, 3780) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -548,8 +548,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `552` // Estimated: `8967` - // Minimum execution time: 34_738_000 picoseconds. - Weight::from_parts(35_918_000, 8967) + // Minimum execution time: 35_361_000 picoseconds. + Weight::from_parts(36_714_000, 8967) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -558,10 +558,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_094_000 picoseconds. - Weight::from_parts(10_253_702, 0) - // Standard Error: 223 - .saturating_add(Weight::from_parts(250_757, 0).saturating_mul(r.into())) + // Minimum execution time: 9_340_000 picoseconds. + Weight::from_parts(9_360_237, 0) + // Standard Error: 269 + .saturating_add(Weight::from_parts(249_611, 0).saturating_mul(r.into())) } /// Storage: `Contracts::ContractInfoOf` (r:1600 w:0) /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) @@ -570,10 +570,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `509 + r * (77 ±0)` // Estimated: `1467 + r * (2552 ±0)` - // Minimum execution time: 9_102_000 picoseconds. - Weight::from_parts(9_238_000, 1467) - // Standard Error: 6_076 - .saturating_add(Weight::from_parts(3_293_012, 0).saturating_mul(r.into())) + // Minimum execution time: 9_059_000 picoseconds. + Weight::from_parts(9_201_000, 1467) + // Standard Error: 5_643 + .saturating_add(Weight::from_parts(3_343_859, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2552).saturating_mul(r.into())) } @@ -584,10 +584,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `517 + r * (170 ±0)` // Estimated: `1468 + r * (2645 ±0)` - // Minimum execution time: 9_255_000 picoseconds. - Weight::from_parts(9_406_000, 1468) - // Standard Error: 6_826 - .saturating_add(Weight::from_parts(4_205_039, 0).saturating_mul(r.into())) + // Minimum execution time: 9_220_000 picoseconds. + Weight::from_parts(9_399_000, 1468) + // Standard Error: 6_194 + .saturating_add(Weight::from_parts(4_172_011, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2645).saturating_mul(r.into())) } @@ -596,50 +596,50 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_388_000 picoseconds. - Weight::from_parts(9_322_209, 0) - // Standard Error: 269 - .saturating_add(Weight::from_parts(358_189, 0).saturating_mul(r.into())) + // Minimum execution time: 9_707_000 picoseconds. + Weight::from_parts(10_100_456, 0) + // Standard Error: 234 + .saturating_add(Weight::from_parts(338_464, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_caller_is_origin(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_300_000 picoseconds. - Weight::from_parts(10_268_326, 0) - // Standard Error: 72 - .saturating_add(Weight::from_parts(104_650, 0).saturating_mul(r.into())) + // Minimum execution time: 9_524_000 picoseconds. + Weight::from_parts(10_813_389, 0) + // Standard Error: 76 + .saturating_add(Weight::from_parts(102_535, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_caller_is_root(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_162_000 picoseconds. - Weight::from_parts(10_059_984, 0) - // Standard Error: 87 - .saturating_add(Weight::from_parts(87_627, 0).saturating_mul(r.into())) + // Minimum execution time: 9_799_000 picoseconds. + Weight::from_parts(10_886_744, 0) + // Standard Error: 75 + .saturating_add(Weight::from_parts(80_901, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_address(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_193_000 picoseconds. - Weight::from_parts(10_160_715, 0) - // Standard Error: 152 - .saturating_add(Weight::from_parts(263_703, 0).saturating_mul(r.into())) + // Minimum execution time: 9_895_000 picoseconds. + Weight::from_parts(10_658_338, 0) + // Standard Error: 189 + .saturating_add(Weight::from_parts(249_694, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_gas_left(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_109_000 picoseconds. - Weight::from_parts(9_766_924, 0) - // Standard Error: 212 - .saturating_add(Weight::from_parts(291_694, 0).saturating_mul(r.into())) + // Minimum execution time: 9_643_000 picoseconds. + Weight::from_parts(10_932_126, 0) + // Standard Error: 153 + .saturating_add(Weight::from_parts(280_924, 0).saturating_mul(r.into())) } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) @@ -648,10 +648,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `3599` - // Minimum execution time: 9_463_000 picoseconds. - Weight::from_parts(9_541_000, 3599) - // Standard Error: 3_075 - .saturating_add(Weight::from_parts(1_606_043, 0).saturating_mul(r.into())) + // Minimum execution time: 9_548_000 picoseconds. + Weight::from_parts(9_737_000, 3599) + // Standard Error: 971 + .saturating_add(Weight::from_parts(1_704_134, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -659,40 +659,40 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_080_000 picoseconds. - Weight::from_parts(8_121_924, 0) - // Standard Error: 198 - .saturating_add(Weight::from_parts(247_527, 0).saturating_mul(r.into())) + // Minimum execution time: 9_172_000 picoseconds. + Weight::from_parts(18_255_933, 0) + // Standard Error: 540 + .saturating_add(Weight::from_parts(230_929, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_minimum_balance(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_393_000 picoseconds. - Weight::from_parts(9_999_247, 0) - // Standard Error: 169 - .saturating_add(Weight::from_parts(244_563, 0).saturating_mul(r.into())) + // Minimum execution time: 9_232_000 picoseconds. + Weight::from_parts(9_796_584, 0) + // Standard Error: 208 + .saturating_add(Weight::from_parts(239_962, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_block_number(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_236_000 picoseconds. - Weight::from_parts(9_561_435, 0) - // Standard Error: 195 - .saturating_add(Weight::from_parts(239_812, 0).saturating_mul(r.into())) + // Minimum execution time: 9_747_000 picoseconds. + Weight::from_parts(8_733_230, 0) + // Standard Error: 377 + .saturating_add(Weight::from_parts(253_801, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_now(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_259_000 picoseconds. - Weight::from_parts(10_353_960, 0) - // Standard Error: 216 - .saturating_add(Weight::from_parts(243_754, 0).saturating_mul(r.into())) + // Minimum execution time: 9_214_000 picoseconds. + Weight::from_parts(10_194_153, 0) + // Standard Error: 516 + .saturating_add(Weight::from_parts(247_621, 0).saturating_mul(r.into())) } /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `Measured`) @@ -701,10 +701,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `67` // Estimated: `1552` - // Minimum execution time: 9_145_000 picoseconds. - Weight::from_parts(16_524_937, 1552) - // Standard Error: 438 - .saturating_add(Weight::from_parts(666_821, 0).saturating_mul(r.into())) + // Minimum execution time: 9_022_000 picoseconds. + Weight::from_parts(22_051_160, 1552) + // Standard Error: 697 + .saturating_add(Weight::from_parts(709_612, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -712,10 +712,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_179_000 picoseconds. - Weight::from_parts(8_893_261, 0) - // Standard Error: 215 - .saturating_add(Weight::from_parts(175_586, 0).saturating_mul(r.into())) + // Minimum execution time: 9_135_000 picoseconds. + Weight::from_parts(10_646_215, 0) + // Standard Error: 161 + .saturating_add(Weight::from_parts(170_336, 0).saturating_mul(r.into())) } /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) @@ -738,10 +738,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `872` // Estimated: `9287` - // Minimum execution time: 259_315_000 picoseconds. - Weight::from_parts(137_461_362, 9287) - // Standard Error: 18 - .saturating_add(Weight::from_parts(1_388, 0).saturating_mul(n.into())) + // Minimum execution time: 273_896_000 picoseconds. + Weight::from_parts(148_309_654, 9287) + // Standard Error: 16 + .saturating_add(Weight::from_parts(1_355, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -750,20 +750,20 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_919_000 picoseconds. - Weight::from_parts(9_465_187, 0) - // Standard Error: 32_481 - .saturating_add(Weight::from_parts(992_912, 0).saturating_mul(r.into())) + // Minimum execution time: 8_906_000 picoseconds. + Weight::from_parts(9_264_446, 0) + // Standard Error: 19_760 + .saturating_add(Weight::from_parts(1_256_053, 0).saturating_mul(r.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_return_per_byte(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_244_000 picoseconds. - Weight::from_parts(10_654_989, 0) + // Minimum execution time: 10_266_000 picoseconds. + Weight::from_parts(10_602_261, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(315, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(318, 0).saturating_mul(n.into())) } /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) @@ -792,10 +792,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `4805 + r * (2121 ±0)` // Estimated: `13220 + r * (81321 ±0)` - // Minimum execution time: 303_028_000 picoseconds. - Weight::from_parts(323_032_397, 13220) - // Standard Error: 848_406 - .saturating_add(Weight::from_parts(242_988_002, 0).saturating_mul(r.into())) + // Minimum execution time: 295_922_000 picoseconds. + Weight::from_parts(322_472_877, 13220) + // Standard Error: 993_812 + .saturating_add(Weight::from_parts(259_075_422, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().reads((36_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3_u64)) @@ -809,10 +809,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `76` // Estimated: `1561` - // Minimum execution time: 9_227_000 picoseconds. - Weight::from_parts(14_055_283, 1561) - // Standard Error: 758 - .saturating_add(Weight::from_parts(1_104_996, 0).saturating_mul(r.into())) + // Minimum execution time: 9_427_000 picoseconds. + Weight::from_parts(12_996_213, 1561) + // Standard Error: 845 + .saturating_add(Weight::from_parts(1_182_642, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -820,10 +820,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_483_000 picoseconds. - Weight::from_parts(20_453_059, 0) - // Standard Error: 3_271 - .saturating_add(Weight::from_parts(1_713_468, 0).saturating_mul(r.into())) + // Minimum execution time: 9_304_000 picoseconds. + Weight::from_parts(25_678_842, 0) + // Standard Error: 1_855 + .saturating_add(Weight::from_parts(1_814_511, 0).saturating_mul(r.into())) } /// Storage: `System::EventTopics` (r:4 w:4) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -833,12 +833,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `990 + t * (2475 ±0)` - // Minimum execution time: 23_517_000 picoseconds. - Weight::from_parts(15_543_153, 990) - // Standard Error: 13_814 - .saturating_add(Weight::from_parts(2_357_255, 0).saturating_mul(t.into())) - // Standard Error: 3 - .saturating_add(Weight::from_parts(573, 0).saturating_mul(n.into())) + // Minimum execution time: 23_425_000 picoseconds. + Weight::from_parts(15_229_010, 990) + // Standard Error: 14_380 + .saturating_add(Weight::from_parts(2_545_653, 0).saturating_mul(t.into())) + // Standard Error: 4 + .saturating_add(Weight::from_parts(594, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) .saturating_add(Weight::from_parts(0, 2475).saturating_mul(t.into())) @@ -848,20 +848,20 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_448_000 picoseconds. - Weight::from_parts(9_845_841, 0) - // Standard Error: 58 - .saturating_add(Weight::from_parts(105_442, 0).saturating_mul(r.into())) + // Minimum execution time: 11_117_000 picoseconds. + Weight::from_parts(12_887_533, 0) + // Standard Error: 83 + .saturating_add(Weight::from_parts(99_373, 0).saturating_mul(r.into())) } /// The range of component `i` is `[0, 1048576]`. fn seal_debug_message_per_byte(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_869_000 picoseconds. - Weight::from_parts(11_024_000, 0) + // Minimum execution time: 10_982_000 picoseconds. + Weight::from_parts(11_176_000, 0) // Standard Error: 8 - .saturating_add(Weight::from_parts(991, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(983, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -870,10 +870,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `108 + r * (150 ±0)` // Estimated: `105 + r * (151 ±0)` - // Minimum execution time: 9_119_000 picoseconds. - Weight::from_parts(9_270_000, 105) - // Standard Error: 8_960 - .saturating_add(Weight::from_parts(5_215_976, 0).saturating_mul(r.into())) + // Minimum execution time: 9_150_000 picoseconds. + Weight::from_parts(9_269_000, 105) + // Standard Error: 8_147 + .saturating_add(Weight::from_parts(5_339_554, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 151).saturating_mul(r.into())) @@ -885,10 +885,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `245` // Estimated: `245` - // Minimum execution time: 17_833_000 picoseconds. - Weight::from_parts(18_940_114, 245) - // Standard Error: 2 - .saturating_add(Weight::from_parts(316, 0).saturating_mul(n.into())) + // Minimum execution time: 19_085_000 picoseconds. + Weight::from_parts(20_007_323, 245) + // Standard Error: 3 + .saturating_add(Weight::from_parts(291, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -899,10 +899,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 18_428_000 picoseconds. - Weight::from_parts(19_372_726, 248) - // Standard Error: 2 - .saturating_add(Weight::from_parts(85, 0).saturating_mul(n.into())) + // Minimum execution time: 19_127_000 picoseconds. + Weight::from_parts(21_152_987, 248) + // Standard Error: 3 + .saturating_add(Weight::from_parts(42, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -914,10 +914,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `108 + r * (150 ±0)` // Estimated: `105 + r * (151 ±0)` - // Minimum execution time: 9_335_000 picoseconds. - Weight::from_parts(9_459_000, 105) - // Standard Error: 9_156 - .saturating_add(Weight::from_parts(5_166_621, 0).saturating_mul(r.into())) + // Minimum execution time: 9_264_000 picoseconds. + Weight::from_parts(9_449_000, 105) + // Standard Error: 8_196 + .saturating_add(Weight::from_parts(5_325_578, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 151).saturating_mul(r.into())) @@ -929,10 +929,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 18_308_000 picoseconds. - Weight::from_parts(19_421_433, 248) + // Minimum execution time: 18_489_000 picoseconds. + Weight::from_parts(19_916_153, 248) // Standard Error: 2 - .saturating_add(Weight::from_parts(83, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(97, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -944,10 +944,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `108 + r * (150 ±0)` // Estimated: `105 + r * (151 ±0)` - // Minimum execution time: 9_184_000 picoseconds. - Weight::from_parts(9_245_000, 105) - // Standard Error: 8_442 - .saturating_add(Weight::from_parts(4_543_991, 0).saturating_mul(r.into())) + // Minimum execution time: 9_299_000 picoseconds. + Weight::from_parts(9_464_000, 105) + // Standard Error: 6_827 + .saturating_add(Weight::from_parts(4_720_699, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 151).saturating_mul(r.into())) } @@ -958,10 +958,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 17_194_000 picoseconds. - Weight::from_parts(19_032_094, 248) + // Minimum execution time: 17_981_000 picoseconds. + Weight::from_parts(19_802_353, 248) // Standard Error: 3 - .saturating_add(Weight::from_parts(590, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(617, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -972,10 +972,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `108 + r * (150 ±0)` // Estimated: `105 + r * (151 ±0)` - // Minimum execution time: 9_380_000 picoseconds. - Weight::from_parts(9_501_000, 105) - // Standard Error: 7_029 - .saturating_add(Weight::from_parts(4_406_690, 0).saturating_mul(r.into())) + // Minimum execution time: 9_891_000 picoseconds. + Weight::from_parts(10_046_000, 105) + // Standard Error: 6_993 + .saturating_add(Weight::from_parts(4_601_167, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 151).saturating_mul(r.into())) } @@ -986,10 +986,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 16_400_000 picoseconds. - Weight::from_parts(17_993_941, 248) + // Minimum execution time: 17_229_000 picoseconds. + Weight::from_parts(18_302_733, 248) // Standard Error: 2 - .saturating_add(Weight::from_parts(68, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(112, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1000,10 +1000,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `108 + r * (150 ±0)` // Estimated: `105 + r * (151 ±0)` - // Minimum execution time: 9_109_000 picoseconds. - Weight::from_parts(9_265_000, 105) - // Standard Error: 8_733 - .saturating_add(Weight::from_parts(5_218_811, 0).saturating_mul(r.into())) + // Minimum execution time: 9_323_000 picoseconds. + Weight::from_parts(9_462_000, 105) + // Standard Error: 8_031 + .saturating_add(Weight::from_parts(5_433_981, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 151).saturating_mul(r.into())) @@ -1015,10 +1015,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 18_423_000 picoseconds. - Weight::from_parts(20_025_132, 248) + // Minimum execution time: 18_711_000 picoseconds. + Weight::from_parts(20_495_670, 248) // Standard Error: 3 - .saturating_add(Weight::from_parts(628, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(640, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1030,10 +1030,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `770` // Estimated: `4221 + r * (2475 ±0)` - // Minimum execution time: 9_043_000 picoseconds. - Weight::from_parts(9_176_000, 4221) - // Standard Error: 12_901 - .saturating_add(Weight::from_parts(32_297_438, 0).saturating_mul(r.into())) + // Minimum execution time: 9_226_000 picoseconds. + Weight::from_parts(9_394_000, 4221) + // Standard Error: 14_741 + .saturating_add(Weight::from_parts(34_179_316, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(1_u64)) @@ -1055,10 +1055,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `520 + r * (170 ±0)` // Estimated: `6463 + r * (2646 ±0)` - // Minimum execution time: 9_299_000 picoseconds. - Weight::from_parts(9_427_000, 6463) - // Standard Error: 101_949 - .saturating_add(Weight::from_parts(244_143_691, 0).saturating_mul(r.into())) + // Minimum execution time: 9_455_000 picoseconds. + Weight::from_parts(9_671_000, 6463) + // Standard Error: 126_080 + .saturating_add(Weight::from_parts(244_204_040, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -1079,11 +1079,11 @@ impl WeightInfo for SubstrateWeight { fn seal_delegate_call(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + r * (527 ±0)` - // Estimated: `6447 + r * (2583 ±3)` - // Minimum execution time: 9_359_000 picoseconds. - Weight::from_parts(9_425_000, 6447) - // Standard Error: 193_938 - .saturating_add(Weight::from_parts(244_904_401, 0).saturating_mul(r.into())) + // Estimated: `6447 + r * (2583 ±10)` + // Minimum execution time: 9_274_000 picoseconds. + Weight::from_parts(9_437_000, 6447) + // Standard Error: 150_832 + .saturating_add(Weight::from_parts(244_196_269, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2583).saturating_mul(r.into())) @@ -1106,12 +1106,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `699 + t * (277 ±0)` // Estimated: `6639 + t * (3458 ±0)` - // Minimum execution time: 214_588_000 picoseconds. - Weight::from_parts(129_214_481, 6639) - // Standard Error: 2_468_090 - .saturating_add(Weight::from_parts(32_514_739, 0).saturating_mul(t.into())) + // Minimum execution time: 214_483_000 picoseconds. + Weight::from_parts(122_634_366, 6639) + // Standard Error: 2_499_235 + .saturating_add(Weight::from_parts(41_326_008, 0).saturating_mul(t.into())) // Standard Error: 3 - .saturating_add(Weight::from_parts(418, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(422, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -1126,10 +1126,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `Contracts::ContractInfoOf` (r:800 w:801) /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `Parameters::Parameters` (r:2 w:0) - /// Proof: `Parameters::Parameters` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `Measured`) /// Storage: `System::Account` (r:802 w:802) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Parameters::Parameters` (r:2 w:0) + /// Proof: `Parameters::Parameters` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `Measured`) /// Storage: `System::EventTopics` (r:801 w:801) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `r` is `[1, 800]`. @@ -1137,10 +1137,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1097 + r * (188 ±0)` // Estimated: `6990 + r * (2664 ±0)` - // Minimum execution time: 352_925_000 picoseconds. - Weight::from_parts(355_487_000, 6990) - // Standard Error: 261_528 - .saturating_add(Weight::from_parts(337_897_187, 0).saturating_mul(r.into())) + // Minimum execution time: 341_569_000 picoseconds. + Weight::from_parts(360_574_000, 6990) + // Standard Error: 259_746 + .saturating_add(Weight::from_parts(337_944_674, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -1155,10 +1155,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `Contracts::ContractInfoOf` (r:1 w:2) /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `Parameters::Parameters` (r:2 w:0) - /// Proof: `Parameters::Parameters` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `Measured`) /// Storage: `System::Account` (r:3 w:3) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Parameters::Parameters` (r:2 w:0) + /// Proof: `Parameters::Parameters` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `Measured`) /// Storage: `System::EventTopics` (r:2 w:2) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `t` is `[0, 1]`. @@ -1168,12 +1168,14 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `760 + t * (104 ±0)` // Estimated: `6719 + t * (2549 ±1)` - // Minimum execution time: 1_870_832_000 picoseconds. - Weight::from_parts(949_110_245, 6719) - // Standard Error: 24 - .saturating_add(Weight::from_parts(1_084, 0).saturating_mul(i.into())) - // Standard Error: 24 - .saturating_add(Weight::from_parts(1_206, 0).saturating_mul(s.into())) + // Minimum execution time: 1_863_119_000 picoseconds. + Weight::from_parts(900_189_174, 6719) + // Standard Error: 13_040_979 + .saturating_add(Weight::from_parts(4_056_063, 0).saturating_mul(t.into())) + // Standard Error: 20 + .saturating_add(Weight::from_parts(1_028, 0).saturating_mul(i.into())) + // Standard Error: 20 + .saturating_add(Weight::from_parts(1_173, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(10_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(7_u64)) @@ -1185,58 +1187,58 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_142_000 picoseconds. - Weight::from_parts(9_787_220, 0) - // Standard Error: 236 - .saturating_add(Weight::from_parts(267_264, 0).saturating_mul(r.into())) + // Minimum execution time: 9_211_000 picoseconds. + Weight::from_parts(11_696_412, 0) + // Standard Error: 388 + .saturating_add(Weight::from_parts(265_538, 0).saturating_mul(r.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_hash_sha2_256_per_byte(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_485_000 picoseconds. - Weight::from_parts(1_870_250, 0) + // Minimum execution time: 10_296_000 picoseconds. + Weight::from_parts(572_494, 0) // Standard Error: 1 - .saturating_add(Weight::from_parts(1_073, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_067, 0).saturating_mul(n.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_hash_keccak_256(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_005_000 picoseconds. - Weight::from_parts(8_943_937, 0) - // Standard Error: 1_385 - .saturating_add(Weight::from_parts(665_970, 0).saturating_mul(r.into())) + // Minimum execution time: 9_177_000 picoseconds. + Weight::from_parts(8_620_481, 0) + // Standard Error: 249 + .saturating_add(Weight::from_parts(674_502, 0).saturating_mul(r.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_hash_keccak_256_per_byte(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_965_000 picoseconds. - Weight::from_parts(11_749_746, 0) - // Standard Error: 6 - .saturating_add(Weight::from_parts(3_330, 0).saturating_mul(n.into())) + // Minimum execution time: 11_240_000 picoseconds. + Weight::from_parts(8_696_186, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(3_328, 0).saturating_mul(n.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_hash_blake2_256(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_400_000 picoseconds. - Weight::from_parts(13_857_546, 0) - // Standard Error: 246 - .saturating_add(Weight::from_parts(326_483, 0).saturating_mul(r.into())) + // Minimum execution time: 9_889_000 picoseconds. + Weight::from_parts(16_103_170, 0) + // Standard Error: 343 + .saturating_add(Weight::from_parts(328_939, 0).saturating_mul(r.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_hash_blake2_256_per_byte(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_064_000 picoseconds. - Weight::from_parts(1_885_873, 0) + // Minimum execution time: 10_405_000 picoseconds. + Weight::from_parts(2_264_024, 0) // Standard Error: 0 .saturating_add(Weight::from_parts(1_196, 0).saturating_mul(n.into())) } @@ -1245,60 +1247,60 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_068_000 picoseconds. - Weight::from_parts(17_169_362, 0) - // Standard Error: 1_580 - .saturating_add(Weight::from_parts(330_195, 0).saturating_mul(r.into())) + // Minimum execution time: 9_215_000 picoseconds. + Weight::from_parts(10_505_632, 0) + // Standard Error: 240 + .saturating_add(Weight::from_parts(324_854, 0).saturating_mul(r.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_hash_blake2_128_per_byte(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_169_000 picoseconds. - Weight::from_parts(2_159_277, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(1_200, 0).saturating_mul(n.into())) + // Minimum execution time: 10_440_000 picoseconds. + Weight::from_parts(2_575_889, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_199, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 125697]`. fn seal_sr25519_verify_per_byte(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 53_863_000 picoseconds. - Weight::from_parts(54_902_157, 0) - // Standard Error: 9 - .saturating_add(Weight::from_parts(4_588, 0).saturating_mul(n.into())) + // Minimum execution time: 55_119_000 picoseconds. + Weight::from_parts(56_732_248, 0) + // Standard Error: 8 + .saturating_add(Weight::from_parts(4_639, 0).saturating_mul(n.into())) } /// The range of component `r` is `[0, 160]`. fn seal_sr25519_verify(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_107_000 picoseconds. - Weight::from_parts(24_115_247, 0) - // Standard Error: 7_427 - .saturating_add(Weight::from_parts(41_116_827, 0).saturating_mul(r.into())) + // Minimum execution time: 9_176_000 picoseconds. + Weight::from_parts(9_861_102, 0) + // Standard Error: 6_029 + .saturating_add(Weight::from_parts(45_948_571, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 160]`. fn seal_ecdsa_recover(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_404_000 picoseconds. - Weight::from_parts(31_763_334, 0) - // Standard Error: 9_833 - .saturating_add(Weight::from_parts(45_529_880, 0).saturating_mul(r.into())) + // Minimum execution time: 9_293_000 picoseconds. + Weight::from_parts(28_785_765, 0) + // Standard Error: 9_160 + .saturating_add(Weight::from_parts(45_566_150, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 160]`. fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_409_000 picoseconds. - Weight::from_parts(15_072_835, 0) - // Standard Error: 4_591 - .saturating_add(Weight::from_parts(11_619_283, 0).saturating_mul(r.into())) + // Minimum execution time: 9_206_000 picoseconds. + Weight::from_parts(12_420_664, 0) + // Standard Error: 3_489 + .saturating_add(Weight::from_parts(11_628_989, 0).saturating_mul(r.into())) } /// Storage: `Contracts::CodeInfoOf` (r:1536 w:1536) /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) @@ -1312,11 +1314,11 @@ impl WeightInfo for SubstrateWeight { fn seal_set_code_hash(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + r * (926 ±0)` - // Estimated: `8969 + r * (3047 ±10)` - // Minimum execution time: 9_269_000 picoseconds. - Weight::from_parts(9_372_000, 8969) - // Standard Error: 61_354 - .saturating_add(Weight::from_parts(26_280_409, 0).saturating_mul(r.into())) + // Estimated: `8969 + r * (3047 ±7)` + // Minimum execution time: 9_219_000 picoseconds. + Weight::from_parts(9_385_000, 8969) + // Standard Error: 45_562 + .saturating_add(Weight::from_parts(26_360_661, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 3047).saturating_mul(r.into())) @@ -1328,10 +1330,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `274 + r * (78 ±0)` // Estimated: `1265 + r * (2553 ±0)` - // Minimum execution time: 9_103_000 picoseconds. - Weight::from_parts(14_404_626, 1265) - // Standard Error: 9_343 - .saturating_add(Weight::from_parts(5_154_949, 0).saturating_mul(r.into())) + // Minimum execution time: 9_355_000 picoseconds. + Weight::from_parts(15_071_309, 1265) + // Standard Error: 9_722 + .saturating_add(Weight::from_parts(5_328_717, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2553).saturating_mul(r.into())) @@ -1343,10 +1345,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `275 + r * (78 ±0)` // Estimated: `990 + r * (2568 ±0)` - // Minimum execution time: 9_219_000 picoseconds. - Weight::from_parts(14_085_456, 990) - // Standard Error: 11_206 - .saturating_add(Weight::from_parts(4_422_122, 0).saturating_mul(r.into())) + // Minimum execution time: 8_979_000 picoseconds. + Weight::from_parts(14_362_224, 990) + // Standard Error: 9_137 + .saturating_add(Weight::from_parts(4_488_748, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2568).saturating_mul(r.into())) @@ -1372,10 +1374,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `861 + r * (3 ±0)` // Estimated: `9282 + r * (3 ±0)` - // Minimum execution time: 269_333_000 picoseconds. - Weight::from_parts(286_922_618, 9282) - // Standard Error: 443 - .saturating_add(Weight::from_parts(168_869, 0).saturating_mul(r.into())) + // Minimum execution time: 269_704_000 picoseconds. + Weight::from_parts(289_916_035, 9282) + // Standard Error: 408 + .saturating_add(Weight::from_parts(166_040, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) .saturating_add(Weight::from_parts(0, 3).saturating_mul(r.into())) @@ -1385,10 +1387,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_328_000 picoseconds. - Weight::from_parts(14_019_583, 0) - // Standard Error: 171 - .saturating_add(Weight::from_parts(88_751, 0).saturating_mul(r.into())) + // Minimum execution time: 9_361_000 picoseconds. + Weight::from_parts(11_633_836, 0) + // Standard Error: 86 + .saturating_add(Weight::from_parts(83_083, 0).saturating_mul(r.into())) } /// Storage: `Contracts::Nonce` (r:1 w:0) /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) @@ -1397,10 +1399,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `219` // Estimated: `1704` - // Minimum execution time: 9_267_000 picoseconds. - Weight::from_parts(15_304_284, 1704) - // Standard Error: 1_219 - .saturating_add(Weight::from_parts(74_696, 0).saturating_mul(r.into())) + // Minimum execution time: 9_133_000 picoseconds. + Weight::from_parts(13_259_836, 1704) + // Standard Error: 121 + .saturating_add(Weight::from_parts(76_878, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// The range of component `r` is `[0, 5000]`. @@ -1408,10 +1410,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 911_000 picoseconds. - Weight::from_parts(449_666, 0) - // Standard Error: 26 - .saturating_add(Weight::from_parts(14_797, 0).saturating_mul(r.into())) + // Minimum execution time: 851_000 picoseconds. + Weight::from_parts(587_883, 0) + // Standard Error: 16 + .saturating_add(Weight::from_parts(14_912, 0).saturating_mul(r.into())) } } @@ -1423,8 +1425,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `142` // Estimated: `1627` - // Minimum execution time: 2_047_000 picoseconds. - Weight::from_parts(2_116_000, 1627) + // Minimum execution time: 2_149_000 picoseconds. + Weight::from_parts(2_274_000, 1627) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1434,10 +1436,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `452 + k * (69 ±0)` // Estimated: `442 + k * (70 ±0)` - // Minimum execution time: 12_474_000 picoseconds. - Weight::from_parts(12_767_000, 442) - // Standard Error: 1_081 - .saturating_add(Weight::from_parts(1_187_278, 0).saturating_mul(k.into())) + // Minimum execution time: 12_863_000 picoseconds. + Weight::from_parts(13_188_000, 442) + // Standard Error: 1_053 + .saturating_add(Weight::from_parts(1_105_325, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1451,10 +1453,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `211 + c * (1 ±0)` // Estimated: `6149 + c * (1 ±0)` - // Minimum execution time: 8_307_000 picoseconds. - Weight::from_parts(8_939_322, 6149) + // Minimum execution time: 8_432_000 picoseconds. + Weight::from_parts(9_203_290, 6149) // Standard Error: 1 - .saturating_add(Weight::from_parts(1_190, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(1_186, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) @@ -1467,8 +1469,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `510` // Estimated: `6450` - // Minimum execution time: 16_915_000 picoseconds. - Weight::from_parts(17_638_000, 6450) + // Minimum execution time: 17_177_000 picoseconds. + Weight::from_parts(17_663_000, 6450) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1481,10 +1483,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `171 + k * (1 ±0)` // Estimated: `3635 + k * (1 ±0)` - // Minimum execution time: 3_607_000 picoseconds. - Weight::from_parts(1_979_323, 3635) - // Standard Error: 1_018 - .saturating_add(Weight::from_parts(1_196_162, 0).saturating_mul(k.into())) + // Minimum execution time: 3_636_000 picoseconds. + Weight::from_parts(3_774_000, 3635) + // Standard Error: 542 + .saturating_add(Weight::from_parts(1_260_058, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -1505,10 +1507,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `328 + c * (1 ±0)` // Estimated: `6266 + c * (1 ±0)` - // Minimum execution time: 21_056_000 picoseconds. - Weight::from_parts(21_633_895, 6266) + // Minimum execution time: 21_585_000 picoseconds. + Weight::from_parts(22_069_944, 6266) // Standard Error: 1 - .saturating_add(Weight::from_parts(390, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(404, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) @@ -1519,8 +1521,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `440` // Estimated: `6380` - // Minimum execution time: 12_860_000 picoseconds. - Weight::from_parts(13_525_000, 6380) + // Minimum execution time: 13_283_000 picoseconds. + Weight::from_parts(14_015_000, 6380) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1534,8 +1536,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `352` // Estimated: `6292` - // Minimum execution time: 46_926_000 picoseconds. - Weight::from_parts(47_828_000, 6292) + // Minimum execution time: 48_022_000 picoseconds. + Weight::from_parts(49_627_000, 6292) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1547,8 +1549,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `594` // Estimated: `6534` - // Minimum execution time: 55_081_000 picoseconds. - Weight::from_parts(56_899_000, 6534) + // Minimum execution time: 58_374_000 picoseconds. + Weight::from_parts(59_615_000, 6534) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1558,8 +1560,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `409` // Estimated: `6349` - // Minimum execution time: 12_595_000 picoseconds. - Weight::from_parts(13_059_000, 6349) + // Minimum execution time: 12_559_000 picoseconds. + Weight::from_parts(12_947_000, 6349) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1570,7 +1572,7 @@ impl WeightInfo for () { // Measured: `142` // Estimated: `1627` // Minimum execution time: 2_480_000 picoseconds. - Weight::from_parts(2_663_000, 1627) + Weight::from_parts(2_680_000, 1627) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1582,8 +1584,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `166` // Estimated: `3631` - // Minimum execution time: 12_115_000 picoseconds. - Weight::from_parts(12_506_000, 3631) + // Minimum execution time: 12_625_000 picoseconds. + Weight::from_parts(13_094_000, 3631) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1593,8 +1595,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `142` // Estimated: `3607` - // Minimum execution time: 4_757_000 picoseconds. - Weight::from_parts(5_082_000, 3607) + // Minimum execution time: 4_836_000 picoseconds. + Weight::from_parts(5_182_000, 3607) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) @@ -1605,8 +1607,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `167` // Estimated: `3632` - // Minimum execution time: 6_017_000 picoseconds. - Weight::from_parts(6_421_000, 3632) + // Minimum execution time: 6_319_000 picoseconds. + Weight::from_parts(6_582_000, 3632) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) @@ -1617,8 +1619,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `142` // Estimated: `3607` - // Minimum execution time: 6_238_000 picoseconds. - Weight::from_parts(6_587_000, 3607) + // Minimum execution time: 6_532_000 picoseconds. + Weight::from_parts(6_909_000, 3607) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1643,10 +1645,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `804 + c * (1 ±0)` // Estimated: `9217 + c * (1 ±0)` - // Minimum execution time: 288_968_000 picoseconds. - Weight::from_parts(267_291_922, 9217) - // Standard Error: 78 - .saturating_add(Weight::from_parts(34_879, 0).saturating_mul(c.into())) + // Minimum execution time: 305_778_000 picoseconds. + Weight::from_parts(282_321_249, 9217) + // Standard Error: 72 + .saturating_add(Weight::from_parts(33_456, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) @@ -1678,14 +1680,14 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `326` // Estimated: `8740` - // Minimum execution time: 3_948_426_000 picoseconds. - Weight::from_parts(440_017_623, 8740) - // Standard Error: 555 - .saturating_add(Weight::from_parts(71_483, 0).saturating_mul(c.into())) - // Standard Error: 66 - .saturating_add(Weight::from_parts(1_831, 0).saturating_mul(i.into())) - // Standard Error: 66 - .saturating_add(Weight::from_parts(1_694, 0).saturating_mul(s.into())) + // Minimum execution time: 3_810_809_000 picoseconds. + Weight::from_parts(739_511_598, 8740) + // Standard Error: 140 + .saturating_add(Weight::from_parts(67_574, 0).saturating_mul(c.into())) + // Standard Error: 16 + .saturating_add(Weight::from_parts(1_488, 0).saturating_mul(i.into())) + // Standard Error: 16 + .saturating_add(Weight::from_parts(1_537, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(14_u64)) .saturating_add(RocksDbWeight::get().writes(10_u64)) } @@ -1715,12 +1717,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `563` // Estimated: `8982` - // Minimum execution time: 2_011_037_000 picoseconds. - Weight::from_parts(2_047_025_000, 8982) - // Standard Error: 28 - .saturating_add(Weight::from_parts(968, 0).saturating_mul(i.into())) - // Standard Error: 28 - .saturating_add(Weight::from_parts(780, 0).saturating_mul(s.into())) + // Minimum execution time: 1_986_789_000 picoseconds. + Weight::from_parts(2_017_466_000, 8982) + // Standard Error: 26 + .saturating_add(Weight::from_parts(827, 0).saturating_mul(i.into())) + // Standard Error: 26 + .saturating_add(Weight::from_parts(781, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -1744,8 +1746,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `829` // Estimated: `9244` - // Minimum execution time: 202_190_000 picoseconds. - Weight::from_parts(209_378_000, 9244) + // Minimum execution time: 210_724_000 picoseconds. + Weight::from_parts(218_608_000, 9244) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1766,10 +1768,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `145` // Estimated: `6085` - // Minimum execution time: 271_161_000 picoseconds. - Weight::from_parts(279_218_977, 6085) - // Standard Error: 80 - .saturating_add(Weight::from_parts(33_973, 0).saturating_mul(c.into())) + // Minimum execution time: 271_259_000 picoseconds. + Weight::from_parts(298_852_854, 6085) + // Standard Error: 65 + .saturating_add(Weight::from_parts(33_547, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1790,10 +1792,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `145` // Estimated: `6085` - // Minimum execution time: 273_684_000 picoseconds. - Weight::from_parts(284_348_722, 6085) - // Standard Error: 79 - .saturating_add(Weight::from_parts(34_205, 0).saturating_mul(c.into())) + // Minimum execution time: 278_167_000 picoseconds. + Weight::from_parts(311_888_941, 6085) + // Standard Error: 58 + .saturating_add(Weight::from_parts(33_595, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1811,8 +1813,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `315` // Estimated: `3780` - // Minimum execution time: 45_150_000 picoseconds. - Weight::from_parts(46_780_000, 3780) + // Minimum execution time: 47_403_000 picoseconds. + Weight::from_parts(48_707_000, 3780) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1828,8 +1830,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `552` // Estimated: `8967` - // Minimum execution time: 34_738_000 picoseconds. - Weight::from_parts(35_918_000, 8967) + // Minimum execution time: 35_361_000 picoseconds. + Weight::from_parts(36_714_000, 8967) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1838,10 +1840,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_094_000 picoseconds. - Weight::from_parts(10_253_702, 0) - // Standard Error: 223 - .saturating_add(Weight::from_parts(250_757, 0).saturating_mul(r.into())) + // Minimum execution time: 9_340_000 picoseconds. + Weight::from_parts(9_360_237, 0) + // Standard Error: 269 + .saturating_add(Weight::from_parts(249_611, 0).saturating_mul(r.into())) } /// Storage: `Contracts::ContractInfoOf` (r:1600 w:0) /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) @@ -1850,10 +1852,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `509 + r * (77 ±0)` // Estimated: `1467 + r * (2552 ±0)` - // Minimum execution time: 9_102_000 picoseconds. - Weight::from_parts(9_238_000, 1467) - // Standard Error: 6_076 - .saturating_add(Weight::from_parts(3_293_012, 0).saturating_mul(r.into())) + // Minimum execution time: 9_059_000 picoseconds. + Weight::from_parts(9_201_000, 1467) + // Standard Error: 5_643 + .saturating_add(Weight::from_parts(3_343_859, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2552).saturating_mul(r.into())) } @@ -1864,10 +1866,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `517 + r * (170 ±0)` // Estimated: `1468 + r * (2645 ±0)` - // Minimum execution time: 9_255_000 picoseconds. - Weight::from_parts(9_406_000, 1468) - // Standard Error: 6_826 - .saturating_add(Weight::from_parts(4_205_039, 0).saturating_mul(r.into())) + // Minimum execution time: 9_220_000 picoseconds. + Weight::from_parts(9_399_000, 1468) + // Standard Error: 6_194 + .saturating_add(Weight::from_parts(4_172_011, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2645).saturating_mul(r.into())) } @@ -1876,50 +1878,50 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_388_000 picoseconds. - Weight::from_parts(9_322_209, 0) - // Standard Error: 269 - .saturating_add(Weight::from_parts(358_189, 0).saturating_mul(r.into())) + // Minimum execution time: 9_707_000 picoseconds. + Weight::from_parts(10_100_456, 0) + // Standard Error: 234 + .saturating_add(Weight::from_parts(338_464, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_caller_is_origin(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_300_000 picoseconds. - Weight::from_parts(10_268_326, 0) - // Standard Error: 72 - .saturating_add(Weight::from_parts(104_650, 0).saturating_mul(r.into())) + // Minimum execution time: 9_524_000 picoseconds. + Weight::from_parts(10_813_389, 0) + // Standard Error: 76 + .saturating_add(Weight::from_parts(102_535, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_caller_is_root(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_162_000 picoseconds. - Weight::from_parts(10_059_984, 0) - // Standard Error: 87 - .saturating_add(Weight::from_parts(87_627, 0).saturating_mul(r.into())) + // Minimum execution time: 9_799_000 picoseconds. + Weight::from_parts(10_886_744, 0) + // Standard Error: 75 + .saturating_add(Weight::from_parts(80_901, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_address(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_193_000 picoseconds. - Weight::from_parts(10_160_715, 0) - // Standard Error: 152 - .saturating_add(Weight::from_parts(263_703, 0).saturating_mul(r.into())) + // Minimum execution time: 9_895_000 picoseconds. + Weight::from_parts(10_658_338, 0) + // Standard Error: 189 + .saturating_add(Weight::from_parts(249_694, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_gas_left(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_109_000 picoseconds. - Weight::from_parts(9_766_924, 0) - // Standard Error: 212 - .saturating_add(Weight::from_parts(291_694, 0).saturating_mul(r.into())) + // Minimum execution time: 9_643_000 picoseconds. + Weight::from_parts(10_932_126, 0) + // Standard Error: 153 + .saturating_add(Weight::from_parts(280_924, 0).saturating_mul(r.into())) } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) @@ -1928,10 +1930,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `140` // Estimated: `3599` - // Minimum execution time: 9_463_000 picoseconds. - Weight::from_parts(9_541_000, 3599) - // Standard Error: 3_075 - .saturating_add(Weight::from_parts(1_606_043, 0).saturating_mul(r.into())) + // Minimum execution time: 9_548_000 picoseconds. + Weight::from_parts(9_737_000, 3599) + // Standard Error: 971 + .saturating_add(Weight::from_parts(1_704_134, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1939,40 +1941,40 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_080_000 picoseconds. - Weight::from_parts(8_121_924, 0) - // Standard Error: 198 - .saturating_add(Weight::from_parts(247_527, 0).saturating_mul(r.into())) + // Minimum execution time: 9_172_000 picoseconds. + Weight::from_parts(18_255_933, 0) + // Standard Error: 540 + .saturating_add(Weight::from_parts(230_929, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_minimum_balance(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_393_000 picoseconds. - Weight::from_parts(9_999_247, 0) - // Standard Error: 169 - .saturating_add(Weight::from_parts(244_563, 0).saturating_mul(r.into())) + // Minimum execution time: 9_232_000 picoseconds. + Weight::from_parts(9_796_584, 0) + // Standard Error: 208 + .saturating_add(Weight::from_parts(239_962, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_block_number(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_236_000 picoseconds. - Weight::from_parts(9_561_435, 0) - // Standard Error: 195 - .saturating_add(Weight::from_parts(239_812, 0).saturating_mul(r.into())) + // Minimum execution time: 9_747_000 picoseconds. + Weight::from_parts(8_733_230, 0) + // Standard Error: 377 + .saturating_add(Weight::from_parts(253_801, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_now(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_259_000 picoseconds. - Weight::from_parts(10_353_960, 0) - // Standard Error: 216 - .saturating_add(Weight::from_parts(243_754, 0).saturating_mul(r.into())) + // Minimum execution time: 9_214_000 picoseconds. + Weight::from_parts(10_194_153, 0) + // Standard Error: 516 + .saturating_add(Weight::from_parts(247_621, 0).saturating_mul(r.into())) } /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `Measured`) @@ -1981,10 +1983,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `67` // Estimated: `1552` - // Minimum execution time: 9_145_000 picoseconds. - Weight::from_parts(16_524_937, 1552) - // Standard Error: 438 - .saturating_add(Weight::from_parts(666_821, 0).saturating_mul(r.into())) + // Minimum execution time: 9_022_000 picoseconds. + Weight::from_parts(22_051_160, 1552) + // Standard Error: 697 + .saturating_add(Weight::from_parts(709_612, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1992,10 +1994,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_179_000 picoseconds. - Weight::from_parts(8_893_261, 0) - // Standard Error: 215 - .saturating_add(Weight::from_parts(175_586, 0).saturating_mul(r.into())) + // Minimum execution time: 9_135_000 picoseconds. + Weight::from_parts(10_646_215, 0) + // Standard Error: 161 + .saturating_add(Weight::from_parts(170_336, 0).saturating_mul(r.into())) } /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) @@ -2018,10 +2020,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `872` // Estimated: `9287` - // Minimum execution time: 259_315_000 picoseconds. - Weight::from_parts(137_461_362, 9287) - // Standard Error: 18 - .saturating_add(Weight::from_parts(1_388, 0).saturating_mul(n.into())) + // Minimum execution time: 273_896_000 picoseconds. + Weight::from_parts(148_309_654, 9287) + // Standard Error: 16 + .saturating_add(Weight::from_parts(1_355, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -2030,20 +2032,20 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_919_000 picoseconds. - Weight::from_parts(9_465_187, 0) - // Standard Error: 32_481 - .saturating_add(Weight::from_parts(992_912, 0).saturating_mul(r.into())) + // Minimum execution time: 8_906_000 picoseconds. + Weight::from_parts(9_264_446, 0) + // Standard Error: 19_760 + .saturating_add(Weight::from_parts(1_256_053, 0).saturating_mul(r.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_return_per_byte(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_244_000 picoseconds. - Weight::from_parts(10_654_989, 0) + // Minimum execution time: 10_266_000 picoseconds. + Weight::from_parts(10_602_261, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(315, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(318, 0).saturating_mul(n.into())) } /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) @@ -2072,10 +2074,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `4805 + r * (2121 ±0)` // Estimated: `13220 + r * (81321 ±0)` - // Minimum execution time: 303_028_000 picoseconds. - Weight::from_parts(323_032_397, 13220) - // Standard Error: 848_406 - .saturating_add(Weight::from_parts(242_988_002, 0).saturating_mul(r.into())) + // Minimum execution time: 295_922_000 picoseconds. + Weight::from_parts(322_472_877, 13220) + // Standard Error: 993_812 + .saturating_add(Weight::from_parts(259_075_422, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().reads((36_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(3_u64)) @@ -2089,10 +2091,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `76` // Estimated: `1561` - // Minimum execution time: 9_227_000 picoseconds. - Weight::from_parts(14_055_283, 1561) - // Standard Error: 758 - .saturating_add(Weight::from_parts(1_104_996, 0).saturating_mul(r.into())) + // Minimum execution time: 9_427_000 picoseconds. + Weight::from_parts(12_996_213, 1561) + // Standard Error: 845 + .saturating_add(Weight::from_parts(1_182_642, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -2100,10 +2102,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_483_000 picoseconds. - Weight::from_parts(20_453_059, 0) - // Standard Error: 3_271 - .saturating_add(Weight::from_parts(1_713_468, 0).saturating_mul(r.into())) + // Minimum execution time: 9_304_000 picoseconds. + Weight::from_parts(25_678_842, 0) + // Standard Error: 1_855 + .saturating_add(Weight::from_parts(1_814_511, 0).saturating_mul(r.into())) } /// Storage: `System::EventTopics` (r:4 w:4) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -2113,12 +2115,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `990 + t * (2475 ±0)` - // Minimum execution time: 23_517_000 picoseconds. - Weight::from_parts(15_543_153, 990) - // Standard Error: 13_814 - .saturating_add(Weight::from_parts(2_357_255, 0).saturating_mul(t.into())) - // Standard Error: 3 - .saturating_add(Weight::from_parts(573, 0).saturating_mul(n.into())) + // Minimum execution time: 23_425_000 picoseconds. + Weight::from_parts(15_229_010, 990) + // Standard Error: 14_380 + .saturating_add(Weight::from_parts(2_545_653, 0).saturating_mul(t.into())) + // Standard Error: 4 + .saturating_add(Weight::from_parts(594, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(t.into()))) .saturating_add(Weight::from_parts(0, 2475).saturating_mul(t.into())) @@ -2128,20 +2130,20 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_448_000 picoseconds. - Weight::from_parts(9_845_841, 0) - // Standard Error: 58 - .saturating_add(Weight::from_parts(105_442, 0).saturating_mul(r.into())) + // Minimum execution time: 11_117_000 picoseconds. + Weight::from_parts(12_887_533, 0) + // Standard Error: 83 + .saturating_add(Weight::from_parts(99_373, 0).saturating_mul(r.into())) } /// The range of component `i` is `[0, 1048576]`. fn seal_debug_message_per_byte(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_869_000 picoseconds. - Weight::from_parts(11_024_000, 0) + // Minimum execution time: 10_982_000 picoseconds. + Weight::from_parts(11_176_000, 0) // Standard Error: 8 - .saturating_add(Weight::from_parts(991, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(983, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -2150,10 +2152,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `108 + r * (150 ±0)` // Estimated: `105 + r * (151 ±0)` - // Minimum execution time: 9_119_000 picoseconds. - Weight::from_parts(9_270_000, 105) - // Standard Error: 8_960 - .saturating_add(Weight::from_parts(5_215_976, 0).saturating_mul(r.into())) + // Minimum execution time: 9_150_000 picoseconds. + Weight::from_parts(9_269_000, 105) + // Standard Error: 8_147 + .saturating_add(Weight::from_parts(5_339_554, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 151).saturating_mul(r.into())) @@ -2165,10 +2167,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `245` // Estimated: `245` - // Minimum execution time: 17_833_000 picoseconds. - Weight::from_parts(18_940_114, 245) - // Standard Error: 2 - .saturating_add(Weight::from_parts(316, 0).saturating_mul(n.into())) + // Minimum execution time: 19_085_000 picoseconds. + Weight::from_parts(20_007_323, 245) + // Standard Error: 3 + .saturating_add(Weight::from_parts(291, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -2179,10 +2181,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 18_428_000 picoseconds. - Weight::from_parts(19_372_726, 248) - // Standard Error: 2 - .saturating_add(Weight::from_parts(85, 0).saturating_mul(n.into())) + // Minimum execution time: 19_127_000 picoseconds. + Weight::from_parts(21_152_987, 248) + // Standard Error: 3 + .saturating_add(Weight::from_parts(42, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -2194,10 +2196,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `108 + r * (150 ±0)` // Estimated: `105 + r * (151 ±0)` - // Minimum execution time: 9_335_000 picoseconds. - Weight::from_parts(9_459_000, 105) - // Standard Error: 9_156 - .saturating_add(Weight::from_parts(5_166_621, 0).saturating_mul(r.into())) + // Minimum execution time: 9_264_000 picoseconds. + Weight::from_parts(9_449_000, 105) + // Standard Error: 8_196 + .saturating_add(Weight::from_parts(5_325_578, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 151).saturating_mul(r.into())) @@ -2209,10 +2211,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 18_308_000 picoseconds. - Weight::from_parts(19_421_433, 248) + // Minimum execution time: 18_489_000 picoseconds. + Weight::from_parts(19_916_153, 248) // Standard Error: 2 - .saturating_add(Weight::from_parts(83, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(97, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -2224,10 +2226,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `108 + r * (150 ±0)` // Estimated: `105 + r * (151 ±0)` - // Minimum execution time: 9_184_000 picoseconds. - Weight::from_parts(9_245_000, 105) - // Standard Error: 8_442 - .saturating_add(Weight::from_parts(4_543_991, 0).saturating_mul(r.into())) + // Minimum execution time: 9_299_000 picoseconds. + Weight::from_parts(9_464_000, 105) + // Standard Error: 6_827 + .saturating_add(Weight::from_parts(4_720_699, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 151).saturating_mul(r.into())) } @@ -2238,10 +2240,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 17_194_000 picoseconds. - Weight::from_parts(19_032_094, 248) + // Minimum execution time: 17_981_000 picoseconds. + Weight::from_parts(19_802_353, 248) // Standard Error: 3 - .saturating_add(Weight::from_parts(590, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(617, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -2252,10 +2254,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `108 + r * (150 ±0)` // Estimated: `105 + r * (151 ±0)` - // Minimum execution time: 9_380_000 picoseconds. - Weight::from_parts(9_501_000, 105) - // Standard Error: 7_029 - .saturating_add(Weight::from_parts(4_406_690, 0).saturating_mul(r.into())) + // Minimum execution time: 9_891_000 picoseconds. + Weight::from_parts(10_046_000, 105) + // Standard Error: 6_993 + .saturating_add(Weight::from_parts(4_601_167, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 151).saturating_mul(r.into())) } @@ -2266,10 +2268,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 16_400_000 picoseconds. - Weight::from_parts(17_993_941, 248) + // Minimum execution time: 17_229_000 picoseconds. + Weight::from_parts(18_302_733, 248) // Standard Error: 2 - .saturating_add(Weight::from_parts(68, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(112, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -2280,10 +2282,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `108 + r * (150 ±0)` // Estimated: `105 + r * (151 ±0)` - // Minimum execution time: 9_109_000 picoseconds. - Weight::from_parts(9_265_000, 105) - // Standard Error: 8_733 - .saturating_add(Weight::from_parts(5_218_811, 0).saturating_mul(r.into())) + // Minimum execution time: 9_323_000 picoseconds. + Weight::from_parts(9_462_000, 105) + // Standard Error: 8_031 + .saturating_add(Weight::from_parts(5_433_981, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 151).saturating_mul(r.into())) @@ -2295,10 +2297,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 18_423_000 picoseconds. - Weight::from_parts(20_025_132, 248) + // Minimum execution time: 18_711_000 picoseconds. + Weight::from_parts(20_495_670, 248) // Standard Error: 3 - .saturating_add(Weight::from_parts(628, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(640, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -2310,10 +2312,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `770` // Estimated: `4221 + r * (2475 ±0)` - // Minimum execution time: 9_043_000 picoseconds. - Weight::from_parts(9_176_000, 4221) - // Standard Error: 12_901 - .saturating_add(Weight::from_parts(32_297_438, 0).saturating_mul(r.into())) + // Minimum execution time: 9_226_000 picoseconds. + Weight::from_parts(9_394_000, 4221) + // Standard Error: 14_741 + .saturating_add(Weight::from_parts(34_179_316, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(1_u64)) @@ -2335,10 +2337,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `520 + r * (170 ±0)` // Estimated: `6463 + r * (2646 ±0)` - // Minimum execution time: 9_299_000 picoseconds. - Weight::from_parts(9_427_000, 6463) - // Standard Error: 101_949 - .saturating_add(Weight::from_parts(244_143_691, 0).saturating_mul(r.into())) + // Minimum execution time: 9_455_000 picoseconds. + Weight::from_parts(9_671_000, 6463) + // Standard Error: 126_080 + .saturating_add(Weight::from_parts(244_204_040, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -2359,11 +2361,11 @@ impl WeightInfo for () { fn seal_delegate_call(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + r * (527 ±0)` - // Estimated: `6447 + r * (2583 ±3)` - // Minimum execution time: 9_359_000 picoseconds. - Weight::from_parts(9_425_000, 6447) - // Standard Error: 193_938 - .saturating_add(Weight::from_parts(244_904_401, 0).saturating_mul(r.into())) + // Estimated: `6447 + r * (2583 ±10)` + // Minimum execution time: 9_274_000 picoseconds. + Weight::from_parts(9_437_000, 6447) + // Standard Error: 150_832 + .saturating_add(Weight::from_parts(244_196_269, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2583).saturating_mul(r.into())) @@ -2386,12 +2388,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `699 + t * (277 ±0)` // Estimated: `6639 + t * (3458 ±0)` - // Minimum execution time: 214_588_000 picoseconds. - Weight::from_parts(129_214_481, 6639) - // Standard Error: 2_468_090 - .saturating_add(Weight::from_parts(32_514_739, 0).saturating_mul(t.into())) + // Minimum execution time: 214_483_000 picoseconds. + Weight::from_parts(122_634_366, 6639) + // Standard Error: 2_499_235 + .saturating_add(Weight::from_parts(41_326_008, 0).saturating_mul(t.into())) // Standard Error: 3 - .saturating_add(Weight::from_parts(418, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(422, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -2406,10 +2408,10 @@ impl WeightInfo for () { /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `Contracts::ContractInfoOf` (r:800 w:801) /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `Parameters::Parameters` (r:2 w:0) - /// Proof: `Parameters::Parameters` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `Measured`) /// Storage: `System::Account` (r:802 w:802) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Parameters::Parameters` (r:2 w:0) + /// Proof: `Parameters::Parameters` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `Measured`) /// Storage: `System::EventTopics` (r:801 w:801) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `r` is `[1, 800]`. @@ -2417,10 +2419,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1097 + r * (188 ±0)` // Estimated: `6990 + r * (2664 ±0)` - // Minimum execution time: 352_925_000 picoseconds. - Weight::from_parts(355_487_000, 6990) - // Standard Error: 261_528 - .saturating_add(Weight::from_parts(337_897_187, 0).saturating_mul(r.into())) + // Minimum execution time: 341_569_000 picoseconds. + Weight::from_parts(360_574_000, 6990) + // Standard Error: 259_746 + .saturating_add(Weight::from_parts(337_944_674, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -2435,10 +2437,10 @@ impl WeightInfo for () { /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `Contracts::ContractInfoOf` (r:1 w:2) /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `Parameters::Parameters` (r:2 w:0) - /// Proof: `Parameters::Parameters` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `Measured`) /// Storage: `System::Account` (r:3 w:3) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Parameters::Parameters` (r:2 w:0) + /// Proof: `Parameters::Parameters` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `Measured`) /// Storage: `System::EventTopics` (r:2 w:2) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `t` is `[0, 1]`. @@ -2448,12 +2450,14 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `760 + t * (104 ±0)` // Estimated: `6719 + t * (2549 ±1)` - // Minimum execution time: 1_870_832_000 picoseconds. - Weight::from_parts(949_110_245, 6719) - // Standard Error: 24 - .saturating_add(Weight::from_parts(1_084, 0).saturating_mul(i.into())) - // Standard Error: 24 - .saturating_add(Weight::from_parts(1_206, 0).saturating_mul(s.into())) + // Minimum execution time: 1_863_119_000 picoseconds. + Weight::from_parts(900_189_174, 6719) + // Standard Error: 13_040_979 + .saturating_add(Weight::from_parts(4_056_063, 0).saturating_mul(t.into())) + // Standard Error: 20 + .saturating_add(Weight::from_parts(1_028, 0).saturating_mul(i.into())) + // Standard Error: 20 + .saturating_add(Weight::from_parts(1_173, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(10_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(7_u64)) @@ -2465,58 +2469,58 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_142_000 picoseconds. - Weight::from_parts(9_787_220, 0) - // Standard Error: 236 - .saturating_add(Weight::from_parts(267_264, 0).saturating_mul(r.into())) + // Minimum execution time: 9_211_000 picoseconds. + Weight::from_parts(11_696_412, 0) + // Standard Error: 388 + .saturating_add(Weight::from_parts(265_538, 0).saturating_mul(r.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_hash_sha2_256_per_byte(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_485_000 picoseconds. - Weight::from_parts(1_870_250, 0) + // Minimum execution time: 10_296_000 picoseconds. + Weight::from_parts(572_494, 0) // Standard Error: 1 - .saturating_add(Weight::from_parts(1_073, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_067, 0).saturating_mul(n.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_hash_keccak_256(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_005_000 picoseconds. - Weight::from_parts(8_943_937, 0) - // Standard Error: 1_385 - .saturating_add(Weight::from_parts(665_970, 0).saturating_mul(r.into())) + // Minimum execution time: 9_177_000 picoseconds. + Weight::from_parts(8_620_481, 0) + // Standard Error: 249 + .saturating_add(Weight::from_parts(674_502, 0).saturating_mul(r.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_hash_keccak_256_per_byte(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_965_000 picoseconds. - Weight::from_parts(11_749_746, 0) - // Standard Error: 6 - .saturating_add(Weight::from_parts(3_330, 0).saturating_mul(n.into())) + // Minimum execution time: 11_240_000 picoseconds. + Weight::from_parts(8_696_186, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(3_328, 0).saturating_mul(n.into())) } /// The range of component `r` is `[0, 1600]`. fn seal_hash_blake2_256(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_400_000 picoseconds. - Weight::from_parts(13_857_546, 0) - // Standard Error: 246 - .saturating_add(Weight::from_parts(326_483, 0).saturating_mul(r.into())) + // Minimum execution time: 9_889_000 picoseconds. + Weight::from_parts(16_103_170, 0) + // Standard Error: 343 + .saturating_add(Weight::from_parts(328_939, 0).saturating_mul(r.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_hash_blake2_256_per_byte(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_064_000 picoseconds. - Weight::from_parts(1_885_873, 0) + // Minimum execution time: 10_405_000 picoseconds. + Weight::from_parts(2_264_024, 0) // Standard Error: 0 .saturating_add(Weight::from_parts(1_196, 0).saturating_mul(n.into())) } @@ -2525,60 +2529,60 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_068_000 picoseconds. - Weight::from_parts(17_169_362, 0) - // Standard Error: 1_580 - .saturating_add(Weight::from_parts(330_195, 0).saturating_mul(r.into())) + // Minimum execution time: 9_215_000 picoseconds. + Weight::from_parts(10_505_632, 0) + // Standard Error: 240 + .saturating_add(Weight::from_parts(324_854, 0).saturating_mul(r.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_hash_blake2_128_per_byte(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_169_000 picoseconds. - Weight::from_parts(2_159_277, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(1_200, 0).saturating_mul(n.into())) + // Minimum execution time: 10_440_000 picoseconds. + Weight::from_parts(2_575_889, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_199, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 125697]`. fn seal_sr25519_verify_per_byte(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 53_863_000 picoseconds. - Weight::from_parts(54_902_157, 0) - // Standard Error: 9 - .saturating_add(Weight::from_parts(4_588, 0).saturating_mul(n.into())) + // Minimum execution time: 55_119_000 picoseconds. + Weight::from_parts(56_732_248, 0) + // Standard Error: 8 + .saturating_add(Weight::from_parts(4_639, 0).saturating_mul(n.into())) } /// The range of component `r` is `[0, 160]`. fn seal_sr25519_verify(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_107_000 picoseconds. - Weight::from_parts(24_115_247, 0) - // Standard Error: 7_427 - .saturating_add(Weight::from_parts(41_116_827, 0).saturating_mul(r.into())) + // Minimum execution time: 9_176_000 picoseconds. + Weight::from_parts(9_861_102, 0) + // Standard Error: 6_029 + .saturating_add(Weight::from_parts(45_948_571, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 160]`. fn seal_ecdsa_recover(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_404_000 picoseconds. - Weight::from_parts(31_763_334, 0) - // Standard Error: 9_833 - .saturating_add(Weight::from_parts(45_529_880, 0).saturating_mul(r.into())) + // Minimum execution time: 9_293_000 picoseconds. + Weight::from_parts(28_785_765, 0) + // Standard Error: 9_160 + .saturating_add(Weight::from_parts(45_566_150, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 160]`. fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_409_000 picoseconds. - Weight::from_parts(15_072_835, 0) - // Standard Error: 4_591 - .saturating_add(Weight::from_parts(11_619_283, 0).saturating_mul(r.into())) + // Minimum execution time: 9_206_000 picoseconds. + Weight::from_parts(12_420_664, 0) + // Standard Error: 3_489 + .saturating_add(Weight::from_parts(11_628_989, 0).saturating_mul(r.into())) } /// Storage: `Contracts::CodeInfoOf` (r:1536 w:1536) /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) @@ -2592,11 +2596,11 @@ impl WeightInfo for () { fn seal_set_code_hash(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + r * (926 ±0)` - // Estimated: `8969 + r * (3047 ±10)` - // Minimum execution time: 9_269_000 picoseconds. - Weight::from_parts(9_372_000, 8969) - // Standard Error: 61_354 - .saturating_add(Weight::from_parts(26_280_409, 0).saturating_mul(r.into())) + // Estimated: `8969 + r * (3047 ±7)` + // Minimum execution time: 9_219_000 picoseconds. + Weight::from_parts(9_385_000, 8969) + // Standard Error: 45_562 + .saturating_add(Weight::from_parts(26_360_661, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 3047).saturating_mul(r.into())) @@ -2608,10 +2612,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `274 + r * (78 ±0)` // Estimated: `1265 + r * (2553 ±0)` - // Minimum execution time: 9_103_000 picoseconds. - Weight::from_parts(14_404_626, 1265) - // Standard Error: 9_343 - .saturating_add(Weight::from_parts(5_154_949, 0).saturating_mul(r.into())) + // Minimum execution time: 9_355_000 picoseconds. + Weight::from_parts(15_071_309, 1265) + // Standard Error: 9_722 + .saturating_add(Weight::from_parts(5_328_717, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2553).saturating_mul(r.into())) @@ -2623,10 +2627,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `275 + r * (78 ±0)` // Estimated: `990 + r * (2568 ±0)` - // Minimum execution time: 9_219_000 picoseconds. - Weight::from_parts(14_085_456, 990) - // Standard Error: 11_206 - .saturating_add(Weight::from_parts(4_422_122, 0).saturating_mul(r.into())) + // Minimum execution time: 8_979_000 picoseconds. + Weight::from_parts(14_362_224, 990) + // Standard Error: 9_137 + .saturating_add(Weight::from_parts(4_488_748, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2568).saturating_mul(r.into())) @@ -2652,10 +2656,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `861 + r * (3 ±0)` // Estimated: `9282 + r * (3 ±0)` - // Minimum execution time: 269_333_000 picoseconds. - Weight::from_parts(286_922_618, 9282) - // Standard Error: 443 - .saturating_add(Weight::from_parts(168_869, 0).saturating_mul(r.into())) + // Minimum execution time: 269_704_000 picoseconds. + Weight::from_parts(289_916_035, 9282) + // Standard Error: 408 + .saturating_add(Weight::from_parts(166_040, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) .saturating_add(Weight::from_parts(0, 3).saturating_mul(r.into())) @@ -2665,10 +2669,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_328_000 picoseconds. - Weight::from_parts(14_019_583, 0) - // Standard Error: 171 - .saturating_add(Weight::from_parts(88_751, 0).saturating_mul(r.into())) + // Minimum execution time: 9_361_000 picoseconds. + Weight::from_parts(11_633_836, 0) + // Standard Error: 86 + .saturating_add(Weight::from_parts(83_083, 0).saturating_mul(r.into())) } /// Storage: `Contracts::Nonce` (r:1 w:0) /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) @@ -2677,10 +2681,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `219` // Estimated: `1704` - // Minimum execution time: 9_267_000 picoseconds. - Weight::from_parts(15_304_284, 1704) - // Standard Error: 1_219 - .saturating_add(Weight::from_parts(74_696, 0).saturating_mul(r.into())) + // Minimum execution time: 9_133_000 picoseconds. + Weight::from_parts(13_259_836, 1704) + // Standard Error: 121 + .saturating_add(Weight::from_parts(76_878, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// The range of component `r` is `[0, 5000]`. @@ -2688,9 +2692,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 911_000 picoseconds. - Weight::from_parts(449_666, 0) - // Standard Error: 26 - .saturating_add(Weight::from_parts(14_797, 0).saturating_mul(r.into())) + // Minimum execution time: 851_000 picoseconds. + Weight::from_parts(587_883, 0) + // Standard Error: 16 + .saturating_add(Weight::from_parts(14_912, 0).saturating_mul(r.into())) } } diff --git a/substrate/frame/contracts/uapi/src/host.rs b/substrate/frame/contracts/uapi/src/host.rs index 459cb59bead94fc5af18fb43955621f0e72d7e75..92065eda5d635352a0d7f7facb743960ba9189b7 100644 --- a/substrate/frame/contracts/uapi/src/host.rs +++ b/substrate/frame/contracts/uapi/src/host.rs @@ -790,7 +790,7 @@ pub trait HostFn { /// /// # Parameters /// - /// - `dest`: The XCM destination, should be decodable as [MultiLocation](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedLocation.html), + /// - `dest`: The XCM destination, should be decodable as [VersionedLocation](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedLocation.html), /// traps otherwise. /// - `msg`: The message, should be decodable as a [VersionedXcm](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedXcm.html), /// traps otherwise. diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs index 83083c912094bc4b39287f5105021fe2e44a4bb0..c00bb66ea13044f8b11aa781e30896e03225aea8 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs @@ -23,7 +23,6 @@ pub(crate) const LOG_TARGET: &str = "tests::e2e-epm"; use frame_support::{assert_err, assert_noop, assert_ok}; use mock::*; use sp_core::Get; -use sp_npos_elections::{to_supports, StakedAssignment}; use sp_runtime::Perbill; use crate::mock::RuntimeOrigin; @@ -127,75 +126,48 @@ fn offchainify_works() { } #[test] -/// Replicates the Kusama incident of 8th Dec 2022 and its resolution through the governance +/// Inspired by the Kusama incident of 8th Dec 2022 and its resolution through the governance /// fallback. /// -/// After enough slashes exceeded the `Staking::OffendingValidatorsThreshold`, the staking pallet -/// set `Forcing::ForceNew`. When a new session starts, staking will start to force a new era and -/// calls ::elect(). If at this point EPM and the staking miners did not -/// have enough time to queue a new solution (snapshot + solution submission), the election request -/// fails. If there is no election fallback mechanism in place, EPM enters in emergency mode. -/// Recovery: Once EPM is in emergency mode, subsequent calls to `elect()` will fail until a new -/// solution is added to EPM's `QueuedSolution` queue. This can be achieved through -/// `Call::set_emergency_election_result` or `Call::governance_fallback` dispatchables. Once a new -/// solution is added to the queue, EPM phase transitions to `Phase::Off` and the election flow -/// restarts. Note that in this test case, the emergency throttling is disabled. -fn enters_emergency_phase_after_forcing_before_elect() { +/// Mass slash of validators shouldn't disable more than 1/3 of them (the byzantine threshold). Also +/// no new era should be forced which could lead to EPM entering emergency mode. +fn mass_slash_doesnt_enter_emergency_phase() { let epm_builder = EpmExtBuilder::default().disable_emergency_throttling(); - let (ext, pool_state, _) = ExtBuilder::default().epm(epm_builder).build_offchainify(); - - execute_with(ext, || { - log!( - trace, - "current validators (staking): {:?}", - >::validators() - ); - let session_validators_before = Session::validators(); - - roll_to_epm_off(); - assert!(ElectionProviderMultiPhase::current_phase().is_off()); + let staking_builder = StakingExtBuilder::default().validator_count(7); + let (mut ext, _, _) = ExtBuilder::default() + .epm(epm_builder) + .staking(staking_builder) + .build_offchainify(); + ext.execute_with(|| { assert_eq!(pallet_staking::ForceEra::::get(), pallet_staking::Forcing::NotForcing); - // slashes so that staking goes into `Forcing::ForceNew`. - slash_through_offending_threshold(); - assert_eq!(pallet_staking::ForceEra::::get(), pallet_staking::Forcing::ForceNew); + let active_set_size_before_slash = Session::validators().len(); - advance_session_delayed_solution(pool_state.clone()); - assert!(ElectionProviderMultiPhase::current_phase().is_emergency()); - log_current_time(); + // Slash more than 1/3 of the active validators + let mut slashed = slash_half_the_active_set(); - let era_before_delayed_next = Staking::current_era(); - // try to advance 2 eras. - assert!(start_next_active_era_delayed_solution(pool_state.clone()).is_ok()); - assert_eq!(Staking::current_era(), era_before_delayed_next); - assert!(start_next_active_era(pool_state).is_err()); - assert_eq!(Staking::current_era(), era_before_delayed_next); + let active_set_size_after_slash = Session::validators().len(); - // EPM is still in emergency phase. - assert!(ElectionProviderMultiPhase::current_phase().is_emergency()); + // active set should stay the same before and after the slash + assert_eq!(active_set_size_before_slash, active_set_size_after_slash); - // session validator set remains the same. - assert_eq!(Session::validators(), session_validators_before); - - // performs recovery through the set emergency result. - let supports = to_supports(&vec![ - StakedAssignment { who: 21, distribution: vec![(21, 10)] }, - StakedAssignment { who: 31, distribution: vec![(21, 10), (31, 10)] }, - StakedAssignment { who: 41, distribution: vec![(41, 10)] }, - ]); - assert!(ElectionProviderMultiPhase::set_emergency_election_result( - RuntimeOrigin::root(), - supports - ) - .is_ok()); + // Slashed validators are disabled up to a limit + slashed.truncate( + pallet_staking::UpToLimitDisablingStrategy::::disable_limit( + active_set_size_after_slash, + ), + ); - // EPM can now roll to signed phase to proceed with elections. The validator set is the - // expected (ie. set through `set_emergency_election_result`). - roll_to_epm_signed(); - //assert!(ElectionProviderMultiPhase::current_phase().is_signed()); - assert_eq!(Session::validators(), vec![21, 31, 41]); - assert_eq!(Staking::current_era(), era_before_delayed_next.map(|e| e + 1)); + // Find the indices of the disabled validators + let active_set = Session::validators(); + let expected_disabled = slashed + .into_iter() + .map(|d| active_set.iter().position(|a| *a == d).unwrap() as u32) + .collect::>(); + + assert_eq!(pallet_staking::ForceEra::::get(), pallet_staking::Forcing::NotForcing); + assert_eq!(Session::disabled_validators(), expected_disabled); }); } @@ -253,77 +225,7 @@ fn continuous_slashes_below_offending_threshold() { } #[test] -/// Slashed validator sets intentions in the same era of slashing. -/// -/// When validators are slashed, they are chilled and removed from the current `VoterList`. Thus, -/// the slashed validator should not be considered in the next validator set. However, if the -/// slashed validator sets its intention to validate again in the same era when it was slashed and -/// chilled, the validator may not be removed from the active validator set across eras, provided -/// it would selected in the subsequent era if there was no slash. Nominators of the slashed -/// validator will also be slashed and chilled, as expected, but the nomination intentions will -/// remain after the validator re-set the intention to be validating again. -/// -/// This behaviour is due to removing implicit chill upon slash -/// . -/// -/// Related to . -fn set_validation_intention_after_chilled() { - use frame_election_provider_support::SortedListProvider; - use pallet_staking::{Event, Forcing, Nominators}; - - let (ext, pool_state, _) = ExtBuilder::default() - .epm(EpmExtBuilder::default()) - .staking(StakingExtBuilder::default()) - .build_offchainify(); - - execute_with(ext, || { - assert_eq!(active_era(), 0); - // validator is part of the validator set. - assert!(Session::validators().contains(&41)); - assert!(::VoterList::contains(&41)); - - // nominate validator 81. - assert_ok!(Staking::nominate(RuntimeOrigin::signed(21), vec![41])); - assert_eq!(Nominators::::get(21).unwrap().targets, vec![41]); - - // validator is slashed. it is removed from the `VoterList` through chilling but in the - // current era, the validator is still part of the active validator set. - add_slash(&41); - assert!(Session::validators().contains(&41)); - assert!(!::VoterList::contains(&41)); - assert_eq!( - staking_events(), - [ - Event::Chilled { stash: 41 }, - Event::ForceEra { mode: Forcing::ForceNew }, - Event::SlashReported { - validator: 41, - slash_era: 0, - fraction: Perbill::from_percent(10) - } - ], - ); - - // after the nominator is slashed and chilled, the nominations remain. - assert_eq!(Nominators::::get(21).unwrap().targets, vec![41]); - - // validator sets intention to stake again in the same era it was chilled. - assert_ok!(Staking::validate(RuntimeOrigin::signed(41), Default::default())); - - // progress era and check that the slashed validator is still part of the validator - // set. - assert!(start_next_active_era(pool_state).is_ok()); - assert_eq!(active_era(), 1); - assert!(Session::validators().contains(&41)); - assert!(::VoterList::contains(&41)); - - // nominations are still active as before the slash. - assert_eq!(Nominators::::get(21).unwrap().targets, vec![41]); - }) -} - -#[test] -/// Active ledger balance may fall below ED if account chills before unbonding. +/// Active ledger balance may fall below ED if account chills before unbounding. /// /// Unbonding call fails if the remaining ledger's stash balance falls below the existential /// deposit. However, if the stash is chilled before unbonding, the ledger's active balance may diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index a727e3bf816251495c2b5e9e32eb688e51c4e339..8f1775a7e5951ad1c9271f486a782b18fe5da900 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -35,7 +35,7 @@ use sp_runtime::{ transaction_validity, BuildStorage, PerU16, Perbill, Percent, }; use sp_staking::{ - offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, + offence::{OffenceDetails, OnOffenceHandler}, EraIndex, SessionIndex, }; use sp_std::prelude::*; @@ -236,7 +236,6 @@ parameter_types! { pub const SessionsPerEra: sp_staking::SessionIndex = 2; pub static BondingDuration: sp_staking::EraIndex = 28; pub const SlashDeferDuration: sp_staking::EraIndex = 7; // 1/4 the bonding duration. - pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(40); pub HistoryDepth: u32 = 84; } @@ -290,6 +289,8 @@ parameter_types! { /// Upper limit on the number of NPOS nominations. const MAX_QUOTA_NOMINATIONS: u32 = 16; +/// Disabling factor set explicitly to byzantine threshold +pub(crate) const SLASHING_DISABLING_FACTOR: usize = 3; impl pallet_staking::Config for Runtime { type Currency = Balances; @@ -308,7 +309,6 @@ impl pallet_staking::Config for Runtime { type EraPayout = (); type NextNewSession = Session; type MaxExposurePageSize = ConstU32<256>; - type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainExecution; type VoterList = BagsList; @@ -320,6 +320,7 @@ impl pallet_staking::Config for Runtime { type EventListeners = Pools; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } impl frame_system::offchain::SendTransactionTypes for Runtime @@ -871,7 +872,6 @@ pub(crate) fn on_offence_now( offenders, slash_fraction, Staking::eras_start_session_index(now).unwrap(), - DisableStrategy::WhenSlashed, ); } @@ -886,19 +886,16 @@ pub(crate) fn add_slash(who: &AccountId) { ); } -// Slashes enough validators to cross the `Staking::OffendingValidatorsThreshold`. -pub(crate) fn slash_through_offending_threshold() { - let validators = Session::validators(); - let mut remaining_slashes = - ::OffendingValidatorsThreshold::get() * - validators.len() as u32; +// Slashes 1/2 of the active set. Returns the `AccountId`s of the slashed validators. +pub(crate) fn slash_half_the_active_set() -> Vec { + let mut slashed = Session::validators(); + slashed.truncate(slashed.len() / 2); - for v in validators.into_iter() { - if remaining_slashes != 0 { - add_slash(&v); - remaining_slashes -= 1; - } + for v in slashed.iter() { + add_slash(v); } + + slashed } // Slashes a percentage of the active nominators that haven't been slashed yet, with diff --git a/substrate/frame/election-provider-support/benchmarking/src/inner.rs b/substrate/frame/election-provider-support/benchmarking/src/inner.rs new file mode 100644 index 0000000000000000000000000000000000000000..4722680cfcc1cb9131f55d6002b666b28fb3d2ae --- /dev/null +++ b/substrate/frame/election-provider-support/benchmarking/src/inner.rs @@ -0,0 +1,89 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Election provider support pallet benchmarking. +//! This is separated into its own crate to avoid bloating the size of the runtime. + +use codec::Decode; +use frame_benchmarking::v1::benchmarks; +use frame_election_provider_support::{NposSolver, PhragMMS, SequentialPhragmen}; +use sp_std::vec::Vec; + +pub struct Pallet(frame_system::Pallet); +pub trait Config: frame_system::Config {} + +const VOTERS: [u32; 2] = [1_000, 2_000]; +const TARGETS: [u32; 2] = [500, 1_000]; +const VOTES_PER_VOTER: [u32; 2] = [5, 16]; + +const SEED: u32 = 999; +fn set_up_voters_targets( + voters_len: u32, + targets_len: u32, + degree: usize, +) -> (Vec<(AccountId, u64, impl IntoIterator)>, Vec) { + // fill targets. + let mut targets = (0..targets_len) + .map(|i| frame_benchmarking::account::("Target", i, SEED)) + .collect::>(); + assert!(targets.len() > degree, "we should always have enough voters to fill"); + targets.truncate(degree); + + // fill voters. + let voters = (0..voters_len) + .map(|i| { + let voter = frame_benchmarking::account::("Voter", i, SEED); + (voter, 1_000, targets.clone()) + }) + .collect::>(); + + (voters, targets) +} + +benchmarks! { + phragmen { + // number of votes in snapshot. + let v in (VOTERS[0]) .. VOTERS[1]; + // number of targets in snapshot. + let t in (TARGETS[0]) .. TARGETS[1]; + // number of votes per voter (ie the degree). + let d in (VOTES_PER_VOTER[0]) .. VOTES_PER_VOTER[1]; + + let (voters, targets) = set_up_voters_targets::(v, t, d as usize); + }: { + assert!( + SequentialPhragmen:: + ::solve(d as usize, targets, voters).is_ok() + ); + } + + phragmms { + // number of votes in snapshot. + let v in (VOTERS[0]) .. VOTERS[1]; + // number of targets in snapshot. + let t in (TARGETS[0]) .. TARGETS[1]; + // number of votes per voter (ie the degree). + let d in (VOTES_PER_VOTER[0]) .. VOTES_PER_VOTER[1]; + + let (voters, targets) = set_up_voters_targets::(v, t, d as usize); + }: { + assert!( + PhragMMS:: + ::solve(d as usize, targets, voters).is_ok() + ); + } +} diff --git a/substrate/frame/election-provider-support/benchmarking/src/lib.rs b/substrate/frame/election-provider-support/benchmarking/src/lib.rs index 6c75aed0a911d3c66802951f0387ba675f8cfdba..78b226e52af6c2cec67706acb67dee280b818af3 100644 --- a/substrate/frame/election-provider-support/benchmarking/src/lib.rs +++ b/substrate/frame/election-provider-support/benchmarking/src/lib.rs @@ -16,77 +16,11 @@ // limitations under the License. //! Election provider support pallet benchmarking. -//! This is separated into its own crate to avoid bloating the size of the runtime. -#![cfg(feature = "runtime-benchmarks")] #![cfg_attr(not(feature = "std"), no_std)] -use codec::Decode; -use frame_benchmarking::v1::benchmarks; -use frame_election_provider_support::{NposSolver, PhragMMS, SequentialPhragmen}; -use sp_std::vec::Vec; +#[cfg(feature = "runtime-benchmarks")] +pub mod inner; -pub struct Pallet(frame_system::Pallet); -pub trait Config: frame_system::Config {} - -const VOTERS: [u32; 2] = [1_000, 2_000]; -const TARGETS: [u32; 2] = [500, 1_000]; -const VOTES_PER_VOTER: [u32; 2] = [5, 16]; - -const SEED: u32 = 999; -fn set_up_voters_targets( - voters_len: u32, - targets_len: u32, - degree: usize, -) -> (Vec<(AccountId, u64, impl IntoIterator)>, Vec) { - // fill targets. - let mut targets = (0..targets_len) - .map(|i| frame_benchmarking::account::("Target", i, SEED)) - .collect::>(); - assert!(targets.len() > degree, "we should always have enough voters to fill"); - targets.truncate(degree); - - // fill voters. - let voters = (0..voters_len) - .map(|i| { - let voter = frame_benchmarking::account::("Voter", i, SEED); - (voter, 1_000, targets.clone()) - }) - .collect::>(); - - (voters, targets) -} - -benchmarks! { - phragmen { - // number of votes in snapshot. - let v in (VOTERS[0]) .. VOTERS[1]; - // number of targets in snapshot. - let t in (TARGETS[0]) .. TARGETS[1]; - // number of votes per voter (ie the degree). - let d in (VOTES_PER_VOTER[0]) .. VOTES_PER_VOTER[1]; - - let (voters, targets) = set_up_voters_targets::(v, t, d as usize); - }: { - assert!( - SequentialPhragmen:: - ::solve(d as usize, targets, voters).is_ok() - ); - } - - phragmms { - // number of votes in snapshot. - let v in (VOTERS[0]) .. VOTERS[1]; - // number of targets in snapshot. - let t in (TARGETS[0]) .. TARGETS[1]; - // number of votes per voter (ie the degree). - let d in (VOTES_PER_VOTER[0]) .. VOTES_PER_VOTER[1]; - - let (voters, targets) = set_up_voters_targets::(v, t, d as usize); - }: { - assert!( - PhragMMS:: - ::solve(d as usize, targets, voters).is_ok() - ); - } -} +#[cfg(feature = "runtime-benchmarks")] +pub use inner::*; diff --git a/substrate/frame/examples/dev-mode/src/lib.rs b/substrate/frame/examples/dev-mode/src/lib.rs index d57e7a5b76b82f92425596b69f2f495ab0865f1e..15f1a4b5d6199c29158e1600ad825e15d04479bf 100644 --- a/substrate/frame/examples/dev-mode/src/lib.rs +++ b/substrate/frame/examples/dev-mode/src/lib.rs @@ -30,6 +30,7 @@ use frame_support::dispatch::DispatchResult; use frame_system::ensure_signed; +use sp_std::{vec, vec::Vec}; // Re-export pallet items so that they can be accessed from the crate namespace. pub use pallet::*; diff --git a/substrate/frame/examples/frame-crate/Cargo.toml b/substrate/frame/examples/frame-crate/Cargo.toml index 3a0e4f720f95ddd16bf012274cfee24add6f3883..48cb25f90949a9401a8261999c0332d16d497d04 100644 --- a/substrate/frame/examples/frame-crate/Cargo.toml +++ b/substrate/frame/examples/frame-crate/Cargo.toml @@ -2,7 +2,7 @@ name = "pallet-example-frame-crate" version = "0.0.1" authors = ["Parity Technologies "] -edition = "2021" +edition.workspace = true license = "MIT-0" homepage = "https://substrate.io" repository.workspace = true diff --git a/substrate/frame/examples/offchain-worker/Cargo.toml b/substrate/frame/examples/offchain-worker/Cargo.toml index 468af0345cae71d97afea4c9ce02f2ab2491e5db..9363f7533526650e69bcc791e99d37fac17155a7 100644 --- a/substrate/frame/examples/offchain-worker/Cargo.toml +++ b/substrate/frame/examples/offchain-worker/Cargo.toml @@ -24,7 +24,7 @@ frame-support = { path = "../../support", default-features = false } frame-system = { path = "../../system", default-features = false } sp-core = { path = "../../../primitives/core", default-features = false } sp-io = { path = "../../../primitives/io", default-features = false } -sp-keystore = { path = "../../../primitives/keystore", optional = true } +sp-keystore = { path = "../../../primitives/keystore", optional = true, default-features = false } sp-runtime = { path = "../../../primitives/runtime", default-features = false } sp-std = { path = "../../../primitives/std", default-features = false } diff --git a/substrate/frame/examples/tasks/src/lib.rs b/substrate/frame/examples/tasks/src/lib.rs index c65d8095bcf6a2c2295bb87b11e4041fbab88173..1908a235ba15868c41f1fe62c3edd43532e329cf 100644 --- a/substrate/frame/examples/tasks/src/lib.rs +++ b/substrate/frame/examples/tasks/src/lib.rs @@ -19,6 +19,9 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::dispatch::DispatchResult; +use frame_system::offchain::SendTransactionTypes; +#[cfg(feature = "experimental")] +use frame_system::offchain::SubmitTransaction; // Re-export pallet items so that they can be accessed from the crate namespace. pub use pallet::*; @@ -31,10 +34,14 @@ mod benchmarking; pub mod weights; pub use weights::*; +#[cfg(feature = "experimental")] +const LOG_TARGET: &str = "pallet-example-tasks"; + #[frame_support::pallet(dev_mode)] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; #[pallet::error] pub enum Error { @@ -59,9 +66,36 @@ pub mod pallet { } } + #[pallet::hooks] + impl Hooks> for Pallet { + #[cfg(feature = "experimental")] + fn offchain_worker(_block_number: BlockNumberFor) { + if let Some(key) = Numbers::::iter_keys().next() { + // Create a valid task + let task = Task::::AddNumberIntoTotal { i: key }; + let runtime_task = ::RuntimeTask::from(task); + let call = frame_system::Call::::do_task { task: runtime_task.into() }; + + // Submit the task as an unsigned transaction + let res = + SubmitTransaction::>::submit_unsigned_transaction( + call.into(), + ); + match res { + Ok(_) => log::info!(target: LOG_TARGET, "Submitted the task."), + Err(e) => log::error!(target: LOG_TARGET, "Error submitting task: {:?}", e), + } + } + } + } + #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeTask: frame_support::traits::Task; + pub trait Config: + SendTransactionTypes> + frame_system::Config + { + type RuntimeTask: frame_support::traits::Task + + IsType<::RuntimeTask> + + From>; type WeightInfo: WeightInfo; } diff --git a/substrate/frame/examples/tasks/src/mock.rs b/substrate/frame/examples/tasks/src/mock.rs index 76ac9e76bff8a79dbedfd6f6a4746a452a685eba..33912bb5269c5ba58cb9f5256cceb26d681577e5 100644 --- a/substrate/frame/examples/tasks/src/mock.rs +++ b/substrate/frame/examples/tasks/src/mock.rs @@ -20,6 +20,7 @@ use crate::{self as tasks_example}; use frame_support::derive_impl; +use sp_runtime::testing::TestXt; pub type AccountId = u32; pub type Balance = u32; @@ -32,12 +33,32 @@ frame_support::construct_runtime!( } ); +pub type Extrinsic = TestXt; + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { type Block = Block; } +impl frame_system::offchain::SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type OverarchingCall = RuntimeCall; + type Extrinsic = Extrinsic; +} + impl tasks_example::Config for Runtime { type RuntimeTask = RuntimeTask; type WeightInfo = (); } + +pub fn advance_to(b: u64) { + #[cfg(feature = "experimental")] + use frame_support::traits::Hooks; + while System::block_number() < b { + System::set_block_number(System::block_number() + 1); + #[cfg(feature = "experimental")] + TasksExample::offchain_worker(System::block_number()); + } +} diff --git a/substrate/frame/examples/tasks/src/tests.rs b/substrate/frame/examples/tasks/src/tests.rs index fc3c69f4aef95601c2a96faf1c939c27ee419fd4..6c8acb0194bde6cba27e4e2706f78676468c0057 100644 --- a/substrate/frame/examples/tasks/src/tests.rs +++ b/substrate/frame/examples/tasks/src/tests.rs @@ -19,7 +19,11 @@ #![cfg(test)] use crate::{mock::*, Numbers}; +#[cfg(feature = "experimental")] +use codec::Decode; use frame_support::traits::Task; +#[cfg(feature = "experimental")] +use sp_core::offchain::{testing, OffchainWorkerExt, TransactionPoolExt}; use sp_runtime::BuildStorage; #[cfg(feature = "experimental")] @@ -130,3 +134,29 @@ fn task_execution_fails_for_invalid_task() { ); }); } + +#[cfg(feature = "experimental")] +#[test] +fn task_with_offchain_worker() { + let (offchain, _offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + + let mut t = sp_io::TestExternalities::default(); + t.register_extension(OffchainWorkerExt::new(offchain)); + t.register_extension(TransactionPoolExt::new(pool)); + + t.execute_with(|| { + advance_to(1); + assert!(pool_state.read().transactions.is_empty()); + + Numbers::::insert(0, 10); + assert_eq!(crate::Total::::get(), (0, 0)); + + advance_to(2); + + let tx = pool_state.write().transactions.pop().unwrap(); + assert!(pool_state.read().transactions.is_empty()); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + assert_eq!(tx.signature, None); + }); +} diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index b731cb822f336d6dd2da8e0d5ca501090909a31a..d876f9f6171e5ed55d9cbe95fd678fa071b5bd9b 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -134,7 +134,6 @@ impl pallet_staking::Config for Runtime { type NextNewSession = (); type HistoryDepth = ConstU32<84>; type MaxExposurePageSize = ConstU32<64>; - type OffendingValidatorsThreshold = (); type ElectionProvider = MockElection; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; @@ -145,6 +144,7 @@ impl pallet_staking::Config for Runtime { type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } pub struct BalanceToU256; diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index 4a21da655e5b3d4b219caed097619fa3268eca57..2d54f525b1f0c6b6a78f8df848c5770391f4fa2d 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -146,7 +146,6 @@ parameter_types! { pub const SessionsPerEra: SessionIndex = 3; pub const BondingDuration: EraIndex = 3; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); pub static ElectionsBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default().build(); } @@ -176,7 +175,6 @@ impl pallet_staking::Config for Test { type UnixTime = pallet_timestamp::Pallet; type EraPayout = pallet_staking::ConvertCurve; type MaxExposurePageSize = ConstU32<64>; - type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; @@ -189,6 +187,7 @@ impl pallet_staking::Config for Test { type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } impl pallet_offences::Config for Test { diff --git a/substrate/frame/im-online/src/lib.rs b/substrate/frame/im-online/src/lib.rs index 239b47834d1f8b1625a186e4a8f90e9861bfa303..f91a473e53d5389275974ed0e62c1d75d0daff54 100644 --- a/substrate/frame/im-online/src/lib.rs +++ b/substrate/frame/im-online/src/lib.rs @@ -104,7 +104,7 @@ use sp_runtime::{ PerThing, Perbill, Permill, RuntimeDebug, SaturatedConversion, }; use sp_staking::{ - offence::{DisableStrategy, Kind, Offence, ReportOffence}, + offence::{Kind, Offence, ReportOffence}, SessionIndex, }; use sp_std::prelude::*; @@ -847,10 +847,6 @@ impl Offence for UnresponsivenessOffence { self.session_index } - fn disable_strategy(&self) -> DisableStrategy { - DisableStrategy::Never - } - fn slash_fraction(&self, offenders: u32) -> Perbill { // the formula is min((3 * (k - (n / 10 + 1))) / n, 1) * 0.07 // basically, 10% can be offline with no slash, but after that, it linearly climbs up to 7% diff --git a/substrate/frame/im-online/src/tests.rs b/substrate/frame/im-online/src/tests.rs index f9959593494a0b94c4093f4c06f0e45c1e3e4f59..12333d59ef8959d26836330bb4a3a1be93ac49ab 100644 --- a/substrate/frame/im-online/src/tests.rs +++ b/substrate/frame/im-online/src/tests.rs @@ -50,9 +50,6 @@ fn test_unresponsiveness_slash_fraction() { dummy_offence.slash_fraction(17), Perbill::from_parts(46200000), // 4.62% ); - - // Offline offences should never lead to being disabled. - assert_eq!(dummy_offence.disable_strategy(), DisableStrategy::Never); } #[test] diff --git a/substrate/frame/indices/Cargo.toml b/substrate/frame/indices/Cargo.toml index 7b14bf358f1ef3e929e8e6be67f33a54b3590928..8684f347270f59b3032a0056ba25bbb133cd3a22 100644 --- a/substrate/frame/indices/Cargo.toml +++ b/substrate/frame/indices/Cargo.toml @@ -23,7 +23,7 @@ frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } sp-io = { path = "../../primitives/io", default-features = false } -sp-keyring = { path = "../../primitives/keyring", optional = true } +sp-keyring = { path = "../../primitives/keyring", optional = true, default-features = false } sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } @@ -42,6 +42,7 @@ std = [ "sp-core/std", "sp-io/std", "sp-keyring", + "sp-keyring?/std", "sp-runtime/std", "sp-std/std", ] diff --git a/substrate/frame/merkle-mountain-range/src/lib.rs b/substrate/frame/merkle-mountain-range/src/lib.rs index 7b6edb37b7f7bc8608a417aa9eff6f0713edf9df..e2b40974579e8376723f466198d74755603285d0 100644 --- a/substrate/frame/merkle-mountain-range/src/lib.rs +++ b/substrate/frame/merkle-mountain-range/src/lib.rs @@ -103,6 +103,24 @@ impl LeafDataProvider for ParentNumberAndHash { } } +/// Block hash provider for a given block number. +pub trait BlockHashProvider { + fn block_hash(block_number: BlockNumber) -> BlockHash; +} + +/// Default implementation of BlockHashProvider using frame_system. +pub struct DefaultBlockHashProvider { + _phantom: sp_std::marker::PhantomData, +} + +impl BlockHashProvider, T::Hash> + for DefaultBlockHashProvider +{ + fn block_hash(block_number: BlockNumberFor) -> T::Hash { + frame_system::Pallet::::block_hash(block_number) + } +} + pub trait WeightInfo { fn on_initialize(peaks: NodeIndex) -> Weight; } @@ -177,6 +195,12 @@ pub mod pallet { /// Clients. Hook complexity should be `O(1)`. type OnNewRoot: primitives::OnNewRoot>; + /// Block hash provider for a given block number. + type BlockHashProvider: BlockHashProvider< + BlockNumberFor, + ::Hash, + >; + /// Weights for this pallet. type WeightInfo: WeightInfo; } diff --git a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs index 96a20c3445eedea0bfa3d02e99ed07022d30da80..f2acc35a137ffb46a39396762eafe30d7296e017 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs @@ -29,7 +29,7 @@ use sp_std::prelude::*; use crate::{ mmr::{Node, NodeOf}, primitives::{self, NodeIndex}, - Config, Nodes, NumberOfLeaves, Pallet, + BlockHashProvider, Config, Nodes, NumberOfLeaves, Pallet, }; /// A marker type for runtime-specific storage implementation. @@ -87,7 +87,7 @@ where // Fall through to searching node using fork-specific key. let ancestor_parent_block_num = Pallet::::leaf_index_to_parent_block_num(ancestor_leaf_idx, leaves); - let ancestor_parent_hash = >::block_hash(ancestor_parent_block_num); + let ancestor_parent_hash = T::BlockHashProvider::block_hash(ancestor_parent_block_num); let temp_key = Pallet::::node_temp_offchain_key(pos, ancestor_parent_hash); debug!( target: "runtime::mmr::offchain", diff --git a/substrate/frame/merkle-mountain-range/src/mock.rs b/substrate/frame/merkle-mountain-range/src/mock.rs index 212012a052a027cd0582b90e3a29a409980c4e66..8318b20e83074cd3bcb540b854fd7abae998438e 100644 --- a/substrate/frame/merkle-mountain-range/src/mock.rs +++ b/substrate/frame/merkle-mountain-range/src/mock.rs @@ -44,6 +44,7 @@ impl Config for Test { type Hashing = Keccak256; type LeafData = Compact, LeafData)>; type OnNewRoot = (); + type BlockHashProvider = DefaultBlockHashProvider; type WeightInfo = (); } diff --git a/substrate/frame/message-queue/src/lib.rs b/substrate/frame/message-queue/src/lib.rs index ec85c785f79eb6eea628d9a8df6db0667d2bf70e..ef3420d21be520f8056b1c1299e8369754186560 100644 --- a/substrate/frame/message-queue/src/lib.rs +++ b/substrate/frame/message-queue/src/lib.rs @@ -765,6 +765,13 @@ enum MessageExecutionStatus { Processed, /// The message was processed and resulted in a, possibly permanent, error. Unprocessable { permanent: bool }, + /// The stack depth limit was reached. + /// + /// We cannot just return `Unprocessable` in this case, because the processability of the + /// message depends on how the function was called. This may be a permanent error if it was + /// called by a top-level function, or a transient error if it was already called in a nested + /// function. + StackLimitReached, } impl Pallet { @@ -984,7 +991,8 @@ impl Pallet { // additional overweight event being deposited. ) { Overweight | InsufficientWeight => Err(Error::::InsufficientWeight), - Unprocessable { permanent: false } => Err(Error::::TemporarilyUnprocessable), + StackLimitReached | Unprocessable { permanent: false } => + Err(Error::::TemporarilyUnprocessable), Unprocessable { permanent: true } | Processed => { page.note_processed_at_pos(pos); book_state.message_count.saturating_dec(); @@ -1250,7 +1258,7 @@ impl Pallet { let is_processed = match res { InsufficientWeight => return ItemExecutionStatus::Bailed, Unprocessable { permanent: false } => return ItemExecutionStatus::NoProgress, - Processed | Unprocessable { permanent: true } => true, + Processed | Unprocessable { permanent: true } | StackLimitReached => true, Overweight => false, }; @@ -1461,6 +1469,10 @@ impl Pallet { Self::deposit_event(Event::::ProcessingFailed { id: id.into(), origin, error }); MessageExecutionStatus::Unprocessable { permanent: true } }, + Err(error @ StackLimitReached) => { + Self::deposit_event(Event::::ProcessingFailed { id: id.into(), origin, error }); + MessageExecutionStatus::StackLimitReached + }, Ok(success) => { // Success let weight_used = meter.consumed().saturating_sub(prev_consumed); diff --git a/substrate/frame/message-queue/src/mock.rs b/substrate/frame/message-queue/src/mock.rs index 1281de6b0a66f37e265071b390981138b30dad2e..66a242d5a18ff7f72dd08a4c0a0519c82f1cfbbc 100644 --- a/substrate/frame/message-queue/src/mock.rs +++ b/substrate/frame/message-queue/src/mock.rs @@ -198,6 +198,7 @@ impl ProcessMessage for RecordingMessageProcessor { parameter_types! { pub static Callback: Box = Box::new(|_, _| {}); + pub static IgnoreStackOvError: bool = false; } /// Processed a mocked message. Messages that end with `badformat`, `corrupt`, `unsupported` or @@ -216,6 +217,8 @@ fn processing_message(msg: &[u8], origin: &MessageOrigin) -> Result<(), ProcessM Err(ProcessMessageError::Unsupported) } else if msg.ends_with("yield") { Err(ProcessMessageError::Yield) + } else if msg.ends_with("stacklimitreached") && !IgnoreStackOvError::get() { + Err(ProcessMessageError::StackLimitReached) } else { Ok(()) } diff --git a/substrate/frame/message-queue/src/tests.rs b/substrate/frame/message-queue/src/tests.rs index d6788847d57173da6a7a06740daf55946f15b15f..e89fdb8b3208e2e218f2f8ba8dd797f3d08e12bb 100644 --- a/substrate/frame/message-queue/src/tests.rs +++ b/substrate/frame/message-queue/src/tests.rs @@ -174,9 +174,10 @@ fn service_queues_failing_messages_works() { MessageQueue::enqueue_message(msg("badformat"), Here); MessageQueue::enqueue_message(msg("corrupt"), Here); MessageQueue::enqueue_message(msg("unsupported"), Here); + MessageQueue::enqueue_message(msg("stacklimitreached"), Here); MessageQueue::enqueue_message(msg("yield"), Here); // Starts with four pages. - assert_pages(&[0, 1, 2, 3]); + assert_pages(&[0, 1, 2, 3, 4]); assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_last_event::( @@ -206,9 +207,9 @@ fn service_queues_failing_messages_works() { .into(), ); assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); - assert_eq!(System::events().len(), 3); + assert_eq!(System::events().len(), 4); // Last page with the `yield` stays in. - assert_pages(&[3]); + assert_pages(&[4]); }); } @@ -1880,3 +1881,97 @@ fn process_enqueued_on_idle_requires_enough_weight() { assert_eq!(MessagesProcessed::take(), vec![]); }) } + +/// A message that reports `StackLimitReached` will not be put into the overweight queue when +/// executed from the top level. +#[test] +fn process_discards_stack_ov_message() { + use MessageOrigin::*; + build_and_execute::(|| { + MessageQueue::enqueue_message(msg("stacklimitreached"), Here); + + MessageQueue::service_queues(10.into_weight()); + + assert_last_event::( + Event::ProcessingFailed { + id: blake2_256(b"stacklimitreached").into(), + origin: MessageOrigin::Here, + error: ProcessMessageError::StackLimitReached, + } + .into(), + ); + + assert!(MessagesProcessed::take().is_empty()); + // Message is gone and not overweight: + assert_pages(&[]); + }); +} + +/// A message that reports `StackLimitReached` will stay in the overweight queue when it is executed +/// by `execute_overweight`. +#[test] +fn execute_overweight_keeps_stack_ov_message() { + use MessageOrigin::*; + build_and_execute::(|| { + // We need to create a mocked message that first reports insufficient weight, and then + // `StackLimitReached`: + IgnoreStackOvError::set(true); + MessageQueue::enqueue_message(msg("stacklimitreached"), Here); + MessageQueue::service_queues(0.into_weight()); + + assert_last_event::( + Event::OverweightEnqueued { + id: blake2_256(b"stacklimitreached"), + origin: MessageOrigin::Here, + message_index: 0, + page_index: 0, + } + .into(), + ); + // Does not count as 'processed': + assert!(MessagesProcessed::take().is_empty()); + assert_pages(&[0]); + + // Now let it return `StackLimitReached`. Note that this case would normally not happen, + // since we assume that the top-level execution is the one with the most remaining stack + // depth. + IgnoreStackOvError::set(false); + // Ensure that trying to execute the message does not change any state (besides events). + System::reset_events(); + let storage_noop = StorageNoopGuard::new(); + assert_eq!( + ::execute_overweight(3.into_weight(), (Here, 0, 0)), + Err(ExecuteOverweightError::Other) + ); + assert_last_event::( + Event::ProcessingFailed { + id: blake2_256(b"stacklimitreached").into(), + origin: MessageOrigin::Here, + error: ProcessMessageError::StackLimitReached, + } + .into(), + ); + System::reset_events(); + drop(storage_noop); + + // Now let's process it normally: + IgnoreStackOvError::set(true); + assert_eq!( + ::execute_overweight(1.into_weight(), (Here, 0, 0)) + .unwrap(), + 1.into_weight() + ); + + assert_last_event::( + Event::Processed { + id: blake2_256(b"stacklimitreached").into(), + origin: MessageOrigin::Here, + weight_used: 1.into_weight(), + success: true, + } + .into(), + ); + assert_pages(&[]); + System::reset_events(); + }); +} diff --git a/substrate/frame/mixnet/Cargo.toml b/substrate/frame/mixnet/Cargo.toml index 6a4ef5c29ac8b0afe770e51e802f39ca0409eb72..964d6acb889ad0e02b900efb0264f761c5afbeb7 100644 --- a/substrate/frame/mixnet/Cargo.toml +++ b/substrate/frame/mixnet/Cargo.toml @@ -4,7 +4,7 @@ name = "pallet-mixnet" version = "0.4.0" license = "Apache-2.0" authors = ["Parity Technologies "] -edition = "2021" +edition.workspace = true homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" diff --git a/substrate/frame/node-authorization/src/lib.rs b/substrate/frame/node-authorization/src/lib.rs index 9019a863ad8124fdb8aacac6a8e532d860861730..a7967536079f9cf1c72940ff1cdc8847052e9318 100644 --- a/substrate/frame/node-authorization/src/lib.rs +++ b/substrate/frame/node-authorization/src/lib.rs @@ -47,7 +47,7 @@ pub mod weights; pub use pallet::*; use sp_core::OpaquePeerId as PeerId; use sp_runtime::traits::StaticLookup; -use sp_std::{collections::btree_set::BTreeSet, iter::FromIterator, prelude::*}; +use sp_std::{collections::btree_set::BTreeSet, prelude::*}; pub use weights::WeightInfo; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; diff --git a/substrate/frame/nomination-pools/Cargo.toml b/substrate/frame/nomination-pools/Cargo.toml index 55e9ef6fbd3382f65f2d4be3cfdb5eed1b318345..eddcc8e4e1ddb9a51fcbec8dd1c922d20c43833c 100644 --- a/substrate/frame/nomination-pools/Cargo.toml +++ b/substrate/frame/nomination-pools/Cargo.toml @@ -34,8 +34,8 @@ sp-io = { path = "../../primitives/io", default-features = false } log = { workspace = true } # Optional: use for testing and/or fuzzing -pallet-balances = { path = "../balances", optional = true } -sp-tracing = { path = "../../primitives/tracing", optional = true } +pallet-balances = { path = "../balances", optional = true, default-features = false } +sp-tracing = { path = "../../primitives/tracing", optional = true, default-features = false } [dev-dependencies] pallet-balances = { path = "../balances" } diff --git a/substrate/frame/nomination-pools/benchmarking/src/inner.rs b/substrate/frame/nomination-pools/benchmarking/src/inner.rs new file mode 100644 index 0000000000000000000000000000000000000000..277060e7f640f962bd112a6f337339ec1cf2559e --- /dev/null +++ b/substrate/frame/nomination-pools/benchmarking/src/inner.rs @@ -0,0 +1,846 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarks for the nomination pools coupled with the staking and bags list pallets. + +use frame_benchmarking::v1::{account, whitelist_account}; +use frame_election_provider_support::SortedListProvider; +use frame_support::{ + assert_ok, ensure, + traits::{ + fungible::{Inspect, Mutate, Unbalanced}, + Get, + }, +}; +use frame_system::RawOrigin as RuntimeOrigin; +use pallet_nomination_pools::{ + BalanceOf, BondExtra, BondedPoolInner, BondedPools, ClaimPermission, ClaimPermissions, + Commission, CommissionChangeRate, CommissionClaimPermission, ConfigOp, GlobalMaxCommission, + MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, + Pallet as Pools, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage, +}; +use pallet_staking::MaxNominationsOf; +use sp_runtime::{ + traits::{Bounded, StaticLookup, Zero}, + Perbill, +}; +use sp_staking::{EraIndex, StakingInterface}; +use sp_std::{vec, vec::Vec}; +// `frame_benchmarking::benchmarks!` macro needs this +use pallet_nomination_pools::Call; + +type CurrencyOf = ::Currency; + +const USER_SEED: u32 = 0; +const MAX_SPANS: u32 = 100; + +pub(crate) type VoterBagsListInstance = pallet_bags_list::Instance1; +pub trait Config: + pallet_nomination_pools::Config + + pallet_staking::Config + + pallet_bags_list::Config +{ +} + +pub struct Pallet(Pools); + +fn create_funded_user_with_balance( + string: &'static str, + n: u32, + balance: BalanceOf, +) -> T::AccountId { + let user = account(string, n, USER_SEED); + T::Currency::set_balance(&user, balance); + user +} + +// Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free +// balance. +fn create_pool_account( + n: u32, + balance: BalanceOf, + commission: Option, +) -> (T::AccountId, T::AccountId) { + let ed = CurrencyOf::::minimum_balance(); + let pool_creator: T::AccountId = + create_funded_user_with_balance::("pool_creator", n, ed + balance * 2u32.into()); + let pool_creator_lookup = T::Lookup::unlookup(pool_creator.clone()); + + Pools::::create( + RuntimeOrigin::Signed(pool_creator.clone()).into(), + balance, + pool_creator_lookup.clone(), + pool_creator_lookup.clone(), + pool_creator_lookup, + ) + .unwrap(); + + if let Some(c) = commission { + let pool_id = pallet_nomination_pools::LastPoolId::::get(); + Pools::::set_commission( + RuntimeOrigin::Signed(pool_creator.clone()).into(), + pool_id, + Some((c, pool_creator.clone())), + ) + .expect("pool just created, commission can be set by root; qed"); + } + + let pool_account = pallet_nomination_pools::BondedPools::::iter() + .find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator) + .map(|(pool_id, _)| Pools::::create_bonded_account(pool_id)) + .expect("pool_creator created a pool above"); + + (pool_creator, pool_account) +} + +fn vote_to_balance( + vote: u64, +) -> Result, &'static str> { + vote.try_into().map_err(|_| "could not convert u64 to Balance") +} + +#[allow(unused)] +struct ListScenario { + /// Stash/Controller that is expected to be moved. + origin1: T::AccountId, + creator1: T::AccountId, + dest_weight: BalanceOf, + origin1_member: Option, +} + +impl ListScenario { + /// An expensive scenario for bags-list implementation: + /// + /// - the node to be updated (r) is the head of a bag that has at least one other node. The bag + /// itself will need to be read and written to update its head. The node pointed to by r.next + /// will need to be read and written as it will need to have its prev pointer updated. Note + /// that there are two other worst case scenarios for bag removal: 1) the node is a tail and + /// 2) the node is a middle node with prev and next; all scenarios end up with the same number + /// of storage reads and writes. + /// + /// - the destination bag has at least one node, which will need its next pointer updated. + pub(crate) fn new( + origin_weight: BalanceOf, + is_increase: bool, + ) -> Result { + ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); + + ensure!( + pallet_nomination_pools::MaxPools::::get().unwrap_or(0) >= 3, + "must allow at least three pools for benchmarks" + ); + + // Burn the entire issuance. + CurrencyOf::::set_total_issuance(Zero::zero()); + + // Create accounts with the origin weight + let (pool_creator1, pool_origin1) = + create_pool_account::(USER_SEED + 1, origin_weight, Some(Perbill::from_percent(50))); + + T::Staking::nominate( + &pool_origin1, + // NOTE: these don't really need to be validators. + vec![account("random_validator", 0, USER_SEED)], + )?; + + let (_, pool_origin2) = + create_pool_account::(USER_SEED + 2, origin_weight, Some(Perbill::from_percent(50))); + + T::Staking::nominate( + &pool_origin2, + vec![account("random_validator", 0, USER_SEED)].clone(), + )?; + + // Find a destination weight that will trigger the worst case scenario + let dest_weight_as_vote = ::VoterList::score_update_worst_case( + &pool_origin1, + is_increase, + ); + + let dest_weight: BalanceOf = + dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; + + // Create an account with the worst case destination weight + let (_, pool_dest1) = + create_pool_account::(USER_SEED + 3, dest_weight, Some(Perbill::from_percent(50))); + + T::Staking::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?; + + let weight_of = pallet_staking::Pallet::::weight_of_fn(); + assert_eq!(vote_to_balance::(weight_of(&pool_origin1)).unwrap(), origin_weight); + assert_eq!(vote_to_balance::(weight_of(&pool_origin2)).unwrap(), origin_weight); + assert_eq!(vote_to_balance::(weight_of(&pool_dest1)).unwrap(), dest_weight); + + Ok(ListScenario { + origin1: pool_origin1, + creator1: pool_creator1, + dest_weight, + origin1_member: None, + }) + } + + fn add_joiner(mut self, amount: BalanceOf) -> Self { + let amount = MinJoinBond::::get() + .max(CurrencyOf::::minimum_balance()) + // Max `amount` with minimum thresholds for account balance and joining a pool + // to ensure 1) the user can be created and 2) can join the pool + .max(amount); + + let joiner: T::AccountId = account("joiner", USER_SEED, 0); + self.origin1_member = Some(joiner.clone()); + CurrencyOf::::set_balance(&joiner, amount * 2u32.into()); + + let original_bonded = T::Staking::active_stake(&self.origin1).unwrap(); + + // Unbond `amount` from the underlying pool account so when the member joins + // we will maintain `current_bonded`. + T::Staking::unbond(&self.origin1, amount).expect("the pool was created in `Self::new`."); + + // Account pool points for the unbonded balance. + BondedPools::::mutate(&1, |maybe_pool| { + maybe_pool.as_mut().map(|pool| pool.points -= amount) + }); + + Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), amount, 1).unwrap(); + + // check that the vote weight is still the same as the original bonded + let weight_of = pallet_staking::Pallet::::weight_of_fn(); + assert_eq!(vote_to_balance::(weight_of(&self.origin1)).unwrap(), original_bonded); + + // check the member was added correctly + let member = PoolMembers::::get(&joiner).unwrap(); + assert_eq!(member.points, amount); + assert_eq!(member.pool_id, 1); + + self + } +} + +frame_benchmarking::benchmarks! { + join { + let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); + + // setup the worst case list scenario. + let scenario = ListScenario::::new(origin_weight, true)?; + assert_eq!( + T::Staking::active_stake(&scenario.origin1).unwrap(), + origin_weight + ); + + let max_additional = scenario.dest_weight - origin_weight; + let joiner_free = CurrencyOf::::minimum_balance() + max_additional; + + let joiner: T::AccountId + = create_funded_user_with_balance::("joiner", 0, joiner_free); + + whitelist_account!(joiner); + }: _(RuntimeOrigin::Signed(joiner.clone()), max_additional, 1) + verify { + assert_eq!(CurrencyOf::::balance(&joiner), joiner_free - max_additional); + assert_eq!( + T::Staking::active_stake(&scenario.origin1).unwrap(), + scenario.dest_weight + ); + } + + bond_extra_transfer { + let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); + let scenario = ListScenario::::new(origin_weight, true)?; + let extra = scenario.dest_weight - origin_weight; + + // creator of the src pool will bond-extra, bumping itself to dest bag. + + }: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra)) + verify { + assert!( + T::Staking::active_stake(&scenario.origin1).unwrap() >= + scenario.dest_weight + ); + } + + bond_extra_other { + let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0); + + let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); + let scenario = ListScenario::::new(origin_weight, true)?; + let extra = (scenario.dest_weight - origin_weight).max(CurrencyOf::::minimum_balance()); + + // set claim preferences to `PermissionlessAll` to any account to bond extra on member's behalf. + let _ = Pools::::set_claim_permission(RuntimeOrigin::Signed(scenario.creator1.clone()).into(), ClaimPermission::PermissionlessAll); + + // transfer exactly `extra` to the depositor of the src pool (1), + let reward_account1 = Pools::::create_reward_account(1); + assert!(extra >= CurrencyOf::::minimum_balance()); + let _ = CurrencyOf::::mint_into(&reward_account1, extra); + + }: _(RuntimeOrigin::Signed(claimer), T::Lookup::unlookup(scenario.creator1.clone()), BondExtra::Rewards) + verify { + // commission of 50% deducted here. + assert!( + T::Staking::active_stake(&scenario.origin1).unwrap() >= + scenario.dest_weight / 2u32.into() + ); + } + + claim_payout { + let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0); + let commission = Perbill::from_percent(50); + let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); + let ed = CurrencyOf::::minimum_balance(); + let (depositor, pool_account) = create_pool_account::(0, origin_weight, Some(commission)); + let reward_account = Pools::::create_reward_account(1); + + // Send funds to the reward account of the pool + CurrencyOf::::set_balance(&reward_account, ed + origin_weight); + + // set claim preferences to `PermissionlessAll` so any account can claim rewards on member's + // behalf. + let _ = Pools::::set_claim_permission(RuntimeOrigin::Signed(depositor.clone()).into(), ClaimPermission::PermissionlessAll); + + // Sanity check + assert_eq!( + CurrencyOf::::balance(&depositor), + origin_weight + ); + + whitelist_account!(depositor); + }:claim_payout_other(RuntimeOrigin::Signed(claimer), depositor.clone()) + verify { + assert_eq!( + CurrencyOf::::balance(&depositor), + origin_weight + commission * origin_weight + ); + assert_eq!( + CurrencyOf::::balance(&reward_account), + ed + commission * origin_weight + ); + } + + + unbond { + // The weight the nominator will start at. The value used here is expected to be + // significantly higher than the first position in a list (e.g. the first bag threshold). + let origin_weight = Pools::::depositor_min_bond() * 200u32.into(); + let scenario = ListScenario::::new(origin_weight, false)?; + let amount = origin_weight - scenario.dest_weight; + + let scenario = scenario.add_joiner(amount); + let member_id = scenario.origin1_member.unwrap().clone(); + let member_id_lookup = T::Lookup::unlookup(member_id.clone()); + let all_points = PoolMembers::::get(&member_id).unwrap().points; + whitelist_account!(member_id); + }: _(RuntimeOrigin::Signed(member_id.clone()), member_id_lookup, all_points) + verify { + let bonded_after = T::Staking::active_stake(&scenario.origin1).unwrap(); + // We at least went down to the destination bag + assert!(bonded_after <= scenario.dest_weight); + let member = PoolMembers::::get( + &member_id + ) + .unwrap(); + assert_eq!( + member.unbonding_eras.keys().cloned().collect::>(), + vec![0 + T::Staking::bonding_duration()] + ); + assert_eq!( + member.unbonding_eras.values().cloned().collect::>(), + vec![all_points] + ); + } + + pool_withdraw_unbonded { + let s in 0 .. MAX_SPANS; + + let min_create_bond = Pools::::depositor_min_bond(); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); + + // Add a new member + let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); + let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); + Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1) + .unwrap(); + + // Sanity check join worked + assert_eq!( + T::Staking::active_stake(&pool_account).unwrap(), + min_create_bond + min_join_bond + ); + assert_eq!(CurrencyOf::::balance(&joiner), min_join_bond); + + // Unbond the new member + Pools::::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + + // Sanity check that unbond worked + assert_eq!( + T::Staking::active_stake(&pool_account).unwrap(), + min_create_bond + ); + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); + // Set the current era + pallet_staking::CurrentEra::::put(EraIndex::max_value()); + + // Add `s` count of slashing spans to storage. + pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); + whitelist_account!(pool_account); + }: _(RuntimeOrigin::Signed(pool_account.clone()), 1, s) + verify { + // The joiners funds didn't change + assert_eq!(CurrencyOf::::balance(&joiner), min_join_bond); + // The unlocking chunk was removed + assert_eq!(pallet_staking::Ledger::::get(pool_account).unwrap().unlocking.len(), 0); + } + + withdraw_unbonded_update { + let s in 0 .. MAX_SPANS; + + let min_create_bond = Pools::::depositor_min_bond(); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); + + // Add a new member + let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); + let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); + let joiner_lookup = T::Lookup::unlookup(joiner.clone()); + Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1) + .unwrap(); + + // Sanity check join worked + assert_eq!( + T::Staking::active_stake(&pool_account).unwrap(), + min_create_bond + min_join_bond + ); + assert_eq!(CurrencyOf::::balance(&joiner), min_join_bond); + + // Unbond the new member + pallet_staking::CurrentEra::::put(0); + Pools::::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + + // Sanity check that unbond worked + assert_eq!( + T::Staking::active_stake(&pool_account).unwrap(), + min_create_bond + ); + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); + + // Set the current era to ensure we can withdraw unbonded funds + pallet_staking::CurrentEra::::put(EraIndex::max_value()); + + pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); + whitelist_account!(joiner); + }: withdraw_unbonded(RuntimeOrigin::Signed(joiner.clone()), joiner_lookup, s) + verify { + assert_eq!( + CurrencyOf::::balance(&joiner), min_join_bond * 2u32.into() + ); + // The unlocking chunk was removed + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 0); + } + + withdraw_unbonded_kill { + let s in 0 .. MAX_SPANS; + + let min_create_bond = Pools::::depositor_min_bond(); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); + let depositor_lookup = T::Lookup::unlookup(depositor.clone()); + + // We set the pool to the destroying state so the depositor can leave + BondedPools::::try_mutate(&1, |maybe_bonded_pool| { + maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { + bonded_pool.state = PoolState::Destroying; + }) + }) + .unwrap(); + + // Unbond the creator + pallet_staking::CurrentEra::::put(0); + // Simulate some rewards so we can check if the rewards storage is cleaned up. We check this + // here to ensure the complete flow for destroying a pool works - the reward pool account + // should never exist by time the depositor withdraws so we test that it gets cleaned + // up when unbonding. + let reward_account = Pools::::create_reward_account(1); + assert!(frame_system::Account::::contains_key(&reward_account)); + Pools::::fully_unbond(RuntimeOrigin::Signed(depositor.clone()).into(), depositor.clone()).unwrap(); + + // Sanity check that unbond worked + assert_eq!( + T::Staking::active_stake(&pool_account).unwrap(), + Zero::zero() + ); + assert_eq!( + CurrencyOf::::balance(&pool_account), + min_create_bond + ); + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); + + // Set the current era to ensure we can withdraw unbonded funds + pallet_staking::CurrentEra::::put(EraIndex::max_value()); + + // Some last checks that storage items we expect to get cleaned up are present + assert!(pallet_staking::Ledger::::contains_key(&pool_account)); + assert!(BondedPools::::contains_key(&1)); + assert!(SubPoolsStorage::::contains_key(&1)); + assert!(RewardPools::::contains_key(&1)); + assert!(PoolMembers::::contains_key(&depositor)); + assert!(frame_system::Account::::contains_key(&reward_account)); + + whitelist_account!(depositor); + }: withdraw_unbonded(RuntimeOrigin::Signed(depositor.clone()), depositor_lookup, s) + verify { + // Pool removal worked + assert!(!pallet_staking::Ledger::::contains_key(&pool_account)); + assert!(!BondedPools::::contains_key(&1)); + assert!(!SubPoolsStorage::::contains_key(&1)); + assert!(!RewardPools::::contains_key(&1)); + assert!(!PoolMembers::::contains_key(&depositor)); + assert!(!frame_system::Account::::contains_key(&pool_account)); + assert!(!frame_system::Account::::contains_key(&reward_account)); + + // Funds where transferred back correctly + assert_eq!( + CurrencyOf::::balance(&depositor), + // gets bond back + rewards collecting when unbonding + min_create_bond * 2u32.into() + CurrencyOf::::minimum_balance() + ); + } + + create { + let min_create_bond = Pools::::depositor_min_bond(); + let depositor: T::AccountId = account("depositor", USER_SEED, 0); + let depositor_lookup = T::Lookup::unlookup(depositor.clone()); + + // Give the depositor some balance to bond + CurrencyOf::::set_balance(&depositor, min_create_bond * 2u32.into()); + + // Make sure no Pools exist at a pre-condition for our verify checks + assert_eq!(RewardPools::::count(), 0); + assert_eq!(BondedPools::::count(), 0); + + whitelist_account!(depositor); + }: _( + RuntimeOrigin::Signed(depositor.clone()), + min_create_bond, + depositor_lookup.clone(), + depositor_lookup.clone(), + depositor_lookup + ) + verify { + assert_eq!(RewardPools::::count(), 1); + assert_eq!(BondedPools::::count(), 1); + let (_, new_pool) = BondedPools::::iter().next().unwrap(); + assert_eq!( + new_pool, + BondedPoolInner { + commission: Commission::default(), + member_counter: 1, + points: min_create_bond, + roles: PoolRoles { + depositor: depositor.clone(), + root: Some(depositor.clone()), + nominator: Some(depositor.clone()), + bouncer: Some(depositor.clone()), + }, + state: PoolState::Open, + } + ); + assert_eq!( + T::Staking::active_stake(&Pools::::create_bonded_account(1)), + Ok(min_create_bond) + ); + } + + nominate { + let n in 1 .. MaxNominationsOf::::get(); + + // Create a pool + let min_create_bond = Pools::::depositor_min_bond() * 2u32.into(); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); + + // Create some accounts to nominate. For the sake of benchmarking they don't need to be + // actual validators + let validators: Vec<_> = (0..n) + .map(|i| account("stash", USER_SEED, i)) + .collect(); + + whitelist_account!(depositor); + }:_(RuntimeOrigin::Signed(depositor.clone()), 1, validators) + verify { + assert_eq!(RewardPools::::count(), 1); + assert_eq!(BondedPools::::count(), 1); + let (_, new_pool) = BondedPools::::iter().next().unwrap(); + assert_eq!( + new_pool, + BondedPoolInner { + commission: Commission::default(), + member_counter: 1, + points: min_create_bond, + roles: PoolRoles { + depositor: depositor.clone(), + root: Some(depositor.clone()), + nominator: Some(depositor.clone()), + bouncer: Some(depositor.clone()), + }, + state: PoolState::Open, + } + ); + assert_eq!( + T::Staking::active_stake(&Pools::::create_bonded_account(1)), + Ok(min_create_bond) + ); + } + + set_state { + // Create a pool + let min_create_bond = Pools::::depositor_min_bond(); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); + BondedPools::::mutate(&1, |maybe_pool| { + // Force the pool into an invalid state + maybe_pool.as_mut().map(|pool| pool.points = min_create_bond * 10u32.into()); + }); + + let caller = account("caller", 0, USER_SEED); + whitelist_account!(caller); + }:_(RuntimeOrigin::Signed(caller), 1, PoolState::Destroying) + verify { + assert_eq!(BondedPools::::get(1).unwrap().state, PoolState::Destroying); + } + + set_metadata { + let n in 1 .. ::MaxMetadataLen::get(); + + // Create a pool + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); + + // Create metadata of the max possible size + let metadata: Vec = (0..n).map(|_| 42).collect(); + + whitelist_account!(depositor); + }:_(RuntimeOrigin::Signed(depositor), 1, metadata.clone()) + verify { + assert_eq!(Metadata::::get(&1), metadata); + } + + set_configs { + }:_( + RuntimeOrigin::Root, + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(Perbill::max_value()) + ) verify { + assert_eq!(MinJoinBond::::get(), BalanceOf::::max_value()); + assert_eq!(MinCreateBond::::get(), BalanceOf::::max_value()); + assert_eq!(MaxPools::::get(), Some(u32::MAX)); + assert_eq!(MaxPoolMembers::::get(), Some(u32::MAX)); + assert_eq!(MaxPoolMembersPerPool::::get(), Some(u32::MAX)); + assert_eq!(GlobalMaxCommission::::get(), Some(Perbill::max_value())); + } + + update_roles { + let first_id = pallet_nomination_pools::LastPoolId::::get() + 1; + let (root, _) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); + let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED); + }:_( + RuntimeOrigin::Signed(root.clone()), + first_id, + ConfigOp::Set(random.clone()), + ConfigOp::Set(random.clone()), + ConfigOp::Set(random.clone()) + ) verify { + assert_eq!( + pallet_nomination_pools::BondedPools::::get(first_id).unwrap().roles, + pallet_nomination_pools::PoolRoles { + depositor: root, + nominator: Some(random.clone()), + bouncer: Some(random.clone()), + root: Some(random), + }, + ) + } + + chill { + // Create a pool + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); + + // Nominate with the pool. + let validators: Vec<_> = (0..MaxNominationsOf::::get()) + .map(|i| account("stash", USER_SEED, i)) + .collect(); + + assert_ok!(T::Staking::nominate(&pool_account, validators)); + assert!(T::Staking::nominations(&Pools::::create_bonded_account(1)).is_some()); + + whitelist_account!(depositor); + }:_(RuntimeOrigin::Signed(depositor.clone()), 1) + verify { + assert!(T::Staking::nominations(&Pools::::create_bonded_account(1)).is_none()); + } + + set_commission { + // Create a pool - do not set a commission yet. + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); + // set a max commission + Pools::::set_commission_max(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), Perbill::from_percent(50)).unwrap(); + // set a change rate + Pools::::set_commission_change_rate(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), CommissionChangeRate { + max_increase: Perbill::from_percent(20), + min_delay: 0u32.into(), + }).unwrap(); + // set a claim permission to an account. + Pools::::set_commission_claim_permission( + RuntimeOrigin::Signed(depositor.clone()).into(), + 1u32.into(), + Some(CommissionClaimPermission::Account(depositor.clone())) + ).unwrap(); + + }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some((Perbill::from_percent(20), depositor.clone()))) + verify { + assert_eq!(BondedPools::::get(1).unwrap().commission, Commission { + current: Some((Perbill::from_percent(20), depositor.clone())), + max: Some(Perbill::from_percent(50)), + change_rate: Some(CommissionChangeRate { + max_increase: Perbill::from_percent(20), + min_delay: 0u32.into() + }), + throttle_from: Some(1u32.into()), + claim_permission: Some(CommissionClaimPermission::Account(depositor)), + }); + } + + set_commission_max { + // Create a pool, setting a commission that will update when max commission is set. + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), Some(Perbill::from_percent(50))); + }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Perbill::from_percent(50)) + verify { + assert_eq!( + BondedPools::::get(1).unwrap().commission, Commission { + current: Some((Perbill::from_percent(50), depositor)), + max: Some(Perbill::from_percent(50)), + change_rate: None, + throttle_from: Some(0u32.into()), + claim_permission: None, + }); + } + + set_commission_change_rate { + // Create a pool + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); + }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), CommissionChangeRate { + max_increase: Perbill::from_percent(50), + min_delay: 1000u32.into(), + }) + verify { + assert_eq!( + BondedPools::::get(1).unwrap().commission, Commission { + current: None, + max: None, + change_rate: Some(CommissionChangeRate { + max_increase: Perbill::from_percent(50), + min_delay: 1000u32.into(), + }), + throttle_from: Some(1_u32.into()), + claim_permission: None, + }); + } + + set_commission_claim_permission { + // Create a pool. + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); + }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some(CommissionClaimPermission::Account(depositor.clone()))) + verify { + assert_eq!( + BondedPools::::get(1).unwrap().commission, Commission { + current: None, + max: None, + change_rate: None, + throttle_from: None, + claim_permission: Some(CommissionClaimPermission::Account(depositor)), + }); + } + + set_claim_permission { + // Create a pool + let min_create_bond = Pools::::depositor_min_bond(); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); + + // Join pool + let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); + let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 4u32.into()); + let joiner_lookup = T::Lookup::unlookup(joiner.clone()); + Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1) + .unwrap(); + + // Sanity check join worked + assert_eq!( + T::Staking::active_stake(&pool_account).unwrap(), + min_create_bond + min_join_bond + ); + }:_(RuntimeOrigin::Signed(joiner.clone()), ClaimPermission::Permissioned) + verify { + assert_eq!(ClaimPermissions::::get(joiner), ClaimPermission::Permissioned); + } + + claim_commission { + let claimer: T::AccountId = account("claimer_member", USER_SEED + 4, 0); + let commission = Perbill::from_percent(50); + let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); + let ed = CurrencyOf::::minimum_balance(); + let (depositor, pool_account) = create_pool_account::(0, origin_weight, Some(commission)); + let reward_account = Pools::::create_reward_account(1); + CurrencyOf::::set_balance(&reward_account, ed + origin_weight); + + // member claims a payout to make some commission available. + let _ = Pools::::claim_payout(RuntimeOrigin::Signed(claimer.clone()).into()); + // set a claim permission to an account. + let _ = Pools::::set_commission_claim_permission( + RuntimeOrigin::Signed(depositor.clone()).into(), + 1u32.into(), + Some(CommissionClaimPermission::Account(claimer)) + ); + whitelist_account!(depositor); + }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into()) + verify { + assert_eq!( + CurrencyOf::::balance(&depositor), + origin_weight + commission * origin_weight + ); + assert_eq!( + CurrencyOf::::balance(&reward_account), + ed + commission * origin_weight + ); + } + + adjust_pool_deposit { + // Create a pool + let (depositor, _) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); + + // Remove ed freeze to create a scenario where the ed deposit needs to be adjusted. + let _ = Pools::::unfreeze_pool_deposit(&Pools::::create_reward_account(1)); + assert!(&Pools::::check_ed_imbalance().is_err()); + + whitelist_account!(depositor); + }:_(RuntimeOrigin::Signed(depositor), 1) + verify { + assert!(&Pools::::check_ed_imbalance().is_ok()); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(), + crate::mock::Runtime + ); +} diff --git a/substrate/frame/nomination-pools/benchmarking/src/lib.rs b/substrate/frame/nomination-pools/benchmarking/src/lib.rs index f7df173ec04eab4c681757ec21d01654b9fff9a6..45e8f1f27e99a52b5900aa5e18c5439d9d7baa5c 100644 --- a/substrate/frame/nomination-pools/benchmarking/src/lib.rs +++ b/substrate/frame/nomination-pools/benchmarking/src/lib.rs @@ -17,836 +17,13 @@ //! Benchmarks for the nomination pools coupled with the staking and bags list pallets. -#![cfg(feature = "runtime-benchmarks")] #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(test)] -mod mock; +#[cfg(feature = "runtime-benchmarks")] +pub mod inner; -use frame_benchmarking::v1::{account, whitelist_account}; -use frame_election_provider_support::SortedListProvider; -use frame_support::{ - assert_ok, ensure, - traits::{ - fungible::{Inspect, Mutate, Unbalanced}, - Get, - }, -}; -use frame_system::RawOrigin as RuntimeOrigin; -use pallet_nomination_pools::{ - BalanceOf, BondExtra, BondedPoolInner, BondedPools, ClaimPermission, ClaimPermissions, - Commission, CommissionChangeRate, CommissionClaimPermission, ConfigOp, GlobalMaxCommission, - MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, - Pallet as Pools, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage, -}; -use pallet_staking::MaxNominationsOf; -use sp_runtime::{ - traits::{Bounded, StaticLookup, Zero}, - Perbill, -}; -use sp_staking::{EraIndex, StakingInterface}; -use sp_std::{vec, vec::Vec}; -// `frame_benchmarking::benchmarks!` macro needs this -use pallet_nomination_pools::Call; +#[cfg(feature = "runtime-benchmarks")] +pub use inner::*; -type CurrencyOf = ::Currency; - -const USER_SEED: u32 = 0; -const MAX_SPANS: u32 = 100; - -type VoterBagsListInstance = pallet_bags_list::Instance1; -pub trait Config: - pallet_nomination_pools::Config - + pallet_staking::Config - + pallet_bags_list::Config -{ -} - -pub struct Pallet(Pools); - -fn create_funded_user_with_balance( - string: &'static str, - n: u32, - balance: BalanceOf, -) -> T::AccountId { - let user = account(string, n, USER_SEED); - T::Currency::set_balance(&user, balance); - user -} - -// Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free -// balance. -fn create_pool_account( - n: u32, - balance: BalanceOf, - commission: Option, -) -> (T::AccountId, T::AccountId) { - let ed = CurrencyOf::::minimum_balance(); - let pool_creator: T::AccountId = - create_funded_user_with_balance::("pool_creator", n, ed + balance * 2u32.into()); - let pool_creator_lookup = T::Lookup::unlookup(pool_creator.clone()); - - Pools::::create( - RuntimeOrigin::Signed(pool_creator.clone()).into(), - balance, - pool_creator_lookup.clone(), - pool_creator_lookup.clone(), - pool_creator_lookup, - ) - .unwrap(); - - if let Some(c) = commission { - let pool_id = pallet_nomination_pools::LastPoolId::::get(); - Pools::::set_commission( - RuntimeOrigin::Signed(pool_creator.clone()).into(), - pool_id, - Some((c, pool_creator.clone())), - ) - .expect("pool just created, commission can be set by root; qed"); - } - - let pool_account = pallet_nomination_pools::BondedPools::::iter() - .find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator) - .map(|(pool_id, _)| Pools::::create_bonded_account(pool_id)) - .expect("pool_creator created a pool above"); - - (pool_creator, pool_account) -} - -fn vote_to_balance( - vote: u64, -) -> Result, &'static str> { - vote.try_into().map_err(|_| "could not convert u64 to Balance") -} - -#[allow(unused)] -struct ListScenario { - /// Stash/Controller that is expected to be moved. - origin1: T::AccountId, - creator1: T::AccountId, - dest_weight: BalanceOf, - origin1_member: Option, -} - -impl ListScenario { - /// An expensive scenario for bags-list implementation: - /// - /// - the node to be updated (r) is the head of a bag that has at least one other node. The bag - /// itself will need to be read and written to update its head. The node pointed to by r.next - /// will need to be read and written as it will need to have its prev pointer updated. Note - /// that there are two other worst case scenarios for bag removal: 1) the node is a tail and - /// 2) the node is a middle node with prev and next; all scenarios end up with the same number - /// of storage reads and writes. - /// - /// - the destination bag has at least one node, which will need its next pointer updated. - pub(crate) fn new( - origin_weight: BalanceOf, - is_increase: bool, - ) -> Result { - ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); - - ensure!( - pallet_nomination_pools::MaxPools::::get().unwrap_or(0) >= 3, - "must allow at least three pools for benchmarks" - ); - - // Burn the entire issuance. - CurrencyOf::::set_total_issuance(Zero::zero()); - - // Create accounts with the origin weight - let (pool_creator1, pool_origin1) = - create_pool_account::(USER_SEED + 1, origin_weight, Some(Perbill::from_percent(50))); - - T::Staking::nominate( - &pool_origin1, - // NOTE: these don't really need to be validators. - vec![account("random_validator", 0, USER_SEED)], - )?; - - let (_, pool_origin2) = - create_pool_account::(USER_SEED + 2, origin_weight, Some(Perbill::from_percent(50))); - - T::Staking::nominate( - &pool_origin2, - vec![account("random_validator", 0, USER_SEED)].clone(), - )?; - - // Find a destination weight that will trigger the worst case scenario - let dest_weight_as_vote = ::VoterList::score_update_worst_case( - &pool_origin1, - is_increase, - ); - - let dest_weight: BalanceOf = - dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; - - // Create an account with the worst case destination weight - let (_, pool_dest1) = - create_pool_account::(USER_SEED + 3, dest_weight, Some(Perbill::from_percent(50))); - - T::Staking::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?; - - let weight_of = pallet_staking::Pallet::::weight_of_fn(); - assert_eq!(vote_to_balance::(weight_of(&pool_origin1)).unwrap(), origin_weight); - assert_eq!(vote_to_balance::(weight_of(&pool_origin2)).unwrap(), origin_weight); - assert_eq!(vote_to_balance::(weight_of(&pool_dest1)).unwrap(), dest_weight); - - Ok(ListScenario { - origin1: pool_origin1, - creator1: pool_creator1, - dest_weight, - origin1_member: None, - }) - } - - fn add_joiner(mut self, amount: BalanceOf) -> Self { - let amount = MinJoinBond::::get() - .max(CurrencyOf::::minimum_balance()) - // Max `amount` with minimum thresholds for account balance and joining a pool - // to ensure 1) the user can be created and 2) can join the pool - .max(amount); - - let joiner: T::AccountId = account("joiner", USER_SEED, 0); - self.origin1_member = Some(joiner.clone()); - CurrencyOf::::set_balance(&joiner, amount * 2u32.into()); - - let original_bonded = T::Staking::active_stake(&self.origin1).unwrap(); - - // Unbond `amount` from the underlying pool account so when the member joins - // we will maintain `current_bonded`. - T::Staking::unbond(&self.origin1, amount).expect("the pool was created in `Self::new`."); - - // Account pool points for the unbonded balance. - BondedPools::::mutate(&1, |maybe_pool| { - maybe_pool.as_mut().map(|pool| pool.points -= amount) - }); - - Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), amount, 1).unwrap(); - - // check that the vote weight is still the same as the original bonded - let weight_of = pallet_staking::Pallet::::weight_of_fn(); - assert_eq!(vote_to_balance::(weight_of(&self.origin1)).unwrap(), original_bonded); - - // check the member was added correctly - let member = PoolMembers::::get(&joiner).unwrap(); - assert_eq!(member.points, amount); - assert_eq!(member.pool_id, 1); - - self - } -} - -frame_benchmarking::benchmarks! { - join { - let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); - - // setup the worst case list scenario. - let scenario = ListScenario::::new(origin_weight, true)?; - assert_eq!( - T::Staking::active_stake(&scenario.origin1).unwrap(), - origin_weight - ); - - let max_additional = scenario.dest_weight - origin_weight; - let joiner_free = CurrencyOf::::minimum_balance() + max_additional; - - let joiner: T::AccountId - = create_funded_user_with_balance::("joiner", 0, joiner_free); - - whitelist_account!(joiner); - }: _(RuntimeOrigin::Signed(joiner.clone()), max_additional, 1) - verify { - assert_eq!(CurrencyOf::::balance(&joiner), joiner_free - max_additional); - assert_eq!( - T::Staking::active_stake(&scenario.origin1).unwrap(), - scenario.dest_weight - ); - } - - bond_extra_transfer { - let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); - let scenario = ListScenario::::new(origin_weight, true)?; - let extra = scenario.dest_weight - origin_weight; - - // creator of the src pool will bond-extra, bumping itself to dest bag. - - }: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra)) - verify { - assert!( - T::Staking::active_stake(&scenario.origin1).unwrap() >= - scenario.dest_weight - ); - } - - bond_extra_other { - let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0); - - let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); - let scenario = ListScenario::::new(origin_weight, true)?; - let extra = (scenario.dest_weight - origin_weight).max(CurrencyOf::::minimum_balance()); - - // set claim preferences to `PermissionlessAll` to any account to bond extra on member's behalf. - let _ = Pools::::set_claim_permission(RuntimeOrigin::Signed(scenario.creator1.clone()).into(), ClaimPermission::PermissionlessAll); - - // transfer exactly `extra` to the depositor of the src pool (1), - let reward_account1 = Pools::::create_reward_account(1); - assert!(extra >= CurrencyOf::::minimum_balance()); - let _ = CurrencyOf::::mint_into(&reward_account1, extra); - - }: _(RuntimeOrigin::Signed(claimer), T::Lookup::unlookup(scenario.creator1.clone()), BondExtra::Rewards) - verify { - // commission of 50% deducted here. - assert!( - T::Staking::active_stake(&scenario.origin1).unwrap() >= - scenario.dest_weight / 2u32.into() - ); - } - - claim_payout { - let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0); - let commission = Perbill::from_percent(50); - let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); - let ed = CurrencyOf::::minimum_balance(); - let (depositor, pool_account) = create_pool_account::(0, origin_weight, Some(commission)); - let reward_account = Pools::::create_reward_account(1); - - // Send funds to the reward account of the pool - CurrencyOf::::set_balance(&reward_account, ed + origin_weight); - - // set claim preferences to `PermissionlessAll` so any account can claim rewards on member's - // behalf. - let _ = Pools::::set_claim_permission(RuntimeOrigin::Signed(depositor.clone()).into(), ClaimPermission::PermissionlessAll); - - // Sanity check - assert_eq!( - CurrencyOf::::balance(&depositor), - origin_weight - ); - - whitelist_account!(depositor); - }:claim_payout_other(RuntimeOrigin::Signed(claimer), depositor.clone()) - verify { - assert_eq!( - CurrencyOf::::balance(&depositor), - origin_weight + commission * origin_weight - ); - assert_eq!( - CurrencyOf::::balance(&reward_account), - ed + commission * origin_weight - ); - } - - - unbond { - // The weight the nominator will start at. The value used here is expected to be - // significantly higher than the first position in a list (e.g. the first bag threshold). - let origin_weight = Pools::::depositor_min_bond() * 200u32.into(); - let scenario = ListScenario::::new(origin_weight, false)?; - let amount = origin_weight - scenario.dest_weight; - - let scenario = scenario.add_joiner(amount); - let member_id = scenario.origin1_member.unwrap().clone(); - let member_id_lookup = T::Lookup::unlookup(member_id.clone()); - let all_points = PoolMembers::::get(&member_id).unwrap().points; - whitelist_account!(member_id); - }: _(RuntimeOrigin::Signed(member_id.clone()), member_id_lookup, all_points) - verify { - let bonded_after = T::Staking::active_stake(&scenario.origin1).unwrap(); - // We at least went down to the destination bag - assert!(bonded_after <= scenario.dest_weight); - let member = PoolMembers::::get( - &member_id - ) - .unwrap(); - assert_eq!( - member.unbonding_eras.keys().cloned().collect::>(), - vec![0 + T::Staking::bonding_duration()] - ); - assert_eq!( - member.unbonding_eras.values().cloned().collect::>(), - vec![all_points] - ); - } - - pool_withdraw_unbonded { - let s in 0 .. MAX_SPANS; - - let min_create_bond = Pools::::depositor_min_bond(); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); - - // Add a new member - let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); - let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); - Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1) - .unwrap(); - - // Sanity check join worked - assert_eq!( - T::Staking::active_stake(&pool_account).unwrap(), - min_create_bond + min_join_bond - ); - assert_eq!(CurrencyOf::::balance(&joiner), min_join_bond); - - // Unbond the new member - Pools::::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); - - // Sanity check that unbond worked - assert_eq!( - T::Staking::active_stake(&pool_account).unwrap(), - min_create_bond - ); - assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); - // Set the current era - pallet_staking::CurrentEra::::put(EraIndex::max_value()); - - // Add `s` count of slashing spans to storage. - pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); - whitelist_account!(pool_account); - }: _(RuntimeOrigin::Signed(pool_account.clone()), 1, s) - verify { - // The joiners funds didn't change - assert_eq!(CurrencyOf::::balance(&joiner), min_join_bond); - // The unlocking chunk was removed - assert_eq!(pallet_staking::Ledger::::get(pool_account).unwrap().unlocking.len(), 0); - } - - withdraw_unbonded_update { - let s in 0 .. MAX_SPANS; - - let min_create_bond = Pools::::depositor_min_bond(); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); - - // Add a new member - let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); - let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); - let joiner_lookup = T::Lookup::unlookup(joiner.clone()); - Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1) - .unwrap(); - - // Sanity check join worked - assert_eq!( - T::Staking::active_stake(&pool_account).unwrap(), - min_create_bond + min_join_bond - ); - assert_eq!(CurrencyOf::::balance(&joiner), min_join_bond); - - // Unbond the new member - pallet_staking::CurrentEra::::put(0); - Pools::::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); - - // Sanity check that unbond worked - assert_eq!( - T::Staking::active_stake(&pool_account).unwrap(), - min_create_bond - ); - assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); - - // Set the current era to ensure we can withdraw unbonded funds - pallet_staking::CurrentEra::::put(EraIndex::max_value()); - - pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); - whitelist_account!(joiner); - }: withdraw_unbonded(RuntimeOrigin::Signed(joiner.clone()), joiner_lookup, s) - verify { - assert_eq!( - CurrencyOf::::balance(&joiner), min_join_bond * 2u32.into() - ); - // The unlocking chunk was removed - assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 0); - } - - withdraw_unbonded_kill { - let s in 0 .. MAX_SPANS; - - let min_create_bond = Pools::::depositor_min_bond(); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); - let depositor_lookup = T::Lookup::unlookup(depositor.clone()); - - // We set the pool to the destroying state so the depositor can leave - BondedPools::::try_mutate(&1, |maybe_bonded_pool| { - maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { - bonded_pool.state = PoolState::Destroying; - }) - }) - .unwrap(); - - // Unbond the creator - pallet_staking::CurrentEra::::put(0); - // Simulate some rewards so we can check if the rewards storage is cleaned up. We check this - // here to ensure the complete flow for destroying a pool works - the reward pool account - // should never exist by time the depositor withdraws so we test that it gets cleaned - // up when unbonding. - let reward_account = Pools::::create_reward_account(1); - assert!(frame_system::Account::::contains_key(&reward_account)); - Pools::::fully_unbond(RuntimeOrigin::Signed(depositor.clone()).into(), depositor.clone()).unwrap(); - - // Sanity check that unbond worked - assert_eq!( - T::Staking::active_stake(&pool_account).unwrap(), - Zero::zero() - ); - assert_eq!( - CurrencyOf::::balance(&pool_account), - min_create_bond - ); - assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); - - // Set the current era to ensure we can withdraw unbonded funds - pallet_staking::CurrentEra::::put(EraIndex::max_value()); - - // Some last checks that storage items we expect to get cleaned up are present - assert!(pallet_staking::Ledger::::contains_key(&pool_account)); - assert!(BondedPools::::contains_key(&1)); - assert!(SubPoolsStorage::::contains_key(&1)); - assert!(RewardPools::::contains_key(&1)); - assert!(PoolMembers::::contains_key(&depositor)); - assert!(frame_system::Account::::contains_key(&reward_account)); - - whitelist_account!(depositor); - }: withdraw_unbonded(RuntimeOrigin::Signed(depositor.clone()), depositor_lookup, s) - verify { - // Pool removal worked - assert!(!pallet_staking::Ledger::::contains_key(&pool_account)); - assert!(!BondedPools::::contains_key(&1)); - assert!(!SubPoolsStorage::::contains_key(&1)); - assert!(!RewardPools::::contains_key(&1)); - assert!(!PoolMembers::::contains_key(&depositor)); - assert!(!frame_system::Account::::contains_key(&pool_account)); - assert!(!frame_system::Account::::contains_key(&reward_account)); - - // Funds where transferred back correctly - assert_eq!( - CurrencyOf::::balance(&depositor), - // gets bond back + rewards collecting when unbonding - min_create_bond * 2u32.into() + CurrencyOf::::minimum_balance() - ); - } - - create { - let min_create_bond = Pools::::depositor_min_bond(); - let depositor: T::AccountId = account("depositor", USER_SEED, 0); - let depositor_lookup = T::Lookup::unlookup(depositor.clone()); - - // Give the depositor some balance to bond - CurrencyOf::::set_balance(&depositor, min_create_bond * 2u32.into()); - - // Make sure no Pools exist at a pre-condition for our verify checks - assert_eq!(RewardPools::::count(), 0); - assert_eq!(BondedPools::::count(), 0); - - whitelist_account!(depositor); - }: _( - RuntimeOrigin::Signed(depositor.clone()), - min_create_bond, - depositor_lookup.clone(), - depositor_lookup.clone(), - depositor_lookup - ) - verify { - assert_eq!(RewardPools::::count(), 1); - assert_eq!(BondedPools::::count(), 1); - let (_, new_pool) = BondedPools::::iter().next().unwrap(); - assert_eq!( - new_pool, - BondedPoolInner { - commission: Commission::default(), - member_counter: 1, - points: min_create_bond, - roles: PoolRoles { - depositor: depositor.clone(), - root: Some(depositor.clone()), - nominator: Some(depositor.clone()), - bouncer: Some(depositor.clone()), - }, - state: PoolState::Open, - } - ); - assert_eq!( - T::Staking::active_stake(&Pools::::create_bonded_account(1)), - Ok(min_create_bond) - ); - } - - nominate { - let n in 1 .. MaxNominationsOf::::get(); - - // Create a pool - let min_create_bond = Pools::::depositor_min_bond() * 2u32.into(); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); - - // Create some accounts to nominate. For the sake of benchmarking they don't need to be - // actual validators - let validators: Vec<_> = (0..n) - .map(|i| account("stash", USER_SEED, i)) - .collect(); - - whitelist_account!(depositor); - }:_(RuntimeOrigin::Signed(depositor.clone()), 1, validators) - verify { - assert_eq!(RewardPools::::count(), 1); - assert_eq!(BondedPools::::count(), 1); - let (_, new_pool) = BondedPools::::iter().next().unwrap(); - assert_eq!( - new_pool, - BondedPoolInner { - commission: Commission::default(), - member_counter: 1, - points: min_create_bond, - roles: PoolRoles { - depositor: depositor.clone(), - root: Some(depositor.clone()), - nominator: Some(depositor.clone()), - bouncer: Some(depositor.clone()), - }, - state: PoolState::Open, - } - ); - assert_eq!( - T::Staking::active_stake(&Pools::::create_bonded_account(1)), - Ok(min_create_bond) - ); - } - - set_state { - // Create a pool - let min_create_bond = Pools::::depositor_min_bond(); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); - BondedPools::::mutate(&1, |maybe_pool| { - // Force the pool into an invalid state - maybe_pool.as_mut().map(|pool| pool.points = min_create_bond * 10u32.into()); - }); - - let caller = account("caller", 0, USER_SEED); - whitelist_account!(caller); - }:_(RuntimeOrigin::Signed(caller), 1, PoolState::Destroying) - verify { - assert_eq!(BondedPools::::get(1).unwrap().state, PoolState::Destroying); - } - - set_metadata { - let n in 1 .. ::MaxMetadataLen::get(); - - // Create a pool - let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); - - // Create metadata of the max possible size - let metadata: Vec = (0..n).map(|_| 42).collect(); - - whitelist_account!(depositor); - }:_(RuntimeOrigin::Signed(depositor), 1, metadata.clone()) - verify { - assert_eq!(Metadata::::get(&1), metadata); - } - - set_configs { - }:_( - RuntimeOrigin::Root, - ConfigOp::Set(BalanceOf::::max_value()), - ConfigOp::Set(BalanceOf::::max_value()), - ConfigOp::Set(u32::MAX), - ConfigOp::Set(u32::MAX), - ConfigOp::Set(u32::MAX), - ConfigOp::Set(Perbill::max_value()) - ) verify { - assert_eq!(MinJoinBond::::get(), BalanceOf::::max_value()); - assert_eq!(MinCreateBond::::get(), BalanceOf::::max_value()); - assert_eq!(MaxPools::::get(), Some(u32::MAX)); - assert_eq!(MaxPoolMembers::::get(), Some(u32::MAX)); - assert_eq!(MaxPoolMembersPerPool::::get(), Some(u32::MAX)); - assert_eq!(GlobalMaxCommission::::get(), Some(Perbill::max_value())); - } - - update_roles { - let first_id = pallet_nomination_pools::LastPoolId::::get() + 1; - let (root, _) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); - let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED); - }:_( - RuntimeOrigin::Signed(root.clone()), - first_id, - ConfigOp::Set(random.clone()), - ConfigOp::Set(random.clone()), - ConfigOp::Set(random.clone()) - ) verify { - assert_eq!( - pallet_nomination_pools::BondedPools::::get(first_id).unwrap().roles, - pallet_nomination_pools::PoolRoles { - depositor: root, - nominator: Some(random.clone()), - bouncer: Some(random.clone()), - root: Some(random), - }, - ) - } - - chill { - // Create a pool - let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); - - // Nominate with the pool. - let validators: Vec<_> = (0..MaxNominationsOf::::get()) - .map(|i| account("stash", USER_SEED, i)) - .collect(); - - assert_ok!(T::Staking::nominate(&pool_account, validators)); - assert!(T::Staking::nominations(&Pools::::create_bonded_account(1)).is_some()); - - whitelist_account!(depositor); - }:_(RuntimeOrigin::Signed(depositor.clone()), 1) - verify { - assert!(T::Staking::nominations(&Pools::::create_bonded_account(1)).is_none()); - } - - set_commission { - // Create a pool - do not set a commission yet. - let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); - // set a max commission - Pools::::set_commission_max(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), Perbill::from_percent(50)).unwrap(); - // set a change rate - Pools::::set_commission_change_rate(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), CommissionChangeRate { - max_increase: Perbill::from_percent(20), - min_delay: 0u32.into(), - }).unwrap(); - // set a claim permission to an account. - Pools::::set_commission_claim_permission( - RuntimeOrigin::Signed(depositor.clone()).into(), - 1u32.into(), - Some(CommissionClaimPermission::Account(depositor.clone())) - ).unwrap(); - - }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some((Perbill::from_percent(20), depositor.clone()))) - verify { - assert_eq!(BondedPools::::get(1).unwrap().commission, Commission { - current: Some((Perbill::from_percent(20), depositor.clone())), - max: Some(Perbill::from_percent(50)), - change_rate: Some(CommissionChangeRate { - max_increase: Perbill::from_percent(20), - min_delay: 0u32.into() - }), - throttle_from: Some(1u32.into()), - claim_permission: Some(CommissionClaimPermission::Account(depositor)), - }); - } - - set_commission_max { - // Create a pool, setting a commission that will update when max commission is set. - let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), Some(Perbill::from_percent(50))); - }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Perbill::from_percent(50)) - verify { - assert_eq!( - BondedPools::::get(1).unwrap().commission, Commission { - current: Some((Perbill::from_percent(50), depositor)), - max: Some(Perbill::from_percent(50)), - change_rate: None, - throttle_from: Some(0u32.into()), - claim_permission: None, - }); - } - - set_commission_change_rate { - // Create a pool - let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); - }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), CommissionChangeRate { - max_increase: Perbill::from_percent(50), - min_delay: 1000u32.into(), - }) - verify { - assert_eq!( - BondedPools::::get(1).unwrap().commission, Commission { - current: None, - max: None, - change_rate: Some(CommissionChangeRate { - max_increase: Perbill::from_percent(50), - min_delay: 1000u32.into(), - }), - throttle_from: Some(1_u32.into()), - claim_permission: None, - }); - } - - set_commission_claim_permission { - // Create a pool. - let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); - }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some(CommissionClaimPermission::Account(depositor.clone()))) - verify { - assert_eq!( - BondedPools::::get(1).unwrap().commission, Commission { - current: None, - max: None, - change_rate: None, - throttle_from: None, - claim_permission: Some(CommissionClaimPermission::Account(depositor)), - }); - } - - set_claim_permission { - // Create a pool - let min_create_bond = Pools::::depositor_min_bond(); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); - - // Join pool - let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); - let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 4u32.into()); - let joiner_lookup = T::Lookup::unlookup(joiner.clone()); - Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1) - .unwrap(); - - // Sanity check join worked - assert_eq!( - T::Staking::active_stake(&pool_account).unwrap(), - min_create_bond + min_join_bond - ); - }:_(RuntimeOrigin::Signed(joiner.clone()), ClaimPermission::Permissioned) - verify { - assert_eq!(ClaimPermissions::::get(joiner), ClaimPermission::Permissioned); - } - - claim_commission { - let claimer: T::AccountId = account("claimer_member", USER_SEED + 4, 0); - let commission = Perbill::from_percent(50); - let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); - let ed = CurrencyOf::::minimum_balance(); - let (depositor, pool_account) = create_pool_account::(0, origin_weight, Some(commission)); - let reward_account = Pools::::create_reward_account(1); - CurrencyOf::::set_balance(&reward_account, ed + origin_weight); - - // member claims a payout to make some commission available. - let _ = Pools::::claim_payout(RuntimeOrigin::Signed(claimer.clone()).into()); - // set a claim permission to an account. - let _ = Pools::::set_commission_claim_permission( - RuntimeOrigin::Signed(depositor.clone()).into(), - 1u32.into(), - Some(CommissionClaimPermission::Account(claimer)) - ); - whitelist_account!(depositor); - }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into()) - verify { - assert_eq!( - CurrencyOf::::balance(&depositor), - origin_weight + commission * origin_weight - ); - assert_eq!( - CurrencyOf::::balance(&reward_account), - ed + commission * origin_weight - ); - } - - adjust_pool_deposit { - // Create a pool - let (depositor, _) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); - - // Remove ed freeze to create a scenario where the ed deposit needs to be adjusted. - let _ = Pools::::unfreeze_pool_deposit(&Pools::::create_reward_account(1)); - assert!(&Pools::::check_ed_imbalance().is_err()); - - whitelist_account!(depositor); - }:_(RuntimeOrigin::Signed(depositor), 1) - verify { - assert!(&Pools::::check_ed_imbalance().is_ok()); - } - - impl_benchmark_test_suite!( - Pallet, - crate::mock::new_test_ext(), - crate::mock::Runtime - ); -} +#[cfg(all(feature = "runtime-benchmarks", test))] +pub(crate) mod mock; diff --git a/substrate/frame/nomination-pools/benchmarking/src/mock.rs b/substrate/frame/nomination-pools/benchmarking/src/mock.rs index a59f8f3f40e7f6e67ca3163edb56b43257fdc6ee..2752d53a6b9f3364c234d9144a6c40b10b449a5b 100644 --- a/substrate/frame/nomination-pools/benchmarking/src/mock.rs +++ b/substrate/frame/nomination-pools/benchmarking/src/mock.rs @@ -111,7 +111,6 @@ impl pallet_staking::Config for Runtime { type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = (); type MaxExposurePageSize = ConstU32<64>; - type OffendingValidatorsThreshold = (); type ElectionProvider = frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; type GenesisElectionProvider = Self::ElectionProvider; @@ -124,6 +123,7 @@ impl pallet_staking::Config for Runtime { type EventListeners = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } parameter_types! { diff --git a/substrate/frame/nomination-pools/src/mock.rs b/substrate/frame/nomination-pools/src/mock.rs index b9301a400953e0c4f9c7589b3f74241d094c597f..686402b843492e8f92bb26231a7ed3becf0c0e03 100644 --- a/substrate/frame/nomination-pools/src/mock.rs +++ b/substrate/frame/nomination-pools/src/mock.rs @@ -131,6 +131,10 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } + fn update_payee(_stash: &Self::AccountId, _reward_acc: &Self::AccountId) -> DispatchResult { + unimplemented!("method currently not used in testing") + } + fn chill(_: &Self::AccountId) -> sp_runtime::DispatchResult { Ok(()) } @@ -223,6 +227,10 @@ impl sp_staking::StakingInterface for StakingMock { fn max_exposure_page_size() -> sp_staking::Page { unimplemented!("method currently not used in testing") } + + fn slash_reward_fraction() -> Perbill { + unimplemented!("method currently not used in testing") + } } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] diff --git a/substrate/frame/nomination-pools/test-staking/src/mock.rs b/substrate/frame/nomination-pools/test-staking/src/mock.rs index 2ec47e0d164558defb0e70d260def626406680e7..93a05ddfae990108c7277c2448ff1470ae11d2ae 100644 --- a/substrate/frame/nomination-pools/test-staking/src/mock.rs +++ b/substrate/frame/nomination-pools/test-staking/src/mock.rs @@ -125,7 +125,6 @@ impl pallet_staking::Config for Runtime { type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = (); type MaxExposurePageSize = ConstU32<64>; - type OffendingValidatorsThreshold = (); type ElectionProvider = frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; type GenesisElectionProvider = Self::ElectionProvider; @@ -138,6 +137,7 @@ impl pallet_staking::Config for Runtime { type EventListeners = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } parameter_types! { diff --git a/substrate/frame/offences/benchmarking/src/inner.rs b/substrate/frame/offences/benchmarking/src/inner.rs new file mode 100644 index 0000000000000000000000000000000000000000..9aa88f7a0d6d08e41d25c6b9e425fc71ba0f78d2 --- /dev/null +++ b/substrate/frame/offences/benchmarking/src/inner.rs @@ -0,0 +1,250 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Offences pallet benchmarking. + +use sp_std::{prelude::*, vec}; + +use frame_benchmarking::v1::{account, benchmarks}; +use frame_support::traits::{Currency, Get}; +use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin}; + +use sp_runtime::{ + traits::{Convert, Saturating, StaticLookup}, + Perbill, +}; +use sp_staking::offence::ReportOffence; + +use pallet_babe::EquivocationOffence as BabeEquivocationOffence; +use pallet_balances::Config as BalancesConfig; +use pallet_grandpa::{ + EquivocationOffence as GrandpaEquivocationOffence, TimeSlot as GrandpaTimeSlot, +}; +use pallet_offences::{Config as OffencesConfig, Pallet as Offences}; +use pallet_session::{ + historical::{Config as HistoricalConfig, IdentificationTuple}, + Config as SessionConfig, Pallet as Session, SessionManager, +}; +use pallet_staking::{ + Config as StakingConfig, Exposure, IndividualExposure, MaxNominationsOf, Pallet as Staking, + RewardDestination, ValidatorPrefs, +}; + +const SEED: u32 = 0; + +const MAX_NOMINATORS: u32 = 100; + +pub struct Pallet(Offences); + +pub trait Config: + SessionConfig + + StakingConfig + + OffencesConfig + + HistoricalConfig + + BalancesConfig + + IdTupleConvert +{ +} + +/// A helper trait to make sure we can convert `IdentificationTuple` coming from historical +/// and the one required by offences. +pub trait IdTupleConvert { + /// Convert identification tuple from `historical` trait to the one expected by `offences`. + fn convert(id: IdentificationTuple) -> ::IdentificationTuple; +} + +impl IdTupleConvert for T +where + ::IdentificationTuple: From>, +{ + fn convert(id: IdentificationTuple) -> ::IdentificationTuple { + id.into() + } +} + +type LookupSourceOf = <::Lookup as StaticLookup>::Source; +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +struct Offender { + pub controller: T::AccountId, + #[allow(dead_code)] + pub stash: T::AccountId, + #[allow(dead_code)] + pub nominator_stashes: Vec, +} + +fn bond_amount() -> BalanceOf { + T::Currency::minimum_balance().saturating_mul(10_000u32.into()) +} + +fn create_offender(n: u32, nominators: u32) -> Result, &'static str> { + let stash: T::AccountId = account("stash", n, SEED); + let stash_lookup: LookupSourceOf = T::Lookup::unlookup(stash.clone()); + let reward_destination = RewardDestination::Staked; + let amount = bond_amount::(); + // add twice as much balance to prevent the account from being killed. + let free_amount = amount.saturating_mul(2u32.into()); + T::Currency::make_free_balance_be(&stash, free_amount); + Staking::::bond( + RawOrigin::Signed(stash.clone()).into(), + amount, + reward_destination.clone(), + )?; + + let validator_prefs = + ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; + Staking::::validate(RawOrigin::Signed(stash.clone()).into(), validator_prefs)?; + + let mut individual_exposures = vec![]; + let mut nominator_stashes = vec![]; + // Create n nominators + for i in 0..nominators { + let nominator_stash: T::AccountId = + account("nominator stash", n * MAX_NOMINATORS + i, SEED); + T::Currency::make_free_balance_be(&nominator_stash, free_amount); + + Staking::::bond( + RawOrigin::Signed(nominator_stash.clone()).into(), + amount, + reward_destination.clone(), + )?; + + let selected_validators: Vec> = vec![stash_lookup.clone()]; + Staking::::nominate( + RawOrigin::Signed(nominator_stash.clone()).into(), + selected_validators, + )?; + + individual_exposures + .push(IndividualExposure { who: nominator_stash.clone(), value: amount }); + nominator_stashes.push(nominator_stash.clone()); + } + + let exposure = Exposure { total: amount * n.into(), own: amount, others: individual_exposures }; + let current_era = 0u32; + Staking::::add_era_stakers(current_era, stash.clone(), exposure); + + Ok(Offender { controller: stash.clone(), stash, nominator_stashes }) +} + +fn make_offenders( + num_offenders: u32, + num_nominators: u32, +) -> Result<(Vec>, Vec>), &'static str> { + Staking::::new_session(0); + + let mut offenders = vec![]; + for i in 0..num_offenders { + let offender = create_offender::(i + 1, num_nominators)?; + offenders.push(offender); + } + + Staking::::start_session(0); + + let id_tuples = offenders + .iter() + .map(|offender| { + ::ValidatorIdOf::convert(offender.controller.clone()) + .expect("failed to get validator id from account id") + }) + .map(|validator_id| { + ::FullIdentificationOf::convert(validator_id.clone()) + .map(|full_id| (validator_id, full_id)) + .expect("failed to convert validator id to full identification") + }) + .collect::>>(); + Ok((id_tuples, offenders)) +} + +benchmarks! { + report_offence_grandpa { + let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::::get()); + + // for grandpa equivocation reports the number of reporters + // and offenders is always 1 + let reporters = vec![account("reporter", 1, SEED)]; + + // make sure reporters actually get rewarded + Staking::::set_slash_reward_fraction(Perbill::one()); + + let (mut offenders, raw_offenders) = make_offenders::(1, n)?; + let validator_set_count = Session::::validators().len() as u32; + + let offence = GrandpaEquivocationOffence { + time_slot: GrandpaTimeSlot { set_id: 0, round: 0 }, + session_index: 0, + validator_set_count, + offender: T::convert(offenders.pop().unwrap()), + }; + assert_eq!(System::::event_count(), 0); + }: { + let _ = Offences::::report_offence(reporters, offence); + } + verify { + // make sure that all slashes have been applied + #[cfg(test)] + assert_eq!( + System::::event_count(), 0 + + 1 // offence + + 3 // reporter (reward + endowment) + + 1 // offenders reported + + 3 // offenders slashed + + 1 // offenders chilled + + 3 * n // nominators slashed + ); + } + + report_offence_babe { + let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::::get()); + + // for babe equivocation reports the number of reporters + // and offenders is always 1 + let reporters = vec![account("reporter", 1, SEED)]; + + // make sure reporters actually get rewarded + Staking::::set_slash_reward_fraction(Perbill::one()); + + let (mut offenders, raw_offenders) = make_offenders::(1, n)?; + let validator_set_count = Session::::validators().len() as u32; + + let offence = BabeEquivocationOffence { + slot: 0u64.into(), + session_index: 0, + validator_set_count, + offender: T::convert(offenders.pop().unwrap()), + }; + assert_eq!(System::::event_count(), 0); + }: { + let _ = Offences::::report_offence(reporters, offence); + } + verify { + // make sure that all slashes have been applied + #[cfg(test)] + assert_eq!( + System::::event_count(), 0 + + 1 // offence + + 3 // reporter (reward + endowment) + + 1 // offenders reported + + 3 // offenders slashed + + 1 // offenders chilled + + 3 * n // nominators slashed + ); + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/substrate/frame/offences/benchmarking/src/lib.rs b/substrate/frame/offences/benchmarking/src/lib.rs index 563aa4755cec08ece465236208495baf872e325d..b08955a133297f356a5fa884394b3a88a7e28727 100644 --- a/substrate/frame/offences/benchmarking/src/lib.rs +++ b/substrate/frame/offences/benchmarking/src/lib.rs @@ -17,239 +17,13 @@ //! Offences pallet benchmarking. -#![cfg(feature = "runtime-benchmarks")] #![cfg_attr(not(feature = "std"), no_std)] -mod mock; +#[cfg(feature = "runtime-benchmarks")] +pub mod inner; -use sp_std::{prelude::*, vec}; +#[cfg(feature = "runtime-benchmarks")] +pub use inner::*; -use frame_benchmarking::v1::{account, benchmarks}; -use frame_support::traits::{Currency, Get}; -use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin}; - -use sp_runtime::{ - traits::{Convert, Saturating, StaticLookup}, - Perbill, -}; -use sp_staking::offence::ReportOffence; - -use pallet_babe::EquivocationOffence as BabeEquivocationOffence; -use pallet_balances::Config as BalancesConfig; -use pallet_grandpa::{ - EquivocationOffence as GrandpaEquivocationOffence, TimeSlot as GrandpaTimeSlot, -}; -use pallet_offences::{Config as OffencesConfig, Pallet as Offences}; -use pallet_session::{ - historical::{Config as HistoricalConfig, IdentificationTuple}, - Config as SessionConfig, Pallet as Session, SessionManager, -}; -use pallet_staking::{ - Config as StakingConfig, Exposure, IndividualExposure, MaxNominationsOf, Pallet as Staking, - RewardDestination, ValidatorPrefs, -}; - -const SEED: u32 = 0; - -const MAX_NOMINATORS: u32 = 100; - -pub struct Pallet(Offences); - -pub trait Config: - SessionConfig - + StakingConfig - + OffencesConfig - + HistoricalConfig - + BalancesConfig - + IdTupleConvert -{ -} - -/// A helper trait to make sure we can convert `IdentificationTuple` coming from historical -/// and the one required by offences. -pub trait IdTupleConvert { - /// Convert identification tuple from `historical` trait to the one expected by `offences`. - fn convert(id: IdentificationTuple) -> ::IdentificationTuple; -} - -impl IdTupleConvert for T -where - ::IdentificationTuple: From>, -{ - fn convert(id: IdentificationTuple) -> ::IdentificationTuple { - id.into() - } -} - -type LookupSourceOf = <::Lookup as StaticLookup>::Source; -type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; - -struct Offender { - pub controller: T::AccountId, - #[allow(dead_code)] - pub stash: T::AccountId, - #[allow(dead_code)] - pub nominator_stashes: Vec, -} - -fn bond_amount() -> BalanceOf { - T::Currency::minimum_balance().saturating_mul(10_000u32.into()) -} - -fn create_offender(n: u32, nominators: u32) -> Result, &'static str> { - let stash: T::AccountId = account("stash", n, SEED); - let stash_lookup: LookupSourceOf = T::Lookup::unlookup(stash.clone()); - let reward_destination = RewardDestination::Staked; - let amount = bond_amount::(); - // add twice as much balance to prevent the account from being killed. - let free_amount = amount.saturating_mul(2u32.into()); - T::Currency::make_free_balance_be(&stash, free_amount); - Staking::::bond( - RawOrigin::Signed(stash.clone()).into(), - amount, - reward_destination.clone(), - )?; - - let validator_prefs = - ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; - Staking::::validate(RawOrigin::Signed(stash.clone()).into(), validator_prefs)?; - - let mut individual_exposures = vec![]; - let mut nominator_stashes = vec![]; - // Create n nominators - for i in 0..nominators { - let nominator_stash: T::AccountId = - account("nominator stash", n * MAX_NOMINATORS + i, SEED); - T::Currency::make_free_balance_be(&nominator_stash, free_amount); - - Staking::::bond( - RawOrigin::Signed(nominator_stash.clone()).into(), - amount, - reward_destination.clone(), - )?; - - let selected_validators: Vec> = vec![stash_lookup.clone()]; - Staking::::nominate( - RawOrigin::Signed(nominator_stash.clone()).into(), - selected_validators, - )?; - - individual_exposures - .push(IndividualExposure { who: nominator_stash.clone(), value: amount }); - nominator_stashes.push(nominator_stash.clone()); - } - - let exposure = Exposure { total: amount * n.into(), own: amount, others: individual_exposures }; - let current_era = 0u32; - Staking::::add_era_stakers(current_era, stash.clone(), exposure); - - Ok(Offender { controller: stash.clone(), stash, nominator_stashes }) -} - -fn make_offenders( - num_offenders: u32, - num_nominators: u32, -) -> Result<(Vec>, Vec>), &'static str> { - Staking::::new_session(0); - - let mut offenders = vec![]; - for i in 0..num_offenders { - let offender = create_offender::(i + 1, num_nominators)?; - offenders.push(offender); - } - - Staking::::start_session(0); - - let id_tuples = offenders - .iter() - .map(|offender| { - ::ValidatorIdOf::convert(offender.controller.clone()) - .expect("failed to get validator id from account id") - }) - .map(|validator_id| { - ::FullIdentificationOf::convert(validator_id.clone()) - .map(|full_id| (validator_id, full_id)) - .expect("failed to convert validator id to full identification") - }) - .collect::>>(); - Ok((id_tuples, offenders)) -} - -benchmarks! { - report_offence_grandpa { - let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::::get()); - - // for grandpa equivocation reports the number of reporters - // and offenders is always 1 - let reporters = vec![account("reporter", 1, SEED)]; - - // make sure reporters actually get rewarded - Staking::::set_slash_reward_fraction(Perbill::one()); - - let (mut offenders, raw_offenders) = make_offenders::(1, n)?; - let validator_set_count = Session::::validators().len() as u32; - - let offence = GrandpaEquivocationOffence { - time_slot: GrandpaTimeSlot { set_id: 0, round: 0 }, - session_index: 0, - validator_set_count, - offender: T::convert(offenders.pop().unwrap()), - }; - assert_eq!(System::::event_count(), 0); - }: { - let _ = Offences::::report_offence(reporters, offence); - } - verify { - // make sure that all slashes have been applied - #[cfg(test)] - assert_eq!( - System::::event_count(), 0 - + 1 // offence - + 3 // reporter (reward + endowment) - + 1 // offenders reported - + 3 // offenders slashed - + 1 // offenders chilled - + 3 * n // nominators slashed - ); - } - - report_offence_babe { - let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::::get()); - - // for babe equivocation reports the number of reporters - // and offenders is always 1 - let reporters = vec![account("reporter", 1, SEED)]; - - // make sure reporters actually get rewarded - Staking::::set_slash_reward_fraction(Perbill::one()); - - let (mut offenders, raw_offenders) = make_offenders::(1, n)?; - let validator_set_count = Session::::validators().len() as u32; - - let offence = BabeEquivocationOffence { - slot: 0u64.into(), - session_index: 0, - validator_set_count, - offender: T::convert(offenders.pop().unwrap()), - }; - assert_eq!(System::::event_count(), 0); - }: { - let _ = Offences::::report_offence(reporters, offence); - } - verify { - // make sure that all slashes have been applied - #[cfg(test)] - assert_eq!( - System::::event_count(), 0 - + 1 // offence - + 3 // reporter (reward + endowment) - + 1 // offenders reported - + 3 // offenders slashed - + 1 // offenders chilled - + 3 * n // nominators slashed - ); - } - - impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); -} +#[cfg(all(feature = "runtime-benchmarks", test))] +pub(crate) mod mock; diff --git a/substrate/frame/offences/benchmarking/src/mock.rs b/substrate/frame/offences/benchmarking/src/mock.rs index ea2e9e93ed68aabf1b30de0a1cbe5a235acdbcad..eeaa1364504abbb3aae3c2953d99f21952198b72 100644 --- a/substrate/frame/offences/benchmarking/src/mock.rs +++ b/substrate/frame/offences/benchmarking/src/mock.rs @@ -17,9 +17,6 @@ //! Mock file for offences benchmarking. -#![cfg(test)] - -use super::*; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, onchain, SequentialPhragmen, @@ -33,7 +30,7 @@ use pallet_session::historical as pallet_session_historical; use sp_runtime::{ testing::{Header, UintAuthorityId}, traits::IdentityLookup, - BuildStorage, + BuildStorage, Perbill, }; type AccountId = u64; @@ -177,7 +174,6 @@ impl pallet_staking::Config for Test { type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; type MaxExposurePageSize = ConstU32<64>; - type OffendingValidatorsThreshold = (); type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; @@ -189,6 +185,7 @@ impl pallet_staking::Config for Test { type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } impl pallet_im_online::Config for Test { diff --git a/substrate/frame/offences/src/lib.rs b/substrate/frame/offences/src/lib.rs index 1c7ffeca7198325374f473cbae9e2f116697530c..a328b2fee4e2e72d3254f0a0a8283951bf5a5fc2 100644 --- a/substrate/frame/offences/src/lib.rs +++ b/substrate/frame/offences/src/lib.rs @@ -132,7 +132,6 @@ where &concurrent_offenders, &slash_perbill, offence.session_index(), - offence.disable_strategy(), ); // Deposit the event. diff --git a/substrate/frame/offences/src/migration.rs b/substrate/frame/offences/src/migration.rs index 3b5cf3ce926952df21248ea5b35271d6596a554b..199f47491369b8281e414076f35e43be823931c8 100644 --- a/substrate/frame/offences/src/migration.rs +++ b/substrate/frame/offences/src/migration.rs @@ -23,7 +23,7 @@ use frame_support::{ weights::Weight, Twox64Concat, }; -use sp_staking::offence::{DisableStrategy, OnOffenceHandler}; +use sp_staking::offence::OnOffenceHandler; use sp_std::vec::Vec; #[cfg(feature = "try-runtime")] @@ -106,12 +106,7 @@ pub fn remove_deferred_storage() -> Weight { let deferred = >::take(); log::info!(target: LOG_TARGET, "have {} deferred offences, applying.", deferred.len()); for (offences, perbill, session) in deferred.iter() { - let consumed = T::OnOffenceHandler::on_offence( - offences, - perbill, - *session, - DisableStrategy::WhenSlashed, - ); + let consumed = T::OnOffenceHandler::on_offence(offences, perbill, *session); weight = weight.saturating_add(consumed); } diff --git a/substrate/frame/offences/src/mock.rs b/substrate/frame/offences/src/mock.rs index 31d5f805f3e4894407cd245db8bd7856d73c57cc..9a3120e41eaa0ae04ef4d211868c53782e3a4cbf 100644 --- a/substrate/frame/offences/src/mock.rs +++ b/substrate/frame/offences/src/mock.rs @@ -33,7 +33,7 @@ use sp_runtime::{ BuildStorage, Perbill, }; use sp_staking::{ - offence::{self, DisableStrategy, Kind, OffenceDetails}, + offence::{self, Kind, OffenceDetails}, SessionIndex, }; @@ -51,7 +51,6 @@ impl offence::OnOffenceHandler _offenders: &[OffenceDetails], slash_fraction: &[Perbill], _offence_session: SessionIndex, - _disable_strategy: DisableStrategy, ) -> Weight { OnOffencePerbill::mutate(|f| { *f = slash_fraction.to_vec(); diff --git a/substrate/frame/parameters/src/tests/mock.rs b/substrate/frame/parameters/src/tests/mock.rs index 4c7dda639a9ae762c94b355174e7e4ca59869afe..6cfd7c8f30b8119ff1e1f0a63165c98afba1577b 100644 --- a/substrate/frame/parameters/src/tests/mock.rs +++ b/substrate/frame/parameters/src/tests/mock.rs @@ -16,6 +16,7 @@ // limitations under the License. #![cfg(any(test, feature = "runtime-benchmarks"))] +#![allow(non_snake_case)] //! Mock runtime that configures the `pallet_example_basic` to use dynamic params for testing. @@ -66,6 +67,20 @@ pub mod dynamic_params { #[codec(index = 0)] pub static Key3: u128 = 4; } + + #[dynamic_pallet_params] + #[codec(index = 2)] + pub mod nis { + #[codec(index = 0)] + pub static Target: u64 = 0; + } + + #[dynamic_pallet_params] + #[codec(index = 3)] + pub mod somE_weird_SPElLInG_s { + #[codec(index = 0)] + pub static V: u64 = 0; + } } #[docify::export(benchmarking_default)] @@ -98,6 +113,8 @@ mod custom_origin { } match key { + RuntimeParametersKey::SomEWeirdSPElLInGS(_) | + RuntimeParametersKey::Nis(_) | RuntimeParametersKey::Pallet1(_) => ensure_root(origin.clone()), RuntimeParametersKey::Pallet2(_) => ensure_signed(origin.clone()).map(|_| ()), } diff --git a/substrate/frame/root-offences/Cargo.toml b/substrate/frame/root-offences/Cargo.toml index ad3dcf1f90eaf48d95cac601d397c2be673d9824..f4d83c237b9cb55a81a2ab027934dd9021981d6f 100644 --- a/substrate/frame/root-offences/Cargo.toml +++ b/substrate/frame/root-offences/Cargo.toml @@ -24,7 +24,7 @@ pallet-staking = { path = "../staking", default-features = false } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } -sp-runtime = { path = "../../primitives/runtime" } +sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-staking = { path = "../../primitives/staking", default-features = false } [dev-dependencies] @@ -34,7 +34,7 @@ pallet-staking-reward-curve = { path = "../staking/reward-curve" } sp-core = { path = "../../primitives/core" } sp-io = { path = "../../primitives/io", default-features = false } -sp-std = { path = "../../primitives/std", default-features = false } +sp-std = { path = "../../primitives/std" } frame-election-provider-support = { path = "../election-provider-support" } @@ -74,5 +74,4 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-staking/std", - "sp-std/std", ] diff --git a/substrate/frame/root-offences/src/lib.rs b/substrate/frame/root-offences/src/lib.rs index e6bb5bb188199c05c75def4cb19adbe4e5e1fecb..6531080b8d10436def07dfc3ae23e74c2b5962d4 100644 --- a/substrate/frame/root-offences/src/lib.rs +++ b/substrate/frame/root-offences/src/lib.rs @@ -27,10 +27,13 @@ mod mock; #[cfg(test)] mod tests; +extern crate alloc; + +use alloc::vec::Vec; use pallet_session::historical::IdentificationTuple; use pallet_staking::{BalanceOf, Exposure, ExposureOf, Pallet as Staking}; use sp_runtime::Perbill; -use sp_staking::offence::{DisableStrategy, OnOffenceHandler}; +use sp_staking::offence::OnOffenceHandler; pub use pallet::*; @@ -112,7 +115,7 @@ pub mod pallet { .into_iter() .map(|(o, _)| OffenceDetails:: { offender: (o.clone(), Staking::::eras_stakers(now, &o)), - reporters: vec![], + reporters: Default::default(), }) .collect()) } @@ -125,7 +128,7 @@ pub mod pallet { T::AccountId, IdentificationTuple, Weight, - >>::on_offence(&offenders, &slash_fraction, session_index, DisableStrategy::WhenSlashed); + >>::on_offence(&offenders, &slash_fraction, session_index); } } } diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index 626db138c2bf955b5d8b6b018f28f7e01acd753a..7e7332c3f7e3b39ca9457c6da05c7eb66197d0c4 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -133,7 +133,6 @@ parameter_types! { pub static SlashDeferDuration: EraIndex = 0; pub const BondingDuration: EraIndex = 3; pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); - pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(75); } impl pallet_staking::Config for Test { @@ -153,7 +152,6 @@ impl pallet_staking::Config for Test { type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; type MaxExposurePageSize = ConstU32<64>; - type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; type TargetList = pallet_staking::UseValidatorsMap; @@ -165,6 +163,7 @@ impl pallet_staking::Config for Test { type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } impl pallet_session::historical::Config for Test { diff --git a/substrate/frame/safe-mode/src/lib.rs b/substrate/frame/safe-mode/src/lib.rs index 2bf2ebee0a4ac88aebe6d2ccc498ac8b5d958352..4be0776d6c1fa439da906ef239db072c26c68247 100644 --- a/substrate/frame/safe-mode/src/lib.rs +++ b/substrate/frame/safe-mode/src/lib.rs @@ -79,7 +79,6 @@ pub mod mock; mod tests; pub mod weights; -use core::convert::TryInto; use frame_support::{ defensive_assert, pallet_prelude::*, diff --git a/substrate/frame/sassafras/Cargo.toml b/substrate/frame/sassafras/Cargo.toml index 09977142efc89a95b982ddef69be8d7552e5eee7..888b1d8f31fc587a063a6323b2789454a461cef6 100644 --- a/substrate/frame/sassafras/Cargo.toml +++ b/substrate/frame/sassafras/Cargo.toml @@ -2,7 +2,7 @@ name = "pallet-sassafras" version = "0.3.5-dev" authors = ["Parity Technologies "] -edition = "2021" +edition.workspace = true license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" diff --git a/substrate/frame/session/benchmarking/src/inner.rs b/substrate/frame/session/benchmarking/src/inner.rs new file mode 100644 index 0000000000000000000000000000000000000000..d86c5d9ad278ea9d914877c63d7a316eebf148c9 --- /dev/null +++ b/substrate/frame/session/benchmarking/src/inner.rs @@ -0,0 +1,162 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarks for the Session Pallet. +// This is separated into its own crate due to cyclic dependency issues. + +use sp_runtime::traits::{One, StaticLookup, TrailingZeroInput}; +use sp_std::{prelude::*, vec}; + +use codec::Decode; +use frame_benchmarking::v1::benchmarks; +use frame_support::traits::{Get, KeyOwnerProofSystem, OnInitialize}; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; +use pallet_session::{historical::Pallet as Historical, Pallet as Session, *}; +use pallet_staking::{ + benchmarking::create_validator_with_nominators, testing_utils::create_validators, + MaxNominationsOf, RewardDestination, +}; + +const MAX_VALIDATORS: u32 = 1000; + +pub struct Pallet(pallet_session::Pallet); +pub trait Config: + pallet_session::Config + pallet_session::historical::Config + pallet_staking::Config +{ +} + +impl OnInitialize> for Pallet { + fn on_initialize(n: BlockNumberFor) -> frame_support::weights::Weight { + pallet_session::Pallet::::on_initialize(n) + } +} + +benchmarks! { + set_keys { + let n = MaxNominationsOf::::get(); + let (v_stash, _) = create_validator_with_nominators::( + n, + MaxNominationsOf::::get(), + false, + true, + RewardDestination::Staked, + )?; + let v_controller = pallet_staking::Pallet::::bonded(&v_stash).ok_or("not stash")?; + + let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap(); + let proof: Vec = vec![0,1,2,3]; + // Whitelist controller account from further DB operations. + let v_controller_key = frame_system::Account::::hashed_key_for(&v_controller); + frame_benchmarking::benchmarking::add_to_whitelist(v_controller_key.into()); + }: _(RawOrigin::Signed(v_controller), keys, proof) + + purge_keys { + let n = MaxNominationsOf::::get(); + let (v_stash, _) = create_validator_with_nominators::( + n, + MaxNominationsOf::::get(), + false, + true, + RewardDestination::Staked, + )?; + let v_controller = pallet_staking::Pallet::::bonded(&v_stash).ok_or("not stash")?; + let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap(); + let proof: Vec = vec![0,1,2,3]; + Session::::set_keys(RawOrigin::Signed(v_controller.clone()).into(), keys, proof)?; + // Whitelist controller account from further DB operations. + let v_controller_key = frame_system::Account::::hashed_key_for(&v_controller); + frame_benchmarking::benchmarking::add_to_whitelist(v_controller_key.into()); + }: _(RawOrigin::Signed(v_controller)) + + #[extra] + check_membership_proof_current_session { + let n in 2 .. MAX_VALIDATORS as u32; + + let (key, key_owner_proof1) = check_membership_proof_setup::(n); + let key_owner_proof2 = key_owner_proof1.clone(); + }: { + Historical::::check_proof(key, key_owner_proof1); + } + verify { + assert!(Historical::::check_proof(key, key_owner_proof2).is_some()); + } + + #[extra] + check_membership_proof_historical_session { + let n in 2 .. MAX_VALIDATORS as u32; + + let (key, key_owner_proof1) = check_membership_proof_setup::(n); + + // skip to the next session so that the session is historical + // and the membership merkle proof must be checked. + Session::::rotate_session(); + + let key_owner_proof2 = key_owner_proof1.clone(); + }: { + Historical::::check_proof(key, key_owner_proof1); + } + verify { + assert!(Historical::::check_proof(key, key_owner_proof2).is_some()); + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test, extra = false); +} + +/// Sets up the benchmark for checking a membership proof. It creates the given +/// number of validators, sets random session keys and then creates a membership +/// proof for the first authority and returns its key and the proof. +fn check_membership_proof_setup( + n: u32, +) -> ((sp_runtime::KeyTypeId, &'static [u8; 32]), sp_session::MembershipProof) { + pallet_staking::ValidatorCount::::put(n); + + // create validators and set random session keys + for (n, who) in create_validators::(n, 1000).unwrap().into_iter().enumerate() { + use rand::{RngCore, SeedableRng}; + + let validator = T::Lookup::lookup(who).unwrap(); + let controller = pallet_staking::Pallet::::bonded(&validator).unwrap(); + + let keys = { + let mut keys = [0u8; 128]; + + // we keep the keys for the first validator as 0x00000... + if n > 0 { + let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64); + rng.fill_bytes(&mut keys); + } + + keys + }; + + let keys: T::Keys = Decode::decode(&mut &keys[..]).unwrap(); + let proof: Vec = vec![]; + + Session::::set_keys(RawOrigin::Signed(controller).into(), keys, proof).unwrap(); + } + + Pallet::::on_initialize(frame_system::pallet_prelude::BlockNumberFor::::one()); + + // skip sessions until the new validator set is enacted + while Session::::validators().len() < n as usize { + Session::::rotate_session(); + } + + let key = (sp_runtime::KeyTypeId(*b"babe"), &[0u8; 32]); + + (key, Historical::::prove(key).unwrap()) +} diff --git a/substrate/frame/session/benchmarking/src/lib.rs b/substrate/frame/session/benchmarking/src/lib.rs index 84258d84994f45233e47a1ca413cb5b89ccf306a..b08955a133297f356a5fa884394b3a88a7e28727 100644 --- a/substrate/frame/session/benchmarking/src/lib.rs +++ b/substrate/frame/session/benchmarking/src/lib.rs @@ -15,153 +15,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Benchmarks for the Session Pallet. -// This is separated into its own crate due to cyclic dependency issues. +//! Offences pallet benchmarking. -#![cfg(feature = "runtime-benchmarks")] #![cfg_attr(not(feature = "std"), no_std)] -mod mock; +#[cfg(feature = "runtime-benchmarks")] +pub mod inner; -use sp_runtime::traits::{One, StaticLookup, TrailingZeroInput}; -use sp_std::{prelude::*, vec}; +#[cfg(feature = "runtime-benchmarks")] +pub use inner::*; -use codec::Decode; -use frame_benchmarking::v1::benchmarks; -use frame_support::traits::{Get, KeyOwnerProofSystem, OnInitialize}; -use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; -use pallet_session::{historical::Pallet as Historical, Pallet as Session, *}; -use pallet_staking::{ - benchmarking::create_validator_with_nominators, testing_utils::create_validators, - MaxNominationsOf, RewardDestination, -}; - -const MAX_VALIDATORS: u32 = 1000; - -pub struct Pallet(pallet_session::Pallet); -pub trait Config: - pallet_session::Config + pallet_session::historical::Config + pallet_staking::Config -{ -} - -impl OnInitialize> for Pallet { - fn on_initialize(n: BlockNumberFor) -> frame_support::weights::Weight { - pallet_session::Pallet::::on_initialize(n) - } -} - -benchmarks! { - set_keys { - let n = MaxNominationsOf::::get(); - let (v_stash, _) = create_validator_with_nominators::( - n, - MaxNominationsOf::::get(), - false, - true, - RewardDestination::Staked, - )?; - let v_controller = pallet_staking::Pallet::::bonded(&v_stash).ok_or("not stash")?; - - let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap(); - let proof: Vec = vec![0,1,2,3]; - // Whitelist controller account from further DB operations. - let v_controller_key = frame_system::Account::::hashed_key_for(&v_controller); - frame_benchmarking::benchmarking::add_to_whitelist(v_controller_key.into()); - }: _(RawOrigin::Signed(v_controller), keys, proof) - - purge_keys { - let n = MaxNominationsOf::::get(); - let (v_stash, _) = create_validator_with_nominators::( - n, - MaxNominationsOf::::get(), - false, - true, - RewardDestination::Staked, - )?; - let v_controller = pallet_staking::Pallet::::bonded(&v_stash).ok_or("not stash")?; - let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap(); - let proof: Vec = vec![0,1,2,3]; - Session::::set_keys(RawOrigin::Signed(v_controller.clone()).into(), keys, proof)?; - // Whitelist controller account from further DB operations. - let v_controller_key = frame_system::Account::::hashed_key_for(&v_controller); - frame_benchmarking::benchmarking::add_to_whitelist(v_controller_key.into()); - }: _(RawOrigin::Signed(v_controller)) - - #[extra] - check_membership_proof_current_session { - let n in 2 .. MAX_VALIDATORS as u32; - - let (key, key_owner_proof1) = check_membership_proof_setup::(n); - let key_owner_proof2 = key_owner_proof1.clone(); - }: { - Historical::::check_proof(key, key_owner_proof1); - } - verify { - assert!(Historical::::check_proof(key, key_owner_proof2).is_some()); - } - - #[extra] - check_membership_proof_historical_session { - let n in 2 .. MAX_VALIDATORS as u32; - - let (key, key_owner_proof1) = check_membership_proof_setup::(n); - - // skip to the next session so that the session is historical - // and the membership merkle proof must be checked. - Session::::rotate_session(); - - let key_owner_proof2 = key_owner_proof1.clone(); - }: { - Historical::::check_proof(key, key_owner_proof1); - } - verify { - assert!(Historical::::check_proof(key, key_owner_proof2).is_some()); - } - - impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test, extra = false); -} - -/// Sets up the benchmark for checking a membership proof. It creates the given -/// number of validators, sets random session keys and then creates a membership -/// proof for the first authority and returns its key and the proof. -fn check_membership_proof_setup( - n: u32, -) -> ((sp_runtime::KeyTypeId, &'static [u8; 32]), sp_session::MembershipProof) { - pallet_staking::ValidatorCount::::put(n); - - // create validators and set random session keys - for (n, who) in create_validators::(n, 1000).unwrap().into_iter().enumerate() { - use rand::{RngCore, SeedableRng}; - - let validator = T::Lookup::lookup(who).unwrap(); - let controller = pallet_staking::Pallet::::bonded(&validator).unwrap(); - - let keys = { - let mut keys = [0u8; 128]; - - // we keep the keys for the first validator as 0x00000... - if n > 0 { - let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64); - rng.fill_bytes(&mut keys); - } - - keys - }; - - let keys: T::Keys = Decode::decode(&mut &keys[..]).unwrap(); - let proof: Vec = vec![]; - - Session::::set_keys(RawOrigin::Signed(controller).into(), keys, proof).unwrap(); - } - - Pallet::::on_initialize(frame_system::pallet_prelude::BlockNumberFor::::one()); - - // skip sessions until the new validator set is enacted - while Session::::validators().len() < n as usize { - Session::::rotate_session(); - } - - let key = (sp_runtime::KeyTypeId(*b"babe"), &[0u8; 32]); - - (key, Historical::::prove(key).unwrap()) -} +#[cfg(all(feature = "runtime-benchmarks", test))] +pub(crate) mod mock; diff --git a/substrate/frame/session/benchmarking/src/mock.rs b/substrate/frame/session/benchmarking/src/mock.rs index 81052141fd8650106a2bbc68a5a67f2dbf457545..6cefa8f39a8c6081be0f5dfcec4b1d7ed0f8122c 100644 --- a/substrate/frame/session/benchmarking/src/mock.rs +++ b/substrate/frame/session/benchmarking/src/mock.rs @@ -174,7 +174,6 @@ impl pallet_staking::Config for Test { type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; type MaxExposurePageSize = ConstU32<64>; - type OffendingValidatorsThreshold = (); type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; type MaxUnlockingChunks = ConstU32<32>; @@ -186,6 +185,7 @@ impl pallet_staking::Config for Test { type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } impl crate::Config for Test {} diff --git a/substrate/frame/session/src/lib.rs b/substrate/frame/session/src/lib.rs index 17b6aa7a4640ce764919c7e20a07cb7ba1261486..9506e98adf7d70004a3caf57adb9c0c1dd44d5f3 100644 --- a/substrate/frame/session/src/lib.rs +++ b/substrate/frame/session/src/lib.rs @@ -627,7 +627,7 @@ impl Pallet { Validators::::put(&validators); if changed { - // reset disabled validators + // reset disabled validators if active set was changed >::take(); } diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index f93f4d31e777c93e999f4346b9b74e2a13979e4d..90c446808daff46c544048b3e841569a249953aa 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -34,9 +34,9 @@ //! //! See [`polkadot_sdk::frame`](../polkadot_sdk_docs/polkadot_sdk/frame_runtime/index.html). //! -//! ## Warning: Experimental +//! ## WARNING: Experimental //! -//! This crate and all of its content is experimental, and should not yet be used in production. +//! **This crate and all of its content is experimental, and should not yet be used in production.** //! //! ## Underlying dependencies //! diff --git a/substrate/frame/staking/CHANGELOG.md b/substrate/frame/staking/CHANGELOG.md index 719aa388755fce3523cadb273bb542e48076d3f5..113b7a6200b6e5b8014db37c0b43b7f45b96e62b 100644 --- a/substrate/frame/staking/CHANGELOG.md +++ b/substrate/frame/staking/CHANGELOG.md @@ -7,6 +7,25 @@ on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). We maintain a single integer version number for staking pallet to keep track of all storage migrations. +## [v15] + +### Added + +- New trait `DisablingStrategy` which is responsible for making a decision which offenders should be + disabled on new offence. +- Default implementation of `DisablingStrategy` - `UpToLimitDisablingStrategy`. It + disables each new offender up to a threshold (1/3 by default). Offenders are not runtime disabled for + offences in previous era(s). But they will be low-priority node-side disabled for dispute initiation. +- `OffendingValidators` storage item is replaced with `DisabledValidators`. The former keeps all + offenders and if they are disabled or not. The latter just keeps a list of all offenders as they + are disabled by default. + +### Deprecated + +- `enum DisableStrategy` is no longer needed because disabling is not related to the type of the + offence anymore. A decision if a offender is disabled or not is made by a `DisablingStrategy` + implementation. + ## [v14] ### Added diff --git a/substrate/frame/staking/runtime-api/src/lib.rs b/substrate/frame/staking/runtime-api/src/lib.rs index b04c383a077dc8fcb1c70b2fe6ff6117837c2867..7955f4184a434545a013b2967e6d15a5c63b9764 100644 --- a/substrate/frame/staking/runtime-api/src/lib.rs +++ b/substrate/frame/staking/runtime-api/src/lib.rs @@ -30,7 +30,10 @@ sp_api::decl_runtime_apis! { /// Returns the nominations quota for a nominator with a given balance. fn nominations_quota(balance: Balance) -> u32; - /// Returns the page count of exposures for a validator in a given era. + /// Returns the page count of exposures for a validator `account` in a given era. fn eras_stakers_page_count(era: sp_staking::EraIndex, account: AccountId) -> sp_staking::Page; + + /// Returns true if validator `account` has pages to be claimed for the given era. + fn pending_rewards(era: sp_staking::EraIndex, account: AccountId) -> bool; } } diff --git a/substrate/frame/staking/src/ledger.rs b/substrate/frame/staking/src/ledger.rs index 9461daefed65e74c778a248a0bbaeeedd3ee6222..67a86b86226cfb1aa5da4c8bca794f315f348009 100644 --- a/substrate/frame/staking/src/ledger.rs +++ b/substrate/frame/staking/src/ledger.rs @@ -33,13 +33,14 @@ use frame_support::{ defensive, ensure, - traits::{Defensive, LockableCurrency, WithdrawReasons}, + traits::{Defensive, LockableCurrency}, }; use sp_staking::StakingAccount; use sp_std::prelude::*; use crate::{ - BalanceOf, Bonded, Config, Error, Ledger, Payee, RewardDestination, StakingLedger, STAKING_ID, + BalanceOf, Bonded, Config, Error, Ledger, Pallet, Payee, RewardDestination, StakingLedger, + VirtualStakers, STAKING_ID, }; #[cfg(any(feature = "runtime-benchmarks", test))] @@ -187,7 +188,17 @@ impl StakingLedger { return Err(Error::::NotStash) } - T::Currency::set_lock(STAKING_ID, &self.stash, self.total, WithdrawReasons::all()); + // We skip locking virtual stakers. + if !Pallet::::is_virtual_staker(&self.stash) { + // for direct stakers, update lock on stash based on ledger. + T::Currency::set_lock( + STAKING_ID, + &self.stash, + self.total, + frame_support::traits::WithdrawReasons::all(), + ); + } + Ledger::::insert( &self.controller().ok_or_else(|| { defensive!("update called on a ledger that is not bonded."); @@ -204,22 +215,22 @@ impl StakingLedger { /// It sets the reward preferences for the bonded stash. pub(crate) fn bond(self, payee: RewardDestination) -> Result<(), Error> { if >::contains_key(&self.stash) { - Err(Error::::AlreadyBonded) - } else { - >::insert(&self.stash, payee); - >::insert(&self.stash, &self.stash); - self.update() + return Err(Error::::AlreadyBonded) } + + >::insert(&self.stash, payee); + >::insert(&self.stash, &self.stash); + self.update() } /// Sets the ledger Payee. pub(crate) fn set_payee(self, payee: RewardDestination) -> Result<(), Error> { if !>::contains_key(&self.stash) { - Err(Error::::NotStash) - } else { - >::insert(&self.stash, payee); - Ok(()) + return Err(Error::::NotStash) } + + >::insert(&self.stash, payee); + Ok(()) } /// Sets the ledger controller to its stash. @@ -252,12 +263,16 @@ impl StakingLedger { let controller = >::get(stash).ok_or(Error::::NotStash)?; >::get(&controller).ok_or(Error::::NotController).map(|ledger| { - T::Currency::remove_lock(STAKING_ID, &ledger.stash); Ledger::::remove(controller); - >::remove(&stash); >::remove(&stash); + // kill virtual staker if it exists. + if >::take(&stash).is_none() { + // if not virtual staker, clear locks. + T::Currency::remove_lock(STAKING_ID, &ledger.stash); + } + Ok(()) })? } diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index f5b7e3eca3de7cd49b6a5cbdef37dfa4feff80ec..692e62acfdff503c515344434c5155ebd7622d13 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -1035,11 +1035,37 @@ where /// can and add more functions to it as needed. pub struct EraInfo(sp_std::marker::PhantomData); impl EraInfo { + /// Returns true if validator has one or more page of era rewards not claimed yet. + // Also looks at legacy storage that can be cleaned up after #433. + pub fn pending_rewards(era: EraIndex, validator: &T::AccountId) -> bool { + let page_count = if let Some(overview) = >::get(&era, validator) { + overview.page_count + } else { + if >::contains_key(era, validator) { + // this means non paged exposure, and we treat them as single paged. + 1 + } else { + // if no exposure, then no rewards to claim. + return false + } + }; + + // check if era is marked claimed in legacy storage. + if >::get(validator) + .map(|l| l.legacy_claimed_rewards.contains(&era)) + .unwrap_or_default() + { + return false + } + + ClaimedRewards::::get(era, validator).len() < page_count as usize + } + /// Temporary function which looks at both (1) passed param `T::StakingLedger` for legacy /// non-paged rewards, and (2) `T::ClaimedRewards` for paged rewards. This function can be /// removed once `T::HistoryDepth` eras have passed and none of the older non-paged rewards /// are relevant/claimable. - // Refer tracker issue for cleanup: #13034 + // Refer tracker issue for cleanup: https://github.com/paritytech/polkadot-sdk/issues/433 pub(crate) fn is_rewards_claimed_with_legacy_fallback( era: EraIndex, ledger: &StakingLedger, @@ -1239,3 +1265,79 @@ impl BenchmarkingConfig for TestBenchmarkingConfig { type MaxValidators = frame_support::traits::ConstU32<100>; type MaxNominators = frame_support::traits::ConstU32<100>; } + +/// Controls validator disabling +pub trait DisablingStrategy { + /// Make a disabling decision. Returns the index of the validator to disable or `None` if no new + /// validator should be disabled. + fn decision( + offender_stash: &T::AccountId, + slash_era: EraIndex, + currently_disabled: &Vec, + ) -> Option; +} + +/// Implementation of [`DisablingStrategy`] which disables validators from the active set up to a +/// threshold. `DISABLING_LIMIT_FACTOR` is the factor of the maximum disabled validators in the +/// active set. E.g. setting this value to `3` means no more than 1/3 of the validators in the +/// active set can be disabled in an era. +/// By default a factor of 3 is used which is the byzantine threshold. +pub struct UpToLimitDisablingStrategy; + +impl UpToLimitDisablingStrategy { + /// Disabling limit calculated from the total number of validators in the active set. When + /// reached no more validators will be disabled. + pub fn disable_limit(validators_len: usize) -> usize { + validators_len + .saturating_sub(1) + .checked_div(DISABLING_LIMIT_FACTOR) + .unwrap_or_else(|| { + defensive!("DISABLING_LIMIT_FACTOR should not be 0"); + 0 + }) + } +} + +impl DisablingStrategy + for UpToLimitDisablingStrategy +{ + fn decision( + offender_stash: &T::AccountId, + slash_era: EraIndex, + currently_disabled: &Vec, + ) -> Option { + let active_set = T::SessionInterface::validators(); + + // We don't disable more than the limit + if currently_disabled.len() >= Self::disable_limit(active_set.len()) { + log!( + debug, + "Won't disable: reached disabling limit {:?}", + Self::disable_limit(active_set.len()) + ); + return None + } + + // We don't disable for offences in previous eras + if ActiveEra::::get().map(|e| e.index).unwrap_or_default() > slash_era { + log!( + debug, + "Won't disable: current_era {:?} > slash_era {:?}", + Pallet::::current_era().unwrap_or_default(), + slash_era + ); + return None + } + + let offender_idx = if let Some(idx) = active_set.iter().position(|i| i == offender_stash) { + idx as u32 + } else { + log!(debug, "Won't disable: offender not in active set",); + return None + }; + + log!(debug, "Will disable {:?}", offender_idx); + + Some(offender_idx) + } +} diff --git a/substrate/frame/staking/src/migrations.rs b/substrate/frame/staking/src/migrations.rs index d5b18421d5b67fbeaac27cbbdecde174fa3d024b..510252be26c93e95e640af601ad8efdfd8801383 100644 --- a/substrate/frame/staking/src/migrations.rs +++ b/substrate/frame/staking/src/migrations.rs @@ -20,9 +20,10 @@ use super::*; use frame_election_provider_support::SortedListProvider; use frame_support::{ + migrations::VersionedMigration, pallet_prelude::ValueQuery, storage_alias, - traits::{GetStorageVersion, OnRuntimeUpgrade}, + traits::{GetStorageVersion, OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade}, }; #[cfg(feature = "try-runtime")] @@ -59,11 +60,61 @@ impl Default for ObsoleteReleases { #[storage_alias] type StorageVersion = StorageValue, ObsoleteReleases, ValueQuery>; +/// Migrating `OffendingValidators` from `Vec<(u32, bool)>` to `Vec` +pub mod v15 { + use super::*; + + // The disabling strategy used by staking pallet + type DefaultDisablingStrategy = UpToLimitDisablingStrategy; + + pub struct VersionUncheckedMigrateV14ToV15(sp_std::marker::PhantomData); + impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV14ToV15 { + fn on_runtime_upgrade() -> Weight { + let mut migrated = v14::OffendingValidators::::take() + .into_iter() + .filter(|p| p.1) // take only disabled validators + .map(|p| p.0) + .collect::>(); + + // Respect disabling limit + migrated.truncate(DefaultDisablingStrategy::disable_limit( + T::SessionInterface::validators().len(), + )); + + DisabledValidators::::set(migrated); + + log!(info, "v15 applied successfully."); + T::DbWeight::get().reads_writes(1, 1) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + frame_support::ensure!( + v14::OffendingValidators::::decode_len().is_none(), + "OffendingValidators is not empty after the migration" + ); + Ok(()) + } + } + + pub type MigrateV14ToV15 = VersionedMigration< + 14, + 15, + VersionUncheckedMigrateV14ToV15, + Pallet, + ::DbWeight, + >; +} + /// Migration of era exposure storage items to paged exposures. /// Changelog: [v14.](https://github.com/paritytech/substrate/blob/ankan/paged-rewards-rebased2/frame/staking/CHANGELOG.md#14) pub mod v14 { use super::*; + #[frame_support::storage_alias] + pub(crate) type OffendingValidators = + StorageValue, Vec<(u32, bool)>, ValueQuery>; + pub struct MigrateToV14(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV14 { fn on_runtime_upgrade() -> Weight { @@ -73,10 +124,10 @@ pub mod v14 { if in_code == 14 && on_chain == 13 { in_code.put::>(); - log!(info, "v14 applied successfully."); + log!(info, "staking v14 applied successfully."); T::DbWeight::get().reads_writes(1, 1) } else { - log!(warn, "v14 not applied."); + log!(warn, "staking v14 not applied."); T::DbWeight::get().reads(1) } } diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 6db462c1a70fbce4ed2d531bf9ad4d837e83b585..8c60dec65a81a123b5d1bd04b3ea8614ae1e9f0f 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -34,7 +34,7 @@ use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_io; use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; use sp_staking::{ - offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, + offence::{OffenceDetails, OnOffenceHandler}, OnStakingUpdate, }; @@ -186,7 +186,6 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const BondingDuration: EraIndex = 3; pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS; - pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(75); } parameter_types! { @@ -249,20 +248,27 @@ parameter_types! { pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); + pub static SlashObserver: BTreeMap> = BTreeMap::new(); } pub struct EventListenerMock; impl OnStakingUpdate for EventListenerMock { fn on_slash( - _pool_account: &AccountId, + pool_account: &AccountId, slashed_bonded: Balance, slashed_chunks: &BTreeMap, - _total_slashed: Balance, + total_slashed: Balance, ) { LedgerSlashPerEra::set((slashed_bonded, slashed_chunks.clone())); + SlashObserver::mutate(|map| { + map.insert(*pool_account, map.get(pool_account).unwrap_or(&0) + total_slashed) + }); } } +// Disabling threshold for `UpToLimitDisablingStrategy` +pub(crate) const DISABLING_LIMIT_FACTOR: usize = 3; + impl crate::pallet::pallet::Config for Test { type Currency = Balances; type CurrencyBalance = ::Balance; @@ -280,7 +286,6 @@ impl crate::pallet::pallet::Config for Test { type EraPayout = ConvertCurve; type NextNewSession = Session; type MaxExposurePageSize = MaxExposurePageSize; - type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. @@ -293,6 +298,7 @@ impl crate::pallet::pallet::Config for Test { type EventListeners = EventListenerMock; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } pub struct WeightedNominationsQuota; @@ -457,6 +463,8 @@ impl ExtBuilder { (31, self.balance_factor * 2000), (41, self.balance_factor * 2000), (51, self.balance_factor * 2000), + (201, self.balance_factor * 2000), + (202, self.balance_factor * 2000), // optional nominator (100, self.balance_factor * 2000), (101, self.balance_factor * 2000), @@ -484,8 +492,10 @@ impl ExtBuilder { (31, 31, self.balance_factor * 500, StakerStatus::::Validator), // an idle validator (41, 41, self.balance_factor * 1000, StakerStatus::::Idle), - ]; - // optionally add a nominator + (51, 51, self.balance_factor * 1000, StakerStatus::::Idle), + (201, 201, self.balance_factor * 1000, StakerStatus::::Idle), + (202, 202, self.balance_factor * 1000, StakerStatus::::Idle), + ]; // optionally add a nominator if self.nominate { stakers.push(( 101, @@ -598,6 +608,21 @@ pub(crate) fn bond_nominator(who: AccountId, val: Balance, target: Vec, +) { + // In a real scenario, `who` is a keyless account managed by another pallet which provides for + // it. + System::inc_providers(&who); + + // Bond who virtually. + assert_ok!(::virtual_bond(&who, val, &payee)); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who), target)); +} + /// Progress to the given block, triggering session and era changes as we progress. /// /// This will finalize the previous block, initialize up to the given block, essentially simulating @@ -709,12 +734,11 @@ pub(crate) fn on_offence_in_era( >], slash_fraction: &[Perbill], era: EraIndex, - disable_strategy: DisableStrategy, ) { let bonded_eras = crate::BondedEras::::get(); for &(bonded_era, start_session) in bonded_eras.iter() { if bonded_era == era { - let _ = Staking::on_offence(offenders, slash_fraction, start_session, disable_strategy); + let _ = Staking::on_offence(offenders, slash_fraction, start_session); return } else if bonded_era > era { break @@ -726,7 +750,6 @@ pub(crate) fn on_offence_in_era( offenders, slash_fraction, Staking::eras_start_session_index(era).unwrap(), - disable_strategy, ); } else { panic!("cannot slash in era {}", era); @@ -741,7 +764,7 @@ pub(crate) fn on_offence_now( slash_fraction: &[Perbill], ) { let now = Staking::active_era().unwrap().index; - on_offence_in_era(offenders, slash_fraction, now, DisableStrategy::WhenSlashed) + on_offence_in_era(offenders, slash_fraction, now) } pub(crate) fn add_slash(who: &AccountId) { diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 2f43e4847e451a7cc10413bb58e3d98d82577e9f..5b2a55303e2c855a814f0d4f9705fb7d89b2767c 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -28,19 +28,22 @@ use frame_support::{ pallet_prelude::*, traits::{ Currency, Defensive, DefensiveSaturating, EstimateNextNewSession, Get, Imbalance, - InspectLockableCurrency, Len, OnUnbalanced, TryCollect, UnixTime, + InspectLockableCurrency, Len, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, }, weights::Weight, }; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_session::historical; use sp_runtime::{ - traits::{Bounded, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero}, - Perbill, Percent, + traits::{ + Bounded, CheckedAdd, CheckedSub, Convert, One, SaturatedConversion, Saturating, + StaticLookup, Zero, + }, + ArithmeticError, Perbill, Percent, }; use sp_staking::{ currency_to_vote::CurrencyToVote, - offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, + offence::{OffenceDetails, OnOffenceHandler}, EraIndex, OnStakingUpdate, Page, SessionIndex, Stake, StakingAccount::{self, Controller, Stash}, StakingInterface, @@ -84,10 +87,12 @@ impl Pallet { StakingLedger::::paired_account(Stash(stash.clone())) } - /// Inspects and returns the corruption state of a ledger and bond, if any. + /// Inspects and returns the corruption state of a ledger and direct bond, if any. /// /// Note: all operations in this method access directly the `Bonded` and `Ledger` storage maps /// instead of using the [`StakingLedger`] API since the bond and/or ledger may be corrupted. + /// It is also meant to check state for direct bonds and may not work as expected for virtual + /// bonds. pub(crate) fn inspect_bond_state( stash: &T::AccountId, ) -> Result> { @@ -149,6 +154,39 @@ impl Pallet { Self::slashable_balance_of_vote_weight(who, issuance) } + pub(super) fn do_bond_extra(stash: &T::AccountId, additional: BalanceOf) -> DispatchResult { + let mut ledger = Self::ledger(StakingAccount::Stash(stash.clone()))?; + + // for virtual stakers, we don't need to check the balance. Since they are only accessed + // via low level apis, we can assume that the caller has done the due diligence. + let extra = if Self::is_virtual_staker(stash) { + additional + } else { + // additional amount or actual balance of stash whichever is lower. + additional.min( + T::Currency::free_balance(stash) + .checked_sub(&ledger.total) + .ok_or(ArithmeticError::Overflow)?, + ) + }; + + ledger.total = ledger.total.checked_add(&extra).ok_or(ArithmeticError::Overflow)?; + ledger.active = ledger.active.checked_add(&extra).ok_or(ArithmeticError::Overflow)?; + // last check: the new active amount of ledger must be more than ED. + ensure!(ledger.active >= T::Currency::minimum_balance(), Error::::InsufficientBond); + + // NOTE: ledger must be updated prior to calling `Self::weight_of`. + ledger.update()?; + // update this staker in the sorted list, if they exist in it. + if T::VoterList::contains(stash) { + let _ = T::VoterList::on_update(&stash, Self::weight_of(stash)).defensive(); + } + + Self::deposit_event(Event::::Bonded { stash: stash.clone(), amount: extra }); + + Ok(()) + } + pub(super) fn do_withdraw_unbonded( controller: &T::AccountId, num_slashing_spans: u32, @@ -469,10 +507,8 @@ impl Pallet { } // disable all offending validators that have been disabled for the whole era - for (index, disabled) in >::get() { - if disabled { - T::SessionInterface::disable_validator(index); - } + for index in >::get() { + T::SessionInterface::disable_validator(index); } } @@ -562,8 +598,8 @@ impl Pallet { >::insert(&active_era.index, validator_payout); T::RewardRemainder::on_unbalanced(T::Currency::issue(remainder)); - // Clear offending validators. - >::kill(); + // Clear disabled validators. + >::kill(); } } @@ -832,14 +868,6 @@ impl Pallet { Self::deposit_event(Event::::ForceEra { mode }); } - /// Ensures that at the end of the current session there will be a new era. - pub(crate) fn ensure_new_era() { - match ForceEra::::get() { - Forcing::ForceAlways | Forcing::ForceNew => (), - _ => Self::set_force_era(Forcing::ForceNew), - } - } - #[cfg(feature = "runtime-benchmarks")] pub fn add_era_stakers( current_era: EraIndex, @@ -1132,6 +1160,11 @@ impl Pallet { ) -> Exposure> { EraInfo::::get_full_exposure(era, account) } + + /// Whether `who` is a virtual staker whose funds are managed by another pallet. + pub(crate) fn is_virtual_staker(who: &T::AccountId) -> bool { + VirtualStakers::::contains_key(who) + } } impl Pallet { @@ -1152,6 +1185,10 @@ impl Pallet { pub fn api_eras_stakers_page_count(era: EraIndex, account: T::AccountId) -> Page { EraInfo::::get_page_count(era, &account) } + + pub fn api_pending_rewards(era: EraIndex, account: T::AccountId) -> bool { + EraInfo::::pending_rewards(era, &account) + } } impl ElectionDataProvider for Pallet { @@ -1406,7 +1443,6 @@ where >], slash_fraction: &[Perbill], slash_session: SessionIndex, - disable_strategy: DisableStrategy, ) -> Weight { let reward_proportion = SlashRewardFraction::::get(); let mut consumed_weight = Weight::from_parts(0, 0); @@ -1471,7 +1507,6 @@ where window_start, now: active_era, reward_proportion, - disable_strategy, }); Self::deposit_event(Event::::SlashReported { @@ -1748,6 +1783,23 @@ impl StakingInterface for Pallet { .map(|_| ()) } + fn update_payee(stash: &Self::AccountId, reward_acc: &Self::AccountId) -> DispatchResult { + // Since virtual stakers are not allowed to compound their rewards as this pallet does not + // manage their locks, we do not allow reward account to be set same as stash. For + // external pallets that manage the virtual bond, they can claim rewards and re-bond them. + ensure!( + !Self::is_virtual_staker(stash) || stash != reward_acc, + Error::::RewardDestinationRestricted + ); + + // since controller is deprecated and this function is never used for old ledgers with + // distinct controllers, we can safely assume that stash is the controller. + Self::set_payee( + RawOrigin::Signed(stash.clone()).into(), + RewardDestination::Account(reward_acc.clone()), + ) + } + fn chill(who: &Self::AccountId) -> DispatchResult { // defensive-only: any account bonded via this interface has the stash set as the // controller, but we have to be sure. Same comment anywhere else that we read this. @@ -1832,6 +1884,10 @@ impl StakingInterface for Pallet { } } + fn slash_reward_fraction() -> Perbill { + SlashRewardFraction::::get() + } + sp_staking::runtime_benchmarks_enabled! { fn nominations(who: &Self::AccountId) -> Option> { Nominators::::get(who).map(|n| n.targets.into_inner()) @@ -1860,6 +1916,55 @@ impl StakingInterface for Pallet { } } +impl sp_staking::StakingUnchecked for Pallet { + fn migrate_to_virtual_staker(who: &Self::AccountId) { + T::Currency::remove_lock(crate::STAKING_ID, who); + VirtualStakers::::insert(who, ()); + } + + /// Virtually bonds `keyless_who` to `payee` with `value`. + /// + /// The payee must not be the same as the `keyless_who`. + fn virtual_bond( + keyless_who: &Self::AccountId, + value: Self::Balance, + payee: &Self::AccountId, + ) -> DispatchResult { + if StakingLedger::::is_bonded(StakingAccount::Stash(keyless_who.clone())) { + return Err(Error::::AlreadyBonded.into()) + } + + // check if payee not same as who. + ensure!(keyless_who != payee, Error::::RewardDestinationRestricted); + + // mark this pallet as consumer of `who`. + frame_system::Pallet::::inc_consumers(&keyless_who).map_err(|_| Error::::BadState)?; + + // mark who as a virtual staker. + VirtualStakers::::insert(keyless_who, ()); + + Self::deposit_event(Event::::Bonded { stash: keyless_who.clone(), amount: value }); + let ledger = StakingLedger::::new(keyless_who.clone(), value); + + ledger.bond(RewardDestination::Account(payee.clone()))?; + + Ok(()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn migrate_to_direct_staker(who: &Self::AccountId) { + assert!(VirtualStakers::::contains_key(who)); + let ledger = StakingLedger::::get(Stash(who.clone())).unwrap(); + T::Currency::set_lock( + crate::STAKING_ID, + who, + ledger.total, + frame_support::traits::WithdrawReasons::all(), + ); + VirtualStakers::::remove(who); + } +} + #[cfg(any(test, feature = "try-runtime"))] impl Pallet { pub(crate) fn do_try_state(_: BlockNumberFor) -> Result<(), TryRuntimeError> { @@ -1875,7 +1980,8 @@ impl Pallet { Self::check_nominators()?; Self::check_exposures()?; Self::check_paged_exposures()?; - Self::check_count() + Self::check_count()?; + Self::ensure_disabled_validators_sorted() } /// Invariants: @@ -1980,16 +2086,44 @@ impl Pallet { /// Invariants: /// * Stake consistency: ledger.total == ledger.active + sum(ledger.unlocking). /// * The ledger's controller and stash matches the associated `Bonded` tuple. - /// * Staking locked funds for every bonded stash should be the same as its ledger's total. + /// * Staking locked funds for every bonded stash (non virtual stakers) should be the same as + /// its ledger's total. + /// * For virtual stakers, locked funds should be zero and payee should be non-stash account. /// * Staking ledger and bond are not corrupted. fn check_ledgers() -> Result<(), TryRuntimeError> { Bonded::::iter() .map(|(stash, ctrl)| { // ensure locks consistency. - ensure!( - Self::inspect_bond_state(&stash) == Ok(LedgerIntegrityState::Ok), - "bond, ledger and/or staking lock inconsistent for a bonded stash." - ); + if VirtualStakers::::contains_key(stash.clone()) { + ensure!( + T::Currency::balance_locked(crate::STAKING_ID, &stash) == Zero::zero(), + "virtual stakers should not have any locked balance" + ); + ensure!( + >::get(stash.clone()).unwrap() == stash.clone(), + "stash and controller should be same" + ); + ensure!( + Ledger::::get(stash.clone()).unwrap().stash == stash, + "ledger corrupted for virtual staker" + ); + let reward_destination = >::get(stash.clone()).unwrap(); + if let RewardDestination::Account(payee) = reward_destination { + ensure!( + payee != stash.clone(), + "reward destination should not be same as stash for virtual staker" + ); + } else { + return Err(DispatchError::Other( + "reward destination must be of account variant for virtual staker", + )); + } + } else { + ensure!( + Self::inspect_bond_state(&stash) == Ok(LedgerIntegrityState::Ok), + "bond, ledger and/or staking lock inconsistent for a bonded stash." + ); + } // ensure ledger consistency. Self::ensure_ledger_consistent(ctrl) @@ -2161,4 +2295,12 @@ impl Pallet { Ok(()) } + + fn ensure_disabled_validators_sorted() -> Result<(), TryRuntimeError> { + ensure!( + DisabledValidators::::get().windows(2).all(|pair| pair[0] <= pair[1]), + "DisabledValidators is not sorted" + ); + Ok(()) + } } diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 2e5b3aa7b873e2c123b3176a4e80d6e0cd1b6177..16ad510c562bff4590fa1b7ebc7b3daf3145a2a4 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -32,7 +32,7 @@ use frame_support::{ }; use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; use sp_runtime::{ - traits::{CheckedSub, SaturatedConversion, StaticLookup, Zero}, + traits::{SaturatedConversion, StaticLookup, Zero}, ArithmeticError, Perbill, Percent, }; @@ -47,10 +47,11 @@ mod impls; pub use impls::*; use crate::{ - slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout, - EraRewardPoints, Exposure, ExposurePage, Forcing, LedgerIntegrityState, MaxNominationsOf, - NegativeImbalanceOf, Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, - SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, + slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, DisablingStrategy, + EraPayout, EraRewardPoints, Exposure, ExposurePage, Forcing, LedgerIntegrityState, + MaxNominationsOf, NegativeImbalanceOf, Nominations, NominationsQuota, PositiveImbalanceOf, + RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, + ValidatorPrefs, }; // The speculative number of spans are used as an input of the weight annotation of @@ -67,7 +68,7 @@ pub mod pallet { use super::*; /// The in-code storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(14); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(15); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -217,10 +218,6 @@ pub mod pallet { #[pallet::constant] type MaxExposurePageSize: Get; - /// The fraction of the validator set that is safe to be offending. - /// After the threshold is reached a new era will be forced. - type OffendingValidatorsThreshold: Get; - /// Something that provides a best-effort sorted list of voters aka electing nominators, /// used for NPoS election. /// @@ -278,6 +275,9 @@ pub mod pallet { /// WARNING: this only reports slashing and withdraw events for the time being. type EventListeners: sp_staking::OnStakingUpdate>; + // `DisablingStragegy` controls how validators are disabled + type DisablingStrategy: DisablingStrategy; + /// Some parameters of the benchmarking. type BenchmarkingConfig: BenchmarkingConfig; @@ -379,6 +379,15 @@ pub mod pallet { pub type Nominators = CountedStorageMap<_, Twox64Concat, T::AccountId, Nominations>; + /// Stakers whose funds are managed by other pallets. + /// + /// This pallet does not apply any locks on them, therefore they are only virtually bonded. They + /// are expected to be keyless accounts and hence should not be allowed to mutate their ledger + /// directly via this pallet. Instead, these accounts are managed by other pallets and accessed + /// via low level apis. We keep track of them to do minimal integrity checks. + #[pallet::storage] + pub type VirtualStakers = CountedStorageMap<_, Twox64Concat, T::AccountId, ()>; + /// The maximum nominator count before we stop allowing new validators to join. /// /// When this value is not set, no limits are enforced. @@ -645,19 +654,16 @@ pub mod pallet { #[pallet::getter(fn current_planned_session)] pub type CurrentPlannedSession = StorageValue<_, SessionIndex, ValueQuery>; - /// Indices of validators that have offended in the active era and whether they are currently - /// disabled. + /// Indices of validators that have offended in the active era. The offenders are disabled for a + /// whole era. For this reason they are kept here - only staking pallet knows about eras. The + /// implementor of [`DisablingStrategy`] defines if a validator should be disabled which + /// implicitly means that the implementor also controls the max number of disabled validators. /// - /// This value should be a superset of disabled validators since not all offences lead to the - /// validator being disabled (if there was no slash). This is needed to track the percentage of - /// validators that have offended in the current era, ensuring a new era is forced if - /// `OffendingValidatorsThreshold` is reached. The vec is always kept sorted so that we can find - /// whether a given validator has previously offended using binary search. It gets cleared when - /// the era ends. + /// The vec is always kept sorted so that we can find whether a given validator has previously + /// offended using binary search. #[pallet::storage] #[pallet::unbounded] - #[pallet::getter(fn offending_validators)] - pub type OffendingValidators = StorageValue<_, Vec<(u32, bool)>, ValueQuery>; + pub type DisabledValidators = StorageValue<_, Vec, ValueQuery>; /// The threshold for when users can start calling `chill_other` for other validators / /// nominators. The threshold is compared to the actual number of validators / nominators @@ -858,6 +864,12 @@ pub mod pallet { ControllerDeprecated, /// Cannot reset a ledger. CannotRestoreLedger, + /// Provided reward destination is not allowed. + RewardDestinationRestricted, + /// Not enough funds available to withdraw. + NotEnoughFunds, + /// Operation not allowed for virtual stakers. + VirtualStakerNotAllowed, } #[pallet::hooks] @@ -985,29 +997,7 @@ pub mod pallet { #[pallet::compact] max_additional: BalanceOf, ) -> DispatchResult { let stash = ensure_signed(origin)?; - let mut ledger = Self::ledger(StakingAccount::Stash(stash.clone()))?; - - let stash_balance = T::Currency::free_balance(&stash); - if let Some(extra) = stash_balance.checked_sub(&ledger.total) { - let extra = extra.min(max_additional); - ledger.total += extra; - ledger.active += extra; - // Last check: the new active amount of ledger must be more than ED. - ensure!( - ledger.active >= T::Currency::minimum_balance(), - Error::::InsufficientBond - ); - - // NOTE: ledger must be updated prior to calling `Self::weight_of`. - ledger.update()?; - // update this staker in the sorted list, if they exist in it. - if T::VoterList::contains(&stash) { - let _ = T::VoterList::on_update(&stash, Self::weight_of(&stash)).defensive(); - } - - Self::deposit_event(Event::::Bonded { stash, amount: extra }); - } - Ok(()) + Self::do_bond_extra(&stash, max_additional) } /// Schedule a portion of the stash to be unlocked ready for transfer out after the bond @@ -1646,6 +1636,9 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; + // virtual stakers should not be allowed to be reaped. + ensure!(!Self::is_virtual_staker(&stash), Error::::VirtualStakerNotAllowed); + let ed = T::Currency::minimum_balance(); let reapable = T::Currency::total_balance(&stash) < ed || Self::ledger(Stash(stash.clone())).map(|l| l.total).unwrap_or_default() < ed; @@ -2006,6 +1999,9 @@ pub mod pallet { ) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; + // cannot restore ledger for virtual stakers. + ensure!(!Self::is_virtual_staker(&stash), Error::::VirtualStakerNotAllowed); + let current_lock = T::Currency::balance_locked(crate::STAKING_ID, &stash); let stash_balance = T::Currency::free_balance(&stash); diff --git a/substrate/frame/staking/src/slashing.rs b/substrate/frame/staking/src/slashing.rs index 709fd1441ec3af313220486aa8000c959e30fd07..f831f625957d4c495fce3b239e68faed737cb3ab 100644 --- a/substrate/frame/staking/src/slashing.rs +++ b/substrate/frame/staking/src/slashing.rs @@ -50,21 +50,21 @@ //! Based on research at use crate::{ - BalanceOf, Config, Error, Exposure, NegativeImbalanceOf, NominatorSlashInEra, - OffendingValidators, Pallet, Perbill, SessionInterface, SpanSlash, UnappliedSlash, + BalanceOf, Config, DisabledValidators, DisablingStrategy, Error, Exposure, NegativeImbalanceOf, + NominatorSlashInEra, Pallet, Perbill, SessionInterface, SpanSlash, UnappliedSlash, ValidatorSlashInEra, }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ ensure, - traits::{Currency, Defensive, DefensiveSaturating, Get, Imbalance, OnUnbalanced}, + traits::{Currency, Defensive, DefensiveSaturating, Imbalance, OnUnbalanced}, }; use scale_info::TypeInfo; use sp_runtime::{ traits::{Saturating, Zero}, DispatchResult, RuntimeDebug, }; -use sp_staking::{offence::DisableStrategy, EraIndex}; +use sp_staking::EraIndex; use sp_std::vec::Vec; /// The proportion of the slashing reward to be paid out on the first slashing detection. @@ -220,8 +220,6 @@ pub(crate) struct SlashParams<'a, T: 'a + Config> { /// The maximum percentage of a slash that ever gets paid out. /// This is f_inf in the paper. pub(crate) reward_proportion: Perbill, - /// When to disable offenders. - pub(crate) disable_strategy: DisableStrategy, } /// Computes a slash of a validator and nominators. It returns an unapplied @@ -280,18 +278,13 @@ pub(crate) fn compute_slash( let target_span = spans.compare_and_update_span_slash(params.slash_era, own_slash); if target_span == Some(spans.span_index()) { - // misbehavior occurred within the current slashing span - take appropriate - // actions. - - // chill the validator - it misbehaved in the current span and should - // not continue in the next election. also end the slashing span. + // misbehavior occurred within the current slashing span - end current span. + // Check for details. spans.end_span(params.now); - >::chill_stash(params.stash); } } - let disable_when_slashed = params.disable_strategy != DisableStrategy::Never; - add_offending_validator::(params.stash, disable_when_slashed); + add_offending_validator::(¶ms); let mut nominators_slashed = Vec::new(); reward_payout += slash_nominators::(params.clone(), prior_slash_p, &mut nominators_slashed); @@ -320,54 +313,31 @@ fn kick_out_if_recent(params: SlashParams) { ); if spans.era_span(params.slash_era).map(|s| s.index) == Some(spans.span_index()) { + // Check https://github.com/paritytech/polkadot-sdk/issues/2650 for details spans.end_span(params.now); - >::chill_stash(params.stash); } - let disable_without_slash = params.disable_strategy == DisableStrategy::Always; - add_offending_validator::(params.stash, disable_without_slash); + add_offending_validator::(¶ms); } -/// Add the given validator to the offenders list and optionally disable it. -/// If after adding the validator `OffendingValidatorsThreshold` is reached -/// a new era will be forced. -fn add_offending_validator(stash: &T::AccountId, disable: bool) { - OffendingValidators::::mutate(|offending| { - let validators = T::SessionInterface::validators(); - let validator_index = match validators.iter().position(|i| i == stash) { - Some(index) => index, - None => return, - }; - - let validator_index_u32 = validator_index as u32; - - match offending.binary_search_by_key(&validator_index_u32, |(index, _)| *index) { - // this is a new offending validator - Err(index) => { - offending.insert(index, (validator_index_u32, disable)); - - let offending_threshold = - T::OffendingValidatorsThreshold::get() * validators.len() as u32; - - if offending.len() >= offending_threshold as usize { - // force a new era, to select a new validator set - >::ensure_new_era() - } - - if disable { - T::SessionInterface::disable_validator(validator_index_u32); - } - }, - Ok(index) => { - if disable && !offending[index].1 { - // the validator had previously offended without being disabled, - // let's make sure we disable it now - offending[index].1 = true; - T::SessionInterface::disable_validator(validator_index_u32); - } - }, +/// Inform the [`DisablingStrategy`] implementation about the new offender and disable the list of +/// validators provided by [`make_disabling_decision`]. +fn add_offending_validator(params: &SlashParams) { + DisabledValidators::::mutate(|disabled| { + if let Some(offender) = + T::DisablingStrategy::decision(params.stash, params.slash_era, &disabled) + { + // Add the validator to `DisabledValidators` and disable it. Do nothing if it is + // already disabled. + if let Err(index) = disabled.binary_search_by_key(&offender, |index| *index) { + disabled.insert(index, offender); + T::SessionInterface::disable_validator(offender); + } } }); + + // `DisabledValidators` should be kept sorted + debug_assert!(DisabledValidators::::get().windows(2).all(|pair| pair[0] < pair[1])); } /// Slash nominators. Accepts general parameters and the prior slash percentage of the validator. @@ -609,8 +579,13 @@ pub fn do_slash( }; let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era); + if value.is_zero() { + // nothing to do + return + } - if !value.is_zero() { + // Skip slashing for virtual stakers. The pallets managing them should handle the slashing. + if !Pallet::::is_virtual_staker(stash) { let (imbalance, missing) = T::Currency::slash(stash, value); slashed_imbalance.subsume(imbalance); @@ -618,17 +593,14 @@ pub fn do_slash( // deduct overslash from the reward payout *reward_payout = reward_payout.saturating_sub(missing); } + } - let _ = ledger - .update() - .defensive_proof("ledger fetched from storage so it exists in storage; qed."); + let _ = ledger + .update() + .defensive_proof("ledger fetched from storage so it exists in storage; qed."); - // trigger the event - >::deposit_event(super::Event::::Slashed { - staker: stash.clone(), - amount: value, - }); - } + // trigger the event + >::deposit_event(super::Event::::Slashed { staker: stash.clone(), amount: value }); } /// Apply a previously-unapplied slash. diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index a5c9abe2f176233f1f66e9872fde0a6bde99bbcb..3cb51604aa6bb1fc7abe06af069d666e70b223c6 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -27,7 +27,7 @@ use frame_support::{ assert_noop, assert_ok, assert_storage_noop, dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, pallet_prelude::*, - traits::{Currency, Get, ReservableCurrency}, + traits::{Currency, Get, InspectLockableCurrency, ReservableCurrency}, }; use mock::*; @@ -38,7 +38,7 @@ use sp_runtime::{ Perbill, Percent, Perquintill, Rounding, TokenError, }; use sp_staking::{ - offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, + offence::{OffenceDetails, OnOffenceHandler}, SessionIndex, }; use sp_std::prelude::*; @@ -623,12 +623,8 @@ fn nominating_and_rewards_should_work() { )); assert_ok!(Staking::nominate(RuntimeOrigin::signed(1), vec![11, 21, 31])); - assert_ok!(Staking::bond( - RuntimeOrigin::signed(3), - 1000, - RewardDestination::Account(3) - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![11, 21, 41])); + // the second nominator is virtual. + bond_virtual_nominator(3, 333, 1000, vec![11, 21, 41]); // the total reward for era 0 let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); @@ -694,10 +690,12 @@ fn nominating_and_rewards_should_work() { ); // Nominator 3: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 21]'s reward. ==> // 2/9 + 3/11 + assert_eq!(Balances::total_balance(&3), initial_balance); + // 333 is the reward destination for 3. assert_eq_error_rate!( - Balances::total_balance(&3), - initial_balance + (2 * payout_for_11 / 9 + 3 * payout_for_21 / 11), - 2, + Balances::total_balance(&333), + 2 * payout_for_11 / 9 + 3 * payout_for_21 / 11, + 2 ); // Validator 11: got 800 / 1800 external stake => 8/18 =? 4/9 => Validator's share = 5/9 @@ -718,56 +716,65 @@ fn nominating_and_rewards_should_work() { #[test] fn nominators_also_get_slashed_pro_rata() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - let slash_percent = Perbill::from_percent(5); - let initial_exposure = Staking::eras_stakers(active_era(), &11); - // 101 is a nominator for 11 - assert_eq!(initial_exposure.others.first().unwrap().who, 101); - - // staked values; - let nominator_stake = Staking::ledger(101.into()).unwrap().active; - let nominator_balance = balances(&101).0; - let validator_stake = Staking::ledger(11.into()).unwrap().active; - let validator_balance = balances(&11).0; - let exposed_stake = initial_exposure.total; - let exposed_validator = initial_exposure.own; - let exposed_nominator = initial_exposure.others.first().unwrap().value; - - // 11 goes offline - on_offence_now( - &[OffenceDetails { offender: (11, initial_exposure.clone()), reporters: vec![] }], - &[slash_percent], - ); + ExtBuilder::default() + .validator_count(4) + .set_status(41, StakerStatus::Validator) + .build_and_execute(|| { + mock::start_active_era(1); + let slash_percent = Perbill::from_percent(5); + let initial_exposure = Staking::eras_stakers(active_era(), &11); + // 101 is a nominator for 11 + assert_eq!(initial_exposure.others.first().unwrap().who, 101); + + // staked values; + let nominator_stake = Staking::ledger(101.into()).unwrap().active; + let nominator_balance = balances(&101).0; + let validator_stake = Staking::ledger(11.into()).unwrap().active; + let validator_balance = balances(&11).0; + let exposed_stake = initial_exposure.total; + let exposed_validator = initial_exposure.own; + let exposed_nominator = initial_exposure.others.first().unwrap().value; + + // 11 goes offline + on_offence_now( + &[OffenceDetails { offender: (11, initial_exposure.clone()), reporters: vec![] }], + &[slash_percent], + ); - // both stakes must have been decreased. - assert!(Staking::ledger(101.into()).unwrap().active < nominator_stake); - assert!(Staking::ledger(11.into()).unwrap().active < validator_stake); + // both stakes must have been decreased. + assert!(Staking::ledger(101.into()).unwrap().active < nominator_stake); + assert!(Staking::ledger(11.into()).unwrap().active < validator_stake); - let slash_amount = slash_percent * exposed_stake; - let validator_share = - Perbill::from_rational(exposed_validator, exposed_stake) * slash_amount; - let nominator_share = - Perbill::from_rational(exposed_nominator, exposed_stake) * slash_amount; + let slash_amount = slash_percent * exposed_stake; + let validator_share = + Perbill::from_rational(exposed_validator, exposed_stake) * slash_amount; + let nominator_share = + Perbill::from_rational(exposed_nominator, exposed_stake) * slash_amount; - // both slash amounts need to be positive for the test to make sense. - assert!(validator_share > 0); - assert!(nominator_share > 0); + // both slash amounts need to be positive for the test to make sense. + assert!(validator_share > 0); + assert!(nominator_share > 0); - // both stakes must have been decreased pro-rata. - assert_eq!(Staking::ledger(101.into()).unwrap().active, nominator_stake - nominator_share); - assert_eq!(Staking::ledger(11.into()).unwrap().active, validator_stake - validator_share); - assert_eq!( - balances(&101).0, // free balance - nominator_balance - nominator_share, - ); - assert_eq!( - balances(&11).0, // free balance - validator_balance - validator_share, - ); - // Because slashing happened. - assert!(is_disabled(11)); - }); + // both stakes must have been decreased pro-rata. + assert_eq!( + Staking::ledger(101.into()).unwrap().active, + nominator_stake - nominator_share + ); + assert_eq!( + Staking::ledger(11.into()).unwrap().active, + validator_stake - validator_share + ); + assert_eq!( + balances(&101).0, // free balance + nominator_balance - nominator_share, + ); + assert_eq!( + balances(&11).0, // free balance + validator_balance - validator_share, + ); + // Because slashing happened. + assert!(is_disabled(11)); + }); } #[test] @@ -1893,7 +1900,7 @@ fn reap_stash_works() { .balance_factor(10) .build_and_execute(|| { // given - assert_eq!(Balances::free_balance(11), 10 * 1000); + assert_eq!(Balances::balance_locked(STAKING_ID, &11), 10 * 1000); assert_eq!(Staking::bonded(&11), Some(11)); assert!(>::contains_key(&11)); @@ -1919,6 +1926,8 @@ fn reap_stash_works() { assert!(!>::contains_key(&11)); assert!(!>::contains_key(&11)); assert!(!>::contains_key(&11)); + // lock is removed. + assert_eq!(Balances::balance_locked(STAKING_ID, &11), 0); }); } @@ -2401,7 +2410,7 @@ fn era_is_always_same_length() { } #[test] -fn offence_forces_new_era() { +fn offence_doesnt_force_new_era() { ExtBuilder::default().build_and_execute(|| { on_offence_now( &[OffenceDetails { @@ -2411,7 +2420,7 @@ fn offence_forces_new_era() { &[Perbill::from_percent(5)], ); - assert_eq!(Staking::force_era(), Forcing::ForceNew); + assert_eq!(Staking::force_era(), Forcing::NotForcing); }); } @@ -2435,26 +2444,32 @@ fn offence_ensures_new_era_without_clobbering() { #[test] fn offence_deselects_validator_even_when_slash_is_zero() { - ExtBuilder::default().build_and_execute(|| { - assert!(Session::validators().contains(&11)); - assert!(>::contains_key(11)); + ExtBuilder::default() + .validator_count(7) + .set_status(41, StakerStatus::Validator) + .set_status(51, StakerStatus::Validator) + .set_status(201, StakerStatus::Validator) + .set_status(202, StakerStatus::Validator) + .build_and_execute(|| { + assert!(Session::validators().contains(&11)); + assert!(>::contains_key(11)); - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), &11)), - reporters: vec![], - }], - &[Perbill::from_percent(0)], - ); + on_offence_now( + &[OffenceDetails { + offender: (11, Staking::eras_stakers(active_era(), &11)), + reporters: vec![], + }], + &[Perbill::from_percent(0)], + ); - assert_eq!(Staking::force_era(), Forcing::ForceNew); - assert!(!>::contains_key(11)); + assert_eq!(Staking::force_era(), Forcing::NotForcing); + assert!(is_disabled(11)); - mock::start_active_era(1); + mock::start_active_era(1); - assert!(!Session::validators().contains(&11)); - assert!(!>::contains_key(11)); - }); + // The validator should be reenabled in the new era + assert!(!is_disabled(11)); + }); } #[test] @@ -2479,71 +2494,70 @@ fn slashing_performed_according_exposure() { } #[test] -fn slash_in_old_span_does_not_deselect() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - - assert!(>::contains_key(11)); - assert!(Session::validators().contains(&11)); - - on_offence_now( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), &11)), - reporters: vec![], - }], - &[Perbill::from_percent(0)], - ); +fn validator_is_not_disabled_for_an_offence_in_previous_era() { + ExtBuilder::default() + .validator_count(4) + .set_status(41, StakerStatus::Validator) + .build_and_execute(|| { + mock::start_active_era(1); - assert_eq!(Staking::force_era(), Forcing::ForceNew); - assert!(!>::contains_key(11)); + assert!(>::contains_key(11)); + assert!(Session::validators().contains(&11)); - mock::start_active_era(2); + on_offence_now( + &[OffenceDetails { + offender: (11, Staking::eras_stakers(active_era(), &11)), + reporters: vec![], + }], + &[Perbill::from_percent(0)], + ); - Staking::validate(RuntimeOrigin::signed(11), Default::default()).unwrap(); - assert_eq!(Staking::force_era(), Forcing::NotForcing); - assert!(>::contains_key(11)); - assert!(!Session::validators().contains(&11)); + assert_eq!(Staking::force_era(), Forcing::NotForcing); + assert!(is_disabled(11)); - mock::start_active_era(3); + mock::start_active_era(2); - // this staker is in a new slashing span now, having re-registered after - // their prior slash. + // the validator is not disabled in the new era + Staking::validate(RuntimeOrigin::signed(11), Default::default()).unwrap(); + assert_eq!(Staking::force_era(), Forcing::NotForcing); + assert!(>::contains_key(11)); + assert!(Session::validators().contains(&11)); - on_offence_in_era( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), &11)), - reporters: vec![], - }], - &[Perbill::from_percent(0)], - 1, - DisableStrategy::WhenSlashed, - ); + mock::start_active_era(3); - // the validator doesn't get chilled again - assert!(Validators::::iter().any(|(stash, _)| stash == 11)); + // an offence committed in era 1 is reported in era 3 + on_offence_in_era( + &[OffenceDetails { + offender: (11, Staking::eras_stakers(active_era(), &11)), + reporters: vec![], + }], + &[Perbill::from_percent(0)], + 1, + ); - // but we are still forcing a new era - assert_eq!(Staking::force_era(), Forcing::ForceNew); + // the validator doesn't get disabled for an old offence + assert!(Validators::::iter().any(|(stash, _)| stash == 11)); + assert!(!is_disabled(11)); - on_offence_in_era( - &[OffenceDetails { - offender: (11, Staking::eras_stakers(active_era(), &11)), - reporters: vec![], - }], - // NOTE: A 100% slash here would clean up the account, causing de-registration. - &[Perbill::from_percent(95)], - 1, - DisableStrategy::WhenSlashed, - ); + // and we are not forcing a new era + assert_eq!(Staking::force_era(), Forcing::NotForcing); - // the validator doesn't get chilled again - assert!(Validators::::iter().any(|(stash, _)| stash == 11)); + on_offence_in_era( + &[OffenceDetails { + offender: (11, Staking::eras_stakers(active_era(), &11)), + reporters: vec![], + }], + // NOTE: A 100% slash here would clean up the account, causing de-registration. + &[Perbill::from_percent(95)], + 1, + ); - // but it's disabled - assert!(is_disabled(11)); - // and we are still forcing a new era - assert_eq!(Staking::force_era(), Forcing::ForceNew); - }); + // the validator doesn't get disabled again + assert!(Validators::::iter().any(|(stash, _)| stash == 11)); + assert!(!is_disabled(11)); + // and we are still not forcing a new era + assert_eq!(Staking::force_era(), Forcing::NotForcing); + }); } #[test] @@ -2671,7 +2685,7 @@ fn dont_slash_if_fraction_is_zero() { // The validator hasn't been slashed. The new era is not forced. assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Staking::force_era(), Forcing::ForceNew); + assert_eq!(Staking::force_era(), Forcing::NotForcing); }); } @@ -2692,7 +2706,7 @@ fn only_slash_for_max_in_era() { // The validator has been slashed and has been force-chilled. assert_eq!(Balances::free_balance(11), 500); - assert_eq!(Staking::force_era(), Forcing::ForceNew); + assert_eq!(Staking::force_era(), Forcing::NotForcing); on_offence_now( &[OffenceDetails { @@ -2833,7 +2847,6 @@ fn slashing_nominators_by_span_max() { }], &[Perbill::from_percent(10)], 2, - DisableStrategy::WhenSlashed, ); assert_eq!(Balances::free_balance(11), 900); @@ -2860,7 +2873,6 @@ fn slashing_nominators_by_span_max() { }], &[Perbill::from_percent(30)], 3, - DisableStrategy::WhenSlashed, ); // 11 was not further slashed, but 21 and 101 were. @@ -2882,7 +2894,6 @@ fn slashing_nominators_by_span_max() { }], &[Perbill::from_percent(20)], 2, - DisableStrategy::WhenSlashed, ); // 11 was further slashed, but 21 and 101 were not. @@ -2999,11 +3010,8 @@ fn deferred_slashes_are_deferred() { assert!(matches!( staking_events_since_last_call().as_slice(), &[ - Event::Chilled { stash: 11 }, - Event::ForceEra { mode: Forcing::ForceNew }, Event::SlashReported { validator: 11, slash_era: 1, .. }, Event::StakersElected, - Event::ForceEra { mode: Forcing::NotForcing }, .., Event::Slashed { staker: 11, amount: 100 }, Event::Slashed { staker: 101, amount: 12 } @@ -3029,7 +3037,6 @@ fn retroactive_deferred_slashes_two_eras_before() { &[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }], &[Perbill::from_percent(10)], 1, // should be deferred for two full eras, and applied at the beginning of era 4. - DisableStrategy::Never, ); mock::start_active_era(4); @@ -3037,8 +3044,6 @@ fn retroactive_deferred_slashes_two_eras_before() { assert!(matches!( staking_events_since_last_call().as_slice(), &[ - Event::Chilled { stash: 11 }, - Event::ForceEra { mode: Forcing::ForceNew }, Event::SlashReported { validator: 11, slash_era: 1, .. }, .., Event::Slashed { staker: 11, amount: 100 }, @@ -3067,7 +3072,6 @@ fn retroactive_deferred_slashes_one_before() { &[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }], &[Perbill::from_percent(10)], 2, // should be deferred for two full eras, and applied at the beginning of era 5. - DisableStrategy::Never, ); mock::start_active_era(4); @@ -3197,7 +3201,6 @@ fn remove_deferred() { &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], &[Perbill::from_percent(15)], 1, - DisableStrategy::WhenSlashed, ); // fails if empty @@ -3312,192 +3315,198 @@ fn remove_multi_deferred() { #[test] fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_validator() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - assert_eq_uvec!(Session::validators(), vec![11, 21]); - - // pre-slash balance - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + ExtBuilder::default() + .validator_count(7) + .set_status(41, StakerStatus::Validator) + .set_status(51, StakerStatus::Validator) + .set_status(201, StakerStatus::Validator) + .set_status(202, StakerStatus::Validator) + .build_and_execute(|| { + mock::start_active_era(1); + assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41, 51, 201, 202]); - // 100 has approval for 11 as of now - assert!(Staking::nominators(101).unwrap().targets.contains(&11)); + // pre-slash balance + assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(101), 2000); - // 11 and 21 both have the support of 100 - let exposure_11 = Staking::eras_stakers(active_era(), &11); - let exposure_21 = Staking::eras_stakers(active_era(), &21); + // 100 has approval for 11 as of now + assert!(Staking::nominators(101).unwrap().targets.contains(&11)); - assert_eq!(exposure_11.total, 1000 + 125); - assert_eq!(exposure_21.total, 1000 + 375); + // 11 and 21 both have the support of 100 + let exposure_11 = Staking::eras_stakers(active_era(), &11); + let exposure_21 = Staking::eras_stakers(active_era(), &21); - on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], - &[Perbill::from_percent(10)], - ); + assert_eq!(exposure_11.total, 1000 + 125); + assert_eq!(exposure_21.total, 1000 + 375); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, - Event::Chilled { stash: 11 }, - Event::ForceEra { mode: Forcing::ForceNew }, - Event::SlashReported { - validator: 11, - fraction: Perbill::from_percent(10), - slash_era: 1 - }, - Event::Slashed { staker: 11, amount: 100 }, - Event::Slashed { staker: 101, amount: 12 }, - ] - ); + on_offence_now( + &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], + &[Perbill::from_percent(10)], + ); - // post-slash balance - let nominator_slash_amount_11 = 125 / 10; - assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(101), 2000 - nominator_slash_amount_11); + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, + Event::SlashReported { + validator: 11, + fraction: Perbill::from_percent(10), + slash_era: 1 + }, + Event::Slashed { staker: 11, amount: 100 }, + Event::Slashed { staker: 101, amount: 12 }, + ] + ); - // check that validator was chilled. - assert!(Validators::::iter().all(|(stash, _)| stash != 11)); + // post-slash balance + let nominator_slash_amount_11 = 125 / 10; + assert_eq!(Balances::free_balance(11), 900); + assert_eq!(Balances::free_balance(101), 2000 - nominator_slash_amount_11); - // actually re-bond the slashed validator - assert_ok!(Staking::validate(RuntimeOrigin::signed(11), Default::default())); + // check that validator was disabled. + assert!(is_disabled(11)); - mock::start_active_era(2); - let exposure_11 = Staking::eras_stakers(active_era(), &11); - let exposure_21 = Staking::eras_stakers(active_era(), &21); + // actually re-bond the slashed validator + assert_ok!(Staking::validate(RuntimeOrigin::signed(11), Default::default())); - // 11's own expo is reduced. sum of support from 11 is less (448), which is 500 - // 900 + 146 - assert!(matches!(exposure_11, Exposure { own: 900, total: 1046, .. })); - // 1000 + 342 - assert!(matches!(exposure_21, Exposure { own: 1000, total: 1342, .. })); - assert_eq!(500 - 146 - 342, nominator_slash_amount_11); - }); + mock::start_active_era(2); + let exposure_11 = Staking::eras_stakers(active_era(), &11); + let exposure_21 = Staking::eras_stakers(active_era(), &21); + + // 11's own expo is reduced. sum of support from 11 is less (448), which is 500 + // 900 + 146 + assert!(matches!(exposure_11, Exposure { own: 900, total: 1046, .. })); + // 1000 + 342 + assert!(matches!(exposure_21, Exposure { own: 1000, total: 1342, .. })); + assert_eq!(500 - 146 - 342, nominator_slash_amount_11); + }); } #[test] -fn non_slashable_offence_doesnt_disable_validator() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - assert_eq_uvec!(Session::validators(), vec![11, 21]); +fn non_slashable_offence_disables_validator() { + ExtBuilder::default() + .validator_count(7) + .set_status(41, StakerStatus::Validator) + .set_status(51, StakerStatus::Validator) + .set_status(201, StakerStatus::Validator) + .set_status(202, StakerStatus::Validator) + .build_and_execute(|| { + mock::start_active_era(1); + assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41, 51, 201, 202]); - let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11); - let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21); + let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11); + let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21); - // offence with no slash associated - on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], - &[Perbill::zero()], - ); + // offence with no slash associated + on_offence_now( + &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], + &[Perbill::zero()], + ); - // it does NOT affect the nominator. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + // it does NOT affect the nominator. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - // offence that slashes 25% of the bond - on_offence_now( - &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], - &[Perbill::from_percent(25)], - ); + // offence that slashes 25% of the bond + on_offence_now( + &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], + &[Perbill::from_percent(25)], + ); - // it DOES NOT affect the nominator. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + // it DOES NOT affect the nominator. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, - Event::Chilled { stash: 11 }, - Event::ForceEra { mode: Forcing::ForceNew }, - Event::SlashReported { - validator: 11, - fraction: Perbill::from_percent(0), - slash_era: 1 - }, - Event::Chilled { stash: 21 }, - Event::SlashReported { - validator: 21, - fraction: Perbill::from_percent(25), - slash_era: 1 - }, - Event::Slashed { staker: 21, amount: 250 }, - Event::Slashed { staker: 101, amount: 94 } - ] - ); + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, + Event::SlashReported { + validator: 11, + fraction: Perbill::from_percent(0), + slash_era: 1 + }, + Event::SlashReported { + validator: 21, + fraction: Perbill::from_percent(25), + slash_era: 1 + }, + Event::Slashed { staker: 21, amount: 250 }, + Event::Slashed { staker: 101, amount: 94 } + ] + ); - // the offence for validator 10 wasn't slashable so it wasn't disabled - assert!(!is_disabled(11)); - // whereas validator 20 gets disabled - assert!(is_disabled(21)); - }); + // the offence for validator 11 wasn't slashable but it is disabled + assert!(is_disabled(11)); + // validator 21 gets disabled too + assert!(is_disabled(21)); + }); } #[test] fn slashing_independent_of_disabling_validator() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); - assert_eq_uvec!(Session::validators(), vec![11, 21]); + ExtBuilder::default() + .validator_count(5) + .set_status(41, StakerStatus::Validator) + .set_status(51, StakerStatus::Validator) + .build_and_execute(|| { + mock::start_active_era(1); + assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41, 51]); - let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11); - let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21); + let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11); + let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21); - let now = Staking::active_era().unwrap().index; + let now = Staking::active_era().unwrap().index; - // offence with no slash associated, BUT disabling - on_offence_in_era( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], - &[Perbill::zero()], - now, - DisableStrategy::Always, - ); + // offence with no slash associated + on_offence_in_era( + &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], + &[Perbill::zero()], + now, + ); - // nomination remains untouched. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + // nomination remains untouched. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - // offence that slashes 25% of the bond, BUT not disabling - on_offence_in_era( - &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], - &[Perbill::from_percent(25)], - now, - DisableStrategy::Never, - ); + // offence that slashes 25% of the bond + on_offence_in_era( + &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], + &[Perbill::from_percent(25)], + now, + ); - // nomination remains untouched. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + // nomination remains untouched. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, - Event::Chilled { stash: 11 }, - Event::ForceEra { mode: Forcing::ForceNew }, - Event::SlashReported { - validator: 11, - fraction: Perbill::from_percent(0), - slash_era: 1 - }, - Event::Chilled { stash: 21 }, - Event::SlashReported { - validator: 21, - fraction: Perbill::from_percent(25), - slash_era: 1 - }, - Event::Slashed { staker: 21, amount: 250 }, - Event::Slashed { staker: 101, amount: 94 } - ] - ); + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, + Event::SlashReported { + validator: 11, + fraction: Perbill::from_percent(0), + slash_era: 1 + }, + Event::SlashReported { + validator: 21, + fraction: Perbill::from_percent(25), + slash_era: 1 + }, + Event::Slashed { staker: 21, amount: 250 }, + Event::Slashed { staker: 101, amount: 94 } + ] + ); - // the offence for validator 10 was explicitly disabled - assert!(is_disabled(11)); - // whereas validator 21 is explicitly not disabled - assert!(!is_disabled(21)); - }); + // first validator is disabled but not slashed + assert!(is_disabled(11)); + // second validator is slashed but not disabled + assert!(!is_disabled(21)); + }); } #[test] -fn offence_threshold_triggers_new_era() { +fn offence_threshold_doesnt_trigger_new_era() { ExtBuilder::default() .validator_count(4) .set_status(41, StakerStatus::Validator) @@ -3506,12 +3515,14 @@ fn offence_threshold_triggers_new_era() { assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41]); assert_eq!( - ::OffendingValidatorsThreshold::get(), - Perbill::from_percent(75), + UpToLimitDisablingStrategy::::disable_limit( + Session::validators().len() + ), + 1 ); - // we have 4 validators and an offending validator threshold of 75%, - // once the third validator commits an offence a new era should be forced + // we have 4 validators and an offending validator threshold of 1/3, + // even if the third validator commits an offence a new era should not be forced let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11); let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21); @@ -3522,6 +3533,9 @@ fn offence_threshold_triggers_new_era() { &[Perbill::zero()], ); + // 11 should be disabled because the byzantine threshold is 1 + assert!(is_disabled(11)); + assert_eq!(ForceEra::::get(), Forcing::NotForcing); on_offence_now( @@ -3529,6 +3543,10 @@ fn offence_threshold_triggers_new_era() { &[Perbill::zero()], ); + // 21 should not be disabled because the number of disabled validators will be above the + // byzantine threshold + assert!(!is_disabled(21)); + assert_eq!(ForceEra::::get(), Forcing::NotForcing); on_offence_now( @@ -3536,28 +3554,29 @@ fn offence_threshold_triggers_new_era() { &[Perbill::zero()], ); - assert_eq!(ForceEra::::get(), Forcing::ForceNew); + // same for 31 + assert!(!is_disabled(31)); + + assert_eq!(ForceEra::::get(), Forcing::NotForcing); }); } #[test] fn disabled_validators_are_kept_disabled_for_whole_era() { ExtBuilder::default() - .validator_count(4) + .validator_count(7) .set_status(41, StakerStatus::Validator) + .set_status(51, StakerStatus::Validator) + .set_status(201, StakerStatus::Validator) + .set_status(202, StakerStatus::Validator) .build_and_execute(|| { mock::start_active_era(1); - assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41]); + assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41, 51, 201, 202]); assert_eq!(::SessionsPerEra::get(), 3); let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11); let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21); - on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], - &[Perbill::zero()], - ); - on_offence_now( &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], &[Perbill::from_percent(25)], @@ -3566,18 +3585,15 @@ fn disabled_validators_are_kept_disabled_for_whole_era() { // nominations are not updated. assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - // validator 11 should not be disabled since the offence wasn't slashable - assert!(!is_disabled(11)); // validator 21 gets disabled since it got slashed assert!(is_disabled(21)); advance_session(); // disabled validators should carry-on through all sessions in the era - assert!(!is_disabled(11)); assert!(is_disabled(21)); - // validator 11 should now get disabled + // validator 11 commits an offence on_offence_now( &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], &[Perbill::from_percent(25)], @@ -3687,27 +3703,34 @@ fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { #[test] fn zero_slash_keeps_nominators() { - ExtBuilder::default().build_and_execute(|| { - mock::start_active_era(1); + ExtBuilder::default() + .validator_count(7) + .set_status(41, StakerStatus::Validator) + .set_status(51, StakerStatus::Validator) + .set_status(201, StakerStatus::Validator) + .set_status(202, StakerStatus::Validator) + .build_and_execute(|| { + mock::start_active_era(1); - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(11), 1000); - let exposure = Staking::eras_stakers(active_era(), &11); - assert_eq!(Balances::free_balance(101), 2000); + let exposure = Staking::eras_stakers(active_era(), &11); + assert_eq!(Balances::free_balance(101), 2000); - on_offence_now( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], - &[Perbill::from_percent(0)], - ); + on_offence_now( + &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], + &[Perbill::from_percent(0)], + ); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(101), 2000); - // 11 is still removed.. - assert!(Validators::::iter().all(|(stash, _)| stash != 11)); - // but their nominations are kept. - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - }); + // 11 is not removed but disabled + assert!(Validators::::iter().any(|(stash, _)| stash == 11)); + assert!(is_disabled(11)); + // and their nominations are kept. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + }); } #[test] @@ -4710,7 +4733,7 @@ fn offences_weight_calculated_correctly() { let zero_offence_weight = ::DbWeight::get().reads_writes(4, 1); assert_eq!( - Staking::on_offence(&[], &[Perbill::from_percent(50)], 0, DisableStrategy::WhenSlashed), + Staking::on_offence(&[], &[Perbill::from_percent(50)], 0), zero_offence_weight ); @@ -4735,7 +4758,6 @@ fn offences_weight_calculated_correctly() { &offenders, &[Perbill::from_percent(50)], 0, - DisableStrategy::WhenSlashed ), n_offence_unapplied_weight ); @@ -4765,7 +4787,6 @@ fn offences_weight_calculated_correctly() { &one_offender, &[Perbill::from_percent(50)], 0, - DisableStrategy::WhenSlashed{} ), one_offence_unapplied_weight ); @@ -6775,6 +6796,113 @@ fn test_validator_exposure_is_backward_compatible_with_non_paged_rewards_payout( }); } +#[test] +fn test_runtime_api_pending_rewards() { + ExtBuilder::default().build_and_execute(|| { + // GIVEN + let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); + let stake = 100; + + // validator with non-paged exposure, rewards marked in legacy claimed rewards. + let validator_one = 301; + // validator with non-paged exposure, rewards marked in paged claimed rewards. + let validator_two = 302; + // validator with paged exposure. + let validator_three = 303; + + // Set staker + for v in validator_one..=validator_three { + let _ = Balances::make_free_balance_be(&v, stake); + assert_ok!(Staking::bond(RuntimeOrigin::signed(v), stake, RewardDestination::Staked)); + } + + // Add reward points + let reward = EraRewardPoints:: { + total: 1, + individual: vec![(validator_one, 1), (validator_two, 1), (validator_three, 1)] + .into_iter() + .collect(), + }; + ErasRewardPoints::::insert(0, reward); + + // build exposure + let mut individual_exposures: Vec> = vec![]; + for i in 0..=MaxExposurePageSize::get() { + individual_exposures.push(IndividualExposure { who: i.into(), value: stake }); + } + let exposure = Exposure:: { + total: stake * (MaxExposurePageSize::get() as Balance + 2), + own: stake, + others: individual_exposures, + }; + + // add non-paged exposure for one and two. + >::insert(0, validator_one, exposure.clone()); + >::insert(0, validator_two, exposure.clone()); + // add paged exposure for third validator + EraInfo::::set_exposure(0, &validator_three, exposure); + + // add some reward to be distributed + ErasValidatorReward::::insert(0, 1000); + + // mark rewards claimed for validator_one in legacy claimed rewards + >::insert( + validator_one, + StakingLedgerInspect { + stash: validator_one, + total: stake, + active: stake, + unlocking: Default::default(), + legacy_claimed_rewards: bounded_vec![0], + }, + ); + + // SCENARIO ONE: rewards already marked claimed in legacy storage. + // runtime api should return false for pending rewards for validator_one. + assert!(!EraInfo::::pending_rewards(0, &validator_one)); + // and if we try to pay, we get an error. + assert_noop!( + Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_one, 0), + Error::::AlreadyClaimed.with_weight(err_weight) + ); + + // SCENARIO TWO: non-paged exposure + // validator two has not claimed rewards, so pending rewards is true. + assert!(EraInfo::::pending_rewards(0, &validator_two)); + // and payout works + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_two, 0)); + // now pending rewards is false. + assert!(!EraInfo::::pending_rewards(0, &validator_two)); + // and payout fails + assert_noop!( + Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_two, 0), + Error::::AlreadyClaimed.with_weight(err_weight) + ); + + // SCENARIO THREE: validator with paged exposure (two pages). + // validator three has not claimed rewards, so pending rewards is true. + assert!(EraInfo::::pending_rewards(0, &validator_three)); + // and payout works + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_three, 0)); + // validator three has two pages of exposure, so pending rewards is still true. + assert!(EraInfo::::pending_rewards(0, &validator_three)); + // payout again + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_three, 0)); + // now pending rewards is false. + assert!(!EraInfo::::pending_rewards(0, &validator_three)); + // and payout fails + assert_noop!( + Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_three, 0), + Error::::AlreadyClaimed.with_weight(err_weight) + ); + + // for eras with no exposure, pending rewards is false. + assert!(!EraInfo::::pending_rewards(0, &validator_one)); + assert!(!EraInfo::::pending_rewards(0, &validator_two)); + assert!(!EraInfo::::pending_rewards(0, &validator_three)); + }); +} + mod staking_interface { use frame_support::storage::with_storage_layer; use sp_staking::StakingInterface; @@ -6849,6 +6977,328 @@ mod staking_interface { } } +mod staking_unchecked { + use sp_staking::{Stake, StakingInterface, StakingUnchecked}; + + use super::*; + + #[test] + fn virtual_bond_does_not_lock() { + ExtBuilder::default().build_and_execute(|| { + mock::start_active_era(1); + assert_eq!(Balances::free_balance(10), 1); + // 10 can bond more than its balance amount since we do not require lock for virtual + // bonding. + assert_ok!(::virtual_bond(&10, 100, &15)); + // nothing is locked on 10. + assert_eq!(Balances::balance_locked(STAKING_ID, &10), 0); + // adding more balance does not lock anything as well. + assert_ok!(::bond_extra(&10, 1000)); + // but ledger is updated correctly. + assert_eq!( + ::stake(&10), + Ok(Stake { total: 1100, active: 1100 }) + ); + + // lets try unbonding some amount. + assert_ok!(::unbond(&10, 200)); + assert_eq!( + Staking::ledger(10.into()).unwrap(), + StakingLedgerInspect { + stash: 10, + total: 1100, + active: 1100 - 200, + unlocking: bounded_vec![UnlockChunk { value: 200, era: 1 + 3 }], + legacy_claimed_rewards: bounded_vec![], + } + ); + + assert_eq!( + ::stake(&10), + Ok(Stake { total: 1100, active: 900 }) + ); + // still no locks. + assert_eq!(Balances::balance_locked(STAKING_ID, &10), 0); + + mock::start_active_era(2); + // cannot withdraw without waiting for unbonding period. + assert_ok!(::withdraw_unbonded(10, 0)); + assert_eq!( + ::stake(&10), + Ok(Stake { total: 1100, active: 900 }) + ); + + // in era 4, 10 can withdraw unlocking amount. + mock::start_active_era(4); + assert_ok!(::withdraw_unbonded(10, 0)); + assert_eq!( + ::stake(&10), + Ok(Stake { total: 900, active: 900 }) + ); + + // unbond all. + assert_ok!(::unbond(&10, 900)); + assert_eq!( + ::stake(&10), + Ok(Stake { total: 900, active: 0 }) + ); + mock::start_active_era(7); + assert_ok!(::withdraw_unbonded(10, 0)); + + // ensure withdrawing all amount cleans up storage. + assert_eq!(Staking::ledger(10.into()), Err(Error::::NotStash)); + assert_eq!(VirtualStakers::::contains_key(10), false); + }) + } + + #[test] + fn virtual_staker_cannot_pay_reward_to_self_account() { + ExtBuilder::default().build_and_execute(|| { + // cannot set payee to self + assert_noop!( + ::virtual_bond(&10, 100, &10), + Error::::RewardDestinationRestricted + ); + + // to another account works + assert_ok!(::virtual_bond(&10, 100, &11)); + + // cannot set via set_payee as well. + assert_noop!( + ::update_payee(&10, &10), + Error::::RewardDestinationRestricted + ); + }); + } + + #[test] + fn virtual_staker_cannot_bond_again() { + ExtBuilder::default().build_and_execute(|| { + // 200 virtual bonds + bond_virtual_nominator(200, 201, 500, vec![11, 21]); + + // Tries bonding again + assert_noop!( + ::virtual_bond(&200, 200, &201), + Error::::AlreadyBonded + ); + + // And again with a different reward destination. + assert_noop!( + ::virtual_bond(&200, 200, &202), + Error::::AlreadyBonded + ); + + // Direct bond is not allowed as well. + assert_noop!( + ::bond(&200, 200, &202), + Error::::AlreadyBonded + ); + }); + } + + #[test] + fn normal_staker_cannot_virtual_bond() { + ExtBuilder::default().build_and_execute(|| { + // 101 is a nominator trying to virtual bond + assert_noop!( + ::virtual_bond(&101, 200, &102), + Error::::AlreadyBonded + ); + + // validator 21 tries to virtual bond + assert_noop!( + ::virtual_bond(&21, 200, &22), + Error::::AlreadyBonded + ); + }); + } + + #[test] + fn migrate_virtual_staker() { + ExtBuilder::default().build_and_execute(|| { + // give some balance to 200 + Balances::make_free_balance_be(&200, 2000); + + // stake + assert_ok!(Staking::bond(RuntimeOrigin::signed(200), 1000, RewardDestination::Staked)); + assert_eq!(Balances::balance_locked(crate::STAKING_ID, &200), 1000); + + // migrate them to virtual staker + ::migrate_to_virtual_staker(&200); + // payee needs to be updated to a non-stash account. + assert_ok!(::update_payee(&200, &201)); + + // ensure the balance is not locked anymore + assert_eq!(Balances::balance_locked(crate::STAKING_ID, &200), 0); + + // and they are marked as virtual stakers + assert_eq!(Pallet::::is_virtual_staker(&200), true); + }); + } + + #[test] + fn virtual_nominators_are_lazily_slashed() { + ExtBuilder::default() + .validator_count(7) + .set_status(41, StakerStatus::Validator) + .set_status(51, StakerStatus::Validator) + .set_status(201, StakerStatus::Validator) + .set_status(202, StakerStatus::Validator) + .build_and_execute(|| { + mock::start_active_era(1); + let slash_percent = Perbill::from_percent(5); + let initial_exposure = Staking::eras_stakers(active_era(), &11); + // 101 is a nominator for 11 + assert_eq!(initial_exposure.others.first().unwrap().who, 101); + // make 101 a virtual nominator + ::migrate_to_virtual_staker(&101); + // set payee different to self. + assert_ok!(::update_payee(&101, &102)); + + // cache values + let nominator_stake = Staking::ledger(101.into()).unwrap().active; + let nominator_balance = balances(&101).0; + let validator_stake = Staking::ledger(11.into()).unwrap().active; + let validator_balance = balances(&11).0; + let exposed_stake = initial_exposure.total; + let exposed_validator = initial_exposure.own; + let exposed_nominator = initial_exposure.others.first().unwrap().value; + + // 11 goes offline + on_offence_now( + &[OffenceDetails { + offender: (11, initial_exposure.clone()), + reporters: vec![], + }], + &[slash_percent], + ); + + let slash_amount = slash_percent * exposed_stake; + let validator_share = + Perbill::from_rational(exposed_validator, exposed_stake) * slash_amount; + let nominator_share = + Perbill::from_rational(exposed_nominator, exposed_stake) * slash_amount; + + // both slash amounts need to be positive for the test to make sense. + assert!(validator_share > 0); + assert!(nominator_share > 0); + + // both stakes must have been decreased pro-rata. + assert_eq!( + Staking::ledger(101.into()).unwrap().active, + nominator_stake - nominator_share + ); + assert_eq!( + Staking::ledger(11.into()).unwrap().active, + validator_stake - validator_share + ); + + // validator balance is slashed as usual + assert_eq!(balances(&11).0, validator_balance - validator_share); + // Because slashing happened. + assert!(is_disabled(11)); + + // but virtual nominator's balance is not slashed. + assert_eq!(Balances::free_balance(&101), nominator_balance); + // but slash is broadcasted to slash observers. + assert_eq!(SlashObserver::get().get(&101).unwrap(), &nominator_share); + }) + } + + #[test] + fn virtual_stakers_cannot_be_reaped() { + ExtBuilder::default() + // we need enough validators such that disables are allowed. + .validator_count(7) + .set_status(41, StakerStatus::Validator) + .set_status(51, StakerStatus::Validator) + .set_status(201, StakerStatus::Validator) + .set_status(202, StakerStatus::Validator) + .build_and_execute(|| { + // make 101 only nominate 11. + assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![11])); + + mock::start_active_era(1); + + // slash all stake. + let slash_percent = Perbill::from_percent(100); + let initial_exposure = Staking::eras_stakers(active_era(), &11); + // 101 is a nominator for 11 + assert_eq!(initial_exposure.others.first().unwrap().who, 101); + // make 101 a virtual nominator + ::migrate_to_virtual_staker(&101); + // set payee different to self. + assert_ok!(::update_payee(&101, &102)); + + // cache values + let validator_balance = Balances::free_balance(&11); + let validator_stake = Staking::ledger(11.into()).unwrap().total; + let nominator_balance = Balances::free_balance(&101); + let nominator_stake = Staking::ledger(101.into()).unwrap().total; + + // 11 goes offline + on_offence_now( + &[OffenceDetails { + offender: (11, initial_exposure.clone()), + reporters: vec![], + }], + &[slash_percent], + ); + + // both stakes must have been decreased to 0. + assert_eq!(Staking::ledger(101.into()).unwrap().active, 0); + assert_eq!(Staking::ledger(11.into()).unwrap().active, 0); + + // all validator stake is slashed + assert_eq_error_rate!( + validator_balance - validator_stake, + Balances::free_balance(&11), + 1 + ); + // Because slashing happened. + assert!(is_disabled(11)); + + // Virtual nominator's balance is not slashed. + assert_eq!(Balances::free_balance(&101), nominator_balance); + // Slash is broadcasted to slash observers. + assert_eq!(SlashObserver::get().get(&101).unwrap(), &nominator_stake); + + // validator can be reaped. + assert_ok!(Staking::reap_stash(RuntimeOrigin::signed(10), 11, u32::MAX)); + // nominator is a virtual staker and cannot be reaped. + assert_noop!( + Staking::reap_stash(RuntimeOrigin::signed(10), 101, u32::MAX), + Error::::VirtualStakerNotAllowed + ); + }) + } + + #[test] + fn restore_ledger_not_allowed_for_virtual_stakers() { + ExtBuilder::default().has_stakers(true).build_and_execute(|| { + setup_double_bonded_ledgers(); + assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Ok); + set_controller_no_checks(&444); + // 333 is corrupted + assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Corrupted); + // migrate to virtual staker. + ::migrate_to_virtual_staker(&333); + + // recover the ledger won't work for virtual staker + assert_noop!( + Staking::restore_ledger(RuntimeOrigin::root(), 333, None, None, None), + Error::::VirtualStakerNotAllowed + ); + + // migrate 333 back to normal staker + >::remove(333); + + // try restore again + assert_ok!(Staking::restore_ledger(RuntimeOrigin::root(), 333, None, None, None)); + }) + } +} mod ledger { use super::*; @@ -7327,7 +7777,6 @@ mod ledger { mod ledger_recovery { use super::*; - use frame_support::traits::InspectLockableCurrency; #[test] fn inspect_recovery_ledger_simple_works() { @@ -7707,3 +8156,69 @@ mod ledger_recovery { }) } } + +mod byzantine_threshold_disabling_strategy { + use crate::{ + tests::Test, ActiveEra, ActiveEraInfo, DisablingStrategy, UpToLimitDisablingStrategy, + }; + use sp_staking::EraIndex; + + // Common test data - the stash of the offending validator, the era of the offence and the + // active set + const OFFENDER_ID: ::AccountId = 7; + const SLASH_ERA: EraIndex = 1; + const ACTIVE_SET: [::ValidatorId; 7] = [1, 2, 3, 4, 5, 6, 7]; + const OFFENDER_VALIDATOR_IDX: u32 = 6; // the offender is with index 6 in the active set + + #[test] + fn dont_disable_for_ancient_offence() { + sp_io::TestExternalities::default().execute_with(|| { + let initially_disabled = vec![]; + pallet_session::Validators::::put(ACTIVE_SET.to_vec()); + ActiveEra::::put(ActiveEraInfo { index: 2, start: None }); + + let disable_offender = + >::decision( + &OFFENDER_ID, + SLASH_ERA, + &initially_disabled, + ); + + assert!(disable_offender.is_none()); + }); + } + + #[test] + fn dont_disable_beyond_byzantine_threshold() { + sp_io::TestExternalities::default().execute_with(|| { + let initially_disabled = vec![1, 2]; + pallet_session::Validators::::put(ACTIVE_SET.to_vec()); + + let disable_offender = + >::decision( + &OFFENDER_ID, + SLASH_ERA, + &initially_disabled, + ); + + assert!(disable_offender.is_none()); + }); + } + + #[test] + fn disable_when_below_byzantine_threshold() { + sp_io::TestExternalities::default().execute_with(|| { + let initially_disabled = vec![1]; + pallet_session::Validators::::put(ACTIVE_SET.to_vec()); + + let disable_offender = + >::decision( + &OFFENDER_ID, + SLASH_ERA, + &initially_disabled, + ); + + assert_eq!(disable_offender, Some(OFFENDER_VALIDATOR_IDX)); + }); + } +} diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs index b0041ccc07541966863e51d40f4b6e26e50a396f..f055e8ce28e904639dba11f9f7639bf64c934d95 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -66,6 +66,7 @@ pub fn expand_outer_dispatch( quote! { #( #query_call_part_macros )* + /// The aggregated runtime call type. #[derive( Clone, PartialEq, Eq, #scrate::__private::codec::Encode, diff --git a/substrate/frame/support/procedural/src/construct_runtime/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/mod.rs index b083abbb2a8db65e1abd1f0cc2e4a15dbd922e2d..1505d158895f0fa172884eef6aa748f44734e042 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/mod.rs @@ -533,6 +533,7 @@ pub(crate) fn decl_all_pallets<'a>( for pallet_declaration in pallet_declarations { let type_name = &pallet_declaration.name; let pallet = &pallet_declaration.path; + let docs = &pallet_declaration.docs; let mut generics = vec![quote!(#runtime)]; generics.extend(pallet_declaration.instance.iter().map(|name| quote!(#pallet::#name))); let mut attrs = Vec::new(); @@ -541,6 +542,7 @@ pub(crate) fn decl_all_pallets<'a>( attrs.extend(TokenStream2::from_str(&feat).expect("was parsed successfully; qed")); } let type_decl = quote!( + #( #[doc = #docs] )* #(#attrs)* pub type #type_name = #pallet::Pallet <#(#generics),*>; ); diff --git a/substrate/frame/support/procedural/src/construct_runtime/parse.rs b/substrate/frame/support/procedural/src/construct_runtime/parse.rs index 31866c787b0f339d6d83b810476e840216f70bbc..ded77bed4c8e29145b721581aec12eef1dcd952a 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/parse.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/parse.rs @@ -605,6 +605,8 @@ pub struct Pallet { pub pallet_parts: Vec, /// Expressions specified inside of a #[cfg] attribute. pub cfg_pattern: Vec, + /// The doc literals + pub docs: Vec, } impl Pallet { @@ -774,6 +776,7 @@ fn convert_pallets(pallets: Vec) -> syn::Result>>()?; diff --git a/substrate/frame/support/procedural/src/dynamic_params.rs b/substrate/frame/support/procedural/src/dynamic_params.rs index 29399a885bc671ddfeefe3d8eefba3d235ad4899..ad62f59e6b0a04ea50c02375d6e1f421fc5689aa 100644 --- a/substrate/frame/support/procedural/src/dynamic_params.rs +++ b/substrate/frame/support/procedural/src/dynamic_params.rs @@ -91,7 +91,7 @@ impl ToTokens for DynamicParamModAttr { let mut quoted_enum = quote! {}; for m in self.inner_mods() { let aggregate_name = - syn::Ident::new(&m.ident.to_string().to_class_case(), m.ident.span()); + syn::Ident::new(&m.ident.to_string().to_pascal_case(), m.ident.span()); let mod_name = &m.ident; let mut attrs = m.attrs.clone(); @@ -222,8 +222,10 @@ impl ToTokens for DynamicPalletParamAttr { let (params_mod, parameter_pallet, runtime_params) = (&self.inner_mod, &self.meta.parameter_pallet, &self.meta.runtime_params); - let aggregate_name = - syn::Ident::new(¶ms_mod.ident.to_string().to_class_case(), params_mod.ident.span()); + let aggregate_name = syn::Ident::new( + ¶ms_mod.ident.to_string().to_pascal_case(), + params_mod.ident.span(), + ); let (mod_name, vis) = (¶ms_mod.ident, ¶ms_mod.vis); let statics = self.statics(); diff --git a/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs b/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs index 99364aaa96cd7808def45563392be207604151d7..1975f059152c81c88c641c80b6013f5ff69f2613 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs @@ -198,9 +198,9 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { macro_rules! #default_parts_unique_id_v2 { { $caller:tt - frame_support = [{ $($frame_support:ident)::* }] + your_tt_return = [{ $my_tt_return:path }] } => { - $($frame_support)*::__private::tt_return! { + $my_tt_return! { $caller tokens = [{ + Pallet #call_part_v2 #storage_part_v2 #event_part_v2 #error_part_v2 #origin_part_v2 #config_part_v2 diff --git a/substrate/frame/support/procedural/src/runtime/expand/mod.rs b/substrate/frame/support/procedural/src/runtime/expand/mod.rs index 93c88fce94b73665efa7912b36056b661b12be1e..43f11896808c71aed001a4660f3a73de2825dc8b 100644 --- a/substrate/frame/support/procedural/src/runtime/expand/mod.rs +++ b/substrate/frame/support/procedural/src/runtime/expand/mod.rs @@ -93,7 +93,7 @@ fn construct_runtime_implicit_to_explicit( let frame_support = generate_access_from_frame_or_crate("frame-support")?; let attr = if legacy_ordering { quote!((legacy_ordering)) } else { quote!() }; let mut expansion = quote::quote!( - #[frame_support::runtime #attr] + #[#frame_support::runtime #attr] #input ); for pallet in definition.pallet_decls.iter() { @@ -103,7 +103,7 @@ fn construct_runtime_implicit_to_explicit( expansion = quote::quote!( #frame_support::__private::tt_call! { macro = [{ #pallet_path::tt_default_parts_v2 }] - frame_support = [{ #frame_support }] + your_tt_return = [{ #frame_support::__private::tt_return }] ~~> #frame_support::match_and_insert! { target = [{ #expansion }] pattern = [{ #pallet_name = #pallet_path #pallet_instance }] @@ -244,7 +244,7 @@ fn construct_runtime_final_expansion( // Prevent UncheckedExtrinsic to print unused warning. const _: () = { #[allow(unused)] - type __hidden_use_of_unchecked_extrinsic = #unchecked_extrinsic; + type __HiddenUseOfUncheckedExtrinsic = #unchecked_extrinsic; }; #[derive( diff --git a/substrate/frame/support/procedural/src/runtime/parse/pallet.rs b/substrate/frame/support/procedural/src/runtime/parse/pallet.rs index d2f1857fb2b4feec841224f28d691076c8853427..09f5290541d3a6ac038cd863a96330f10b605c44 100644 --- a/substrate/frame/support/procedural/src/runtime/parse/pallet.rs +++ b/substrate/frame/support/procedural/src/runtime/parse/pallet.rs @@ -16,6 +16,7 @@ // limitations under the License. use crate::construct_runtime::parse::{Pallet, PalletPart, PalletPartKeyword, PalletPath}; +use frame_support_procedural_tools::get_doc_literals; use quote::ToTokens; use syn::{punctuated::Punctuated, spanned::Spanned, token, Error, Ident, PathArguments}; @@ -86,6 +87,8 @@ impl Pallet { let cfg_pattern = vec![]; + let docs = get_doc_literals(&item.attrs); + Ok(Pallet { is_expanded: true, name, @@ -94,6 +97,7 @@ impl Pallet { instance, cfg_pattern, pallet_parts, + docs, }) } } diff --git a/substrate/frame/support/procedural/src/runtime/parse/pallet_decl.rs b/substrate/frame/support/procedural/src/runtime/parse/pallet_decl.rs index 437a163cfbc44973e753075d4a139fca5df81af8..e167d37d5f14099c1f8f656e9aa5e6e5e44780b5 100644 --- a/substrate/frame/support/procedural/src/runtime/parse/pallet_decl.rs +++ b/substrate/frame/support/procedural/src/runtime/parse/pallet_decl.rs @@ -21,13 +21,14 @@ use syn::{spanned::Spanned, Attribute, Ident, PathArguments}; /// The declaration of a pallet. #[derive(Debug, Clone)] pub struct PalletDeclaration { - /// The name of the pallet, e.g.`System` in `System: frame_system`. + /// The name of the pallet, e.g.`System` in `pub type System = frame_system`. pub name: Ident, /// Optional attributes tagged right above a pallet declaration. pub attrs: Vec, - /// The path of the pallet, e.g. `frame_system` in `System: frame_system`. + /// The path of the pallet, e.g. `frame_system` in `pub type System = frame_system`. pub path: syn::Path, - /// The instance of the pallet, e.g. `Instance1` in `Council: pallet_collective::`. + /// The instance of the pallet, e.g. `Instance1` in `pub type Council = + /// pallet_collective`. pub instance: Option, } diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 895215d364e3dfccad33a805c6db34857c014ae8..7eddea1259d7d040e57084232feb53eb2b6b1270 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -936,6 +936,83 @@ pub mod pallet_prelude { pub use sp_weights::Weight; } +/// The pallet macro has 2 purposes: +/// +/// * [For declaring a pallet as a rust module](#1---pallet-module-declaration) +/// * [For declaring the `struct` placeholder of a +/// pallet](#2---pallet-struct-placeholder-declaration) +/// +/// # 1 - Pallet module declaration +/// +/// The module to declare a pallet is organized as follow: +/// ``` +/// #[frame_support::pallet] // <- the macro +/// mod pallet { +/// #[pallet::pallet] +/// pub struct Pallet(_); +/// +/// #[pallet::config] +/// pub trait Config: frame_system::Config {} +/// +/// #[pallet::call] +/// impl Pallet { +/// } +/// +/// /* ... */ +/// } +/// ``` +/// +/// The documentation for each individual part can be found at [frame_support::pallet_macros] +/// +/// ## Dev Mode (`#[pallet(dev_mode)]`) +/// +/// Syntax: +/// +/// ``` +/// #[frame_support::pallet(dev_mode)] +/// mod pallet { +/// # #[pallet::pallet] +/// # pub struct Pallet(_); +/// # #[pallet::config] +/// # pub trait Config: frame_system::Config {} +/// /* ... */ +/// } +/// ``` +/// +/// Specifying the argument `dev_mode` will allow you to enable dev mode for a pallet. The +/// aim of dev mode is to loosen some of the restrictions and requirements placed on +/// production pallets for easy tinkering and development. Dev mode pallets should not be +/// used in production. Enabling dev mode has the following effects: +/// +/// * Weights no longer need to be specified on every `#[pallet::call]` declaration. By +/// default, dev mode pallets will assume a weight of zero (`0`) if a weight is not +/// specified. This is equivalent to specifying `#[weight(0)]` on all calls that do not +/// specify a weight. +/// * Call indices no longer need to be specified on every `#[pallet::call]` declaration. By +/// default, dev mode pallets will assume a call index based on the order of the call. +/// * All storages are marked as unbounded, meaning you do not need to implement +/// [`MaxEncodedLen`](frame_support::pallet_prelude::MaxEncodedLen) on storage types. This is +/// equivalent to specifying `#[pallet::unbounded]` on all storage type definitions. +/// * Storage hashers no longer need to be specified and can be replaced by `_`. In dev mode, +/// these will be replaced by `Blake2_128Concat`. In case of explicit key-binding, `Hasher` +/// can simply be ignored when in `dev_mode`. +/// +/// Note that the `dev_mode` argument can only be supplied to the `#[pallet]` or +/// `#[frame_support::pallet]` attribute macro that encloses your pallet module. This +/// argument cannot be specified anywhere else, including but not limited to the +/// `#[pallet::pallet]` attribute macro. +/// +///

+/// WARNING:
+/// You should never deploy or use dev mode pallets in production. Doing so can break your
+/// chain. Once you are done tinkering, you should
+/// remove the 'dev_mode' argument from your #[pallet] declaration and fix any compile
+/// errors before attempting to use your pallet in a production scenario.
+/// 
+/// +/// # 2 - Pallet struct placeholder declaration +/// /// The pallet struct placeholder `#[pallet::pallet]` is mandatory and allows you to /// specify pallet information. /// @@ -984,40 +1061,6 @@ pub mod pallet_prelude { /// [`StorageInfoTrait`](frame_support::traits::StorageInfoTrait) for the pallet using the /// [`PartialStorageInfoTrait`](frame_support::traits::PartialStorageInfoTrait) /// implementation of storages. -/// -/// ## Dev Mode (`#[pallet(dev_mode)]`) -/// -/// Specifying the argument `dev_mode` will allow you to enable dev mode for a pallet. The -/// aim of dev mode is to loosen some of the restrictions and requirements placed on -/// production pallets for easy tinkering and development. Dev mode pallets should not be -/// used in production. Enabling dev mode has the following effects: -/// -/// * Weights no longer need to be specified on every `#[pallet::call]` declaration. By -/// default, dev mode pallets will assume a weight of zero (`0`) if a weight is not -/// specified. This is equivalent to specifying `#[weight(0)]` on all calls that do not -/// specify a weight. -/// * Call indices no longer need to be specified on every `#[pallet::call]` declaration. By -/// default, dev mode pallets will assume a call index based on the order of the call. -/// * All storages are marked as unbounded, meaning you do not need to implement -/// [`MaxEncodedLen`](frame_support::pallet_prelude::MaxEncodedLen) on storage types. This is -/// equivalent to specifying `#[pallet::unbounded]` on all storage type definitions. -/// * Storage hashers no longer need to be specified and can be replaced by `_`. In dev mode, -/// these will be replaced by `Blake2_128Concat`. In case of explicit key-binding, `Hasher` -/// can simply be ignored when in `dev_mode`. -/// -/// Note that the `dev_mode` argument can only be supplied to the `#[pallet]` or -/// `#[frame_support::pallet]` attribute macro that encloses your pallet module. This -/// argument cannot be specified anywhere else, including but not limited to the -/// `#[pallet::pallet]` attribute macro. -/// -///
-/// WARNING:
-/// You should not deploy or use dev mode pallets in production. Doing so can break your
-/// chain and therefore should never be done. Once you are done tinkering, you should
-/// remove the 'dev_mode' argument from your #[pallet] declaration and fix any compile
-/// errors before attempting to use your pallet in a production scenario.
-/// 
pub use frame_support_procedural::pallet; /// Contains macro stubs for all of the `pallet::` macros @@ -1456,16 +1499,24 @@ pub mod pallet_macros { /// # use core::fmt::Debug; /// # use frame_support::traits::Contains; /// # + /// # pub trait SomeMoreComplexBound {} + /// # /// #[pallet::pallet] /// pub struct Pallet(_); /// /// #[pallet::config(with_default)] // <- with_default is optional /// pub trait Config: frame_system::Config { /// /// The overarching event type. - /// #[pallet::no_default_bounds] // Default is not supported for RuntimeEvent + /// #[pallet::no_default_bounds] // Default with bounds is not supported for RuntimeEvent /// type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// - /// // ...other config items get default + /// /// A more complex type. + /// #[pallet::no_default] // Example of type where no default should be provided + /// type MoreComplexType: SomeMoreComplexBound; + /// + /// /// A simple type. + /// // Default with bounds is supported for simple types + /// type SimpleType: From; /// } /// /// #[pallet::event] @@ -1475,12 +1526,23 @@ pub mod pallet_macros { /// } /// ``` /// - /// As shown above, you may also attach the [`#[pallet::no_default]`](`no_default`) + /// As shown above: + /// * you may attach the [`#[pallet::no_default]`](`no_default`) /// attribute to specify that a particular trait item _cannot_ be used as a default when a /// test `Config` is derived using the [`#[derive_impl(..)]`](`frame_support::derive_impl`) /// attribute macro. This will cause that particular trait item to simply not appear in /// default testing configs based on this config (the trait item will not be included in /// `DefaultConfig`). + /// * you may attach the [`#[pallet::no_default_bounds]`](`no_default_bounds`) + /// attribute to specify that a particular trait item can be used as a default when a + /// test `Config` is derived using the [`#[derive_impl(..)]`](`frame_support::derive_impl`) + /// attribute macro. But its bounds cannot be enforced at this point and should be + /// discarded when generating the default config trait. + /// * you may not specify any attribute to generate a trait item in the default config + /// trait. + /// + /// In case origin of error is not clear it is recommended to disable all default with + /// [`#[pallet::no_default]`](`no_default`) and enable them one by one. /// /// ### `DefaultConfig` Caveats /// @@ -1500,7 +1562,10 @@ pub mod pallet_macros { /// the `DefaultConfig` trait, and therefore any impl of `DefaultConfig` doesn't need to /// implement such items. /// - /// For more information, see [`frame_support::derive_impl`]. + /// For more information, see: + /// * [`frame_support::derive_impl`]. + /// * [`#[pallet::no_default]`](`no_default`) + /// * [`#[pallet::no_default_bounds]`](`no_default_bounds`) pub use frame_support_procedural::config; /// Allows defining an enum that gets composed as an aggregate enum by `construct_runtime`. @@ -2400,6 +2465,9 @@ pub mod pallet_macros { /// Finally, the `RuntimeTask` can then used by a script or off-chain worker to create and /// submit such tasks via an extrinsic defined in `frame_system` called `do_task`. /// + /// When submitted as unsigned transactions (for example via an off-chain workder), note + /// that the tasks will be executed in a random order. + /// /// ## Example #[doc = docify::embed!("src/tests/tasks.rs", tasks_example)] /// Now, this can be executed as follows: diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 66777cef7b8e81960c1169c2fb1fd1033e58c4a2..a423656c394f28158da2aedd5d552814ccabb3c5 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -36,7 +36,7 @@ mod members; pub use members::{AllowAll, DenyAll, Filter}; pub use members::{ AsContains, ChangeMembers, Contains, ContainsLengthBound, ContainsPair, Equals, Everything, - EverythingBut, FromContainsPair, InitializeMembers, InsideBoth, IsInVec, Nothing, + EverythingBut, FromContains, FromContainsPair, InitializeMembers, InsideBoth, IsInVec, Nothing, RankedMembers, RankedMembersSwapHandler, SortedMembers, TheseExcept, }; diff --git a/substrate/frame/support/src/traits/members.rs b/substrate/frame/support/src/traits/members.rs index 3a6e3719593a22c33ad24c24c56f858ea82779ee..53de84ab22455f2d778c1fd64ba94c348c685db0 100644 --- a/substrate/frame/support/src/traits/members.rs +++ b/substrate/frame/support/src/traits/members.rs @@ -66,6 +66,15 @@ impl> Contains<(A, B)> for FromContainsPair { } } +/// A [`ContainsPair`] implementation that has a `Contains` implementation for each member of the +/// pair. +pub struct FromContains(PhantomData<(CA, CB)>); +impl, CB: Contains> ContainsPair for FromContains { + fn contains(a: &A, b: &B) -> bool { + CA::contains(a) && CB::contains(b) + } +} + /// A [`Contains`] implementation that contains every value. pub enum Everything {} impl Contains for Everything { diff --git a/substrate/frame/support/src/traits/messages.rs b/substrate/frame/support/src/traits/messages.rs index f3d893bcc1d899fce06ef00a38d687bfb3ea0181..2eec606b6d18b20a086e4ce7b44a877c3b8fb4d5 100644 --- a/substrate/frame/support/src/traits/messages.rs +++ b/substrate/frame/support/src/traits/messages.rs @@ -46,6 +46,8 @@ pub enum ProcessMessageError { /// the case that a queue is re-serviced within the same block after *yielding*. A queue is /// not required to *yield* again when it is being re-serviced withing the same block. Yield, + /// The message could not be processed for reaching the stack depth limit. + StackLimitReached, } /// Can process messages from a specific origin. @@ -96,6 +98,8 @@ pub trait ServiceQueues { /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. /// /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. + /// Should only be called in top-level runtime entry points like `on_initialize` or `on_idle`. + /// Otherwise, stack depth limit errors may be miss-handled. fn service_queues(weight_limit: Weight) -> Weight; /// Executes a message that could not be executed by [`Self::service_queues()`] because it was diff --git a/substrate/frame/support/src/traits/tasks.rs b/substrate/frame/support/src/traits/tasks.rs index 24f3430cf50b5a23175c4c586c1642da580cf862..42b837e55970def3c4d99f3c5f3cc9ca5dacaaf3 100644 --- a/substrate/frame/support/src/traits/tasks.rs +++ b/substrate/frame/support/src/traits/tasks.rs @@ -46,6 +46,10 @@ pub trait Task: Sized + FullCodec + TypeInfo + Clone + Debug + PartialEq + Eq { fn iter() -> Self::Enumeration; /// Checks if a particular instance of this `Task` variant is a valid piece of work. + /// + /// This is used to validate tasks for unsigned execution. Hence, it MUST be cheap + /// with minimal to no storage reads. Else, it can make the blockchain vulnerable + /// to DoS attacks. fn is_valid(&self) -> bool; /// Performs the work for this particular `Task` variant. diff --git a/substrate/frame/support/src/traits/tokens.rs b/substrate/frame/support/src/traits/tokens.rs index 3635311e64357bbd2e7041d653a4268dfb65a182..8842b20580181f81e4377a0d9f6223e59a6fee6a 100644 --- a/substrate/frame/support/src/traits/tokens.rs +++ b/substrate/frame/support/src/traits/tokens.rs @@ -31,7 +31,7 @@ pub mod pay; pub use misc::{ AssetId, Balance, BalanceStatus, ConversionFromAssetBalance, ConversionToAssetBalance, ConvertRank, DepositConsequence, ExistenceRequirement, Fortitude, GetSalary, Locker, Precision, - Preservation, Provenance, Restriction, UnityAssetBalanceConversion, WithdrawConsequence, - WithdrawReasons, + Preservation, Provenance, Restriction, UnityAssetBalanceConversion, UnityOrOuterConversion, + WithdrawConsequence, WithdrawReasons, }; pub use pay::{Pay, PayFromAccount, PaymentStatus}; diff --git a/substrate/frame/support/src/traits/tokens/fungible/union_of.rs b/substrate/frame/support/src/traits/tokens/fungible/union_of.rs index 4df9f99aea41662da6df9a5893927088afc26b48..63791b05223701ae8c689f00c4769cb510c6e208 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/union_of.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/union_of.rs @@ -937,3 +937,28 @@ impl< } } } + +impl< + Left: fungible::Inspect, + Right: fungibles::Inspect + fungibles::Refund, + Criterion: Convert>::AssetId>>, + AssetKind: AssetId, + AccountId, + > fungibles::Refund for UnionOf +{ + type AssetId = AssetKind; + type Balance = >::Balance; + + fn deposit_held(asset: AssetKind, who: AccountId) -> Option<(AccountId, Self::Balance)> { + match Criterion::convert(asset) { + Left(()) => None, + Right(a) => >::deposit_held(a, who), + } + } + fn refund(asset: AssetKind, who: AccountId) -> DispatchResult { + match Criterion::convert(asset) { + Left(()) => Err(DispatchError::Unavailable), + Right(a) => >::refund(a, who), + } + } +} diff --git a/substrate/frame/support/src/traits/tokens/fungibles/lifetime.rs b/substrate/frame/support/src/traits/tokens/fungibles/lifetime.rs index 49f6c846ccdd54adeceb668bb10a8cb2ca11281f..7f882c1fc4fe84875c7172664c4720e7c70aed37 100644 --- a/substrate/frame/support/src/traits/tokens/fungibles/lifetime.rs +++ b/substrate/frame/support/src/traits/tokens/fungibles/lifetime.rs @@ -15,13 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Traits for creating and destroying assets. +//! Traits for creating, editing and destroying assets. //! //! See the [`crate::traits::fungibles`] doc for more information about fungibles traits. -use sp_runtime::{DispatchError, DispatchResult}; - use super::Inspect; +use crate::traits::tokens::{AssetId, Balance}; +use sp_runtime::{DispatchError, DispatchResult}; /// Trait for providing the ability to create new fungible assets. pub trait Create: Inspect { @@ -34,6 +34,22 @@ pub trait Create: Inspect { ) -> DispatchResult; } +/// Trait for refunding the existence deposit of a target asset account. +/// +/// The existence deposit might by necessary and present in cases where the asset held by the +/// account is insufficient for the required storage, or when the system cannot provide a consumer +/// reference for any reason. +pub trait Refund { + /// Means of identifying one asset class from another. + type AssetId: AssetId; + /// Scalar type for representing deposit balance of an account. + type Balance: Balance; + /// Returns the amount of account deposit and depositor address, if any. + fn deposit_held(id: Self::AssetId, who: AccountId) -> Option<(AccountId, Self::Balance)>; + /// Return the deposit (if any) of a target asset account. + fn refund(id: Self::AssetId, who: AccountId) -> DispatchResult; +} + /// Trait for providing the ability to destroy existing fungible assets. pub trait Destroy: Inspect { /// Start the destruction an existing fungible asset. diff --git a/substrate/frame/support/src/traits/tokens/fungibles/mod.rs b/substrate/frame/support/src/traits/tokens/fungibles/mod.rs index 2122fdeaed3f28b937fa16a2230fa0f9a56d869a..8b4ea4d13cf9d108558ecf6461f83014148f35fd 100644 --- a/substrate/frame/support/src/traits/tokens/fungibles/mod.rs +++ b/substrate/frame/support/src/traits/tokens/fungibles/mod.rs @@ -44,7 +44,7 @@ pub use hold::{ Unbalanced as UnbalancedHold, }; pub use imbalance::{Credit, Debt, HandleImbalanceDrop, Imbalance}; -pub use lifetime::{Create, Destroy}; +pub use lifetime::{Create, Destroy, Refund}; pub use regular::{ Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Mutate, Unbalanced, }; diff --git a/substrate/frame/support/src/traits/tokens/fungibles/roles.rs b/substrate/frame/support/src/traits/tokens/fungibles/roles.rs index 4f95ad8368c2c555e1a07f1f1b8b9ede7cf574cb..3e68d6b94518d36fd609b5e481bf5ed1702fde6a 100644 --- a/substrate/frame/support/src/traits/tokens/fungibles/roles.rs +++ b/substrate/frame/support/src/traits/tokens/fungibles/roles.rs @@ -19,6 +19,8 @@ //! //! See the [`crate::traits::fungibles`] doc for more information about fungibles traits. +use sp_runtime::DispatchResult; + pub trait Inspect: super::Inspect { // Get owner for an AssetId. fn owner(asset: Self::AssetId) -> Option; @@ -29,3 +31,22 @@ pub trait Inspect: super::Inspect { // Get freezer for an AssetId. fn freezer(asset: Self::AssetId) -> Option; } + +/// Trait for resetting the team configuration of an existing fungible asset. +pub trait ResetTeam: super::Inspect { + /// Reset the team for the asset with the given `id`. + /// + /// ### Parameters + /// - `id`: The identifier of the asset for which the team is being reset. + /// - `owner`: The new `owner` account for the asset. + /// - `admin`: The new `admin` account for the asset. + /// - `issuer`: The new `issuer` account for the asset. + /// - `freezer`: The new `freezer` account for the asset. + fn reset_team( + id: Self::AssetId, + owner: AccountId, + admin: AccountId, + issuer: AccountId, + freezer: AccountId, + ) -> DispatchResult; +} diff --git a/substrate/frame/support/src/traits/tokens/fungibles/union_of.rs b/substrate/frame/support/src/traits/tokens/fungibles/union_of.rs index 79259ecc5b0a65473ac5e444e352866a691e23a4..f4259a78f0a25ae15c7413be07df0bc299342122 100644 --- a/substrate/frame/support/src/traits/tokens/fungibles/union_of.rs +++ b/substrate/frame/support/src/traits/tokens/fungibles/union_of.rs @@ -917,3 +917,35 @@ impl< } } } + +impl< + Left: fungibles::Inspect + fungibles::Refund, + Right: fungibles::Inspect + + fungibles::Refund>::Balance>, + Criterion: Convert< + AssetKind, + Either< + >::AssetId, + >::AssetId, + >, + >, + AssetKind: AssetId, + AccountId, + > fungibles::Refund for UnionOf +{ + type AssetId = AssetKind; + type Balance = >::Balance; + + fn deposit_held(asset: AssetKind, who: AccountId) -> Option<(AccountId, Self::Balance)> { + match Criterion::convert(asset) { + Left(a) => >::deposit_held(a, who), + Right(a) => >::deposit_held(a, who), + } + } + fn refund(asset: AssetKind, who: AccountId) -> DispatchResult { + match Criterion::convert(asset) { + Left(a) => >::refund(a, who), + Right(a) => >::refund(a, who), + } + } +} diff --git a/substrate/frame/support/src/traits/tokens/misc.rs b/substrate/frame/support/src/traits/tokens/misc.rs index a4dd5e4914283e6da028aac038cecee501a2228f..424acb1d550b15b69582d004e2e466e65e6a9b3f 100644 --- a/substrate/frame/support/src/traits/tokens/misc.rs +++ b/substrate/frame/support/src/traits/tokens/misc.rs @@ -17,6 +17,7 @@ //! Miscellaneous types. +use crate::traits::Contains; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_core::RuntimeDebug; @@ -299,6 +300,33 @@ where fn ensure_successful(_: AssetId) {} } +/// Implements [`ConversionFromAssetBalance`], allowing for a 1:1 balance conversion of the asset +/// when it meets the conditions specified by `C`. If the conditions are not met, the conversion is +/// delegated to `O`. +pub struct UnityOrOuterConversion(core::marker::PhantomData<(C, O)>); +impl + ConversionFromAssetBalance for UnityOrOuterConversion +where + C: Contains, + O: ConversionFromAssetBalance, + AssetBalance: Into, +{ + type Error = O::Error; + fn from_asset_balance( + balance: AssetBalance, + asset_id: AssetId, + ) -> Result { + if C::contains(&asset_id) { + return Ok(balance.into()); + } + O::from_asset_balance(balance, asset_id) + } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(asset_id: AssetId) { + O::ensure_successful(asset_id) + } +} + /// Trait to handle NFT locking mechanism to ensure interactions with the asset can be implemented /// downstream to extend logic of Uniques/Nfts current functionality. pub trait Locker { diff --git a/substrate/frame/support/src/traits/try_runtime/mod.rs b/substrate/frame/support/src/traits/try_runtime/mod.rs index bec2dbf549a111b8b5caad19764fb827a619d194..c1bf1feb19e54fe3c94abfb242e9f2321d43d0df 100644 --- a/substrate/frame/support/src/traits/try_runtime/mod.rs +++ b/substrate/frame/support/src/traits/try_runtime/mod.rs @@ -161,22 +161,31 @@ impl TryState Ok(()), Select::All => { - let mut error_count = 0; + let mut errors = Vec::::new(); + for_tuples!(#( - if let Err(_) = Tuple::try_state(n.clone(), targets.clone()) { - error_count += 1; + if let Err(err) = Tuple::try_state(n.clone(), targets.clone()) { + errors.push(err); } )*); - if error_count > 0 { + if !errors.is_empty() { log::error!( target: "try-runtime", - "{} pallets exited with errors while executing try_state checks.", - error_count + "Detected errors while executing `try_state`:", ); + errors.iter().for_each(|err| { + log::error!( + target: "try-runtime", + "{:?}", + err + ); + }); + return Err( - "Detected errors while executing try_state checks. See logs for more info." + "Detected errors while executing `try_state` checks. See logs for more \ + info." .into(), ) } diff --git a/substrate/frame/support/test/tests/benchmark_ui/invalid_origin.stderr b/substrate/frame/support/test/tests/benchmark_ui/invalid_origin.stderr index 87d4f476a60d1be358a31f965d9d01772082e450..30f1289767fc54ca55c5d64125eea402e84344c3 100644 --- a/substrate/frame/support/test/tests/benchmark_ui/invalid_origin.stderr +++ b/substrate/frame/support/test/tests/benchmark_ui/invalid_origin.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `::RuntimeOrigin: --> tests/benchmark_ui/invalid_origin.rs:23:1 | 23 | #[benchmarks] - | ^^^^^^^^^^^^^ the trait `From<{integer}>` is not implemented for `::RuntimeOrigin` + | ^^^^^^^^^^^^^ the trait `From<{integer}>` is not implemented for `::RuntimeOrigin`, which is required by `{integer}: Into<_>` | = note: required for `{integer}` to implement `Into<::RuntimeOrigin>` = note: this error originates in the attribute macro `benchmarks` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr index 09c4d290ef5c3d49d244648242e6324d4cf760e5..96504b7ce77525331aa55abf670df9b53bb1ad76 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr @@ -23,133 +23,139 @@ error: use of deprecated constant `WhereSection::_w`: error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime` + | + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime` | note: required by a bound in `frame_system::Event` --> $WORKSPACE/substrate/frame/system/src/lib.rs | | pub enum Event { | ^^^^^^ required by this bound in `Event` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` | note: required because it appears within the type `RuntimeEvent` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ note: required by a bound in `Clone` --> $RUST/core/src/clone.rs | | pub trait Clone: Sized { | ^^^^^ required by this bound in `Clone` - = note: this error originates in the derive macro `Clone` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Clone` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` | note: required because it appears within the type `RuntimeEvent` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ note: required by a bound in `EncodeLike` --> $CARGO/parity-scale-codec-3.6.5/src/encode_like.rs | | pub trait EncodeLike: Sized + Encode {} | ^^^^^ required by this bound in `EncodeLike` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` | note: required because it appears within the type `RuntimeEvent` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ note: required by a bound in `Decode` --> $CARGO/parity-scale-codec-3.6.5/src/codec.rs | | pub trait Decode: Sized { | ^^^^^ required by this bound in `Decode` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied in `frame_system::Event` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ within `frame_system::Event`, the trait `Config` is not implemented for `Runtime`, which is required by `frame_system::Event: Sized` | -note: required because it appears within the type `Event` +note: required because it appears within the type `frame_system::Event` --> $WORKSPACE/substrate/frame/system/src/lib.rs | | pub enum Event { @@ -159,22 +165,21 @@ note: required by a bound in `From` | | pub trait From: Sized { | ^ required by this bound in `From` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied in `frame_system::Event` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ within `frame_system::Event`, the trait `Config` is not implemented for `Runtime`, which is required by `frame_system::Event: Sized` | -note: required because it appears within the type `Event` +note: required because it appears within the type `frame_system::Event` --> $WORKSPACE/substrate/frame/system/src/lib.rs | | pub enum Event { @@ -184,22 +189,7 @@ note: required by a bound in `TryInto` | | pub trait TryInto: Sized { | ^ required by this bound in `TryInto` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `Runtime: Config` is not satisfied - --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 - | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation -... | - | - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 @@ -212,134 +202,157 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied error[E0277]: the trait bound `RawOrigin<_>: TryFrom` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ the trait `TryFrom` is not implemented for `RawOrigin<_>` | = help: the trait `TryFrom` is implemented for `RawOrigin<::AccountId>` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: Callable` | = help: the trait `Callable` is implemented for `Pallet` = note: required for `Pallet` to implement `Callable` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` | = note: required for `Pallet` to implement `Callable` note: required because it appears within the type `RuntimeCall` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ note: required by a bound in `Clone` --> $RUST/core/src/clone.rs | | pub trait Clone: Sized { | ^^^^^ required by this bound in `Clone` - = note: this error originates in the derive macro `Clone` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Clone` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` | = note: required for `Pallet` to implement `Callable` note: required because it appears within the type `RuntimeCall` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ note: required by a bound in `EncodeLike` --> $CARGO/parity-scale-codec-3.6.5/src/encode_like.rs | | pub trait EncodeLike: Sized + Encode {} | ^^^^^ required by this bound in `EncodeLike` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` | = note: required for `Pallet` to implement `Callable` note: required because it appears within the type `RuntimeCall` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ note: required by a bound in `Decode` --> $CARGO/parity-scale-codec-3.6.5/src/codec.rs | | pub trait Decode: Sized { | ^^^^^ required by this bound in `Decode` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` + | + = note: required for `Pallet` to implement `Callable` +note: required because it appears within the type `RuntimeCall` + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |_^ +note: required by a bound in `frame_support::sp_runtime::traits::Dispatchable::Config` + --> $WORKSPACE/substrate/primitives/runtime/src/traits.rs + | + | type Config; + | ^^^^^^^^^^^^ required by this bound in `Dispatchable::Config` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:26:3 @@ -353,165 +366,124 @@ note: required by a bound in `GenesisConfig` | pub struct GenesisConfig { | ^^^^^^ required by this bound in `GenesisConfig` +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` + | + = note: required for `Pallet` to implement `Callable` +note: required because it appears within the type `RuntimeCall` + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |_^ +note: required by a bound in `frame_support::pallet_prelude::ValidateUnsigned::Call` + --> $WORKSPACE/substrate/primitives/runtime/src/traits.rs + | + | type Call; + | ^^^^^^^^^^ required by this bound in `ValidateUnsigned::Call` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` | note: required because it appears within the type `RuntimeEvent` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ note: required by a bound in `Result` --> $RUST/core/src/result.rs | | pub enum Result { | ^ required by this bound in `Result` - = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Decode` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Decode` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` | note: required because it appears within the type `RuntimeEvent` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ note: required by a bound in `TryInto` --> $RUST/core/src/convert/mod.rs | | pub trait TryInto: Sized { | ^^^^^ required by this bound in `TryInto` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` | = note: required for `Pallet` to implement `Callable` note: required because it appears within the type `RuntimeCall` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, ... | +27 | | } +28 | | } + | |_^ note: required by a bound in `Result` --> $RUST/core/src/result.rs | | pub enum Result { | ^ required by this bound in `Result` - = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Decode` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `Runtime: Config` is not satisfied - --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 - | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation -... | - | - = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` - --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 - | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation -... | -note: required by a bound in `frame_support::sp_runtime::traits::Dispatchable::Config` - --> $WORKSPACE/substrate/primitives/runtime/src/traits.rs - | - | type Config; - | ^^^^^^^^^^^^ required by this bound in `Dispatchable::Config` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `Runtime: Config` is not satisfied - --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 - | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation -... | - | - = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` - --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 - | -20 | // construct_runtime! { -21 | || pub struct Runtime where -22 | || Block = Block, -23 | || NodeBlock = Block, -... || -27 | || } -28 | || } - | ||_- in this macro invocation -... | -note: required by a bound in `frame_support::pallet_prelude::ValidateUnsigned::Call` - --> $WORKSPACE/substrate/primitives/runtime/src/traits.rs - | - | type Call; - | ^^^^^^^^^^ required by this bound in `ValidateUnsigned::Call` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Decode` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr index 6160f8234a350cc3ee0a0761cab0eed6ea022525..dde58ba6959bb1e1b166508d595d14e415be6857 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr @@ -5,23 +5,16 @@ error: The number of pallets exceeds the maximum number of tuple elements. To in | ^^^ error: recursion limit reached while expanding `frame_support::__private::tt_return!` - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:22:1 + --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:66:1 | -22 | / #[frame_support::pallet] -23 | | mod pallet { -24 | | #[pallet::config] -25 | | pub trait Config: frame_system::Config {} +66 | / construct_runtime! { +67 | | pub struct Runtime +68 | | { +69 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, ... | -66 | |/ construct_runtime! { -67 | || pub struct Runtime -68 | || { -69 | || System: frame_system::{Pallet, Call, Storage, Config, Event}, -... || -180 | || } -181 | || } - | ||_^ - | |_| - | in this macro invocation +180 | | } +181 | | } + | |_^ | = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`$CRATE`) = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr index ebbb9ffb0eb0a91b38ce37436a297260baef32c8..75116f71919500736a084cf8e0c854bcd2ac6f50 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr @@ -10,4 +10,4 @@ error[E0080]: evaluation of constant value failed 97 | | } | |_^ the evaluated program panicked at 'The maximum encoded size of the error type in the `Pallet` pallet exceeds `MAX_MODULE_ERROR_ENCODED_SIZE`', $DIR/tests/construct_runtime_ui/pallet_error_too_large.rs:91:1 | - = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr index b9cf58542f20b549b694f12eb1dc0708649618f0..42e72bc90da7811f280af0aa755df4e3387eadf7 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr @@ -13,4 +13,4 @@ error: `Pallet` does not have #[pallet::call] defined, perhaps you should remove 72 | | } | |_- in this macro invocation | - = note: this error originates in the macro `pallet::__substrate_call_check::is_call_part_defined` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `pallet::__substrate_call_check::is_call_part_defined` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr index f527cc2ff773cb2bcd8e9f7f724cb42a20f667b8..add71c2197efa413393da2c6d4d050834a2f41fd 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr @@ -13,7 +13,7 @@ error: `Pallet` does not have #[pallet::validate_unsigned] defined, perhaps you 72 | | } | |_- in this macro invocation | - = note: this error originates in the macro `pallet::__substrate_validate_unsigned_check::is_validate_unsigned_part_defined` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `pallet::__substrate_validate_unsigned_check::is_validate_unsigned_part_defined` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no variant or associated item named `Pallet` found for enum `RuntimeCall` in the current scope --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:70:3 @@ -26,54 +26,50 @@ error[E0599]: no variant or associated item named `Pallet` found for enum `Runti | || -^^^^^^ variant or associated item not found in `RuntimeCall` | ||________| | | -... | +71 | | } +72 | | } + | |__- variant or associated item `Pallet` not found for this enum error[E0599]: no function or associated item named `pre_dispatch` found for struct `pallet::Pallet` in the current scope --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:66:1 | -28 | pub struct Pallet(_); - | -------------------- function or associated item `pre_dispatch` not found for this struct +28 | pub struct Pallet(_); + | -------------------- function or associated item `pre_dispatch` not found for this struct ... -66 | construct_runtime! { - | __^ - | | _| - | || -67 | || pub struct Runtime -68 | || { -69 | || System: frame_system::{Pallet, Call, Storage, Config, Event}, -70 | || Pallet: pallet::{Pallet, ValidateUnsigned}, -71 | || } -72 | || } - | ||_- in this macro invocation -... | +66 | construct_runtime! { + | _^ +67 | | pub struct Runtime +68 | | { +69 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, +70 | | Pallet: pallet::{Pallet, ValidateUnsigned}, +71 | | } +72 | | } + | |_^ function or associated item not found in `Pallet` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following traits define an item `pre_dispatch`, perhaps you need to implement one of them: candidate #1: `SignedExtension` candidate #2: `ValidateUnsigned` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no function or associated item named `validate_unsigned` found for struct `pallet::Pallet` in the current scope --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:66:1 | -28 | pub struct Pallet(_); - | -------------------- function or associated item `validate_unsigned` not found for this struct +28 | pub struct Pallet(_); + | -------------------- function or associated item `validate_unsigned` not found for this struct ... -66 | construct_runtime! { - | __^ - | | _| - | || -67 | || pub struct Runtime -68 | || { -69 | || System: frame_system::{Pallet, Call, Storage, Config, Event}, -70 | || Pallet: pallet::{Pallet, ValidateUnsigned}, -71 | || } -72 | || } - | ||_- in this macro invocation -... | +66 | construct_runtime! { + | _^ +67 | | pub struct Runtime +68 | | { +69 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, +70 | | Pallet: pallet::{Pallet, ValidateUnsigned}, +71 | | } +72 | | } + | |_^ function or associated item not found in `Pallet` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following traits define an item `validate_unsigned`, perhaps you need to implement one of them: candidate #1: `SignedExtension` candidate #2: `ValidateUnsigned` - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/substrate/frame/support/test/tests/derive_no_bound.rs b/substrate/frame/support/test/tests/derive_no_bound.rs index 48a6413c3ac508cad684500969f5be1cbeb01135..b191470780514617f23231c6a310ab570948a846 100644 --- a/substrate/frame/support/test/tests/derive_no_bound.rs +++ b/substrate/frame/support/test/tests/derive_no_bound.rs @@ -24,6 +24,7 @@ use frame_support::{ }; #[derive(RuntimeDebugNoBound)] +#[allow(dead_code)] struct Unnamed(u64); #[test] diff --git a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr index 40f8f129830496df6d9b03c0ed505b9bbd4958cb..2a4ceecd8fa4b361241378e2fa6b71fa5b2ffda1 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr @@ -18,7 +18,7 @@ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` 38 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^^ `::Bar` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | - = help: the trait `std::fmt::Debug` is not implemented for `::Bar` + = help: the trait `std::fmt::Debug` is not implemented for `::Bar`, which is required by `&::Bar: std::fmt::Debug` = note: required for `&::Bar` to implement `std::fmt::Debug` = note: required for the cast from `&&::Bar` to `&dyn std::fmt::Debug` diff --git a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr index 5744c636235081449f6f12767821016a70179f4e..fc993e9ff68f522d5bd29609ffadb22e06b4f20b 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr @@ -18,7 +18,7 @@ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` 38 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^^ `::Bar` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | - = help: the trait `std::fmt::Debug` is not implemented for `::Bar` + = help: the trait `std::fmt::Debug` is not implemented for `::Bar`, which is required by `&::Bar: std::fmt::Debug` = note: required for `&::Bar` to implement `std::fmt::Debug` = note: required for the cast from `&&::Bar` to `&dyn std::fmt::Debug` @@ -41,7 +41,7 @@ error[E0277]: the trait bound `::Bar: WrapperTypeEncode` is | ------------------------ required by a bound introduced by this call ... 38 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^^ the trait `WrapperTypeEncode` is not implemented for `::Bar` + | ^^^^ the trait `WrapperTypeEncode` is not implemented for `::Bar`, which is required by `::Bar: Encode` | = note: required for `::Bar` to implement `Encode` @@ -49,6 +49,6 @@ error[E0277]: the trait bound `::Bar: WrapperTypeDecode` is --> tests/pallet_ui/call_argument_invalid_bound_2.rs:38:42 | 38 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar` + | ^^^^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar`, which is required by `::Bar: Decode` | = note: required for `::Bar` to implement `Decode` diff --git a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr index b58e4516bceb975ea4937f90ac15acec796829cc..d6486a490794d51caf6fa38b9e90d596447a5dfb 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr @@ -18,7 +18,7 @@ error[E0277]: `Bar` doesn't implement `std::fmt::Debug` 40 | pub fn foo(origin: OriginFor, _bar: Bar) -> DispatchResultWithPostInfo { | ^^^^ `Bar` cannot be formatted using `{:?}` | - = help: the trait `std::fmt::Debug` is not implemented for `Bar` + = help: the trait `std::fmt::Debug` is not implemented for `Bar`, which is required by `&Bar: std::fmt::Debug` = note: add `#[derive(Debug)]` to `Bar` or manually `impl std::fmt::Debug for Bar` = note: required for `&Bar` to implement `std::fmt::Debug` = note: required for the cast from `&&Bar` to `&dyn std::fmt::Debug` diff --git a/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr index 02ead305dd81ad3d3dd0bdab9b74d2a255e8b61f..629fefebbe2c709147c29ad307ca58bea79b2440 100644 --- a/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -35,7 +35,7 @@ error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied ... | 35 | | #[pallet::storage] 36 | | type MyStorage = StorageValue<_, Vec>; - | |__________________^ the trait `MaxEncodedLen` is not implemented for `Vec` + | |__________________^ the trait `MaxEncodedLen` is not implemented for `Vec`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>: StorageInfoTrait` | = help: the following other types implement trait `MaxEncodedLen`: bool diff --git a/substrate/frame/support/test/tests/pallet_ui/event_field_not_member.stderr b/substrate/frame/support/test/tests/pallet_ui/event_field_not_member.stderr index 44660d269060386ab10d749e4a16cd4e65b97950..e9c2eae686baf43af57d5b54074869de57298723 100644 --- a/substrate/frame/support/test/tests/pallet_ui/event_field_not_member.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/event_field_not_member.stderr @@ -16,6 +16,6 @@ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` 41 | B { b: T::Bar }, | ^ `::Bar` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | - = help: the trait `std::fmt::Debug` is not implemented for `::Bar` + = help: the trait `std::fmt::Debug` is not implemented for `::Bar`, which is required by `&::Bar: std::fmt::Debug` = note: required for `&::Bar` to implement `std::fmt::Debug` = note: required for the cast from `&&::Bar` to `&dyn std::fmt::Debug` diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index d269e6d2726d793277a90334686ac11745f4d1c0..c8c41e8050145be714e53394e95d5fdda0b7dcbd 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -9,7 +9,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied ... | 38 | | #[pallet::storage] 39 | | type Foo = StorageValue; - | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar` + | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `WrapperTypeDecode`: Box @@ -31,7 +31,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied ... | 38 | | #[pallet::storage] 39 | | type Foo = StorageValue; - | |____________^ the trait `EncodeLike` is not implemented for `Bar` + | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `EncodeLike`: @@ -58,7 +58,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied ... | 38 | | #[pallet::storage] 39 | | type Foo = StorageValue; - | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar` + | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `WrapperTypeEncode`: Box @@ -81,7 +81,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue; - | |____________^ the trait `TypeInfo` is not implemented for `Bar` + | |____________^ the trait `TypeInfo` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `TypeInfo`: bool @@ -102,7 +102,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue; - | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar` + | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `WrapperTypeDecode`: Box @@ -119,7 +119,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue; - | |____________^ the trait `EncodeLike` is not implemented for `Bar` + | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `EncodeLike`: @@ -141,7 +141,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue; - | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar` + | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `WrapperTypeEncode`: Box @@ -164,7 +164,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue; - | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar` + | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `WrapperTypeDecode`: Box @@ -181,7 +181,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue; - | |____________^ the trait `EncodeLike` is not implemented for `Bar` + | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `EncodeLike`: @@ -203,7 +203,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue; - | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar` + | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `WrapperTypeEncode`: Box diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index 13d761d65d2012d124279cdd857aac35fcec9ac0..08b35eb8ed1536d83678e5b438687cdef51cf91f 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -9,7 +9,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied ... | 38 | | #[pallet::storage] 39 | | type Foo = StorageValue<_, Bar>; - | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar` + | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `WrapperTypeDecode`: Box @@ -31,7 +31,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied ... | 38 | | #[pallet::storage] 39 | | type Foo = StorageValue<_, Bar>; - | |____________^ the trait `EncodeLike` is not implemented for `Bar` + | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `EncodeLike`: @@ -58,7 +58,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied ... | 38 | | #[pallet::storage] 39 | | type Foo = StorageValue<_, Bar>; - | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar` + | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `WrapperTypeEncode`: Box @@ -81,7 +81,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue<_, Bar>; - | |____________^ the trait `TypeInfo` is not implemented for `Bar` + | |____________^ the trait `TypeInfo` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `TypeInfo`: bool @@ -102,7 +102,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue<_, Bar>; - | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar` + | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `WrapperTypeDecode`: Box @@ -119,7 +119,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue<_, Bar>; - | |____________^ the trait `EncodeLike` is not implemented for `Bar` + | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `EncodeLike`: @@ -141,7 +141,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue<_, Bar>; - | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar` + | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `WrapperTypeEncode`: Box @@ -164,7 +164,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue<_, Bar>; - | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar` + | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `WrapperTypeDecode`: Box @@ -181,7 +181,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue<_, Bar>; - | |____________^ the trait `EncodeLike` is not implemented for `Bar` + | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `EncodeLike`: @@ -203,7 +203,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied 38 | #[pallet::storage] | _______________^ 39 | | type Foo = StorageValue<_, Bar>; - | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar` + | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `WrapperTypeEncode`: Box diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index 504db21feeb2b226da122e4a1719e935145d0c31..042a6f67fd316c27a802c14f2497d86f6c9bd5bc 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -9,7 +9,7 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied ... | 38 | | #[pallet::storage] 39 | | type Foo = StorageValue<_, Bar>; - | |____________^ the trait `MaxEncodedLen` is not implemented for `Bar` + | |____________^ the trait `MaxEncodedLen` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageInfoTrait` | = help: the following other types implement trait `MaxEncodedLen`: bool diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index 6fd0b1959c860affc94b56c63b76392a310f2eea..9f57b85f3a8a3ae0797efb618c1f8c1123da070d 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -9,7 +9,7 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied ... | 41 | | #[pallet::storage] 42 | | type Foo = StorageNMap<_, Key, u32>; - | |____________^ the trait `MaxEncodedLen` is not implemented for `Bar` + | |____________^ the trait `MaxEncodedLen` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, NMapKey, u32>: StorageInfoTrait` | = help: the following other types implement trait `MaxEncodedLen`: bool diff --git a/substrate/frame/system/benchmarking/src/inner.rs b/substrate/frame/system/benchmarking/src/inner.rs new file mode 100644 index 0000000000000000000000000000000000000000..c1631b0a2e334e985c9b0ab8a87f1a318548dab1 --- /dev/null +++ b/substrate/frame/system/benchmarking/src/inner.rs @@ -0,0 +1,230 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Frame System benchmarks. + +use codec::Encode; +use frame_benchmarking::v2::*; +use frame_support::{dispatch::DispatchClass, storage, traits::Get}; +use frame_system::{Call, Pallet as System, RawOrigin}; +use sp_core::storage::well_known_keys; +use sp_runtime::traits::Hash; +use sp_std::{prelude::*, vec}; + +pub struct Pallet(System); +pub trait Config: frame_system::Config { + /// Adds ability to the Runtime to test against their sample code. + /// + /// Default is `../res/kitchensink_runtime.compact.compressed.wasm`. + fn prepare_set_code_data() -> Vec { + include_bytes!("../res/kitchensink_runtime.compact.compressed.wasm").to_vec() + } + + /// Adds ability to the Runtime to prepare/initialize before running benchmark `set_code`. + fn setup_set_code_requirements(_code: &Vec) -> Result<(), BenchmarkError> { + Ok(()) + } + + /// Adds ability to the Runtime to do custom validation after benchmark. + /// + /// Default is checking for `CodeUpdated` event . + fn verify_set_code() { + System::::assert_last_event(frame_system::Event::::CodeUpdated.into()); + } +} + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn remark( + b: Linear<0, { *T::BlockLength::get().max.get(DispatchClass::Normal) as u32 }>, + ) -> Result<(), BenchmarkError> { + let remark_message = vec![1; b as usize]; + let caller = whitelisted_caller(); + + #[extrinsic_call] + remark(RawOrigin::Signed(caller), remark_message); + + Ok(()) + } + + #[benchmark] + fn remark_with_event( + b: Linear<0, { *T::BlockLength::get().max.get(DispatchClass::Normal) as u32 }>, + ) -> Result<(), BenchmarkError> { + let remark_message = vec![1; b as usize]; + let caller: T::AccountId = whitelisted_caller(); + let hash = T::Hashing::hash(&remark_message[..]); + + #[extrinsic_call] + remark_with_event(RawOrigin::Signed(caller.clone()), remark_message); + + System::::assert_last_event( + frame_system::Event::::Remarked { sender: caller, hash }.into(), + ); + Ok(()) + } + + #[benchmark] + fn set_heap_pages() -> Result<(), BenchmarkError> { + #[extrinsic_call] + set_heap_pages(RawOrigin::Root, Default::default()); + + Ok(()) + } + + #[benchmark] + fn set_code() -> Result<(), BenchmarkError> { + let runtime_blob = T::prepare_set_code_data(); + T::setup_set_code_requirements(&runtime_blob)?; + + #[extrinsic_call] + set_code(RawOrigin::Root, runtime_blob); + + T::verify_set_code(); + Ok(()) + } + + #[benchmark(extra)] + fn set_code_without_checks() -> Result<(), BenchmarkError> { + // Assume Wasm ~4MB + let code = vec![1; 4_000_000 as usize]; + T::setup_set_code_requirements(&code)?; + + #[block] + { + System::::set_code_without_checks(RawOrigin::Root.into(), code)?; + } + + let current_code = + storage::unhashed::get_raw(well_known_keys::CODE).ok_or("Code not stored.")?; + assert_eq!(current_code.len(), 4_000_000 as usize); + Ok(()) + } + + #[benchmark(skip_meta)] + fn set_storage(i: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> { + // Set up i items to add + let mut items = Vec::new(); + for j in 0..i { + let hash = (i, j).using_encoded(T::Hashing::hash).as_ref().to_vec(); + items.push((hash.clone(), hash.clone())); + } + + let items_to_verify = items.clone(); + + #[extrinsic_call] + set_storage(RawOrigin::Root, items); + + // Verify that they're actually in the storage. + for (item, _) in items_to_verify { + let value = storage::unhashed::get_raw(&item).ok_or("No value stored")?; + assert_eq!(value, *item); + } + Ok(()) + } + + #[benchmark(skip_meta)] + fn kill_storage(i: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> { + // Add i items to storage + let mut items = Vec::with_capacity(i as usize); + for j in 0..i { + let hash = (i, j).using_encoded(T::Hashing::hash).as_ref().to_vec(); + storage::unhashed::put_raw(&hash, &hash); + items.push(hash); + } + + // Verify that they're actually in the storage. + for item in &items { + let value = storage::unhashed::get_raw(item).ok_or("No value stored")?; + assert_eq!(value, *item); + } + + let items_to_verify = items.clone(); + + #[extrinsic_call] + kill_storage(RawOrigin::Root, items); + + // Verify that they're not in the storage anymore. + for item in items_to_verify { + assert!(storage::unhashed::get_raw(&item).is_none()); + } + Ok(()) + } + + #[benchmark(skip_meta)] + fn kill_prefix(p: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> { + let prefix = p.using_encoded(T::Hashing::hash).as_ref().to_vec(); + let mut items = Vec::with_capacity(p as usize); + // add p items that share a prefix + for i in 0..p { + let hash = (p, i).using_encoded(T::Hashing::hash).as_ref().to_vec(); + let key = [&prefix[..], &hash[..]].concat(); + storage::unhashed::put_raw(&key, &key); + items.push(key); + } + + // Verify that they're actually in the storage. + for item in &items { + let value = storage::unhashed::get_raw(item).ok_or("No value stored")?; + assert_eq!(value, *item); + } + + #[extrinsic_call] + kill_prefix(RawOrigin::Root, prefix, p); + + // Verify that they're not in the storage anymore. + for item in items { + assert!(storage::unhashed::get_raw(&item).is_none()); + } + Ok(()) + } + + #[benchmark] + fn authorize_upgrade() -> Result<(), BenchmarkError> { + let runtime_blob = T::prepare_set_code_data(); + T::setup_set_code_requirements(&runtime_blob)?; + let hash = T::Hashing::hash(&runtime_blob); + + #[extrinsic_call] + authorize_upgrade(RawOrigin::Root, hash); + + assert!(System::::authorized_upgrade().is_some()); + Ok(()) + } + + #[benchmark] + fn apply_authorized_upgrade() -> Result<(), BenchmarkError> { + let runtime_blob = T::prepare_set_code_data(); + T::setup_set_code_requirements(&runtime_blob)?; + let hash = T::Hashing::hash(&runtime_blob); + // Will be heavier when it needs to do verification (i.e. don't use `...without_checks`). + System::::authorize_upgrade(RawOrigin::Root.into(), hash)?; + + #[extrinsic_call] + apply_authorized_upgrade(RawOrigin::Root, runtime_blob); + + // Can't check for `CodeUpdated` in parachain upgrades. Just check that the authorization is + // gone. + assert!(System::::authorized_upgrade().is_none()); + Ok(()) + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/substrate/frame/system/benchmarking/src/lib.rs b/substrate/frame/system/benchmarking/src/lib.rs index 29100faa75142a8b945bf15f1870e9c3d685d35e..e55038aeb9551f1bbee2f38371b1e0c63371cce5 100644 --- a/substrate/frame/system/benchmarking/src/lib.rs +++ b/substrate/frame/system/benchmarking/src/lib.rs @@ -15,221 +15,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Benchmarks for Utility Pallet +//! Frame System benchmarks. #![cfg_attr(not(feature = "std"), no_std)] -#![cfg(feature = "runtime-benchmarks")] -use codec::Encode; -use frame_benchmarking::v2::*; -use frame_support::{dispatch::DispatchClass, storage, traits::Get}; -use frame_system::{Call, Pallet as System, RawOrigin}; -use sp_core::storage::well_known_keys; -use sp_runtime::traits::Hash; -use sp_std::{prelude::*, vec}; +#[cfg(feature = "runtime-benchmarks")] +pub mod inner; -mod mock; +#[cfg(feature = "runtime-benchmarks")] +pub use inner::*; -pub struct Pallet(System); -pub trait Config: frame_system::Config { - /// Adds ability to the Runtime to test against their sample code. - /// - /// Default is `../res/kitchensink_runtime.compact.compressed.wasm`. - fn prepare_set_code_data() -> Vec { - include_bytes!("../res/kitchensink_runtime.compact.compressed.wasm").to_vec() - } - - /// Adds ability to the Runtime to prepare/initialize before running benchmark `set_code`. - fn setup_set_code_requirements(_code: &Vec) -> Result<(), BenchmarkError> { - Ok(()) - } - - /// Adds ability to the Runtime to do custom validation after benchmark. - /// - /// Default is checking for `CodeUpdated` event . - fn verify_set_code() { - System::::assert_last_event(frame_system::Event::::CodeUpdated.into()); - } -} - -#[benchmarks] -mod benchmarks { - use super::*; - - #[benchmark] - fn remark( - b: Linear<0, { *T::BlockLength::get().max.get(DispatchClass::Normal) as u32 }>, - ) -> Result<(), BenchmarkError> { - let remark_message = vec![1; b as usize]; - let caller = whitelisted_caller(); - - #[extrinsic_call] - remark(RawOrigin::Signed(caller), remark_message); - - Ok(()) - } - - #[benchmark] - fn remark_with_event( - b: Linear<0, { *T::BlockLength::get().max.get(DispatchClass::Normal) as u32 }>, - ) -> Result<(), BenchmarkError> { - let remark_message = vec![1; b as usize]; - let caller: T::AccountId = whitelisted_caller(); - let hash = T::Hashing::hash(&remark_message[..]); - - #[extrinsic_call] - remark_with_event(RawOrigin::Signed(caller.clone()), remark_message); - - System::::assert_last_event( - frame_system::Event::::Remarked { sender: caller, hash }.into(), - ); - Ok(()) - } - - #[benchmark] - fn set_heap_pages() -> Result<(), BenchmarkError> { - #[extrinsic_call] - set_heap_pages(RawOrigin::Root, Default::default()); - - Ok(()) - } - - #[benchmark] - fn set_code() -> Result<(), BenchmarkError> { - let runtime_blob = T::prepare_set_code_data(); - T::setup_set_code_requirements(&runtime_blob)?; - - #[extrinsic_call] - set_code(RawOrigin::Root, runtime_blob); - - T::verify_set_code(); - Ok(()) - } - - #[benchmark(extra)] - fn set_code_without_checks() -> Result<(), BenchmarkError> { - // Assume Wasm ~4MB - let code = vec![1; 4_000_000 as usize]; - T::setup_set_code_requirements(&code)?; - - #[block] - { - System::::set_code_without_checks(RawOrigin::Root.into(), code)?; - } - - let current_code = - storage::unhashed::get_raw(well_known_keys::CODE).ok_or("Code not stored.")?; - assert_eq!(current_code.len(), 4_000_000 as usize); - Ok(()) - } - - #[benchmark(skip_meta)] - fn set_storage(i: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> { - // Set up i items to add - let mut items = Vec::new(); - for j in 0..i { - let hash = (i, j).using_encoded(T::Hashing::hash).as_ref().to_vec(); - items.push((hash.clone(), hash.clone())); - } - - let items_to_verify = items.clone(); - - #[extrinsic_call] - set_storage(RawOrigin::Root, items); - - // Verify that they're actually in the storage. - for (item, _) in items_to_verify { - let value = storage::unhashed::get_raw(&item).ok_or("No value stored")?; - assert_eq!(value, *item); - } - Ok(()) - } - - #[benchmark(skip_meta)] - fn kill_storage(i: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> { - // Add i items to storage - let mut items = Vec::with_capacity(i as usize); - for j in 0..i { - let hash = (i, j).using_encoded(T::Hashing::hash).as_ref().to_vec(); - storage::unhashed::put_raw(&hash, &hash); - items.push(hash); - } - - // Verify that they're actually in the storage. - for item in &items { - let value = storage::unhashed::get_raw(item).ok_or("No value stored")?; - assert_eq!(value, *item); - } - - let items_to_verify = items.clone(); - - #[extrinsic_call] - kill_storage(RawOrigin::Root, items); - - // Verify that they're not in the storage anymore. - for item in items_to_verify { - assert!(storage::unhashed::get_raw(&item).is_none()); - } - Ok(()) - } - - #[benchmark(skip_meta)] - fn kill_prefix(p: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> { - let prefix = p.using_encoded(T::Hashing::hash).as_ref().to_vec(); - let mut items = Vec::with_capacity(p as usize); - // add p items that share a prefix - for i in 0..p { - let hash = (p, i).using_encoded(T::Hashing::hash).as_ref().to_vec(); - let key = [&prefix[..], &hash[..]].concat(); - storage::unhashed::put_raw(&key, &key); - items.push(key); - } - - // Verify that they're actually in the storage. - for item in &items { - let value = storage::unhashed::get_raw(item).ok_or("No value stored")?; - assert_eq!(value, *item); - } - - #[extrinsic_call] - kill_prefix(RawOrigin::Root, prefix, p); - - // Verify that they're not in the storage anymore. - for item in items { - assert!(storage::unhashed::get_raw(&item).is_none()); - } - Ok(()) - } - - #[benchmark] - fn authorize_upgrade() -> Result<(), BenchmarkError> { - let runtime_blob = T::prepare_set_code_data(); - T::setup_set_code_requirements(&runtime_blob)?; - let hash = T::Hashing::hash(&runtime_blob); - - #[extrinsic_call] - authorize_upgrade(RawOrigin::Root, hash); - - assert!(System::::authorized_upgrade().is_some()); - Ok(()) - } - - #[benchmark] - fn apply_authorized_upgrade() -> Result<(), BenchmarkError> { - let runtime_blob = T::prepare_set_code_data(); - T::setup_set_code_requirements(&runtime_blob)?; - let hash = T::Hashing::hash(&runtime_blob); - // Will be heavier when it needs to do verification (i.e. don't use `...without_checks`). - System::::authorize_upgrade(RawOrigin::Root.into(), hash)?; - - #[extrinsic_call] - apply_authorized_upgrade(RawOrigin::Root, runtime_blob); - - // Can't check for `CodeUpdated` in parachain upgrades. Just check that the authorization is - // gone. - assert!(System::::authorized_upgrade().is_none()); - Ok(()) - } - - impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); -} +#[cfg(all(feature = "runtime-benchmarks", test))] +pub(crate) mod mock; diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 184f27b61ed2a78fa298e4883db6d4eb39d8a184..30df4dcfd43e9058cb3acc1a8f9b1863bf925e11 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -741,9 +741,7 @@ pub mod pallet { #[cfg(feature = "experimental")] #[pallet::call_index(8)] #[pallet::weight(task.weight())] - pub fn do_task(origin: OriginFor, task: T::RuntimeTask) -> DispatchResultWithPostInfo { - ensure_signed(origin)?; - + pub fn do_task(_origin: OriginFor, task: T::RuntimeTask) -> DispatchResultWithPostInfo { if !task.is_valid() { return Err(Error::::InvalidTask.into()) } @@ -1032,6 +1030,18 @@ pub mod pallet { }) } } + #[cfg(feature = "experimental")] + if let Call::do_task { ref task } = call { + if task.is_valid() { + return Ok(ValidTransaction { + priority: u64::max_value(), + requires: Vec::new(), + provides: vec![T::Hashing::hash_of(&task.encode()).as_ref().to_vec()], + longevity: TransactionLongevity::max_value(), + propagate: true, + }) + } + } Err(InvalidTransaction::Call.into()) } } diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs index d61558cf536362bbaa5abd4a451fa5f4589a85bd..9a2b22b817096916193db338df62269415ec7a52 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs @@ -244,6 +244,11 @@ ord_parameter_types! { pub const AssetConversionOrigin: u64 = AccountIdConversion::::into_account_truncating(&AssetConversionPalletId::get()); } +pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< + AssetConversionPalletId, + (NativeOrWithId, NativeOrWithId), +>; + impl pallet_asset_conversion::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; @@ -252,8 +257,8 @@ impl pallet_asset_conversion::Config for Runtime { type Assets = UnionOf, AccountId>; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = Chain< - WithFirstAsset>, - Ascending>, + WithFirstAsset, PoolIdToAccountId>, + Ascending, PoolIdToAccountId>, >; type PoolAssetId = u32; type PoolAssets = PoolAssets; diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs index 62faed269d377cc1dc2b75091d79210401cd0a26..aa2f26f3a6a8d5ce0edc45e1694c51a2c12da44c 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs @@ -201,6 +201,8 @@ fn transaction_payment_in_asset_possible() { .base_weight(Weight::from_parts(base_weight, 0)) .build() .execute_with(|| { + System::set_block_number(1); + // create the asset let asset_id = 1; let min_balance = 2; @@ -246,6 +248,12 @@ fn transaction_payment_in_asset_possible() { // check that fee was charged in the given asset assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset); + System::assert_has_event(RuntimeEvent::Assets(pallet_assets::Event::Withdrawn { + asset_id, + who: caller, + amount: fee_in_asset, + })); + assert_ok!(ChargeAssetTxPayment::::post_dispatch( Some(pre), &info_from_weight(WEIGHT_5), // estimated tx weight @@ -385,6 +393,8 @@ fn asset_transaction_payment_with_tip_and_refund() { .base_weight(Weight::from_parts(base_weight, 0)) .build() .execute_with(|| { + System::set_block_number(1); + // create the asset let asset_id = 1; let min_balance = 2; @@ -434,6 +444,12 @@ fn asset_transaction_payment_with_tip_and_refund() { ) .unwrap(); + System::assert_has_event(RuntimeEvent::Assets(pallet_assets::Event::Withdrawn { + asset_id, + who: caller, + amount: fee_in_asset, + })); + assert_ok!(ChargeAssetTxPayment::::post_dispatch( Some(pre), &info_from_weight(WEIGHT_100), @@ -451,6 +467,12 @@ fn asset_transaction_payment_with_tip_and_refund() { balance - fee_in_asset + expected_token_refund ); assert_eq!(Balances::free_balance(caller), 20 * balance_factor); + + System::assert_has_event(RuntimeEvent::Assets(pallet_assets::Event::Deposited { + asset_id, + who: caller, + amount: expected_token_refund, + })); }); } diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs index 8df98ceda9971565788576ee839792f6d58da2b0..098ecf11dd92fd41cf1ad3bcddaa679da1d6c0fb 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -157,6 +157,8 @@ fn transaction_payment_in_asset_possible() { .base_weight(Weight::from_parts(base_weight, 0)) .build() .execute_with(|| { + System::set_block_number(1); + // create the asset let asset_id = 1; let min_balance = 2; @@ -188,6 +190,12 @@ fn transaction_payment_in_asset_possible() { assert_eq!(Assets::balance(asset_id, caller), balance - fee); assert_eq!(Assets::balance(asset_id, BLOCK_AUTHOR), 0); + System::assert_has_event(RuntimeEvent::Assets(pallet_assets::Event::Withdrawn { + asset_id, + who: caller, + amount: fee, + })); + assert_ok!(ChargeAssetTxPayment::::post_dispatch( Some(pre), &info_from_weight(Weight::from_parts(weight, 0)), @@ -198,6 +206,12 @@ fn transaction_payment_in_asset_possible() { assert_eq!(Assets::balance(asset_id, caller), balance - fee); // check that the block author gets rewarded assert_eq!(Assets::balance(asset_id, BLOCK_AUTHOR), fee); + + System::assert_has_event(RuntimeEvent::Assets(pallet_assets::Event::Deposited { + asset_id, + who: BLOCK_AUTHOR, + amount: fee, + })); }); } @@ -263,6 +277,8 @@ fn asset_transaction_payment_with_tip_and_refund() { .base_weight(Weight::from_parts(base_weight, 0)) .build() .execute_with(|| { + System::set_block_number(1); + // create the asset let asset_id = 1; let min_balance = 2; @@ -292,6 +308,12 @@ fn asset_transaction_payment_with_tip_and_refund() { .unwrap(); assert_eq!(Assets::balance(asset_id, caller), balance - fee_with_tip); + System::assert_has_event(RuntimeEvent::Assets(pallet_assets::Event::Withdrawn { + asset_id, + who: caller, + amount: fee_with_tip, + })); + let final_weight = 50; assert_ok!(ChargeAssetTxPayment::::post_dispatch( Some(pre), @@ -304,6 +326,12 @@ fn asset_transaction_payment_with_tip_and_refund() { fee_with_tip - (weight - final_weight) * min_balance / ExistentialDeposit::get(); assert_eq!(Assets::balance(asset_id, caller), balance - (final_fee)); assert_eq!(Assets::balance(asset_id, BLOCK_AUTHOR), final_fee); + + System::assert_has_event(RuntimeEvent::Assets(pallet_assets::Event::Deposited { + asset_id, + who: caller, + amount: fee_with_tip - final_fee, + })); }); } diff --git a/substrate/frame/transaction-payment/rpc/src/lib.rs b/substrate/frame/transaction-payment/rpc/src/lib.rs index f5323cf852e97df015aaaebac7a36f1d7525397d..050c7fb8915ec71d77fbfa9005f75f130c72be92 100644 --- a/substrate/frame/transaction-payment/rpc/src/lib.rs +++ b/substrate/frame/transaction-payment/rpc/src/lib.rs @@ -17,7 +17,7 @@ //! RPC interface for the transaction payment pallet. -use std::{convert::TryInto, sync::Arc}; +use std::sync::Arc; use codec::{Codec, Decode}; use jsonrpsee::{ diff --git a/substrate/frame/try-runtime/src/inner.rs b/substrate/frame/try-runtime/src/inner.rs new file mode 100644 index 0000000000000000000000000000000000000000..591124e2ad9926d0104d270769ae77fddbe78997 --- /dev/null +++ b/substrate/frame/try-runtime/src/inner.rs @@ -0,0 +1,50 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Supporting types for try-runtime, testing and dry-running commands. + +pub use frame_support::traits::{TryStateSelect, UpgradeCheckSelect}; +use frame_support::weights::Weight; + +sp_api::decl_runtime_apis! { + /// Runtime api for testing the execution of a runtime upgrade. + pub trait TryRuntime { + /// dry-run runtime upgrades, returning the total weight consumed. + /// + /// This should do EXACTLY the same operations as the runtime would have done in the case of + /// a runtime upgrade (e.g. pallet ordering must be the same) + /// + /// Returns the consumed weight of the migration in case of a successful one, combined with + /// the total allowed block weight of the runtime. + /// + /// If `checks` is `true`, `pre_migrate` and `post_migrate` of each migration and + /// `try_state` of all pallets will be executed. Else, no. If checks are executed, the PoV + /// tracking is likely inaccurate. + fn on_runtime_upgrade(checks: UpgradeCheckSelect) -> (Weight, Weight); + + /// Execute the given block, but optionally disable state-root and signature checks. + /// + /// Optionally, a number of `try_state` hooks can also be executed after the block + /// execution. + fn execute_block( + block: Block, + state_root_check: bool, + signature_check: bool, + try_state: TryStateSelect, + ) -> Weight; + } +} diff --git a/substrate/frame/try-runtime/src/lib.rs b/substrate/frame/try-runtime/src/lib.rs index 43292efe210428980321e247807e1fc5da2d3c86..9da2dd18ca2b63c93ceb6d5522955ee0e4a3bb3e 100644 --- a/substrate/frame/try-runtime/src/lib.rs +++ b/substrate/frame/try-runtime/src/lib.rs @@ -18,36 +18,9 @@ //! Supporting types for try-runtime, testing and dry-running commands. #![cfg_attr(not(feature = "std"), no_std)] -#![cfg(feature = "try-runtime")] -pub use frame_support::traits::{TryStateSelect, UpgradeCheckSelect}; -use frame_support::weights::Weight; +#[cfg(feature = "try-runtime")] +pub mod inner; -sp_api::decl_runtime_apis! { - /// Runtime api for testing the execution of a runtime upgrade. - pub trait TryRuntime { - /// dry-run runtime upgrades, returning the total weight consumed. - /// - /// This should do EXACTLY the same operations as the runtime would have done in the case of - /// a runtime upgrade (e.g. pallet ordering must be the same) - /// - /// Returns the consumed weight of the migration in case of a successful one, combined with - /// the total allowed block weight of the runtime. - /// - /// If `checks` is `true`, `pre_migrate` and `post_migrate` of each migration and - /// `try_state` of all pallets will be executed. Else, no. If checks are executed, the PoV - /// tracking is likely inaccurate. - fn on_runtime_upgrade(checks: UpgradeCheckSelect) -> (Weight, Weight); - - /// Execute the given block, but optionally disable state-root and signature checks. - /// - /// Optionally, a number of `try_state` hooks can also be executed after the block - /// execution. - fn execute_block( - block: Block, - state_root_check: bool, - signature_check: bool, - try_state: TryStateSelect, - ) -> Weight; - } -} +#[cfg(feature = "try-runtime")] +pub use inner::*; diff --git a/substrate/frame/tx-pause/src/lib.rs b/substrate/frame/tx-pause/src/lib.rs index 31be575fba7cb9e6d5335d9e1bbec3b4f9d4e723..5904b5ed316285c38526bdab47b9cb53048faec7 100644 --- a/substrate/frame/tx-pause/src/lib.rs +++ b/substrate/frame/tx-pause/src/lib.rs @@ -87,7 +87,7 @@ use frame_support::{ }; use frame_system::pallet_prelude::*; use sp_runtime::{traits::Dispatchable, DispatchResult}; -use sp_std::{convert::TryInto, prelude::*}; +use sp_std::prelude::*; pub use pallet::*; pub use weights::*; diff --git a/substrate/primitives/api/Cargo.toml b/substrate/primitives/api/Cargo.toml index 544ba72141ebd22291c04a54f34adbd873b335aa..2f553819b1bc09b911ad3664aa555c661863a40b 100644 --- a/substrate/primitives/api/Cargo.toml +++ b/substrate/primitives/api/Cargo.toml @@ -68,4 +68,4 @@ std = [ disable-logging = ["log/max_level_off"] # Do not report the documentation in the metadata. no-metadata-docs = ["sp-api-proc-macro/no-metadata-docs"] -frame-metadata = ["sp-api-proc-macro/frame-metadata", "sp-metadata-ir"] +frame-metadata = ["sp-metadata-ir"] diff --git a/substrate/primitives/api/proc-macro/Cargo.toml b/substrate/primitives/api/proc-macro/Cargo.toml index f5406758687ae20e9ce13a2cf02959c5cccbd576..b1bc547f3e4ae82e4e4331643c0aef14e1df3451 100644 --- a/substrate/primitives/api/proc-macro/Cargo.toml +++ b/substrate/primitives/api/proc-macro/Cargo.toml @@ -35,4 +35,3 @@ assert_matches = "1.3.0" default = ["std"] std = ["blake2/std"] no-metadata-docs = [] -frame-metadata = [] diff --git a/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs index e34e4c0e7672164afb41702ab3bae87082df9062..cb213f2fd627b628f2b763ff7e3f132eb849bed4 100644 --- a/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs @@ -193,10 +193,7 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result { get_api_version(&found_attributes).map(|v| generate_runtime_api_version(v as u32))?; let id = generate_runtime_api_id(&decl.ident.to_string()); - #[cfg(feature = "frame-metadata")] let metadata = crate::runtime_metadata::generate_decl_runtime_metadata(&decl); - #[cfg(not(feature = "frame-metadata"))] - let metadata = quote!(); let trait_api_version = get_api_version(&found_attributes)?; diff --git a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs index 87a381fd7bf92bc72e30babf018b5dcb0141e571..2c423f8c28dd435599e1cc5b2d5a49807fd73abb 100644 --- a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -821,10 +821,7 @@ fn impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result { let wasm_interface = generate_wasm_interface(api_impls)?; let api_impls_for_runtime_api = generate_api_impl_for_runtime_api(api_impls)?; - #[cfg(feature = "frame-metadata")] let runtime_metadata = crate::runtime_metadata::generate_impl_runtime_metadata(api_impls)?; - #[cfg(not(feature = "frame-metadata"))] - let runtime_metadata = quote!(); let impl_ = quote!( #base_runtime_api diff --git a/substrate/primitives/api/proc-macro/src/lib.rs b/substrate/primitives/api/proc-macro/src/lib.rs index 06e148880e975f88e07836a4a5ca52fd5e5527b1..d34f4b7f9cf6a388438b203dbad9f1e0c68b3481 100644 --- a/substrate/primitives/api/proc-macro/src/lib.rs +++ b/substrate/primitives/api/proc-macro/src/lib.rs @@ -25,7 +25,6 @@ mod common; mod decl_runtime_apis; mod impl_runtime_apis; mod mock_impl_runtime_apis; -#[cfg(feature = "frame-metadata")] mod runtime_metadata; mod utils; diff --git a/substrate/primitives/api/proc-macro/src/runtime_metadata.rs b/substrate/primitives/api/proc-macro/src/runtime_metadata.rs index 41849401291e633924e6f530ffc5bc3474ba6283..9944927d557302db454e9589be681ffb26c7558e 100644 --- a/substrate/primitives/api/proc-macro/src/runtime_metadata.rs +++ b/substrate/primitives/api/proc-macro/src/runtime_metadata.rs @@ -164,15 +164,17 @@ pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 { let (impl_generics, _, where_clause) = generics.split_for_impl(); quote!( - #( #attrs )* - #[inline(always)] - pub fn runtime_metadata #impl_generics () -> #crate_::metadata_ir::RuntimeApiMetadataIR - #where_clause - { - #crate_::metadata_ir::RuntimeApiMetadataIR { - name: #trait_name, - methods: #crate_::vec![ #( #methods, )* ], - docs: #docs, + #crate_::frame_metadata_enabled! { + #( #attrs )* + #[inline(always)] + pub fn runtime_metadata #impl_generics () -> #crate_::metadata_ir::RuntimeApiMetadataIR + #where_clause + { + #crate_::metadata_ir::RuntimeApiMetadataIR { + name: #trait_name, + methods: #crate_::vec![ #( #methods, )* ], + docs: #docs, + } } } ) @@ -255,14 +257,16 @@ pub fn generate_impl_runtime_metadata(impls: &[ItemImpl]) -> Result #crate_::vec::Vec<#crate_::metadata_ir::RuntimeApiMetadataIR> { - #crate_::vec![ #( #metadata, )* ] + #crate_::frame_metadata_enabled! { + #[doc(hidden)] + trait InternalImplRuntimeApis { + #[inline(always)] + fn runtime_metadata(&self) -> #crate_::vec::Vec<#crate_::metadata_ir::RuntimeApiMetadataIR> { + #crate_::vec![ #( #metadata, )* ] + } } + #[doc(hidden)] + impl InternalImplRuntimeApis for #runtime_name {} } - #[doc(hidden)] - impl InternalImplRuntimeApis for #runtime_name {} )) } diff --git a/substrate/primitives/api/proc-macro/src/utils.rs b/substrate/primitives/api/proc-macro/src/utils.rs index a6570a98f1f78361398bd5a5a2c101af0a133988..d90b56058648ba8653992c210586238028629689 100644 --- a/substrate/primitives/api/proc-macro/src/utils.rs +++ b/substrate/primitives/api/proc-macro/src/utils.rs @@ -261,7 +261,6 @@ pub fn versioned_trait_name(trait_ident: &Ident, version: u64) -> Ident { } /// Extract the documentation from the provided attributes. -#[cfg(feature = "frame-metadata")] pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec { use quote::ToTokens; attrs @@ -277,7 +276,6 @@ pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec { } /// Filters all attributes except the cfg ones. -#[cfg(feature = "frame-metadata")] pub fn filter_cfg_attributes(attrs: &[syn::Attribute]) -> Vec { attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect() } diff --git a/substrate/primitives/api/src/lib.rs b/substrate/primitives/api/src/lib.rs index a945b9f21f3cff60d6a9d5e24aa07704a1dc8d31..20f989c4882e35fe06d5496f851b4adec2c6f1c0 100644 --- a/substrate/primitives/api/src/lib.rs +++ b/substrate/primitives/api/src/lib.rs @@ -838,3 +838,4 @@ decl_runtime_apis! { sp_core::generate_feature_enabled_macro!(std_enabled, feature = "std", $); sp_core::generate_feature_enabled_macro!(std_disabled, not(feature = "std"), $); +sp_core::generate_feature_enabled_macro!(frame_metadata_enabled, feature = "frame-metadata", $); diff --git a/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr b/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr index 788d1807f3ba2791c4593ebc7559cdf6d44078ca..535bbb178d5f961a629fe3a0e1951c49060f83a4 100644 --- a/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr +++ b/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr @@ -9,10 +9,12 @@ note: the struct `RuntimeVersion` is defined here | | use sp_version::RuntimeVersion; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: consider importing one of these items instead +help: consider importing this struct instead + | +37 | fn version() -> sp_version::RuntimeVersion { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: import `RuntimeVersion` directly | -37 | fn version() -> sp_api::__private::RuntimeVersion { - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 37 | fn version() -> sp_version::RuntimeVersion { | ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr b/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr index b4df7c068768c9236e98b44c1c2fa878311942af..f4e0f3b0afb047c4c2c377c16daa38ec1238a67e 100644 --- a/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr +++ b/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr @@ -9,10 +9,12 @@ note: the struct `RuntimeVersion` is defined here | | use sp_version::RuntimeVersion; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: consider importing one of these items instead +help: consider importing this struct instead + | +39 | fn version() -> sp_version::RuntimeVersion { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: import `RuntimeVersion` directly | -39 | fn version() -> sp_api::__private::RuntimeVersion { - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 39 | fn version() -> sp_version::RuntimeVersion { | ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -30,8 +32,8 @@ note: type in trait | 27 | fn test(data: u64); | ^^^ - = note: expected signature `fn(u64)` - found signature `fn(&u64)` + = note: expected signature `fn(_)` + found signature `fn(&_)` error[E0308]: mismatched types --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:33:11 diff --git a/substrate/primitives/consensus/babe/Cargo.toml b/substrate/primitives/consensus/babe/Cargo.toml index 2420f48b1f4aa97923a556de37a06a6611ba8e05..799d474aebe45e26a94392899251b054cd39d84b 100644 --- a/substrate/primitives/consensus/babe/Cargo.toml +++ b/substrate/primitives/consensus/babe/Cargo.toml @@ -26,7 +26,7 @@ sp-consensus-slots = { path = "../slots", default-features = false } sp-core = { path = "../../core", default-features = false } sp-inherents = { path = "../../inherents", default-features = false } sp-runtime = { path = "../../runtime", default-features = false } -sp-timestamp = { path = "../../timestamp", optional = true } +sp-timestamp = { path = "../../timestamp", optional = true, default-features = false } [features] default = ["std"] diff --git a/substrate/primitives/consensus/beefy/src/commitment.rs b/substrate/primitives/consensus/beefy/src/commitment.rs index 4fd9e1b0a6ed14ea51ef30e5d4c6748a129d5249..8d3a6c6aa90f981ec64f5f549aaf7188d68b02f4 100644 --- a/substrate/primitives/consensus/beefy/src/commitment.rs +++ b/substrate/primitives/consensus/beefy/src/commitment.rs @@ -19,8 +19,30 @@ use alloc::{vec, vec::Vec}; use codec::{Decode, Encode, Error, Input}; use core::cmp; use scale_info::TypeInfo; +use sp_application_crypto::RuntimeAppPublic; +use sp_runtime::traits::Hash; + +use crate::{BeefyAuthorityId, Payload, ValidatorSet, ValidatorSetId}; + +/// A commitment signature, accompanied by the id of the validator that it belongs to. +#[derive(Debug)] +pub struct KnownSignature { + /// The signing validator. + pub validator_id: TAuthorityId, + /// The signature. + pub signature: TSignature, +} -use crate::{Payload, ValidatorSetId}; +impl KnownSignature<&TAuthorityId, &TSignature> { + /// Creates a `KnownSignature` from an + /// `KnownSignature<&TAuthorityId, &TSignature>`. + pub fn to_owned(&self) -> KnownSignature { + KnownSignature { + validator_id: self.validator_id.clone(), + signature: self.signature.clone(), + } + } +} /// A commitment signed by GRANDPA validators as part of BEEFY protocol. /// @@ -113,9 +135,49 @@ impl core::fmt::Display impl SignedCommitment { /// Return the number of collected signatures. - pub fn no_of_signatures(&self) -> usize { + pub fn signature_count(&self) -> usize { self.signatures.iter().filter(|x| x.is_some()).count() } + + /// Verify all the commitment signatures against the validator set that was active + /// at the block where the commitment was generated. + /// + /// Returns the valid validator-signature pairs if the commitment can be verified. + pub fn verify_signatures<'a, TAuthorityId, MsgHash>( + &'a self, + target_number: TBlockNumber, + validator_set: &'a ValidatorSet, + ) -> Result>, u32> + where + TBlockNumber: Clone + Encode + PartialEq, + TAuthorityId: RuntimeAppPublic + BeefyAuthorityId, + MsgHash: Hash, + { + if self.signatures.len() != validator_set.len() || + self.commitment.validator_set_id != validator_set.id() || + self.commitment.block_number != target_number + { + return Err(0) + } + + // Arrangement of signatures in the commitment should be in the same order + // as validators for that set. + let encoded_commitment = self.commitment.encode(); + let signatories: Vec<_> = validator_set + .validators() + .into_iter() + .zip(self.signatures.iter()) + .filter_map(|(id, maybe_signature)| { + let signature = maybe_signature.as_ref()?; + match BeefyAuthorityId::verify(id, signature, &encoded_commitment) { + true => Some(KnownSignature { validator_id: id, signature }), + false => None, + } + }) + .collect(); + + Ok(signatories) + } } /// Type to be used to denote placement of signatures @@ -439,13 +501,13 @@ mod tests { commitment, signatures: vec![None, None, Some(sigs.0), Some(sigs.1)], }; - assert_eq!(signed.no_of_signatures(), 2); + assert_eq!(signed.signature_count(), 2); // when signed.signatures[2] = None; // then - assert_eq!(signed.no_of_signatures(), 1); + assert_eq!(signed.signature_count(), 1); } #[test] diff --git a/substrate/primitives/consensus/beefy/src/lib.rs b/substrate/primitives/consensus/beefy/src/lib.rs index 70978ca559dd2b50041c45d9cab3c874e8bcc852..6f644c5f790db5aed381c19ca35edeabca93222e 100644 --- a/substrate/primitives/consensus/beefy/src/lib.rs +++ b/substrate/primitives/consensus/beefy/src/lib.rs @@ -43,7 +43,7 @@ pub mod witness; #[cfg(feature = "std")] pub mod test_utils; -pub use commitment::{Commitment, SignedCommitment, VersionedFinalityProof}; +pub use commitment::{Commitment, KnownSignature, SignedCommitment, VersionedFinalityProof}; pub use payload::{known_payloads, BeefyPayloadId, Payload, PayloadProvider}; use alloc::vec::Vec; diff --git a/substrate/primitives/consensus/sassafras/Cargo.toml b/substrate/primitives/consensus/sassafras/Cargo.toml index 07304ed9b2401bfb40ac321ba476be0fd304d14e..50348054da0118ca52137a5f1c62040e6dd86bea 100644 --- a/substrate/primitives/consensus/sassafras/Cargo.toml +++ b/substrate/primitives/consensus/sassafras/Cargo.toml @@ -3,7 +3,7 @@ name = "sp-consensus-sassafras" version = "0.3.4-dev" authors.workspace = true description = "Primitives for Sassafras consensus" -edition = "2021" +edition.workspace = true license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/polkadot-sdk/" diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index 833b2af95cd10c5b533f56affb7a50f237d54905..8437497b02bd10f7dbb254998db6a9989c79edc5 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -37,7 +37,7 @@ ss58-registry = { version = "1.34.0", default-features = false } sp-std = { path = "../std", default-features = false } sp-debug-derive = { path = "../debug-derive", default-features = false } sp-storage = { path = "../storage", default-features = false } -sp-externalities = { path = "../externalities", optional = true } +sp-externalities = { path = "../externalities", optional = true, default-features = false } futures = { version = "0.3.30", optional = true } dyn-clonable = { version = "0.9.0", optional = true } thiserror = { optional = true, workspace = true } diff --git a/substrate/primitives/core/fuzz/Cargo.toml b/substrate/primitives/core/fuzz/Cargo.toml index c6b5a065b6dca7a389e6409fe623018df3cfc083..463eaea8ea30d827c6f39b7cc02e8947a085c88b 100644 --- a/substrate/primitives/core/fuzz/Cargo.toml +++ b/substrate/primitives/core/fuzz/Cargo.toml @@ -2,6 +2,7 @@ name = "sp-core-fuzz" version = "0.0.0" publish = false +edition.workspace = true [lints] workspace = true diff --git a/substrate/primitives/io/src/lib.rs b/substrate/primitives/io/src/lib.rs index ec32b7290330fc7a1fd76dc4d7ce86210bcceba0..c8675a9a90bd2ee16b9deca27457fafa88f8fec0 100644 --- a/substrate/primitives/io/src/lib.rs +++ b/substrate/primitives/io/src/lib.rs @@ -182,7 +182,7 @@ impl From for KillStorageResult { pub trait Storage { /// Returns the data for `key` in the storage or `None` if the key can not be found. fn get(&self, key: &[u8]) -> Option { - self.storage(key).map(|s| bytes::Bytes::from(s.to_vec())) + self.storage(key).map(bytes::Bytes::from) } /// Get `key` from storage, placing the value into `value_out` and return the number of diff --git a/substrate/primitives/mixnet/Cargo.toml b/substrate/primitives/mixnet/Cargo.toml index 07840ca63cb2b8eeaa1773146cf34a74d889b4c8..166609ad922c96aaa62cde3bc42156e125635686 100644 --- a/substrate/primitives/mixnet/Cargo.toml +++ b/substrate/primitives/mixnet/Cargo.toml @@ -4,7 +4,7 @@ name = "sp-mixnet" version = "0.4.0" license = "Apache-2.0" authors = ["Parity Technologies "] -edition = "2021" +edition.workspace = true homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" diff --git a/substrate/primitives/session/Cargo.toml b/substrate/primitives/session/Cargo.toml index cdee4fb03e1254e04e0a068908a380ed28350bee..5314ccd6d965c44de5371a4fbc0888821e725236 100644 --- a/substrate/primitives/session/Cargo.toml +++ b/substrate/primitives/session/Cargo.toml @@ -20,9 +20,9 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } sp-api = { path = "../api", default-features = false } sp-core = { path = "../core", default-features = false } -sp-runtime = { path = "../runtime", optional = true } +sp-runtime = { path = "../runtime", optional = true, default-features = false } sp-staking = { path = "../staking", default-features = false } -sp-keystore = { path = "../keystore", optional = true } +sp-keystore = { path = "../keystore", optional = true, default-features = false } [features] default = ["std"] diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 11b7ef41b9a7b06e1b67d43abb58e9aacc33d8ab..ad6cc6e2f4ffdd8241d951cdd8ec2a9faacc8254 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -29,7 +29,7 @@ use core::ops::Sub; use scale_info::TypeInfo; use sp_runtime::{ traits::{AtLeast32BitUnsigned, Zero}, - DispatchError, DispatchResult, RuntimeDebug, Saturating, + DispatchError, DispatchResult, Perbill, RuntimeDebug, Saturating, }; pub mod offence; @@ -254,6 +254,9 @@ pub trait StakingInterface { /// schedules have reached their unlocking era should allow more calls to this function. fn unbond(stash: &Self::AccountId, value: Self::Balance) -> DispatchResult; + /// Update the reward destination for the ledger associated with the stash. + fn update_payee(stash: &Self::AccountId, reward_acc: &Self::AccountId) -> DispatchResult; + /// Unlock any funds schedule to unlock before or at the current era. /// /// Returns whether the stash was killed because of this withdraw or not. @@ -274,7 +277,7 @@ pub trait StakingInterface { /// Checks whether an account `staker` has been exposed in an era. fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool; - /// Return the status of the given staker, `None` if not staked at all. + /// Return the status of the given staker, `Err` if not staked at all. fn status(who: &Self::AccountId) -> Result, DispatchError>; /// Checks whether or not this is a validator account. @@ -290,6 +293,9 @@ pub trait StakingInterface { } } + /// Returns the fraction of the slash to be rewarded to reporter. + fn slash_reward_fraction() -> Perbill; + #[cfg(feature = "runtime-benchmarks")] fn max_exposure_page_size() -> Page; @@ -304,6 +310,34 @@ pub trait StakingInterface { fn set_current_era(era: EraIndex); } +/// Set of low level apis to manipulate staking ledger. +/// +/// These apis bypass some or all safety checks and should only be used if you know what you are +/// doing. +pub trait StakingUnchecked: StakingInterface { + /// Migrate an existing staker to a virtual staker. + /// + /// It would release all funds held by the implementation pallet. + fn migrate_to_virtual_staker(who: &Self::AccountId); + + /// Book-keep a new bond for `keyless_who` without applying any locks (hence virtual). + /// + /// It is important that `keyless_who` is a keyless account and therefore cannot interact with + /// staking pallet directly. Caller is responsible for ensuring the passed amount is locked and + /// valid. + fn virtual_bond( + keyless_who: &Self::AccountId, + value: Self::Balance, + payee: &Self::AccountId, + ) -> DispatchResult; + + /// Migrate a virtual staker to a direct staker. + /// + /// Only used for testing. + #[cfg(feature = "runtime-benchmarks")] + fn migrate_to_direct_staker(who: &Self::AccountId); +} + /// The amount of exposure for an era that an individual nominator has (susceptible to slashing). #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct IndividualExposure { diff --git a/substrate/primitives/staking/src/offence.rs b/substrate/primitives/staking/src/offence.rs index 30d96d0cbafce9f0540ffbb49cf15c7375ea405f..2c2ebc1fc971f25cff94b0cef4913eca19551e69 100644 --- a/substrate/primitives/staking/src/offence.rs +++ b/substrate/primitives/staking/src/offence.rs @@ -37,29 +37,6 @@ pub type Kind = [u8; 16]; /// so that we can slash it accordingly. pub type OffenceCount = u32; -/// In case of an offence, which conditions get an offending validator disabled. -#[derive( - Clone, - Copy, - PartialEq, - Eq, - Hash, - PartialOrd, - Ord, - Encode, - Decode, - sp_runtime::RuntimeDebug, - scale_info::TypeInfo, -)] -pub enum DisableStrategy { - /// Independently of slashing, this offence will not disable the offender. - Never, - /// Only disable the offender if it is also slashed. - WhenSlashed, - /// Independently of slashing, this offence will always disable the offender. - Always, -} - /// A trait implemented by an offence report. /// /// This trait assumes that the offence is legitimate and was validated already. @@ -102,11 +79,6 @@ pub trait Offence { /// number. Note that for GRANDPA the round number is reset each epoch. fn time_slot(&self) -> Self::TimeSlot; - /// In which cases this offence needs to disable offenders until the next era starts. - fn disable_strategy(&self) -> DisableStrategy { - DisableStrategy::WhenSlashed - } - /// A slash fraction of the total exposure that should be slashed for this /// particular offence for the `offenders_count` that happened at a singular `TimeSlot`. /// @@ -177,15 +149,12 @@ pub trait OnOffenceHandler { /// /// The `session` parameter is the session index of the offence. /// - /// The `disable_strategy` parameter decides if the offenders need to be disabled immediately. - /// /// The receiver might decide to not accept this offence. In this case, the call site is /// responsible for queuing the report and re-submitting again. fn on_offence( offenders: &[OffenceDetails], slash_fraction: &[Perbill], session: SessionIndex, - disable_strategy: DisableStrategy, ) -> Res; } @@ -194,7 +163,6 @@ impl OnOffenceHandler _offenders: &[OffenceDetails], _slash_fraction: &[Perbill], _session: SessionIndex, - _disable_strategy: DisableStrategy, ) -> Res { Default::default() } diff --git a/substrate/primitives/state-machine/src/basic.rs b/substrate/primitives/state-machine/src/basic.rs index ace88aee2628f556ebafffa56543a7400c9428aa..8b6f746eaba0af9438557dc7dbe118bfbc4cdf34 100644 --- a/substrate/primitives/state-machine/src/basic.rs +++ b/substrate/primitives/state-machine/src/basic.rs @@ -33,7 +33,6 @@ use sp_trie::{empty_child_trie_root, LayoutV0, LayoutV1, TrieConfiguration}; use std::{ any::{Any, TypeId}, collections::BTreeMap, - iter::FromIterator, }; /// Simple Map-based Externalities impl. diff --git a/substrate/primitives/transaction-storage-proof/Cargo.toml b/substrate/primitives/transaction-storage-proof/Cargo.toml index 137a232fce73b9937158afba0059b05aefea10bc..6cce469d3f915786ef93ba0ee0e170a94d40fbeb 100644 --- a/substrate/primitives/transaction-storage-proof/Cargo.toml +++ b/substrate/primitives/transaction-storage-proof/Cargo.toml @@ -19,10 +19,10 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = { version = "0.1.79", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } -sp-core = { path = "../core", optional = true } +sp-core = { path = "../core", optional = true, default-features = false } sp-inherents = { path = "../inherents", default-features = false } sp-runtime = { path = "../runtime", default-features = false } -sp-trie = { path = "../trie", optional = true } +sp-trie = { path = "../trie", optional = true, default-features = false } [features] default = ["std"] diff --git a/substrate/utils/frame/remote-externalities/src/lib.rs b/substrate/utils/frame/remote-externalities/src/lib.rs index e429d39669f1b35f73e114df31e997f96c19945c..58cb901470c17df20f877ed96930b9be05f7d88d 100644 --- a/substrate/utils/frame/remote-externalities/src/lib.rs +++ b/substrate/utils/frame/remote-externalities/src/lib.rs @@ -830,19 +830,22 @@ where child_prefix: StorageKey, at: B::Hash, ) -> Result, &'static str> { - // This is deprecated and will generate a warning which causes the CI to fail. - #[allow(warnings)] - let child_keys = substrate_rpc_client::ChildStateApi::storage_keys( - client, - PrefixedStorageKey::new(prefixed_top_key.as_ref().to_vec()), - child_prefix, - Some(at), - ) - .await - .map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); - "rpc child_get_keys failed." - })?; + let retry_strategy = + FixedInterval::new(Self::KEYS_PAGE_RETRY_INTERVAL).take(Self::MAX_RETRIES); + let get_child_keys_closure = || { + #[allow(deprecated)] + substrate_rpc_client::ChildStateApi::storage_keys( + client, + PrefixedStorageKey::new(prefixed_top_key.as_ref().to_vec()), + child_prefix.clone(), + Some(at), + ) + }; + let child_keys = + Retry::spawn(retry_strategy, get_child_keys_closure).await.map_err(|e| { + error!(target: LOG_TARGET, "Error = {:?}", e); + "rpc child_get_keys failed." + })?; debug!( target: LOG_TARGET, diff --git a/substrate/utils/wasm-builder/src/builder.rs b/substrate/utils/wasm-builder/src/builder.rs index d2aaff448bc5fdc2f2c1cb73774e0d7603ae90db..163703fbec628327c0ba3dc0683972285af71e30 100644 --- a/substrate/utils/wasm-builder/src/builder.rs +++ b/substrate/utils/wasm-builder/src/builder.rs @@ -116,6 +116,39 @@ impl WasmBuilder { WasmBuilderSelectProject { _ignore: () } } + /// Build the WASM binary using the recommended default values. + /// + /// This is the same as calling: + /// ```no_run + /// substrate_wasm_builder::WasmBuilder::new() + /// .with_current_project() + /// .import_memory() + /// .export_heap_base() + /// .build(); + /// ``` + pub fn build_using_defaults() { + WasmBuilder::new() + .with_current_project() + .import_memory() + .export_heap_base() + .build(); + } + + /// Init the wasm builder with the recommended default values. + /// + /// In contrast to [`Self::build_using_defaults`] it does not build the WASM binary directly. + /// + /// This is the same as calling: + /// ```no_run + /// substrate_wasm_builder::WasmBuilder::new() + /// .with_current_project() + /// .import_memory() + /// .export_heap_base(); + /// ``` + pub fn init_with_defaults() -> Self { + WasmBuilder::new().with_current_project().import_memory().export_heap_base() + } + /// Enable exporting `__heap_base` as global variable in the WASM binary. /// /// This adds `-Clink-arg=--export=__heap_base` to `RUST_FLAGS`. diff --git a/substrate/utils/wasm-builder/src/lib.rs b/substrate/utils/wasm-builder/src/lib.rs index 178e499e8f5bb6d7531d73004e2f37e1e9b1d146..9ebab38b9cb2f727594e310f1e6d27c221f528dc 100644 --- a/substrate/utils/wasm-builder/src/lib.rs +++ b/substrate/utils/wasm-builder/src/lib.rs @@ -33,15 +33,9 @@ //! use substrate_wasm_builder::WasmBuilder; //! //! fn main() { -//! WasmBuilder::new() -//! // Tell the builder to build the project (crate) this `build.rs` is part of. -//! .with_current_project() -//! // Make sure to export the `heap_base` global, this is required by Substrate -//! .export_heap_base() -//! // Build the Wasm file so that it imports the memory (need to be provided by at instantiation) -//! .import_memory() -//! // Build it. -//! .build() +//! // Builds the WASM binary using the recommended defaults. +//! // If you need more control, you can call `new` or `init_with_defaults`. +//! WasmBuilder::build_using_defaults(); //! } //! ``` //! diff --git a/templates/minimal/Cargo.toml b/templates/minimal/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..6cd28c5a49364a911c9b93fa1269456cf07527d5 --- /dev/null +++ b/templates/minimal/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "minimal-template" +description = "A minimal template built with Substrate, part of Polkadot Sdk." +version = "0.0.0" +license = "MIT-0" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +minimal-template-node = { path = "./node" } +minimal-template-runtime = { path = "./runtime" } +pallet-minimal-template = { path = "./pallets/template" } +polkadot-sdk-docs = { path = "../../docs/sdk" } + +frame = { package = "polkadot-sdk-frame", path = "../../substrate/frame" } + +# How we build docs in rust-docs +simple-mermaid = "0.1.1" +docify = "0.2.7" diff --git a/templates/minimal/README.md b/templates/minimal/README.md index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0541e393db93bd9a67ddfaefe208c7ef22627f44 100644 --- a/templates/minimal/README.md +++ b/templates/minimal/README.md @@ -0,0 +1,13 @@ +# Minimal Template + +This is a minimal template for creating a blockchain using the Polkadot SDK. + +# Docs + +You can generate and view the [Rust +Docs](https://doc.rust-lang.org/cargo/commands/cargo-doc.html) for this template +with this command: + +```sh +cargo doc -p minimal-template --open +``` diff --git a/templates/minimal/runtime/build.rs b/templates/minimal/runtime/build.rs index b7676a70dfe843e2cd47fc600ef599bbe7bff591..e6f92757e225475ff744a4a0cf931787463d1544 100644 --- a/templates/minimal/runtime/build.rs +++ b/templates/minimal/runtime/build.rs @@ -18,10 +18,6 @@ fn main() { #[cfg(feature = "std")] { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build(); + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } } diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index 794f30a054a8e3ef9b50bd3f29e83dc572f62005..d2debbf5689fdf41b8436fb33eac52db5f573a3e 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -15,6 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! A minimal runtime that includes the template [`pallet`](`pallet_minimal_template`). + #![cfg_attr(not(feature = "std"), no_std)] // Make the WASM binary available. @@ -24,6 +26,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); use frame::{ deps::frame_support::{ genesis_builder_helper::{build_state, get_preset}, + runtime, weights::{FixedFee, NoFee}, }, prelude::*, @@ -36,6 +39,7 @@ use frame::{ }, }; +/// The runtime version. #[runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("minimal-template-runtime"), @@ -54,61 +58,108 @@ pub fn native_version() -> NativeVersion { NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } } +/// The signed extensions that are added to the runtime. type SignedExtra = ( + // Checks that the sender is not the zero address. frame_system::CheckNonZeroSender, + // Checks that the runtime version is correct. frame_system::CheckSpecVersion, + // Checks that the transaction version is correct. frame_system::CheckTxVersion, + // Checks that the genesis hash is correct. frame_system::CheckGenesis, + // Checks that the era is valid. frame_system::CheckEra, + // Checks that the nonce is valid. frame_system::CheckNonce, + // Checks that the weight is valid. frame_system::CheckWeight, + // Ensures that the sender has enough funds to pay for the transaction + // and deducts the fee from the sender's account. pallet_transaction_payment::ChargeTransactionPayment, ); -construct_runtime!( - pub enum Runtime { - System: frame_system, - Timestamp: pallet_timestamp, - - Balances: pallet_balances, - Sudo: pallet_sudo, - TransactionPayment: pallet_transaction_payment, - - // our local pallet - Template: pallet_minimal_template, - } -); +// Composes the runtime by adding all the used pallets and deriving necessary types. +#[runtime] +mod runtime { + /// The main runtime type. + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask + )] + pub struct Runtime; + + /// Mandatory system pallet that should always be included in a FRAME runtime. + #[runtime::pallet_index(0)] + pub type System = frame_system; + + /// Provides a way for consensus systems to set and check the onchain time. + #[runtime::pallet_index(1)] + pub type Timestamp = pallet_timestamp; + + /// Provides the ability to keep track of balances. + #[runtime::pallet_index(2)] + pub type Balances = pallet_balances; + + /// Provides a way to execute privileged functions. + #[runtime::pallet_index(3)] + pub type Sudo = pallet_sudo; + + /// Provides the ability to charge for extrinsic execution. + #[runtime::pallet_index(4)] + pub type TransactionPayment = pallet_transaction_payment; + + /// A minimal pallet template. + #[runtime::pallet_index(5)] + pub type Template = pallet_minimal_template; +} parameter_types! { pub const Version: RuntimeVersion = VERSION; } +/// Implements the types required for the system pallet. #[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)] impl frame_system::Config for Runtime { type Block = Block; type Version = Version; - type BlockHashCount = ConstU32<1024>; + // Use the account data from the balances pallet type AccountData = pallet_balances::AccountData<::Balance>; } +// Implements the types required for the balances pallet. #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Runtime { type AccountStore = System; } +// Implements the types required for the sudo pallet. #[derive_impl(pallet_sudo::config_preludes::TestDefaultConfig)] impl pallet_sudo::Config for Runtime {} +// Implements the types required for the sudo pallet. #[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)] impl pallet_timestamp::Config for Runtime {} +// Implements the types required for the transaction payment pallet. #[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)] impl pallet_transaction_payment::Config for Runtime { type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; + // Setting fee as independent of the weight of the extrinsic for demo purposes type WeightToFee = NoFee<::Balance>; + // Setting fee as fixed for any length of the call data for demo purposes type LengthToFee = FixedFee<1, ::Balance>; } +// Implements the types required for the template pallet. impl pallet_minimal_template::Config for Runtime {} type Block = frame::runtime::types_common::BlockOf; diff --git a/templates/minimal/src/lib.rs b/templates/minimal/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..68825d190bb2c0d7862cd8552c0fe211615ec60a --- /dev/null +++ b/templates/minimal/src/lib.rs @@ -0,0 +1,75 @@ +//! # Minimal Template +//! +//! This is a minimal template for creating a blockchain using the Polkadot SDK. +//! +//! ## Components +//! +//! The template consists of the following components: +//! +//! ### Node +//! +//! A minimal blockchain [`node`](`minimal_template_node`) that is capable of running a +//! runtime. It uses a simple chain specification, provides an option to choose Manual or +//! InstantSeal for consensus and exposes a few commands to interact with the node. +//! +//! ### Runtime +//! +//! A minimal [`runtime`](`minimal_template_runtime`) (or a state transition function) that +//! is capable of being run on the node. It is built using the [`FRAME`](`frame`) framework +//! that enables the composition of the core logic via separate modules called "pallets". +//! FRAME defines a complete DSL for building such pallets and the runtime itself. +//! +//! #### Transaction Fees +//! +//! The runtime charges a transaction fee for every transaction that is executed. The fee is +//! calculated based on the weight of the transaction (accouting for the execution time) and +//! length of the call data. Please refer to +//! [`benchmarking docs`](`polkadot_sdk_docs::reference_docs::frame_benchmarking_weight`) for +//! more information on how the weight is calculated. +//! +//! This template sets the fee as independent of the weight of the extrinsic and fixed for any +//! length of the call data for demo purposes. +//! +//! ### Pallet +//! +//! A minimal [`pallet`](`pallet_minimal_template`) that is built using FRAME. It is a unit of +//! encapsulated logic that has a clearly defined responsibility and can be linked to other pallets. +//! +//! ## Getting Started +//! +//! To get started with the template, follow the steps below: +//! +//! ### Build the Node +//! +//! Build the node using the following command: +//! +//! ```bash +//! cargo build -p minimal-template-node --release +//! ``` +//! +//! ### Run the Node +//! +//! Run the node using the following command: +//! +//! ```bash +//! ./target/release/minimal-template-node --dev +//! ``` +//! +//! ### CLI Options +//! +//! The node exposes a few options that can be used to interact with the node. To see the list of +//! available options, run the following command: +//! +//! ```bash +//! ./target/release/minimal-template-node --help +//! ``` +//! +//! #### Consensus Algorithm +//! +//! In order to run the node with a specific consensus algorithm, use the `--consensus` flag. For +//! example, to run the node with ManualSeal consensus with a block time of 5000ms, use the +//! following command: +//! +//! ```bash +//! ./target/release/minimal-template-node --dev --consensus manual-seal-5000 +//! ``` diff --git a/templates/parachain/runtime/build.rs b/templates/parachain/runtime/build.rs index 02d6973f29cf4e48691247d8ee02b500d540c4f5..bb05afe02b1fc526d1c7a2c64514e7f25f33c7be 100644 --- a/templates/parachain/runtime/build.rs +++ b/templates/parachain/runtime/build.rs @@ -1,10 +1,6 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } /// The wasm builder is deactivated when compiling diff --git a/templates/parachain/runtime/src/configs/xcm_config.rs b/templates/parachain/runtime/src/configs/xcm_config.rs index 13da2363b053467ab02c689dfc14ee67118f543e..c6b6e8da1b89d38a8f5f7695809972d35d83cf12 100644 --- a/templates/parachain/runtime/src/configs/xcm_config.rs +++ b/templates/parachain/runtime/src/configs/xcm_config.rs @@ -26,6 +26,8 @@ parameter_types! { pub const RelayLocation: Location = Location::parent(); pub const RelayNetwork: Option = None; pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); + // For the real deployment, it is recommended to set `RelayNetwork` according to the relay chain + // and prepend `UniversalLocation` with `GlobalConsensus(RelayNetwork::get())`. pub UniversalLocation: InteriorLocation = Parachain(ParachainInfo::parachain_id().into()).into(); } diff --git a/templates/solochain/README.md b/templates/solochain/README.md index 6390c9524ce185f7cda7679b874f532694d34a0b..37c65797dcb00a8aed0a4f4566eaaacab37c8359 100644 --- a/templates/solochain/README.md +++ b/templates/solochain/README.md @@ -4,10 +4,10 @@ A fresh [Substrate](https://substrate.io/) node, ready for hacking :rocket: A standalone version of this template is available for each release of Polkadot in the [Substrate Developer Hub Parachain -Template](https://github.com/substrate-developer-hub/substrate-parachain-template/) +Template](https://github.com/substrate-developer-hub/substrate-node-template/) repository. The parachain template is generated directly at each Polkadot -release branch from the [Node Template in -Substrate](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/bin/node-template) +release branch from the [Solochain Template in +Substrate](https://github.com/paritytech/polkadot-sdk/tree/master/templates/solochain) upstream It is usually best to use the stand-alone version to start a new project. All diff --git a/templates/solochain/runtime/build.rs b/templates/solochain/runtime/build.rs index c03d618535be03e94d4d48aacfedb674847e4646..f262c320393bf29ab0d54151803ca8b91d8e451b 100644 --- a/templates/solochain/runtime/build.rs +++ b/templates/solochain/runtime/build.rs @@ -1,10 +1,6 @@ fn main() { #[cfg(feature = "std")] { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build(); + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } }